https://www.linkedin.com/pulse/proxying-web-traffic-via-ssh-mark-el-khoury
Essentially your local machines creates a SOCK5 proxy which connects to a remote server via SSH (using either username/password or SSH PKI Cert/Key combination).
The SOCK5 proxy handles sending the HTTP request via the SSH tunnel.
The remote server will automatically process the HTTP request and handle sending it to its intended destination.
The upstream (i.e. the endpoint being requested) will see the request coming from the SSH server and presume that's where it originated.
package main
import (
"golang.org/x/crypto/ssh"
"golang.org/x/net/proxy"
"net/http"
"net/url"
"io/ioutil"
"log"
"time"
)
func main() {
http.HandleFunc("/", handleRequestAndRedirect)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleRequestAndRedirect(w http.ResponseWriter, r *http.Request) {
// SSH connection details
sshHost := "ssh.example.com:22"
sshUser := "your_ssh_user"
sshPassword := "your_ssh_password"
// Upstream service URL
upstreamURL := "http://upstream-service.example.com"
// Create the SSH client configuration
sshConfig := &ssh.ClientConfig{
User: sshUser,
Auth: []ssh.AuthMethod{
ssh.Password(sshPassword),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 5 * time.Second,
}
// Establish the SSH connection
sshConn, err := ssh.Dial("tcp", sshHost, sshConfig)
if err != nil {
http.Error(w, "Failed to dial SSH: "+err.Error(), http.StatusInternalServerError)
return
}
defer sshConn.Close()
// Create a SOCKS5 proxy client over the SSH connection
socks5Client, err := proxy.SOCKS5("tcp", "localhost:0", nil, &sshDialer{sshConn})
if err != nil {
http.Error(w, "Failed to create SOCKS5 proxy: "+err.Error(), http.StatusInternalServerError)
return
}
// Create an HTTP client that uses the SOCKS5 proxy
httpClient := &http.Client{
Transport: &http.Transport{
Dial: socks5Client.Dial,
},
}
// Create the request to the upstream service
req, err := http.NewRequest(r.Method, upstreamURL, r.Body)
if err != nil {
http.Error(w, "Failed to create request: "+err.Error(), http.StatusInternalServerError)
return
}
// Copy the headers from the original request to the new request
for key, values := range r.Header {
for _, value := range values {
req.Header.Add(key, value)
}
}
// Perform the request
resp, err := httpClient.Do(req)
if err != nil {
http.Error(w, "Failed to perform request: "+err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// Copy the response from the upstream service to the original response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
http.Error(w, "Failed to read response: "+err.Error(), http.StatusInternalServerError)
return
}
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
w.WriteHeader(resp.StatusCode)
w.Write(body)
}
// sshDialer is a custom Dialer implementation that uses an SSH connection
type sshDialer struct {
client *ssh.Client
}
// Dial connects to the address on the named network using the SSH client.
func (d *sshDialer) Dial(network, addr string) (net.Conn, error) {
return d.client.Dial(network, addr)
}