golang教程:在golang中实现ssh相关操作的方法分享

本文是关于golang的教程分享文章,旨在帮助大家掌握golang中的ssh相关操作,希望能给大家带来帮助,详细内容请看本文。


golang教程:在golang中实现ssh相关操作的方法分享


golang教程:在golang中实现ssh相关操作的方法分享


SSH协议是建立在应用层上的安全协议,全称为Secure Shell,采用的是面向连接的TCP协议进行传输,也就意味着它是安全可靠的。需要注意的是文件传输并不能在SSH协议上完成,需要在下面提到的SFTP协议完成。Go官方为我们提供了用于实现SSH连接的package,位于golang.org/x/crypto下,通过在程序中调用包中提供的相关方法,便可以实现与其他机器进行通信。使用前我们需要使用go get导入相关的依赖包。


go get golang.org/x/crypto/ssh

配置参数:


在进行通信之前,我们还需要配置一些用于配置一些用于建立连接的相关参数。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)


建立连接:


在完成了所有的参数初始化之后,我们便可以调用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")
}


创建会话:


在建立了与目标机器的SSH连接之后,我们就可以通过创建SSH会话来与目标机器进行通信。通过NewSession()方法便可以实现这一操作。
//建立SSH会话
sshSession, err := sshClient.NewSession()       if err != nil {
   log.Fatal("unable to create ssh session")
}


执行操作:


与目标机器建立会话后,我们就可以通过执行命令等来操作远程服务器。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()
}



示例代码


这里我们使用Run()方法来演示一下如果去执行命令,其他方法类型就不做演示了。这里我们使用一个标准输出流、错误流来保存执行结果。


演示执行过程:


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)
}


创建伪终端:


    // 设置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()  // 等待退出


完整代码示例:


//机器平台信息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相关操作的方法分享就到这里,翼速应用平台内有更多相关资讯,欢迎查阅!


我来说两句

0 条评论

推荐阅读

  • 响应式布局CSS媒体查询设备像素比介绍

    构建响应式网站布局最常见的是流体网格,灵活调整大小的站点布局技术,确保用户在使用的幕上获得完整的体验。响应式设计如何展示富媒体图像,可以通过以下几种方法。

    admin
  • 提升网站的性能快速加载的实用技巧

    网站速度很重要,快速加载的网站会带来更好的用户体验、更高的转化率、更多的参与度,而且在搜索引擎排名中也扮演重要角色,做SEO,网站硬件是起跑线,如果输在了起跑线,又怎么跟同行竞争。有许多方法可提升网站的性能,有一些技巧可以避免踩坑。

    admin
  • 织梦CMS TAG页找不到标签和实现彩色标签解决方法

    织梦cms是我们常见的网站程序系统的一款,在TAG标签中常常遇到的问题也很多。当我们点击 tags.php 页的某个标签的时候,有时会提示:“系统无此标签,可 能已经移除!” 但是我们检查程序后台,以及前台显示页面。这个标签确实存在,如果解决这个问题那?

    admin
  • HTML关于fieldset标签主要的作用

    在前端开发html页面中常用的标签很多,今天为大家带来的是关于HTML中fieldset标签主要的作用说明,根据技术分析HTML

    admin

精选专题