Model: GPT-4
Avatar of the person chatting

I'm trying to find a reliable way to proxy occasional HTTP access to any of ~100 geographically dispersed IOT devices through a cloud server. I'm using Go on the cloud server and on the IOT devices, so I thought I'd ask here. Situation: We have complete control over the configuration of the IOT devices and the cloud host. We don't have control of the customers' routers and firewalls, but can specify minimum requirements for port openings, etc. FWIW, the IOT devices are BeagleBone Black running Debian Buster and the cloud host will be, typically, a multi-core droplet (or similar) running Linux. The IOT's serve dynamic web pages over HTTP. (HTTPS doesn't seem feasible because of certificate requirements and overall load on the IOT cpu.) The cloud host will have HTTPS capability. This is a low-traffic situation. The IOT's report some overall status information (via rsync/ssh) at 4 minute intervals). We already have a web interface (written in Go) on the cloud server that aggregates and displays the status reports. Access to an IOT's web service will only occur when a user wants to investigate a problem report in more detail. Typically, only one or two users will have credentials to browse the cloud server. The scheme I have in mind is: At configuration time for each IOT device the installation tech will use ssh-copy-id to install the IOT device's public key on the cloud service. The IOT device will then remotely execute a one-shot program (already written and tested) on the cloud server. The IOT will provide a unique identifier as an argument and the program will return a permanent port number and add a record to a database to record the assignment. The IOT will open a reverse SSH tunnel on the server (probably managed by auto-ssh) specifying the permanent port on the server and a local port on which it will listen for HTTP requests. The cloud server, when generating status report pages, will include a link to fetch the home page of each IOT device by embedding its unique identifier specified in step 2 above. The piece I'm missing is how to construct a proxying handler that will use the identifier in the link to look up the tunnel port and fetch the IOT's home page and thereafter make it seem as though the user is directly browsing the IOT.

To build the proxying handler in Go, you can use the net/http package to implement a reverse proxy that forwards requests from the cloud server to the appropriate IoT device through the SSH tunnel.

Here's a high-level outline of how you can construct the handler:

  1. Create a custom http.Handler that receives incoming requests with the unique identifier.
  2. Use the identifier to look up the assigned port number in the database.
  3. Create a url.URL object with the target IoT device's address and port number.
  4. Use the httputil.NewSingleHostReverseProxy function to create a reverse proxy with the target URL.
  5. In the handler's ServeHTTP method, use the reverse proxy's ServeHTTP method to forward the request to the IoT device and return the response to the user.

Here's a code sample to illustrate the above steps:

goCopy code
package main import ( "database/sql" "fmt" "log" "net/http" "net/http/httputil" "net/url" ) // Your custom handler. type IoTProxyHandler struct { db *sql.DB // Replace with your actual database connection. } func (h *IoTProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Extract the unique identifier from the request (e.g., from the query string or path). identifier := r.URL.Query().Get("id") // Look up the assigned port number in the database using the identifier. var port int err := h.db.QueryRow("SELECT port FROM devices WHERE identifier = ?", identifier).Scan(&port) if err != nil { log.Printf("Error looking up port for identifier %s: %v", identifier, err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } // Create the target URL for the IoT device. target := &url.URL{ Scheme: "http", Host: fmt.Sprintf("localhost:%d", port), } // Create the reverse proxy. proxy := httputil.NewSingleHostReverseProxy(target) // Forward the request to the IoT device and return the response to the user. proxy.ServeHTTP(w, r) } func main() { // Replace with your actual database connection. var db *sql.DB // Set up your custom handler. handler := &IoTProxyHandler{db: db} // Start the HTTP server with your handler. http.Handle("/", handler) log.Fatal(http.ListenAndServe(":8080", nil)) }

This code sets up an HTTP server with a custom handler that receives incoming requests, looks up the assigned port number for the specified IoT device, and forwards the request to the device via the reverse SSH tunnel. The response from the device is then returned to the user as if they were browsing the IoT device directly.

You'll need to replace the dummy db variable with your actual database connection, and adjust the SQL query and other parts of the code to match your specific setup.