小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
先说明下做这个的动机。起因是在写程序时需要程序在远程机器上执行一些命令,而每次都重新在命令行重复输入命令说实话感觉有点烦,为了有效的偷懒,不用每次都手动执行命令,我决定让程序自动去执行我们给定的命令。
然而碰巧的是,我无意间发现了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
指定的。user
和auth
还好理解,一个是我们登录的用户名,另一个是让我们使用密码做认证,那这最后一个是什么,咋还给了个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会话的需要。