本文由golang教程栏目给大家介绍关于golang是怎么实现ssh连接等相关操作的,不知道大家对ssh了解多少?下面就给大家详细聊聊go实现ssh相关操作问题,希望对需要的朋友有所帮助!
1.ssh1.1 前言在日常的一些开发场景中,我们需要去和远程服务器进行一些通信,执行一些相关命令操作,这个时候我们就可以使用ssh协议实现目标。ssh协议是建立在应用层上的安全协议,全称为secure shell,采用的是面向连接的tcp协议进行传输,也就意味着它是安全可靠的。需要注意的是文件传输并不能在ssh协议上完成,需要在下面提到的sftp协议完成。
1.2 go实现go官方为我们提供了用于实现ssh连接的package,位于golang.org/x/crypto下,通过在程序中调用包中提供的相关方法,便可以实现与其他机器进行通信。使用前我们需要使用go get导入相关的依赖包。
go get golang.org/x/crypto/ssh
1.2.1 配置相关参数在进行通信之前,我们还需要配置一些用于配置一些用于建立连接的相关参数。ssh包下的clientconfig结构体中,定义了建立ssh连接需要用到的一些配置项,部分项提供了默认参数,我们使用时可以不进行声明。
下面的代码段中,我们首先是声明了用户名和密码,连接超时时间设置为10秒钟,addr变量定义了目标机器的ip地址以及端口。
hostkeycallback项,我们设置了忽略,这是因为ssh协议为客户端提供了两种安全验证方式,一种是基于口令的安全验证,也就是我们常常使用的账号密码形式,另外一种则是基于密钥的安全验证,相较于第一种,这种形式的校验方法极大的提升了安全等级,缺点则是时间损耗相对较长。
如果需要使用这种方式进行校验,首先我们需要在服务器上为自己创建一对密钥,作为客户端进行访问时,首先会向服务端发送安全验证请求,服务端收到请求后,首先会将机器上保存的公钥与客户端发送的公钥进行比较,如果一致,服务端则会向客户端响应加密质询,客户端接受到质询之后,使用私钥进行解密,然后再将解密结果发送给服务端,服务端进行校验后再返回响应结果,到这里就算是完成了一段密钥校验。
//添加配置 config := &ssh.clientconfig{ user: root, auth: []ssh.authmethod{ssh.password(password)}, hostkeycallback: ssh.insecureignorehostkey(), timeout: 10 * time.second, } } addr := fmt.sprintf(%v:%v, ip, port)
1.2.2 建立连接在完成了所有的参数初始化之后,我们便可以调用dial方法建立ssh连接。dial方法一共有三个参数和两个返回值,第一个参数network为网络类型,这里我们使用面向连接的tcp协议,第二个参数addr则为目标机器的ip地址和端口号,第三个参数config则为前面我们生命的配置项。dial会返回一个ssh连接和错误类型。
func dial(network, addr string, config *clientconfig) (*client, error)
//建立ssh连接 sshclient, err := ssh.dial(tcp, addr, config) if err != nil { log.fatal(unable to create ssh conn) }
1.2.3 创建会话在建立了与目标机器的ssh连接之后,我们就可以通过创建ssh会话来与目标机器进行通信。通过newsession()方法便可以实现这一操作。
//建立ssh会话 sshsession, err := sshclient.newsession() if err != nil { log.fatal(unable to create ssh session) }
1.2.4 执行操作与目标机器建立会话后,我们就可以通过执行命令等来操作远程服务器。go目前为我们提供了五个用于操作远程机器的方法,分别是run(), start(), output(), combineoutpt(), shell()。
?其中 output(), **combineoutpt()**这两个方法是对run()方法进行不同程度上的封装,校验了输出流,错误流等相关内容。
// output runs cmd on the remote host and returns its standard output. func (s *session) output(cmd string) ([]byte, error) { if s.stdout != nil { return nil, errors.new(ssh: stdout already set) } var b bytes.buffer s.stdout = &b err := s.run(cmd) return b.bytes(), err } // combinedoutput runs cmd on the remote host and returns its combined // standard output and standard error. func (s *session) combinedoutput(cmd string) ([]byte, error) { if s.stdout != nil { return nil, errors.new(ssh: stdout already set) } if s.stderr != nil { return nil, errors.new(ssh: stderr already set) } var b singlewriter s.stdout = &b s.stderr = &b err := s.run(cmd) return b.b.bytes(), err }
run()方法则是对start()方法进行了封装,添加了wait方法,用于校验远程服务器的退出指令。wait()方法中有一个管道类型的变量exitstatus,它是用来保存每次执行命令后,机器返回的退出状态的。有兴趣的朋友可以去看看这块的代码,这里就不贴代码了。
这里面有一个坑,如果我们在远程机器上去运行一个永远不会停止的程序,这个时候我们的程序一直等待不到远程机器发送的退出指令,就会造成程序一直阻塞而无法正常返回。解决的办法是用一个协程去单独执行这一块的任务,或者是使用定时器来定时结束session会话,来正常返回。
start()方法与shell方法一致,都是返回一个error类型,在底层都是调用了start()方法和sendrequest方法,关于这两个方法的内容这里就不做详细介绍了,有兴趣的朋友可以自行去阅读。唯一的区别是start()方法有一个string类型的参数,用于接收用户输入的参数,而shell()方法是无参数的。
使用shell()方法配合requestpty()等方法可以在本地建立一个伪终端,可以直接通过输入命令的形式操作目标机器。下面都会做一个示例。
//run func (s *session) run(cmd string) error { err := s.start(cmd) if err != nil { fmt.println(err) return err } return s.wait() } // start runs cmd on the remote host. typically, the remote // server passes cmd to the shell for interpretation. // a session only accepts one call to run, start or shell. func (s *session) start(cmd string) error { if s.started { return errors.new(ssh: session already started) } req := execmsg{ command: cmd, } ok, err := s.ch.sendrequest(exec, true, marshal(&req)) if err == nil && !ok { err = fmt.errorf(ssh: command %v failed, cmd) } if err != nil { return err } return s.start() }
1.2.5 示例代码(执行命令)这里我们使用run()方法来演示一下如果去执行命令,其他方法类型就不做演示了。这里我们使用一个标准输出流、错误流来保存执行结果。
这里演示了一个简单的执行过程,使用了cd命令到/home/min目录下,在给helloworld程序添加可执行权限,最后运行程序。
var stdoutbuf, stderrbuf bytes.buffer session.stdout = &stdoutbuf session.stderr = &stderrbuf // cd /home/min // chmod +x helloworld // ./helloworld cmd := fmt.sprintf(cd %v ; chmod +x %v ; %v &, /home/min, helloworld, ./helloworld) err := session.run(cmd) if err != nil { log.fatal([error]: , session.stderr, err) }
1.2.6(创建伪终端) // 设置terminal mode modes := ssh.terminalmodes{ ssh.echo: 0, // 关闭回显 ssh.tty_op_ispeed: 14400, // 设置传输速率 ssh.tty_op_ospeed: 14400, } // 请求伪终端 err = session.requestpty(linux, 32, 160, modes) if err != nil { log.println(err) return } // 设置输入输出 session.stdout = os.stdout session.stdin = os.stdin session.stderr = os.stderr session.shell() // 启动shell session.wait() // 等待退出
1.2.7 完整代码//机器平台信息type machine struct { ip string port string username string password string}//建立ssh连接func createsshconn(m *model.machine) error { //初始化连接信息 config := &ssh.clientconfig{ user: m.username, auth: []ssh.authmethod{ssh.password(m.password)}, hostkeycallback: ssh.insecureignorehostkey(), timeout: 10 * time.second, } addr := fmt.sprintf(%v:%v, m.ip, m.port) //建立ssh连接 sshclient, err := ssh.dial(tcp, addr, config) if err != nil { fmt.println(unable create ssh conn, err) return err } defer sshclient.close() //建立ssh会话 session, err := sshclient.newsession() if err != nil { fmt.println(unable create ssh conn, err) return err } defer session.close() //执行命令 var stdoutbuf, stderrbuf bytes.buffer session.stdout = &stdoutbuf session.stderr = &stderrbuf cmd := fmt.sprintf(cd %v ; chmod +x %v ; %v &, /home/min, helloworld, ./helloworld) if err := session.run(cmd); err != nil { log.fatal([error]: , session.stderr, err) } //创建伪终端 // 设置terminal mode modes := ssh.terminalmodes{ ssh.echo: 0, // 关闭回显 ssh.tty_op_ispeed: 14400, // 设置传输速率 ssh.tty_op_ospeed: 14400, } // 请求伪终端 err = session.requestpty(linux, 32, 160, modes) if err != nil { log.fatal(err) } // 设置输入输出 session.stdout = os.stdout session.stdin = os.stdin session.stderr = os.stderr session.shell() // 启动shell session.wait() // 等待退出 return err}
以上就是一文详解golang如何实现ssh相关操作的详细内容。
