Guide to Implement an SSH Client Using Golang

  sonic0002        2023-11-11 09:19:29       7,986        0          English  简体中文  繁体中文  ภาษาไทย  Tiếng Việt 

SSH,全称 Secure Shell,是一种网络协议,用于安全地远程登录到网络上的其他计算机。我相信大多数后端开发人员都熟悉 SSH。用于登录服务器的常用 shell 工具,如 Xshell、SecureCRT 和 iTerm2,都是基于 SSH 协议的。在 Golang 中,crypto/ssh 包提供了实现 SSH 客户端的功能。在本文中,我们将详细解释如何使用 Golang 实现 SSH 客户端。

创建 SSH 客户端配置

首先,我们需要配置 SSH 客户端连接到服务器的参数。基本配置包括 usernameauthentication methodhost 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 客户端,您需要注意许多细节,例如错误处理、重新连接、超时、信号处理等等。

SSH  GUIDE  GOLANG  SSH CLIENT 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

What an embarrassment

When someone wants to hire someone with 7-10 years NodeJS experience, the NodeJS author jumped out and said that he had just 7 years experience of NodeJS because NodeJS was created in 2009, which is 7 years ago. What an embarrassment for the recruiter.