Skip to content

Instantly share code, notes, and snippets.

@yomon8
Last active February 9, 2018 09:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yomon8/ba5a809e78042ad41b2c1546ffc26f3c to your computer and use it in GitHub Desktop.
Save yomon8/ba5a809e78042ad41b2c1546ffc26f3c to your computer and use it in GitHub Desktop.
SSHTunnel Proxy
package main
import (
"fmt"
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"time"
"golang.org/x/crypto/ssh"
"github.com/labstack/echo"
)
type Template struct {
templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
func WebStart(localServerHostnameOrIP string, localServerPort int, targets []TargetServer) {
htmlTemplate := fmt.Sprintf(`
{{ define "servers" }}
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
</head>
<body>
{{range .}}
<div><a target="_blank" href="http://%s:{{.ExposePort}}/">{{.Name}}</a></div>
{{end}}
</body>
</html>
{{end}}
`, localServerHostnameOrIP)
e := echo.New()
tmpl := template.New("index")
parsedTmpl, err := tmpl.Parse(htmlTemplate)
if err != nil {
panic(err)
}
t := &Template{
templates: parsedTmpl,
}
e.Renderer = t
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "servers", targets)
})
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", localServerPort)))
}
type Endpoint struct {
// Server host address
Host string
// Server port
Port int
}
func (endpoint *Endpoint) String() string {
return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port)
}
type SSHTunnel struct {
Local *Endpoint
Server *Endpoint
Remote *Endpoint
Config *ssh.ClientConfig
}
func NewSSHTunnel(config *ssh.ClientConfig, proxy *TargetServer, backend *TargetServer) *SSHTunnel {
localEndpoint := &Endpoint{
Host: "0.0.0.0",
Port: backend.ExposePort,
}
serverEndpoint := &Endpoint{
Host: proxy.HostnameOrIP,
Port: proxy.ServicePort,
}
remoteEndpoint := &Endpoint{
Host: backend.HostnameOrIP,
Port: backend.ServicePort,
}
return &SSHTunnel{
Config: config,
Local: localEndpoint,
Server: serverEndpoint,
Remote: remoteEndpoint,
}
}
func (tunnel *SSHTunnel) Start() error {
listener, err := net.Listen("tcp", tunnel.Local.String())
if err != nil {
return err
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
return err
}
go tunnel.forward(conn)
}
}
func (tunnel *SSHTunnel) forward(localConn net.Conn) {
serverConn, err := ssh.Dial("tcp", tunnel.Server.String(), tunnel.Config)
if err != nil {
fmt.Printf("Server dial error: %s\n", err)
localConn.Close()
return
}
remoteConn, err := serverConn.Dial("tcp", tunnel.Remote.String())
if err != nil {
fmt.Printf("Remote dial error: %s\n", err)
localConn.Close()
return
}
copyConn := func(writer, reader net.Conn) {
_, err := io.Copy(writer, reader)
if err != nil {
fmt.Printf("io.Copy error: %s", err)
}
}
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
type TargetServer struct {
Name string
HostnameOrIP string
ServicePort int
ExposePort int
}
func main() {
var (
localServerHostnameOrIP = "192.168.33.10" // Server A のホスト
localServerPort = 8080 // Server A のへの接続Port
serverB = TargetServer{
Name: "Server B",
HostnameOrIP: "52.1.1.1", // 例えばAWSのEIPのようなGlobal IPとか別ネットワークから繋がるもの
ServicePort: 22, // sshdに設定しているポート
ExposePort: 0,
}
targetServers = []TargetServer{
TargetServer{
Name: "Server C",
HostnameOrIP: "192.168.11.3", // ServerAとネットワークが異なる
ServicePort: 80, // ServerCのサービスポート
ExposePort: 8083, // ブラウザからサクセスする際に利用するポート
},
TargetServer{
Name: "Server D",
HostnameOrIP: "192.168.11.4",
ServicePort: 80,
ExposePort: 8084,
},
TargetServer{
Name: "Server E",
HostnameOrIP: "192.168.11.5",
ServicePort: 80,
ExposePort: 8085,
},
}
)
// Server1->Server2のSSH接続設定
sshUser := "postgres"
sshTimeoutMilliSec := 500
sshKeyfile := filepath.Join(
os.Getenv("HOME"),
".ssh",
"id_rsa",
)
buf, err := ioutil.ReadFile(sshKeyfile)
if err != nil {
panic(err)
}
key, err := ssh.ParsePrivateKey(buf)
if err != nil {
panic(err)
}
sshConfig := &ssh.ClientConfig{
User: sshUser,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: time.Duration(sshTimeoutMilliSec) * time.Millisecond,
}
// Server C 〜Eにトンネルを掘る
for _, target := range targetServers {
tunnel := NewSSHTunnel(
sshConfig,
&serverB,
&target,
)
go tunnel.Start()
}
// トンネルの一覧を表示するページを生成しWebサーバを起動
WebStart(localServerHostnameOrIP, localServerPort, targetServers)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment