Last active
February 9, 2018 09:22
-
-
Save yomon8/ba5a809e78042ad41b2c1546ffc26f3c to your computer and use it in GitHub Desktop.
SSHTunnel Proxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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