SSH隧道实现HTTP转发的Golang方案
你的问题并不傻,这是SSH隧道应用的典型场景。你需要通过SSH隧道将TCP流量转换为HTTP请求。以下是完整的解决方案:
核心方案:SSH端口转发 + HTTP代理
package main
import (
"fmt"
"io"
"log"
"net"
"net/http"
"time"
"golang.org/x/crypto/ssh"
)
// SSH隧道客户端
type SSHTunnel struct {
LocalAddr string
RemoteAddr string
SSHClient *ssh.Client
}
// 创建SSH客户端配置
func createSSHConfig(privateKeyPath string) (*ssh.ClientConfig, error) {
key, err := ssh.ParsePrivateKey([]byte(privateKeyPath))
if err != nil {
return nil, err
}
config := &ssh.ClientConfig{
User: "your_username",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 30 * time.Second,
}
return config, nil
}
// 启动SSH隧道
func (tunnel *SSHTunnel) Start() error {
listener, err := net.Listen("tcp", tunnel.LocalAddr)
if err != nil {
return err
}
defer listener.Close()
for {
localConn, err := listener.Accept()
if err != nil {
return err
}
go tunnel.forward(localConn)
}
}
// 转发连接
func (tunnel *SSHTunnel) forward(localConn net.Conn) {
remoteConn, err := tunnel.SSHClient.Dial("tcp", tunnel.RemoteAddr)
if err != nil {
log.Printf("远程连接失败: %v", err)
localConn.Close()
return
}
// 双向数据转发
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
func copyConn(dst, src net.Conn) {
defer dst.Close()
defer src.Close()
io.Copy(dst, src)
}
// HTTP代理处理器
func handleHTTPProxy(w http.ResponseWriter, r *http.Request) {
// 通过SSH隧道转发HTTP请求
sshClient, err := ssh.Dial("tcp", "ssh-server:22", sshConfig)
if err != nil {
http.Error(w, "SSH连接失败", http.StatusBadGateway)
return
}
defer sshClient.Close()
// 通过SSH连接到目标HTTP服务
conn, err := sshClient.Dial("tcp", "localhost:80")
if err != nil {
http.Error(w, "目标服务连接失败", http.StatusBadGateway)
return
}
defer conn.Close()
// 转发HTTP请求
err = r.Write(conn)
if err != nil {
http.Error(w, "请求转发失败", http.StatusBadGateway)
return
}
// 读取响应并返回
resp, err := http.ReadResponse(bufio.NewReader(conn), r)
if err != nil {
http.Error(w, "响应读取失败", http.StatusBadGateway)
return
}
defer resp.Body.Close()
// 复制响应头
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
// 完整示例:SSH隧道HTTP代理
func main() {
// 1. 建立SSH连接
sshConfig := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
// 或使用公钥认证
// ssh.PublicKeys(privateKey),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
sshClient, err := ssh.Dial("tcp", "remote-server:22", sshConfig)
if err != nil {
log.Fatal("SSH连接失败:", err)
}
defer sshClient.Close()
// 2. 创建本地HTTP代理服务器
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 通过SSH隧道转发到远程Apache
remoteConn, err := sshClient.Dial("tcp", "localhost:80")
if err != nil {
http.Error(w, "隧道连接失败", http.StatusBadGateway)
return
}
defer remoteConn.Close()
// 修改请求头以通过Apache
r.URL.Scheme = "http"
r.URL.Host = "localhost:80"
r.RequestURI = ""
// 发送请求
err = r.Write(remoteConn)
if err != nil {
http.Error(w, "请求发送失败", http.StatusBadGateway)
return
}
// 读取响应
resp, err := http.ReadResponse(bufio.NewReader(remoteConn), r)
if err != nil {
http.Error(w, "响应读取失败", http.StatusBadGateway)
return
}
defer resp.Body.Close()
// 返回响应
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
})
// 3. 启动本地代理服务器
log.Println("HTTP代理服务器在 :8080 启动")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 替代方案:使用net/http/httputil进行反向代理
func createReverseProxy(sshClient *ssh.Client) *httputil.ReverseProxy {
director := func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = "localhost:80"
// 通过SSH隧道建立连接
dialer := func(network, addr string) (net.Conn, error) {
return sshClient.Dial("tcp", "localhost:80")
}
transport := &http.Transport{
Dial: dialer,
}
httputil.NewSingleHostReverseProxy(req.URL).Transport = transport
}
return &httputil.ReverseProxy{Director: director}
}
简化版本:直接端口转发
package main
import (
"io"
"log"
"net"
"golang.org/x/crypto/ssh"
)
func main() {
// SSH配置
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// 连接SSH服务器
client, err := ssh.Dial("tcp", "remote-server:22", config)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// 监听本地端口
listener, err := net.Listen("tcp", "localhost:8888")
if err != nil {
log.Fatal(err)
}
for {
localConn, err := listener.Accept()
if err != nil {
log.Printf("接受连接失败: %v", err)
continue
}
go func() {
// 通过SSH连接到远程Apache
remoteConn, err := client.Dial("tcp", "localhost:80")
if err != nil {
log.Printf("远程连接失败: %v", err)
localConn.Close()
return
}
// 双向转发
go func() {
defer localConn.Close()
defer remoteConn.Close()
io.Copy(localConn, remoteConn)
}()
go func() {
defer localConn.Close()
defer remoteConn.Close()
io.Copy(remoteConn, localConn)
}()
}()
}
}
使用示例
- 启动SSH隧道代理:
go run ssh-http-proxy.go
-
配置浏览器或应用:
- 代理服务器:
localhost:8080
- 所有HTTP流量将通过SSH隧道转发到远程Apache
-
验证连接:
// 测试代码
resp, err := http.Get("http://localhost:8888/path")
// 请求将通过SSH隧道转发到远程服务器的Apache
这个方案的关键点:
- SSH隧道处理TCP层连接
- HTTP代理在应用层处理HTTP协议
- 保持HTTP头信息完整传递
- 支持HTTPS需要额外处理TLS握手
你的实现方向是正确的,只需要在SSH隧道之上添加HTTP协议处理层即可。