什么是SSH
SSH,全称Secure Shell,是一种网络协议,用于在不安全的网络中提供安全的加密通信。SSH主要用于远程登录会话和其他网络服务的安全性,通过加密和认证机制,有效防止信息泄露,保护设备不受IP地址欺诈、明文密码截取等攻击。SSH基于客户端/服务器结构,客户端使用SSH协议登录到服务器,进行远程访问或文件传输等操作。包括建立连接、版本号协商、密钥交换与算法协商、用户认证、会话请求、会话交互和会话关闭七个阶段。SSH已被全世界广泛使用,成为Linux系统的标准配置,支持远程登录、端口转发等多种应用场景。
SSHClient原理
-
建立连接:
- SSH客户端首先与SSH服务器建立TCP连接,这通常发生在TCP的22端口上。
-
版本协商:
- 连接建立后,客户端和服务器会进行版本协商,以确定双方支持的SSH协议版本。
-
密钥交换与算法协商:
- 接下来,客户端和服务器会进行密钥交换,这通常涉及Diffie-Hellman算法,以生成一个共享的会话密钥。
- 同时,双方还会协商使用哪种加密算法(如AES、DES等)和认证方法(如密码认证、公钥认证等)。
-
用户认证:
- 一旦密钥交换和算法协商完成,SSH客户端就需要进行用户认证。
- 这通常涉及输入密码或使用公钥进行认证。如果使用的是公钥认证,客户端会向服务器发送其公钥,服务器则使用相应的私钥进行验证。
-
会话请求与建立:
- 认证成功后,客户端会向服务器发送一个会话请求,请求建立一个加密的SSH会话。
- 服务器接受请求后,双方会建立一个加密的通道,用于后续的通信。
-
数据交换:
- 在加密的SSH会话中,客户端和服务器可以安全地交换数据,如执行远程命令、传输文件等。
- 所有的数据都会经过加密处理,以确保其机密性和完整性。
-
会话关闭:
- 当客户端完成所有操作后,它会向服务器发送一个会话关闭请求,以结束当前的SSH会话。
- 服务器接受请求后,双方会断开连接,结束通信。
利用现有的包开发SSHClient
在了解了SSHClient实现原理后,我们并不用根据协议自己从底层开始开发。我们可以借助golang官方提供的第三方包进行开发。我们首先需要在项目中添加两个依赖。
go get golang.org/x/crypto
go get golang.org/x/term
添加依赖后,我们可以通过调用ssh包完成ssh-cli的创建。
ssh_cli_config := &ssh.ClientConfig{
User: cmd.SSHClientConfig.User,
Auth: []ssh.AuthMethod{
ssh.Password(cmd.SSHClientConfig.Password),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// 这里的Host和Port填写目标服务器的IP和端口
ssh_client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", Host, Port), ssh_cli_config)
if err != nil {
panic(err)
}
在创建ssh-cli,我们可以通过ssh-cli打开一个和服务端的会话。这个会话可以用于我们与服务端进行交流。
session, err := ssh_client.NewSession()
if err != nil {
panic(err)
}
defer session.Close()
在这之后,我们需要通过term包将标准输入设置为原始模式,并在函数结束时恢复标准输入的原始状态。好奇原因的话,写完后可以删除这段代码再跑一遍对比看看就明白了。
state, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer term.Restore(int(os.Stdin.Fd()), state)
接下来,我们需要配置终端模式并请求一个伪终端。
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
ssh_terminal_modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
err = session.RequestPty("xterm", 80, 40, ssh_terminal_modes)
if err != nil {
panic(err)
}
最后,在远程服务器上启动一个交互式Shell,阻塞等待直到Shell会话结束即可。
err = session.Shell()
if err != nil {
panic(err)
}
err = session.Wait()
if err != nil {
panic(err)
}