Go语言 ssh
Go ssh 交互式执行命令 - 漠然的博客
SSH Client connection in Golang · Software adventures and thoughts
认证方法 rfc4252
3. 使用ssh agent的公钥认证
为解决每次登陆远程机器都需要输入passphrase的问题,ssh-agent被引入了。ssh-agent启动后,可通过ssh-add将私钥加入agent. ssh-add会提示用户输入passphrase以解密私钥,然后将解密后的私钥纳入agent管理。agent可同时管理多个私钥。如果ssh-agent中有多个私钥, 会依次尝试,直到认证通过或遍历所有私钥
passphrase和 passcode都是password的一种,只不过
passphrase是满足了一定规则约束的password, 安全性要高一些;
passcode指由纯数字组成的password
// 1. 根据privte key 或 passwd 生成 ssh.Signer
`signer, err := ssh.ParsePrivateKey(key)`
// 2. 根据 ssh.Signer 生成 []ssh.AuthMethod
`AuthMethod := ssh.PublicKeys(signer)`
// 3. 根据 ssh.AuthMethod 生成 ssh.ClientConfig
ClientConfig := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
ClientConfig.SetDefaults()
// 4. 根据 ssh.ClientConfig 创建 ssh.Clent
`client, err := ssh.Dial("tcp", "host.com:22", ClientConfig)`
// 5. 创建 session
`session, err := client.NewSession()
defer session.Close()`
// 6. 运行
`err := session.Run("/usr/bin/ls")`
v1 和 v2 存在一些区别:What’s the difference between versions 1 and 2 of the SSH protocol?
type AuthMethod interface {
// contains filtered or unexported methods
}
func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod
func Password(secret string) AuthMethod
func PasswordCallback(prompt func() (secret string, err error)) AuthMethod
func PublicKeys(signers ...Signer) AuthMethod
func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod
func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod
有以上几种认证方法,其中 RetryableAuthMethod 方法就是将其他的方法封装一下,然后如果失败使用重试机制,这样确保其他原因导致的连接失败,参数 maxTries 表示重试次数,如果 <= 0 ,则会无限期的重试,直到成功。
PKI - Public Key Infrastructure
// 官方示例:https://godoc.org/golang.org/x/crypto/ssh
var hostKey ssh.PublicKey
// A public key may be used to authenticate against the remote
// server by using an unencrypted PEM-encoded private key file.
//
// If you have an encrypted private key, the crypto/x509 package
// can be used to decrypt it.
key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa")
if err != nil {
log.Fatalf("unable to read private key: %v", err)
}
// 1. Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("unable to parse private key: %v", err)
}
// 2.
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", "host.com:22", config)
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("xxxxx"),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
client, err := ssh.Dial("tcp", "host:port", config)
if err != nil {
panic("Failed to dial: " + err.Error())
}
session, err := client.NewSession()
if err != nil {
panic("Failed to create session: " + err.Error())
}
defer session.Close()
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("/usr/bin/ls"); err != nil {
panic("Failed to run: " + err.Error())
}
Client
type Client struct {
Conn
// contains filtered or unexported fields
}
func Dial(network, addr string, config *ClientConfig) (*Client, error)
建立 Client:
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("xxxxx"),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
client, err := ssh.Dial("tcp", "host:port", config)
if err != nil {
panic("Failed to dial: " + err.Error())
}
我们也可以建立Client,后针对服务端进行一些系统的动作,比如:
端口监听:client.Listen("tcp", "0.0.0.0:8080")
服务器和别的机器又建立联系:func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error)
建立会话:func (c *Client) NewSession() (*Session, error),这样就可以在这个会话上进行一系列的操作了,比如运行命令什么的
…
建立 Client 的时候,我们需要用到一些配置:
type ClientConfig struct {
Config
User string
Auth []AuthMethod
HostKeyCallback HostKeyCallback
ClientVersion string
HostKeyAlgorithms []string
Timeout time.Duration
}
例如:
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("pwd"),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
Session
往往我们通过 Session ,和服务器进行一系列的交互操作。
type Session struct {
// Stdin specifies the remote process's standard input.
// If Stdin is nil, the remote process reads from an empty
// bytes.Buffer.
Stdin io.Reader
// Stdout and Stderr specify the remote process's standard
// output and error.
//
// If either is nil, Run connects the corresponding file
// descriptor to an instance of ioutil.Discard. There is a
// fixed amount of buffering that is shared for the two streams.
// If either blocks it may eventually cause the remote
// command to block.
Stdout io.Writer
Stderr io.Writer
// contains filtered or unexported fields
}
交互操作:
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("xxxx"),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
client, err := ssh.Dial("tcp", "host:port", config)
if err != nil {
panic("Failed to dial: " + err.Error())
}
// Each ClientConn can support multiple interactive sessions,
// represented by a Session.
session, err := client.NewSession()
if err != nil {
panic("Failed to create session: " + err.Error())
}
defer session.Close()
// 需要重定向才能在当前机器上显示对应的结果
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
modes := ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// 设置交互会话窗口属性
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
session.Close()
fmt.Printf("request for pseudo terminal failed: %s", err)
}
session.Run("top")