Maison  >  Article  >  développement back-end  >  Un article explique en détail comment Golang implémente les opérations liées à SSH

Un article explique en détail comment Golang implémente les opérations liées à SSH

藏色散人
藏色散人avant
2022-11-09 16:01:374169parcourir

Cet article est écrit par la rubrique tutoriel golang pour vous présenter comment golang implémente les connexions SSH et d'autres opérations connexes. Je me demande ce que vous savez sur SSH ? Laissez-moi vous parler en détail des problèmes opérationnels liés à l'implémentation de ssh par Go. J'espère que cela sera utile aux amis qui en ont besoin !

Un article explique en détail comment Golang implémente les opérations liées à SSH

1.ssh

1.1 Préface

Dans certains scénarios de développement quotidiens, nous devons communiquer avec le serveur distant et effectuer certaines opérations de commande associées. À ce stade, nous pouvons utiliser le protocole SSH pour atteindre l'objectif. . Le protocole SSH est un protocole de sécurité construit sur la couche application. Son nom complet est Secure Shell. Il utilise le protocole TCP orienté connexion pour la transmission, ce qui signifie qu'il est sûr et fiable. Il convient de noter que le transfert de fichiers ne peut pas être effectué sur le protocole SSH et doit être effectué sur le protocole SFTP mentionné ci-dessous.

1.2 Implémentation de Go

Go nous fournit officiellement un package pour implémenter les connexions SSH, situé sous golang.org/x/crypto En appelant les méthodes pertinentes fournies dans le package dans le programme, vous pouvez établir la communication avec d'autres machines. communiquer. Avant utilisation, nous devons utiliser go get pour importer les packages de dépendances associés.

go get golang.org/x/crypto/ssh

1.2.1 Configurer les paramètres associés

Avant de communiquer, nous devons également configurer certains Configurer certains paramètres associés pour établir une connexion. La structure ClientConfig sous le package ssh définit certains éléments de configuration nécessaires pour établir une connexion SSH. Certains éléments fournissent des paramètres par défaut, que nous n'avons pas besoin de déclarer lors de leur utilisation.

Dans l'extrait de code ci-dessous, nous déclarons d'abord le nom d'utilisateur et le mot de passe, définissons le délai d'expiration de la connexion à 10 secondes et la variable addr définit l'adresse IP et le port de la machine cible.

HostKeyCallback, nous l'avons configuré pour ignorer. En effet, le protocole SSH fournit deux méthodes de vérification de sécurité pour le client. L'une est la vérification de sécurité par mot de passe, qui est le formulaire de mot de passe de compte que nous utilisons souvent, et l'autre est comparée à. Il s'agit du premier type de vérification de sécurité basée sur une clé. Cette forme de méthode de vérification améliore considérablement le niveau de sécurité, mais l'inconvénient est qu'elle prend un temps relativement long.

Si nous devons utiliser cette méthode pour la vérification, nous devons d'abord créer une paire de clés pour nous-mêmes sur le serveur. Lors de l'accès en tant que client, nous enverrons d'abord une demande de vérification de sécurité au serveur une fois que le serveur aura reçu la. demande, Tout d'abord, la clé publique enregistrée sur la machine est comparée à la clé publique envoyée par le client. Si elles sont cohérentes, le serveur répondra au client avec un défi de chiffrement. Après avoir reçu le défi, le client utilise la clé privée. pour le décrypter, puis le déchiffre. Le résultat est envoyé au serveur, et le serveur effectue une vérification puis renvoie le résultat de la réponse. À ce stade, une période de vérification de clé est terminée.

        //添加配置
        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 Établir une connexion

Après avoir terminé l'initialisation de tous les paramètres, nous pouvons appeler la méthode Dial pour établir une connexion SSH. La méthode Dial a un total de trois paramètres et deux valeurs de retour. Le premier paramètre réseau est le type de réseau. Le deuxième paramètre addr est l'adresse IP et le numéro de port de la machine cible. le troisième paramètre est le type de réseau. Le paramètre config est l'élément de configuration de notre vie précédente. Dial renverra une connexion SSH et un type d’erreur.

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 Créer une session

Après avoir établi une connexion SSH avec la machine cible, nous pouvons créer une session SSH avec communiquer avec le machine cible. Cette opération peut être réalisée via la méthode NewSession().

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

1.2.4 Exécuter des opérations

Après avoir établi une session avec la machine cible, nous pouvons faire fonctionner le serveur distant en exécutant des commandes, etc. Go nous propose actuellement cinq méthodes pour faire fonctionner des machines distantes, à savoir Run(), Start(), Output(), CombineOutpt(), Shell().

 ? Parmi elles, Output() et **CombineOutpt()** sont deux méthodes qui encapsulent la méthode Run() à des degrés divers et vérifient le flux de sortie, le flux d'erreurs et d'autres contenus associés. La méthode

        // 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() encapsule la méthode Start() et ajoute une méthode Wait pour vérifier la commande de sortie du serveur distant. Il existe une variable de type pipeline exitStatus dans la méthode Wait(), qui est utilisée pour enregistrer l'état de sortie renvoyé par la machine après l'exécution de chaque commande. Les amis intéressés peuvent consulter le code de cette pièce, mais je ne publierai pas le code ici.

Il y a un piège ici. Si nous exécutons un programme sur une machine distante qui ne s'arrêtera jamais et que notre programme n'attend pas la commande de sortie envoyée par la machine distante, le programme sera bloqué et incapable de revenir. normalement. La solution est d'utiliser une coroutine pour exécuter cette tâche séparément, ou d'utiliser un timer pour terminer régulièrement la session afin de revenir normalement.

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
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer