如何用Go维持一个SSH会话

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

先说明下做这个的动机。起因是在写程序时需要程序在远程机器上执行一些命令,而每次都重新在命令行重复输入命令说实话感觉有点烦,为了有效的偷懒,不用每次都手动执行命令,我决定让程序自动去执行我们给定的命令。

然而碰巧的是,我无意间发现了go官方编写了一个ssh的库,不过现在还没有作为稳定的标准库。甚至在它文档的overview里还赫然写着提示:不保证稳定性,api随时可能变动。

我们主要试试水,而且一般 api 定了也不会有太大的改动。

首先这个ssh库不是直接提供的,为此我们需要下载,包的名称是 golang.org/x/crypto/ssh

通过ssh.Dail建立一个连接,然后由这个连接为我们维持一个会话,之后这个会话就可以让我们运行相应的命令了。代码如下:

  config := &ssh.ClientConfig{
    User: "username",
    Auth: []ssh.AuthMethod{ssh.Password("password")},
    HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  }
  client, err := ssh.Dial("tcp", "host:port", config)
  if err != nil {
    log.Fatalf(err.Error())
  }
  defer client.Close()
  session, err := client.NewSession()
  if err != nil {
    panic(err)
  }
  defer session.Close()
复制代码

先是最简单的密码登录,登录用的密码和账号是通过给定的ClientConfig指定的。userauth还好理解,一个是我们登录的用户名,另一个是让我们使用密码做认证,那这最后一个是什么,咋还给了个Insecure Ignore HostKey 这个回调,怎么是什么不安全地忽略。其实这里是做一个安全性检查。

我们在手动使用ssh登录远程主机地时候,如果遇到一个以前从来没有连接过的主机,是不是会让我们输入yes确认,然后ssh程序会自动把验证信息放到 .ssh 文件夹下地 known_hosts 文件里。以后再连接地时候用这个文件做对比,避免遭受一些劫持攻击。这里HostKeyCallback想要做的就是这个。

我们这里主要试试水,这样写不校验也没什么关系。在生产环境那种对安全性要求比较严格地情况下,就需要实现了。令人欣慰地是,go官方为我们提供了一个ssh的子包 known_hosts ,我们可以直接使用,不需要我们手动解析文件了。只需要导入这个包 golang.org/x/crypto/ssh/knownhosts,然后在代码里指定known_hosts文件的位置就可以了。如下所示:

hostKeyCallback, err := knownhosts.New("~/.ssh/known_hosts")
if err != nil {
    log.Fatal(err)
}

复制代码

然后在config里把相应的回调函数指定为这个就行了。

rsa私钥登录

既然在使用ssh,怎么能少了我们的私钥登陆呢。幸运的是,go的官方也给我们提供了相应的方法。

具体来讲只需要像下面这样

  key, err := ioutil.ReadFile("~/.ssh/id_rsa")
  if err != nil {
    log.Fatalf(err.Error())
  }
  signer, err := ssh.ParsePrivateKey(key)
  if err != nil {
    log.Fatalf(err.Error())
  }

复制代码

然后把配置Auth改成使用rsa登录:

  Auth: []ssh.AuthMethod{
      ssh.PublicKeys(signer),
  },
复制代码

这样就完成了ssh的rsa登录。

最后我们尝试用它来运行一下命令,把它的输出都重定向一下,直接观察输出结果。(实际上也可以不重定向,go还提供了例外几个api比如Output,CombinedOutput,他们把输出作为返回值)

  session.Stdout = os.Stdout
  session.Stderr = os.Stderr
  if err := session.Run("spark-submit app.jar"); err != nil {
    log.Fatal(err.Error())
  }
复制代码

运行后稍等一会儿,相应的输出会显示在屏幕上。

不得不说,go的这个库还是很良心的,无论是密码登录,还是rsa登录都为我们提供了相应的实现,基本能满足我们对ssh会话的需要。

猜你喜欢

转载自juejin.im/post/7019690960900587550