下面的代码是保存为ssh2http.go , 执行如下代码

go mod init example.com/m
go mod tidy
GOOS=linux  GOARCH=amd64 go build  ssh2http.go

# 可以通过 go tool dist list 获取

PLATFORMS=(
  "linux/amd64"
  "linux/arm64"
  "darwin/amd64"
  "darwin/arm64"
  "windows/amd64"
)
# 运行
ssh2http --ssh-host=1.2.3.4:22 \
   --ssh-key-passphrase=123456 \
  --ssh-key=/Users/changhui.wy/.ssh/id_ed25519 \
  --local=:38080

ssh2http --ssh-host=1.2.2.3:22 \
  --ssh-user=root \
  --ssh-password=123456 \
  --local=:38080 

源码如下:

package main

import (
    "bufio"
    "encoding/base64"
    "flag"
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "os"
    "strings"
    "sync"
    "time"

    "golang.org/x/crypto/ssh"
)

var (
    httpProxyUser    = flag.String("http-proxy-user", "", "HTTP proxy username")
    httpProxyPass    = flag.String("http-proxy-pass", "", "HTTP proxy password")
    sshHost          = flag.String("ssh-host", "", "SSH server address (e.g. 1.2.3.4:22)")
    sshUser          = flag.String("ssh-user", "root", "SSH username")
    sshPassword      = flag.String("ssh-password", "", "SSH password (optional if using private key)")
    sshKeyFile       = flag.String("ssh-key", "", "Path to private key file (e.g. id_rsa)")
    localAddr        = flag.String("local", ":8080", "Local HTTP proxy listen address (e.g. :8080)")
    reconnectSec     = flag.Int("reconnect-interval", 5, "Reconnect interval in seconds after failure")
    sshKeyPassphrase = flag.String("ssh-key-passphrase", "", "Passphrase for encrypted private key (optional)")
)

func main() {
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0])
        fmt.Fprintln(os.Stderr, "\nOptions:")
        flag.PrintDefaults()
        fmt.Fprintln(os.Stderr, `
Examples:
  # Encrypted private key
  ssh2http --ssh-host=1.2.3.4:22  --ssh-key=id_rsa --ssh-key-passphrase='mysecret' --http-proxy-user=admin --http-proxy-pass=admin  --local=:8080

  # Unencrypted private key
  ssh2http --ssh-host=1.2.3.4:22 --ssh-user=ubuntu --ssh-key=id_rsa --local=:8080

  # use for shell
  export https_proxy=http://admin:admin@localhost:8080
  export http_proxy=http://admin:admin@localhost:8080

Notes:
  - Either --ssh-key or  (--ssh-password and --ssh-user) must be provided.
  - If private key is encrypted, use --ssh-key-passphrase.
`)
    }
    if len(os.Args) == 1 {
        flag.Usage()
        os.Exit(0)
    }
    flag.Parse()

    if *sshHost == "" {
        log.Fatal("Error: --ssh-host is required")
    }
    if !strings.Contains(*sshHost, ":") {
        *sshHost = *sshHost + ":22"
    }
    if *sshPassword == "" && *sshKeyFile == "" {
        log.Fatal("Error: either --ssh-password or --ssh-key must be provided")
    }

    // 启动 HTTP 代理服务器
    //http代理服务器支持账目

    proxy := &HTTPProxy{
        sshHost:      *sshHost,
        sshUser:      *sshUser,
        sshPassword:  *sshPassword,
        sshKeyFile:   *sshKeyFile,
        reconnectSec: time.Duration(*reconnectSec) * time.Second,
    }

    log.Printf("Starting HTTP proxy on %s, forwarding via SSH to %s", *localAddr, *sshHost)
    err := http.ListenAndServe(*localAddr, proxy)
    if err != nil {
        log.Fatalf("Failed to start HTTP proxy: %v", err)
    }
}

// HTTPProxy 实现 http.Handler,作为 HTTP 代理
type HTTPProxy struct {
    sshHost     string
    sshUser     string
    sshPassword string
    sshKeyFile  string

    reconnectSec time.Duration

    mu        sync.RWMutex
    client    *ssh.Client
    lastError error
}

// 获取当前有效的 SSH 客户端(带自动重连)
func (p *HTTPProxy) getSSHClient() (*ssh.Client, error) {
    p.mu.RLock()
    if p.client != nil {
        // 快速检查连接是否还活着(可选)
        _, _, err := p.client.SendRequest("keepalive@openssh.com", true, nil)
        if err == nil {
            client := p.client
            p.mu.RUnlock()
            return client, nil
        }
        // 连接已失效,关闭并重连
        p.client.Close()
        p.client = nil
    }
    p.mu.RUnlock()

    // 重新连接
    return p.reconnect()
}

func (p *HTTPProxy) reconnect() (*ssh.Client, error) {
    p.mu.Lock()
    defer p.mu.Unlock()

    // 如果已有有效连接,直接返回
    if p.client != nil {
        return p.client, nil
    }

    for {
        log.Printf("Connecting to SSH server: %s", p.sshHost)
        client, err := p.dialSSH()
        if err == nil {
            p.client = client
            p.lastError = nil
            log.Println("SSH connection established")
            go p.keepAlive(client)
            return client, nil
        }

        p.lastError = err
        log.Printf("Failed to connect SSH: %v, retrying in %v...", err, p.reconnectSec)
        time.Sleep(p.reconnectSec)
    }
}

func (p *HTTPProxy) dialSSH() (*ssh.Client, error) {
    var auth []ssh.AuthMethod

    if p.sshPassword != "" {
        auth = append(auth, ssh.Password(p.sshPassword))
    }

    if p.sshKeyFile != "" {
        keyBytes, err := os.ReadFile(p.sshKeyFile)
        if err != nil {
            return nil, fmt.Errorf("read private key file: %w", err)
        }

        var signer ssh.Signer
        var parseErr error

        // 如果提供了 passphrase,尝试用它解密
        if *sshKeyPassphrase != "" {
            signer, parseErr = ssh.ParsePrivateKeyWithPassphrase(keyBytes, []byte(*sshKeyPassphrase))
            if parseErr == nil {
                auth = append(auth, ssh.PublicKeys(signer))
            } else {
                // 解密失败,报错
                return nil, fmt.Errorf("failed to parse encrypted private key with given passphrase: %w", parseErr)
            }
        } else {
            // 没有提供 passphrase,尝试无密码解析
            signer, parseErr = ssh.ParsePrivateKey(keyBytes)
            if parseErr == nil {
                auth = append(auth, ssh.PublicKeys(signer))
            } else {
                // 可能是加密私钥但没给密码
                return nil, fmt.Errorf("failed to parse private key (it may be encrypted; try --ssh-key-passphrase): %w", parseErr)
            }
        }
    }

    config := &ssh.ClientConfig{
        User:            p.sshUser,
        Auth:            auth,
        HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 生产环境建议验证 host key
        Timeout:         10 * time.Second,
    }

    return ssh.Dial("tcp", p.sshHost, config)
}

// 保活协程
func (p *HTTPProxy) keepAlive(client *ssh.Client) {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            // 发送 keepalive 请求
            _, _, err := client.SendRequest("keepalive@openssh.com", true, nil)
            if err != nil {
                log.Printf("SSH keepalive failed: %v", err)
                client.Close()
                return
            }
        }
    }
}

func (p *HTTPProxy) checkAuth(r *http.Request) bool {
    auth := r.Header.Get("Proxy-Authorization")
    if auth == "" {
        return false
    }

    // 格式应为: "Basic base64(user:pass)"
    if !strings.HasPrefix(auth, "Basic ") {
        return false
    }

    payload, err := base64.StdEncoding.DecodeString(auth[6:])
    if err != nil {
        return false
    }

    creds := string(payload)
    parts := strings.SplitN(creds, ":", 2)
    if len(parts) != 2 {
        return false
    }

    username := parts[0]
    password := parts[1]

    // 比较(注意:避免时序攻击,但简单场景可直接比较)
    return username == *httpProxyUser && password == *httpProxyPass
}

// ServeHTTP 实现 HTTP 代理逻辑
func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if *httpProxyUser != "" || *httpProxyPass != "" {
        if !p.checkAuth(r) {
            w.Header().Set("Proxy-Authenticate", "Basic realm=\"Proxy\"")
            http.Error(w, "Proxy authentication required", http.StatusProxyAuthRequired)
            return
        }
    }
    clientIP, _, _ := net.SplitHostPort(r.RemoteAddr)
    log.Printf("[HTTP] Client %s -> Target %s %s %s", clientIP, r.URL.Host, r.Method, r.URL.Path)
    if r.Method == http.MethodConnect {
        p.handleConnect(w, r)
    } else {
        p.handleHTTP(w, r)
    }
}

func (p *HTTPProxy) handleConnect(w http.ResponseWriter, r *http.Request) {
    // HTTPS 代理:建立隧道
    dest := r.Host
    hijacker, ok := w.(http.Hijacker)
    if !ok {
        http.Error(w, "Hijack not supported", http.StatusInternalServerError)
        return
    }

    clientConn, _, err := hijacker.Hijack()
    if err != nil {
        log.Printf("Hijack error: %v", err)
        return
    }
    defer clientConn.Close()

    // 发送 200 Connection Established
    clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))

    // 获取 SSH 客户端
    sshClient, err := p.getSSHClient()
    if err != nil {
        log.Printf("Failed to get SSH client for CONNECT: %v", err)
        return
    }

    // 通过 SSH 打开到目标的 TCP 连接
    targetConn, err := sshClient.Dial("tcp", dest)
    if err != nil {
        log.Printf("Failed to dial target %s via SSH: %v", dest, err)
        return
    }
    defer targetConn.Close()

    // 双向复制
    go io.Copy(targetConn, clientConn)
    io.Copy(clientConn, targetConn)
}

func (p *HTTPProxy) handleHTTP(w http.ResponseWriter, r *http.Request) {
    // 构造目标 URL
    if !strings.HasPrefix(r.URL.String(), "http") {
        http.Error(w, "URL must be absolute", http.StatusBadRequest)
        return
    }

    // 获取 SSH 客户端
    sshClient, err := p.getSSHClient()
    if err != nil {
        http.Error(w, "SSH unavailable", http.StatusServiceUnavailable)
        return
    }

    // 通过 SSH 打开到目标主机的连接
    targetConn, err := sshClient.Dial("tcp", r.URL.Host)
    if err != nil {
        log.Printf("Failed to dial %s: %v", r.URL.Host, err)
        http.Error(w, "Gateway error", http.StatusBadGateway)
        return
    }
    defer targetConn.Close()

    // 转发原始请求
    err = r.Write(targetConn)
    if err != nil {
        log.Printf("Failed to write request: %v", err)
        http.Error(w, "Write error", http.StatusBadGateway)
        return
    }

    // 读取响应
    resp, err := http.ReadResponse(bufio.NewReader(targetConn), r)
    if err != nil {
        log.Printf("Failed to read response: %v", err)
        http.Error(w, "Read error", http.StatusBadGateway)
        return
    }
    defer resp.Body.Close()

    // 写回响应头
    for key, values := range resp.Header {
        for _, value := range values {
            w.Header().Add(key, value)
        }
    }
    w.WriteHeader(resp.StatusCode)

    // 写回响应体
    io.Copy(w, resp.Body)
}