SSH,全称 Secure Shell,是一种网络协议,用于安全地远程登录到网络上的其他计算机。我相信大多数后端开发人员都熟悉 SSH。用于登录服务器的常用 shell 工具,如 Xshell、SecureCRT 和 iTerm2,都是基于 SSH 协议的。在 Golang 中,crypto/ssh
包提供了实现 SSH 客户端的功能。在本文中,我们将详细解释如何使用 Golang 实现 SSH 客户端。
创建 SSH 客户端配置
首先,我们需要配置 SSH 客户端连接到服务器的参数。基本配置包括 username
、authentication method
和 host key callback
。这是一个示例代码片段:
package main
import "golang.org/x/crypto/ssh"
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
}
在此配置中,用户名设置为“username”,密码设置为“password”。ssh.Password
函数用于创建密码身份验证方法。每次连接到新主机时都会调用 HostKeyCallback
函数,它用于验证服务器的主机密钥。在此示例中,使用了 ssh.InsecureIgnoreHostKey
,这意味着接受任何主机密钥。不建议在生产环境中使用此方法,因为不验证主机密钥会带来安全风险。
连接到 SSH 服务器
ssh.Dial
函数用于连接到远程 SSH 服务器。它需要三个参数:网络类型(通常为“tcp”)、服务器的地址和端口以及之前创建的配置对象。这是一个示例代码片段:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
}
使用 IP 地址 192.168.3.111 和端口 22(SSH 协议的默认端口)连接到服务器。如果连接失败,将返回一个错误,您可以使用 log.Fatal
打印错误并退出程序。
创建 SSH 会话
一旦建立 SSH 连接,您就可以创建一个 SSH 会话来与服务器通信。这是一个示例代码片段:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
}
使用 client.NewSession
函数创建一个新会话,如果失败,将返回一个错误。使用 defer
关键字确保在操作完成后关闭会话。
创建伪终端
SSH 协议允许创建伪终端,它模拟真实终端的行为,并允许交互式命令,例如 shell 或文本编辑器。这是一个示例代码片段:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
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("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
}
使用 session.RequestPty
函数请求伪终端。它需要四个参数:终端类型(在本例中为“xterm”)、终端的宽度和高度以及用于设置终端模式的映射。在此映射中,ssh.ECHO
设置为 0,这将禁用回显,并且输入和输出速度设置为 14.4 kbaud。
设置输入和输出
为远程 shell 指定标准输入和输出。这是一个示例代码片段:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"os"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
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("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
//set input and output
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
}
启动远程 Shell
远程 shell 现在已准备就绪,您可以使用 session.Shell
函数启动默认 shell,或使用 session.Run
函数执行特定命令。这是一个示例代码片段:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
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("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
// set input and output
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
if err := session.Shell(); err != nil {
log.Fatal("failed to start shell: ", err)
}
}
启动默认 shell,如果启动失败,将返回一个错误。
等待会话结束
使用 session.Wait
函数阻塞,直到会话结束。这是一个示例代码片段:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"os"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
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("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
//set input and output
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
if err := session.Shell(); err != nil {
log.Fatal("failed to start shell: ", err)
}
err = session.Wait()
if err != nil {
log.Fatal("Failed to run: " + err.Error())
}
}
至此,您已完成基本 SSH 客户端的实现。此客户端可以连接到 SSH 服务器,启动远程 shell,并等待会话结束。
总结
本文中实现的 SSH 客户端仅提供最基本的功能。要创建功能丰富且用户友好的 SSH 客户端,您需要注意许多细节,例如错误处理、重新连接、超时、信号处理等等。