Skip to content

Instantly share code, notes, and snippets.

@blacknon
Last active January 30, 2023 01:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blacknon/a90b01dee1e86f430a8f979c9b6022ec to your computer and use it in GitHub Desktop.
Save blacknon/a90b01dee1e86f430a8f979c9b6022ec to your computer and use it in GitHub Desktop.
goで多段proxy経由でsshでシェルに接続する検証・サンプルコード
package main
import (
"fmt"
"net"
"os"
"os/signal"
"syscall"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
// @note:
// proxy1 ... プロキシサーバ(1個目)
// proxy2 ... プロキシサーバ(2個目)
// target ... プロキシサーバ経由でログインするサーバ
func main() {
// proxy1の情報
proxy1Host := "proxy1.host.local"
proxy1Port := "22"
proxy1User := "user"
proxy1Pass := "password"
// proxy2の情報
proxy2Host := "proxy2.host.local"
proxy2Port := "22"
proxy2User := "user"
proxy2Pass := "password"
// targetの情報
targetHost := "target.host.local"
targetPort := "22"
targetUser := "user"
targetPass := "password"
// sshClientConfigの作成(proxy1)
proxy1SshConfig := &ssh.ClientConfig{
User: proxy1User,
Auth: []ssh.AuthMethod{ssh.Password(proxy1Pass)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// sshClientConfigの作成(proxy2)
proxy2SshConfig := &ssh.ClientConfig{
User: proxy2User,
Auth: []ssh.AuthMethod{ssh.Password(proxy2Pass)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// sshClientConfigの作成(target)
targetSshConfig := &ssh.ClientConfig{
User: targetUser,
Auth: []ssh.AuthMethod{ssh.Password(targetPass)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Proxy1へのsshClientの作成
proxy1Client, err := ssh.Dial("tcp", net.JoinHostPort(proxy1Host, proxy1Port), proxy1SshConfig)
if err != nil {
fmt.Println(err)
}
// Proxy1経由でのProxy2への接続を実施
proxy1Conn, err := proxy1Client.Dial("tcp", net.JoinHostPort(proxy2Host, proxy2Port))
if err != nil {
fmt.Println(err)
}
// Proxy2へのsshClientを作成
pConnect, pChans, pReqs, err := ssh.NewClientConn(proxy1Conn, net.JoinHostPort(proxy2Host, proxy2Port), proxy2SshConfig)
if err != nil {
fmt.Println(err)
}
proxy2Client := ssh.NewClient(pConnect, pChans, pReqs)
// Proxy2経由でのtargetへの接続を実施
proxy2Conn, err := proxy2Client.Dial("tcp", net.JoinHostPort(targetHost, targetPort))
if err != nil {
fmt.Println(err)
}
// TargetへのsshClientを作成
p2Connect, p2Chans, p2Reqs, err := ssh.NewClientConn(proxy2Conn, net.JoinHostPort(targetHost, targetPort), targetSshConfig)
if err != nil {
fmt.Println(err)
}
client := ssh.NewClient(p2Connect, p2Chans, p2Reqs)
// -----------------------------------
// ↓以降はプロキシの処理とは無関係なコード
// -----------------------------------
// Sessionを作成
session, err := client.NewSession()
defer session.Close()
// キー入力を接続先が認識できる形式に変換する(ここがキモ)
fd := int(os.Stdin.Fd())
state, err := terminal.MakeRaw(fd)
if err != nil {
fmt.Println(err)
}
defer terminal.Restore(fd, state)
// ターミナルサイズの取得
w, h, err := terminal.GetSize(fd)
if err != nil {
fmt.Println(err)
}
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
// 仮想端末のリクエスト
err = session.RequestPty("xterm", h, w, modes)
if err != nil {
fmt.Println(err)
}
// sessionの標準入出力の処理
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
// ssh接続先でShellの起動
err = session.Shell()
if err != nil {
fmt.Println(err)
}
// ターミナルサイズの変更検知・処理
signal_chan := make(chan os.Signal, 1)
signal.Notify(signal_chan, syscall.SIGWINCH)
go func() {
for {
s := <-signal_chan
switch s {
case syscall.SIGWINCH:
fd := int(os.Stdout.Fd())
w, h, _ = terminal.GetSize(fd)
session.WindowChange(h, w)
}
}
}()
err = session.Wait()
if err != nil {
fmt.Println(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment