go语言实现ssh打隧道

5,557 阅读3分钟

如何打隧道

使用ssh打个隧道,是我一直想做的事情。出发点是:在公司内部经常有些机器访问不到,只能通过公司提供的开发机,但是开发机我们可以在内网访问到,这个

基础知识

SSH是一种网络协议,用于计算机之间的加密登录,流程大致是:

  1. 远程主机收到用户的登录请求,把自己的公钥发给用户。
  2. 用户使用这个公钥,将登录密码加密后,发送回来。
  3. 远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。

上面这个过程中很容易受到攻击的就是第一步,我们怎么知道收到的公钥是真正的主机的,体现在实际登陆中,就是会出现:

 $ ssh user@host

  The authenticity of host 'host (12.18.429.21)' can't be established.

  RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.

  Are you sure you want to continue connecting (yes/no)?

此处RSA key fingerprint就是对128公钥的MD5签名,当输入yes后,就会保存到$HOME/.ssh/known_hosts,说明是对公钥的认可。

解决了信任问题后,下一个不好的是每次都需要输入密码,于是就有了公钥登陆,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。

所以总结起来 ssh 认证的方式有两种:

  1. 用户名 + 证书
  2. 用户名 + 密码

下面我们来看go中相应的操作方法

golang 实现 ssh client

go get -u golang.org/x/crypto/...

先来说2种认证方式,显示密码,通过ssh.Password来传入密码

sshConfig := &ssh.ClientConfig{
	User: "your_user_name",
	Auth: []ssh.AuthMethod{
		ssh.Password("your_password")
	},
}

第二种是证书的方式,这又细分为两种,一种是我们直接读取自己的私钥,另一种是从ssh-agent读取,ssh-agent是用来帮助我们管理私钥的程序,它的出现主要是为了解决当我们访问不同的主机使用不同的私钥-公钥对,同时解决如果设置了私钥的密码,需要每次都手动设置的问题。 ps:ssh-agent的管理

eval `ssh-agent` 启动agent代理
ssh-add /path/to/key/key_name 添加指定私钥
ssh-agent -k 关闭 agent
ssh-add -l 查看代理中私钥
ssh-add -L 查看代理中私钥对应的公钥
ssh-add -d /path/to/key/key_name 移除指定私钥
ssh-add -D 删除管理的所有私钥

从文件读取

func PublicKeyFile(file string) ssh.AuthMethod {
	buffer, err := ioutil.ReadFile(file)
	if err != nil {
		return nil
	}

	key, err := ssh.ParsePrivateKey(buffer)
	if err != nil {
		return nil
	}
	return ssh.PublicKeys(key)
}

从 ssh-agent 读取

func SSHAgent() ssh.AuthMethod {
	if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
		return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
	}
	return nil
}

client生成

当我们有了认证方式后,下面就是生成ssh-client

sshClient, err := ssh.Dial("tcp", "host:port", sshConfig)
if err != nil {
	return nil, fmt.Errorf("Failed to dial: %s", err)
}

建立连接后,我们要创建一个命令执行的session,一个session就是一次命令执行,在开始执行命令之前,我们还要建立一个伪终端,方便输入输出

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()
	return nil, fmt.Errorf("request for pseudo terminal failed: %s", err)
}

再然后我们就可以开始执行代码,完整代码见ssh_client

参考

how-to-create-an-ssh-tunnel-in-go

golang中ssh基础操作

基础知识