您好,欢迎访问一九零五行业门户网

一文详解golang如何实现ssh相关操作

本文由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相关操作的详细内容。
其它类似信息

推荐信息