Heim  >  Artikel  >  Backend-Entwicklung  >  In einem Artikel wird ausführlich erläutert, wie Golang SSH-bezogene Vorgänge implementiert

In einem Artikel wird ausführlich erläutert, wie Golang SSH-bezogene Vorgänge implementiert

藏色散人
藏色散人nach vorne
2022-11-09 16:01:374240Durchsuche

Dieser Artikel wurde in der Kolumne „Golang-Tutorial“ verfasst, um Ihnen vorzustellen, wie Golang SSH-Verbindungen und andere verwandte Vorgänge implementiert. Ich frage mich, wie viel Sie über SSH wissen. Lassen Sie mich ausführlich über die betrieblichen Probleme im Zusammenhang mit der SSH-Implementierung in Go sprechen. Ich hoffe, dass es Freunden, die es benötigen, hilfreich sein wird!

In einem Artikel wird ausführlich erläutert, wie Golang SSH-bezogene Vorgänge implementiert1.ssh

1.1 Vorwort

In einigen täglichen Entwicklungsszenarien müssen wir mit dem Remote-Server kommunizieren und einige zugehörige Befehlsvorgänge ausführen. Zu diesem Zeitpunkt können wir das SSH-Protokoll verwenden, um das Ziel zu erreichen . Das SSH-Protokoll ist ein Sicherheitsprotokoll, das auf der Anwendungsschicht basiert. Sein vollständiger Name ist Secure Shell. Es verwendet das verbindungsorientierte TCP-Protokoll für die Übertragung, was bedeutet, dass es sicher und zuverlässig ist. Es ist zu beachten, dass die Dateiübertragung nicht über das SSH-Protokoll abgeschlossen werden kann und über das unten genannte SFTP-Protokoll abgeschlossen werden muss.

1.2 Go-Implementierung

Go stellt uns offiziell ein Paket zur Implementierung von SSH-Verbindungen zur Verfügung, das sich unter golang.org/x/crypto befindet. Durch Aufrufen der im Paket bereitgestellten relevanten Methoden können Sie die Kommunikation mit anderen Maschinen erreichen kommunizieren. Vor der Verwendung müssen wir go get verwenden, um verwandte Abhängigkeitspakete zu importieren.

go get golang.org/x/crypto/ssh

1.2.1 Zugehörige Parameter konfigurieren

Vor der Kommunikation müssen wir auch einige

Zugehörige Parameter konfigurieren

für den Verbindungsaufbau konfigurieren. Die ClientConfig-Struktur unter dem SSH-Paket definiert einige Konfigurationselemente, die zum Herstellen einer SSH-Verbindung erforderlich sind. Einige Elemente stellen Standardparameter bereit, die wir bei ihrer Verwendung nicht deklarieren müssen. Im folgenden Codeausschnitt deklarieren wir zunächst den Benutzernamen und das Passwort, legen das Verbindungszeitlimit auf 10 Sekunden fest und die Variable addr definiert die IP-Adresse und den Port des Zielcomputers.

HostKeyCallback-Element, wir haben es auf Ignorieren eingestellt, weil das SSH-Protokoll zwei Sicherheitsüberprüfungsmethoden für den Client bereitstellt. Eine davon ist die passwortbasierte Sicherheitsüberprüfung, die wir häufig verwenden, und die andere ist „Verglichen“. Diese Form der Überprüfungsmethode ist die erste Art der schlüsselbasierten Sicherheitsüberprüfung und verbessert das Sicherheitsniveau erheblich. Der Nachteil besteht jedoch darin, dass sie relativ lange dauert.

Wenn wir diese Methode zur Überprüfung verwenden müssen, müssen wir zunächst ein Schlüsselpaar für uns selbst auf dem Server erstellen. Beim Zugriff als Client senden wir zunächst eine Sicherheitsüberprüfungsanfrage an den Server Anfrage: Zunächst wird der auf dem Computer gespeicherte öffentliche Schlüssel mit dem vom Client gesendeten öffentlichen Schlüssel verglichen. Wenn diese konsistent sind, antwortet der Server dem Client mit einer Verschlüsselungsaufforderung. Nach Erhalt der Herausforderung verwendet der Client den privaten Schlüssel zu entschlüsseln und dann das Ergebnis zu entschlüsseln, und der Server führt eine Überprüfung durch und gibt dann das Antwortergebnis zurück. An diesem Punkt ist ein Zeitraum der Schlüsselüberprüfung abgeschlossen.

        //添加配置
        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 Herstellen einer Verbindung

Nach Abschluss der gesamten Parameterinitialisierung können wir die Dial-Methode aufrufen, um eine SSH-Verbindung herzustellen. Die Dial-Methode verfügt über insgesamt drei Parameter und zwei Rückgabewerte. Der erste Parameter ist der Netzwerktyp. Hier verwenden wir das verbindungsorientierte TCP-Protokoll Der dritte Parameter ist der Netzwerktyp. Der Parameter config ist das Konfigurationselement unseres vorherigen Lebens. Dial gibt eine SSH-Verbindung und einen Fehlertyp zurück.

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 Sitzung erstellen

Nachdem wir eine SSH-Verbindung mit dem Zielcomputer hergestellt haben, können wir eine SSH-Sitzung erstellen, mit der wir kommunizieren Zielmaschine. Dieser Vorgang kann über die NewSession()-Methode erreicht werden.

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

1.2.4 Vorgänge ausführen

Nachdem wir eine Sitzung mit dem Zielcomputer eingerichtet haben, können wir den Remote-Server bedienen, indem wir Befehle usw. ausführen. Go stellt uns derzeit fünf Methoden zum Betreiben von Remote-Maschinen zur Verfügung, nämlich

Run()

, Start(), Output(), CombineOutpt(), Shell(). ? Darunter sind

Output()

und **CombineOutpt()** zwei Methoden, die die Run()-Methode in unterschiedlichem Maße kapseln und den Ausgabestream, den Fehlerstream und andere verwandte Inhalte überprüfen. Die

        // 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()-Methode kapselt die Start()-Methode und fügt eine Wait-Methode hinzu, um den Exit-Befehl des Remote-Servers zu überprüfen. In der Wait()-Methode gibt es eine Variable vom Typ Pipeline

exitStatus

, die zum Speichern des von der Maschine nach jeder Befehlsausführung zurückgegebenen Exit-Status verwendet wird. Interessierte Freunde können sich den Code dieses Artikels ansehen, der Code wird jedoch hier nicht veröffentlicht. Hier besteht eine Gefahr, wenn wir ein Programm auf einem Remote-Computer ausführen, das niemals stoppt, und unser Programm nicht auf den vom Remote-Computer gesendeten Exit-Befehl wartet. Dies führt dazu, dass das Programm blockiert wird und nicht zurückkehren kann normalerweise. Die Lösung besteht darin, eine Coroutine zu verwenden, um diese Aufgabe separat auszuführen, oder einen Timer zu verwenden, um die Sitzung regelmäßig zu beenden, um zur Normalität zurückzukehren.

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
}

Das obige ist der detaillierte Inhalt vonIn einem Artikel wird ausführlich erläutert, wie Golang SSH-bezogene Vorgänge implementiert. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.im. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen