GoLang 实现 SSH 相关示例

2016-07-26 language golang ssh

GoLang 提供了基础的 SSH 库,可以用来作为服务端或者客户端使用,这里整理了常见的用法。

简单示例

package main

import (
    "log"
    //"bytes"
    "os"
    //"fmt"

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

func main() {
    // 建立SSH客户端连接
    client, err := gossh.Dial("tcp", "127.0.0.1:49154", &gossh.ClientConfig{
        User:            "root",
        Auth:            []gossh.AuthMethod{gossh.Password("admin")},
        HostKeyCallback: gossh.InsecureIgnoreHostKey(),
    })
    if err != nil {
        log.Fatalf("SSH dial error: %s", err.Error())
    }

    // 建立新会话
    session, err := client.NewSession()
    if err != nil {
        log.Fatalf("new session error: %s", err.Error())
    }
    defer session.Close()

    // session.run(command)是直接在host执行命令,不关心执行结果。session.Output是将执行命令之后的Stdout返回
    /*
    var b bytes.Buffer
    session.Stdout = &b
    if err := session.Run("ls /"); err != nil {
        panic("Failed to run: " + err.Error())
    }
    fmt.Println(b.String())

    result, err := session.Output("ls -al")
    if err != nil {
        fmt.Fprintf(os.Stdout, "Failed to run command, Err:%s", err.Error())
        os.Exit(0)
    }
    fmt.Println(string(result))
    */

    // 使用VT100终端实现Tab提示、上下键历史查看、clear清屏等快捷键操作
    fd := int(os.Stdin.Fd())
    oldState, err := terminal.MakeRaw(fd)
    if err != nil {
        log.Fatalln(err.Error())
    }
    defer terminal.Restore(fd, oldState)

    modes := gossh.TerminalModes{
        gossh.ECHO:          1,  // 禁用回显(0禁用,1启动)
        gossh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        gossh.TTY_OP_OSPEED: 14400, //output speed = 14.4kbaud
    }
    session.Stdout = os.Stdout // 会话输出关联到系统标准输出设备
    session.Stderr = os.Stderr // 会话错误输出关联到系统标准错误输出设备
    session.Stdin = os.Stdin   // 会话输入关联到系统标准输入设备

    //if err = session.RequestPty("linux", 32, 160, modes); err != nil {
    //if err = session.RequestPty("xterm", 32, 160, modes); err != nil {
    termWidth, termHeight, _ := terminal.GetSize(fd)
    if err = session.RequestPty("screen-256color", termHeight, termWidth, modes); err != nil {
        log.Fatalf("request pty error: %s", err.Error())
    }
    if err = session.Shell(); err != nil {
        log.Fatalf("start shell error: %s", err.Error())
    }
    if err = session.Wait(); err != nil {
        log.Fatalf("return error: %s", err.Error())
    }
}

证书登录

直接通过 ssh 生成的证书登录。

// parse the user's private key:
private, err := os.ReadFile("/tmp/id_cert_rsa")
if err != nil {
    return err
}
signer, err := gossh.ParsePrivateKey(private)
if err != nil {
    return err
}

// parse the user's certificate:
certs, err := os.ReadFile("/tmp/id_cert_rsa-cert.pub")
if err != nil {
    return err
}
cert, _, _, _, err := gossh.ParseAuthorizedKey(certs)
if err != nil {
    return err
}
// create a signer using both the certificate and the private key:
certSigner, err := gossh.NewCertSigner(cert.(*gossh.Certificate), signer)
if err != nil {
    return err
}

// use that signer as an auth method in our client config:
client, err := gossh.Dial("tcp", "127.0.0.1:22", &gossh.ClientConfig{
    User: "andy",
    Auth: []gossh.AuthMethod{
        gossh.PublicKeys(certSigner),
    },
    HostKeyCallback: gossh.InsecureIgnoreHostKey(),
})
if err != nil {
    return err
}

session, err := client.NewSession()
if err != nil {
    return err
}

var b bytes.Buffer
session.Stdout = &b
session.Run("ls /")
fmt.Println(b.String())

使用CA生成证书

// parse the user's private key:
private, err := os.ReadFile("/tmp/host_ca")
if err != nil {
    log.Printf("read private failed %v\n", err)
    return err
}
signer, err := gossh.ParsePrivateKey(private)
if err != nil {
    log.Printf("parse private failed %v\n", err)
    return err
}
//log.Printf("---> %v", gossh.PublicKeys(signer))

public, err := os.ReadFile("/tmp/ssh_host_rsa_key.pub")
if err != nil {
    log.Printf("read public failed %v\n", err)
    return err
}
key, _, _, _, err := gossh.ParseAuthorizedKey(public)
if err != nil {
    log.Printf("parse public failed %v\n", err)
    return err
}

/*
    key, err := gossh.ParsePublicKey(public)
    if err != nil {
        log.Printf("parse public failed %v\n", err)
        return err
    }
*/

cert := &gossh.Certificate{
    ValidPrincipals: []string{"127.0.0.1", "andy"},
    Key:             key,
    ValidBefore:     gossh.CertTimeInfinity,
    CertType:        gossh.HostCert,
}
if err := cert.SignCert(rand.Reader, signer); err != nil {
    log.Printf("sign cert failed %v\n", err)
    return err
}
log.Printf("---> %v\n", string(gossh.MarshalAuthorizedKey(cert)))

/*
    certSigner, err := gossh.NewCertSigner(cert, testSigners["rsa"])
    if err != nil {
        t.Errorf("NewCertSigner: %v", err)
    }
*/