【问题标题】:How do I execute a command on a remote machine in a golang CLI?如何在 golang CLI 中在远程机器上执行命令?
【发布时间】:2016-10-07 09:21:37
【问题描述】:

如何在 golang CLI 中在远程机器上执行命令?我需要编写一个 golang CLI,它可以通过密钥 SSH 到远程机器并执行 shell 命令。此外,我需要能够做到这一点。例如SSH 连接到一台机器(如云堡垒),然后 SSH 连接到另一台内部机器并执行 shell 命令。

我(还)没有找到任何这方面的例子。

【问题讨论】:

标签: go ssh command-line-interface


【解决方案1】:

golang SSH 执行带有超时选项的 shell 命令

import (
    "bytes"
    "context"
    "errors"
    "fmt"
    "golang.org/x/crypto/ssh"
    "time"
)

func SshRemoteRunCommandWithTimeout(sshClient *ssh.Client, command string, timeout time.Duration) (string, error) {
    if timeout < 1 {
        return "", errors.New("timeout must be valid")
    }

    session, err := sshClient.NewSession()
    if err != nil {
        return "", err
    }
    defer session.Close()

    ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
    defer cancelFunc()
    resChan := make(chan string, 1)
    errChan := make(chan error, 1)

    go func() {
        // run shell script
        if output, err := session.CombinedOutput(command); err != nil {
            errChan <- err
        } else {
            resChan <- string(output)
        }
    }()

    select {
    case err := <-errChan:
        return "", err
    case ms := <-resChan:
        return ms, nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

【讨论】:

    【解决方案2】:

    您可以使用 "golang.org/x/crypto/ssh" 包通过 SSH 在远程计算机上运行命令。

    这是一个示例函数,演示了在远程机器上运行单个命令并返回输出的简单用法:

    //e.g. output, err := remoteRun("root", "MY_IP", "PRIVATE_KEY", "ls")
    func remoteRun(user string, addr string, privateKey string, cmd string) (string, error) {
        // privateKey could be read from a file, or retrieved from another storage
        // source, such as the Secret Service / GNOME Keyring
        key, err := ssh.ParsePrivateKey([]byte(privateKey))
        if err != nil {
            return "", err
        }
        // Authentication
        config := &ssh.ClientConfig{
            User: user,
            // https://github.com/golang/go/issues/19767 
            // as clientConfig is non-permissive by default 
            // you can set ssh.InsercureIgnoreHostKey to allow any host 
            HostKeyCallback: ssh.InsecureIgnoreHostKey(),
            Auth: []ssh.AuthMethod{
                ssh.PublicKeys(key),
            },
            //alternatively, you could use a password
            /*
                Auth: []ssh.AuthMethod{
                    ssh.Password("PASSWORD"),
                },
            */
        }
        // Connect
        client, err := ssh.Dial("tcp", net.JoinHostPort(addr, "22"), config)
        if err != nil {
            return "", err
        }
        // Create a session. It is one session per command.
        session, err := client.NewSession()
        if err != nil {
            return "", err
        }
        defer session.Close()
        var b bytes.Buffer  // import "bytes"
        session.Stdout = &b // get output
        // you can also pass what gets input to the stdin, allowing you to pipe
        // content from client to server
        //      session.Stdin = bytes.NewBufferString("My input")
    
        // Finally, run the command
        err = session.Run(cmd)
        return b.String(), err
    }
    

    【讨论】:

    • 最新更改需要主机密钥检查。此处的问题解决方案 - go-review.googlesource.com/c/38701
    • 解决这个问题的危险方法是将 ssh 库提供的 InsecureIgnoreHostKey() 添加到上面: ``` config := &ssh.ClientConfig{ User: user, HostKeyCallback: ssh.InsecureIgnoreHostKey( ), Auth: []ssh.AuthMethod{ ``` 正确的方法是将它们添加到您的allows_hosts文件中并对此进行验证。希望其他人可以添加该示例。
    • 每次我运行这段代码时,它都会返回以下消息:ssh: no key found。关于修复它的任何想法?因为密钥存在并且有效。我用它来连接任何其他服务器。
    • 有没有办法在函数中添加ssh-remote-shell-command执行的超时参数?
    【解决方案3】:

    试用包https://github.com/appleboy/easyssh-proxy

    package main
    
    import (
        "fmt"
        "time"
    
        "github.com/appleboy/easyssh-proxy"
    )
    
    func main() {
        // Create MakeConfig instance with remote username, server address and path to private key.
        ssh := &easyssh.MakeConfig{
            User:   "appleboy",
            Server: "example.com",
            // Optional key or Password without either we try to contact your agent SOCKET
            //Password: "password",
            // Paste your source content of private key
            // Key: `-----BEGIN RSA PRIVATE KEY-----
            // MIIEpAIBAAKCAQEA4e2D/qPN08pzTac+a8ZmlP1ziJOXk45CynMPtva0rtK/RB26
            // 7XC9wlRna4b3Ln8ew3q1ZcBjXwD4ppbTlmwAfQIaZTGJUgQbdsO9YA==
            // -----END RSA PRIVATE KEY-----
            // `,
            KeyPath: "/Users/username/.ssh/id_rsa",
            Port:    "22",
            Timeout: 60 * time.Second,
        }
    
        // Call Run method with command you want to run on remote server.
        stdout, stderr, done, err := ssh.Run("ls -al", 60*time.Second)
        // Handle errors
        if err != nil {
            panic("Can't run remote command: " + err.Error())
        } else {
            fmt.Println("don is :", done, "stdout is :", stdout, ";   stderr is :", stderr)
        }
    
    }
    

    more example

    【讨论】:

      【解决方案4】:

      这里的其他解决方案也可以,但我会抛出另一个您可以尝试的选项:simplessh。我认为它更容易使用。对于这个问题,我会使用下面的选项 3,您可以使用您的密钥进行 ssh。

      选项 1:使用密码通过 SSH 连接到机器,然后运行命令

      import (
          "log"
      
          "github.com/sfreiberg/simplessh"
      )
      
      func main() error {
          var client *simplessh.Client
          var err error
      
          if client, err = simplessh.ConnectWithPassword("hostname_to_ssh_to", "username", "password"); err != nil {
              return err
          }
      
          defer client.Close()
      
          // Now run the commands on the remote machine:
          if _, err := client.Exec("cat /tmp/somefile"); err != nil {
              log.Println(err)
          }
      
          return nil
      }
      

      选项 2:使用一组可能的密码通过 SSH 连接到计算机,然后运行命令

      import (
          "log"
      
          "github.com/sfreiberg/simplessh"
      )
      
      type access struct {
          login    string
          password string
      }
      
      var loginAccess []access
      
      func init() {
          // Initialize all password to try
          loginAccess = append(loginAccess, access{"root", "rootpassword1"})
          loginAccess = append(loginAccess, access{"someuser", "newpassword"})
      }
      
      func main() error {
          var client *simplessh.Client
          var err error
      
          // Try to connect with first password, then tried second else fails gracefully
          for _, credentials := range loginAccess {
              if client, err = simplessh.ConnectWithPassword("hostname_to_ssh_to", credentials.login, credentials.password); err == nil {
                  break
              }
          }
      
          if err != nil {
              return err
          }
      
          defer client.Close()
      
          // Now run the commands on the remote machine:
          if _, err := client.Exec("cat /tmp/somefile"); err != nil {
              log.Println(err)
          }
      
          return nil
      }
      

      选项 3:使用您的密钥通过 SSH 连接到计算机

      import (
          "log"
      
          "github.com/sfreiberg/simplessh"
      )
      
      func SshAndRunCommand() error {
          var client *simplessh.Client
          var err error
      
          // Option A: Using a specific private key path:
          //if client, err = simplessh.ConnectWithKeyFile("hostname_to_ssh_to", "username", "/home/user/.ssh/id_rsa"); err != nil {
      
          // Option B: Using your default private key at $HOME/.ssh/id_rsa:
          //if client, err = simplessh.ConnectWithKeyFile("hostname_to_ssh_to", "username"); err != nil {
      
          // Option C: Use the current user to ssh and the default private key file:
          if client, err = simplessh.ConnectWithKeyFile("hostname_to_ssh_to"); err != nil {
              return err
          }
      
          defer client.Close()
      
          // Now run the commands on the remote machine:
          if _, err := client.Exec("cat /tmp/somefile"); err != nil {
              log.Println(err)
          }
      
          return nil
      }
      

      【讨论】:

        【解决方案5】:

        尝试使用 os/exec https://golang.org/pkg/os/exec/ 执行 ssh

        package main
        
        import (
            "bytes"
            "log"
            "os/exec"
        )
        
        func main() {
            cmd := exec.Command("ssh", "remote-machine", "bash-command")
            var out bytes.Buffer
            cmd.Stdout = &out
            err := cmd.Run()
            if err != nil {
                log.Fatal(err)
            }
        }
        

        要跳过机器,请使用 ssh 配置文件中的 ProxyCommand 指令。

        Host remote_machine_name
          ProxyCommand ssh -q bastion nc remote_machine_ip 22
        

        【讨论】:

        • 您可以在 ssh 中使用 -W 选项,而不是生成 nc 进程
        • 如果远程机器需要密码怎么办?有没有办法同时传递密码?
        • 如何使输出为 utf8 格式?我只看到一堆数字:/
        • @Katie 你想渲染 bytes.Buffer 值吗?如果您使用printf%v 指令,bytes 将呈现为int8 值列表。要呈现为可打印字符串,请确保使用%s
        猜你喜欢
        • 2012-07-27
        • 2012-04-06
        • 1970-01-01
        • 2011-11-23
        • 2019-06-22
        • 2013-06-06
        • 2011-12-18
        • 1970-01-01
        • 2021-08-08
        相关资源
        最近更新 更多