Home > Backend Development > Golang > An article explains in detail how golang implements ssh related operations

An article explains in detail how golang implements ssh related operations

藏色散人
Release: 2022-11-09 21:09:26
forward
4256 people have browsed it

This article is introduced by the golang tutorial column to introduce to you how golang implements SSH connections and other related operations. I wonder how much you know about SSH? Let me talk to you in detail about the operational issues related to Go's implementation of ssh. I hope it will be helpful to friends who need it!

An article explains in detail how golang implements ssh related operations

1.ssh

1.1 Introduction

In some daily development scenarios, we need to communicate with the remote server Some communication and execution of some related command operations. At this time we can use the SSH protocol to achieve the goal. The SSH protocol is a security protocol built on the application layer. Its full name is Secure Shell. It uses the connection-oriented TCP protocol for transmission, which means it is safe and reliable. It should be noted that file transfer cannot be completed on the SSH protocol and needs to be completed on the SFTP protocol mentioned below.

1.2 Go implementation

Go official provides us with a package for implementing SSH connections, located under golang.org/x/crypto, which is provided by calling the package in the program Related methods can be used to communicate with other machines. Before use, we need to use go get to import related dependency packages.

go get golang.org/x/crypto/ssh
Copy after login

1.2.1 Configure related parameters

Before communicating, we also need to configure some related parameters for Configuring some related parameters for establishing a connection. The ClientConfig structure under the ssh package defines some configuration items needed to establish an SSH connection. Some items provide default parameters, which we do not need to declare when using them.

In the code snippet below, we first declare the username and password, set the connection timeout to 10 seconds, and the addr variable defines the IP address and port of the target machine.

HostKeyCallback item, we set it to ignore. This is because the SSH protocol provides two security verification methods for the client. One is password-based security verification, which is the account password form we often use. In addition, One is key-based security verification. Compared with the first type, this form of verification method greatly improves the security level. The disadvantage is that it takes a relatively long time.

If we need to use this method for verification, first we need to create a pair of keys for ourselves on the server. When accessing as a client, we will first send a security verification request to the server, and the server will receive After receiving the request, it will first compare the public key saved on the machine with the public key sent by the client. If they are consistent, the server will respond to the client with an encrypted challenge. After receiving the challenge, the client will use the private key to decrypt. Then the decryption result is sent to the server, and the server performs verification and then returns the response result. At this point, a period of key verification is completed.

        //添加配置
        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)
Copy after login

1.2.2 Establishing a connection

After completing all parameter initialization, we can call the Dial method to establish an SSH connection. The Dial method has a total of three parameters and two return values. The first parameter network is the network type. Here we use the connection-oriented TCP protocol. The second parameter addr is the IP address and port number of the target machine. The third parameter is the network type. The parameter config is the configuration item of our previous life. Dial will return an SSH connection and error type.

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")
        }
Copy after login

1.2.3 Create session

After establishing SSH with the target machine Once connected, we can communicate with the target machine by creating an SSH session. This operation can be achieved through the NewSession() method.

        //建立SSH会话
        sshSession, err := sshClient.NewSession()       if err != nil {
           log.Fatal("unable to create ssh session")
        }
Copy after login

1.2.4 Perform operations

After establishing a session with the target machine, we can operate the remote server by executing commands, etc. Go currently provides us with five methods for operating remote machines, namely Run(), Start(), Output(), CombineOutpt(), Shell().

? Among them, Output() and **CombineOutpt()** are two methods that encapsulate the Run() method to varying degrees and verify the output stream and error stream. and other related content.

        // 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
        }
Copy after login

The Run() method encapsulates the Start() method and adds a Wait method to verify the exit command of the remote server. There is a pipe type variable exitStatus in the Wait() method, which is used to save the exit status returned by the machine after each command is executed. Friends who are interested can take a look at the code of this piece, but the code will not be posted here.

There is a pitfall here. If we run a program on a remote machine that will never stop, and our program has not been waiting for the exit command sent by the remote machine, it will cause the program to be blocked. Unable to return normally. The solution is to use a coroutine to execute this task separately, or use a timer to end the session regularly to return normally.

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()
        }
Copy after login

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)
        }
Copy after login

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()  // 等待退出
Copy after login

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
}
Copy after login

The above is the detailed content of An article explains in detail how golang implements ssh related operations. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:juejin.im
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template