ホームページ >バックエンド開発 >Golang >golang が ssh 関連の操作を実装する方法について詳しく説明した記事

golang が ssh 関連の操作を実装する方法について詳しく説明した記事

藏色散人
藏色散人転載
2022-11-09 16:01:374287ブラウズ

この記事は、golang チュートリアル というコラムで紹介されており、golang が SSH 接続やその他の関連操作を実装する方法を紹介しています。 Go の ssh 実装に関連する運用上の問題について詳しく説明します。必要としている友人の役に立てば幸いです。

golang が ssh 関連の操作を実装する方法について詳しく説明した記事

1.ssh

1.1 はじめに

日常的な開発シナリオでは、リモート サーバーと通信する必要があります。通信といくつかの関連コマンド操作の実行現時点では、SSH プロトコルを使用して目的を達成できます。 SSH プロトコルはアプリケーション層に構築されたセキュリティ プロトコルで、正式名は Secure Shell で、送信にはコネクション型の TCP プロトコルを使用するため、安全で信頼性が高くなります。ファイル転送は SSH プロトコルでは完了できず、後述する SFTP プロトコルで完了する必要があることに注意してください。

1.2 Go の実装

Go 公式は、golang.org/x/crypto にある SSH 接続を実装するためのパッケージを提供します。これは、プログラム内でパッケージを呼び出すことで提供されます。関連するメソッドを使用して、他のマシンと通信できます。使用する前に、 go get を使用して関連する依存関係パッケージをインポートする必要があります。

go get golang.org/x/crypto/ssh

1.2.1 関連パラメータの設定

通信する前に、接続を確立するためのいくつかの関連パラメータを設定する必要もあります。 ssh パッケージの下の ClientConfig 構造は、SSH 接続を確立するために必要ないくつかの構成項目を定義します。一部の項目は、使用時に宣言する必要のないデフォルトのパラメーターを提供します。

以下のコード スニペットでは、最初にユーザー名とパスワードを宣言し、接続タイムアウトを 10 秒に設定し、addr 変数でターゲット マシンの IP アドレスとポートを定義します。

HostKeyCallback 項目を無視するように設定しました。これは、SSH プロトコルがクライアントに 2 つのセキュリティ検証方法を提供しているためです。1 つはパスワード ベースのセキュリティ検証であり、これは私たちがよく使用するアカウント パスワード フォームです。 1 つはキーベースのセキュリティ検証で、前者に比べてセキュリティレベルが大幅に向上しますが、比較的時間がかかるのがデメリットです。

この方法を検証に使用する必要がある場合は、まずサーバー上に自分用のキーのペアを作成する必要があります。クライアントとしてアクセスする場合は、まずサーバーにセキュリティ検証リクエストを送信し、サーバーはリクエストを受信すると、まずマシンに保存されている公開キーとクライアントから送信された公開キーを比較し、それらが一致する場合、サーバーは暗号化されたチャレンジでクライアントに応答します。クライアントは秘密鍵を使用して復号し、復号結果をサーバーに送信し、サーバーは検証を行って応答結果を返します。この時点で、鍵の検証期間が完了します。

        //添加配置
        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 メソッドには、合計 3 つのパラメータと 2 つの戻り値があります。最初のパラメータ network は、ネットワーク タイプです。ここでは、接続指向の TCP プロトコルを使用します。2 番目のパラメータ addr は、ターゲット マシンの IP アドレスとポート番号です。 3 番目のパラメータはネットワークの種類で、パラメータ config は以前の設定項目です。ダイヤルすると、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 確立後、1 回接続したら、SSH セッションを作成してターゲット マシンと通信できます。この操作は NewSession() メソッドを通じて実行できます。

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

ターゲット マシンとのセッションを確立した後、コマンドを実行するなどしてリモート サーバーを操作できます。 Go は現在、リモート マシンを操作するための 5 つのメソッド、

Run()Start()Output()CombineOutpt()# を提供しています。 ##、シェル()? このうち、

Output()

と **CombineOutpt()** は、Run() メソッドをさまざまな程度にカプセル化し、出力ストリームとエラー ストリームを検証する 2 つのメソッドです。その他の関連コンテンツ。

        // 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

があり、各コマンドの実行後にマシンから返される終了ステータスを保存するために使用されます。興味のある友人はこの作品のコードを見てみることができますが、コードはここには掲載されません。 ここには落とし穴があります。決して停止しないプログラムをリモート マシン上で実行し、そのプログラムがリモート マシンから送信される終了コマンドを待機している場合、プログラムはブロックされてしまいます。 . 正常に戻ることができません。解決策は、コルーチンを使用してこのタスクを個別に実行するか、タイマーを使用してセッションを定期的に終了して通常に戻ることです。

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 関連の操作を実装する方法について詳しく説明した記事の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.imで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。