user icon

Go言語のお勉強でSSHクライアント

次の案件でGo言語を使いたいと思って勉強しているmuraveさん、サーバー間連携で使うかもしれないと”golang.org/x/crypto/ssh”を使用するサンプルを作ることにしました。

goでsshクライアントの実装メモ – Qiita

Golangでターミナル接続できるsshクライアントコマンドを作成する(制御キー対応) | 俺的備忘録 〜なんかいろいろ〜

上記の記事を参考にしてSSHクライアントを書いてみました。

コマンドラインの解析にはflagパッケージを使用し、公開鍵認証のみ対応です。

// sshクライアントサンプル(鍵ファイルでの接続のみ対応)
package main

import (
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "os/user"
    "path/filepath"
    "strings"

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

func main() {
    host, userName, absKeyPath := parseCommand()

    auth := []ssh.AuthMethod{}

    key, err := ioutil.ReadFile(absKeyPath)
    fatalLogWhenErr(err, "private key")
    signer, err := ssh.ParsePrivateKey(key)
    fatalLogWhenErr(err, "signer")

    auth = append(auth, ssh.PublicKeys(signer))

    sshConfig := &ssh.ClientConfig{
        User:            userName,
        Auth:            auth,
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }
    startSession(host, sshConfig)

}

// parseCommand コマンドラインをパースする
func parseCommand() (string, string, string) {
    // ヘルプメッセージにホストの書き方を追加
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "%s hostname:22\n", os.Args[0])
        flag.PrintDefaults()
    }
    flagUsername := flag.String("user", "", "user name (default:current user)")
    flagKey := flag.String("key", "~/.ssh/id_rsa", "private key")

    flag.Parse()
    host := flag.Arg(0)
    if host == "" {
        flag.Usage()
        os.Exit(1)
    }

    fmt.Println(*flagUsername, flag.Arg(0))
    usr, err := user.Current()
    fatalLogWhenErr(err, "")

    userName := usr.Username
    if *flagUsername != "" {
        userName = *flagUsername
    }
    keyPath := *flagKey
    var absKeyPath string
    if keyPath[0:1] == "~" {
        absKeyPath = strings.Replace(keyPath, "~", usr.HomeDir, 1)
    } else {
        var err error
        absKeyPath, err = filepath.Abs(keyPath)
        if err != nil {
            log.Fatal(err)
        }
    }

    return host, userName, absKeyPath
}

// startSession sshセッションを開始
func startSession(host string, sshConfig *ssh.ClientConfig) {
    client, err := ssh.Dial("tcp", host, sshConfig)
    fatalLogWhenErr(err, "dial")

    session, err := client.NewSession()
    fatalLogWhenErr(err, "new session")
    defer session.Close()

    // キー入力を接続先が認識できる形式に変換
    fd := int(os.Stdin.Fd())
    state, err := terminal.MakeRaw(fd)
    fatalLogWhenErr(err, "tarminal make raw")
    defer terminal.Restore(fd, state)

    w, h, err := terminal.GetSize(fd)
    fatalLogWhenErr(err, "tarminal get size")
    termmodes := ssh.TerminalModes{
        ssh.ECHO:          1,
        ssh.TTY_OP_ISPEED: 14400,
        ssh.TTY_OP_OSPEED: 14400,
    }
    term := os.Getenv("TERM")
    err = session.RequestPty(term, h, w, termmodes)
    fatalLogWhenErr(err, "session request pty")

    session.Stdout = os.Stdout
    session.Stderr = os.Stderr
    session.Stdin = os.Stdin

    err = session.Shell()
    fatalLogWhenErr(err, "session shell")

    err = session.Wait()
    fatalLogWhenErr(err, "session wait")
}

// fatalLogWhenErr errをチェックしてerrorの場合にはfatal logを出力
func fatalLogWhenErr(err error, msg string) {
    if err != nil {
        if len(msg) > 0 {
            log.Fatalf("%s error: %v", msg, err)
        } else {
            log.Fatal(err)
        }
    }
}

以下のような感じで普通に使えました(./sshがビルドしたバイナリでhostname等は隠しています)

$ ./ssh
./ssh hostname:22
  -key string
        private key (default "~/.ssh/id_rsa")
  -user string
        user name (default:current user)

$ ./ssh -user root ***.***.***.***:22
Last login: Thu Jan  9 15:45:57 2020 from ***-**-**-***.***.***.**
[root ~]# pwd
/root
[root ~]# exit
logout

Facebooktwitterlinkedintumblrmail

タグ: ,

名前
E-mail
URL
コメント

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)