Golang在提示符上输入SSH Sudo密码(或退出)

huangapple go评论155阅读模式
英文:

Golang Enter SSH Sudo Password on Prompt (or exit)

问题

我正在尝试在我的Go程序中通过SSH包运行一个脚本(到目前为止我已经成功了)。

我的问题是,如果用户具有sudo权限,脚本会尝试使用sudo运行命令,这会导致bash脚本暂停,直到用户输入密码。

例如:

  1. [ERROR ] Install cs-server: Checking dependencies: missing: lib32gcc1
  2. # 它尝试使用sudo安装缺失的依赖项,但在这里暂停
  3. [sudo] password for guest:

在我的Go程序中,我编写了类似于以下代码:

  1. // 连接到SSH并获取会话...
  2. out, err := session.StdoutPipe()
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. go func(out io.Reader) {
  7. r := bufio.NewScanner(out)
  8. for r.Scan() {
  9. fmt.Println(r.Text())
  10. }
  11. }(out)
  12. // 执行ssh命令...

我收到的输出与上面的示例完全相同,只是在这种情况下,我甚至看不到[sudo] password for guest: 这一行...它只打印到[ERROR ] Install cs-server: Checking dependencies: missing: lib32gcc1并永远暂停。

如何绕过这个暂停?我的选择是要么从我的Go程序自动输入密码,要么结束ssh执行并只接收输出。

英文:

I'm trying to run a script via the SSH package in my Go program (so far I've had success).

My issue is, the script attempts to run a command with sudo if the user has sudo privileges, and this causes the bash script to pause until a password is entered by the user.

For example:

  1. [ERROR ] Install cs-server: Checking dependencies: missing: lib32gcc1
  2. # It attempts to install the missing dependencies with sudo but pauses here
  3. [sudo] password for guest:

In my Go program, I have written something that looks similar to this:

  1. // Connect to SSH and retreive session...
  2. out, err := session.StdoutPipe()
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. go func(out io.Reader) {
  7. r := bufio.NewScanner(out)
  8. for r.Scan() {
  9. fmt.Println(r.Text())
  10. }
  11. }(out)
  12. // Execute ssh command...

And I receive the exact same output as the example above, only in this case, I don't even see the line [sudo] password for guest: ... it only prints up to [ERROR ] Install cs-server: Checking dependencies: missing: lib32gcc1 and pauses forever.

How can I bypass this pause? My options are to either enter the password from my Go program automatically, or end the ssh execution and just receive the output.

答案1

得分: 10

我使用session.StdoutPipe()session.StdinPipe()解决了这个问题。我编写了一个Go协程,它扫描每个字节,并检查最后一行是否以"[sudo] password for "开头,以": "结尾。它将password + "\n"写入session.StdinPipe(),以继续执行脚本。

以下是我为此问题编写的所有代码。

  1. package ssh
  2. import (
  3. "bufio"
  4. "io"
  5. "log"
  6. "net"
  7. "strings"
  8. "golang.org/x/crypto/ssh"
  9. )
  10. type Connection struct {
  11. *ssh.Client
  12. password string
  13. }
  14. func Connect(addr, user, password string) (*Connection, error) {
  15. sshConfig := &ssh.ClientConfig{
  16. User: user,
  17. Auth: []ssh.AuthMethod{
  18. ssh.Password(password),
  19. },
  20. HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }),
  21. }
  22. conn, err := ssh.Dial("tcp", addr, sshConfig)
  23. if err != nil {
  24. return nil, err
  25. }
  26. return &Connection{conn, password}, nil
  27. }
  28. func (conn *Connection) SendCommands(cmds ...string) ([]byte, error) {
  29. session, err := conn.NewSession()
  30. if err != nil {
  31. log.Fatal(err)
  32. }
  33. defer session.Close()
  34. modes := ssh.TerminalModes{
  35. ssh.ECHO: 0, // disable echoing
  36. ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
  37. ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
  38. }
  39. err = session.RequestPty("xterm", 80, 40, modes)
  40. if err != nil {
  41. return []byte{}, err
  42. }
  43. in, err := session.StdinPipe()
  44. if err != nil {
  45. log.Fatal(err)
  46. }
  47. out, err := session.StdoutPipe()
  48. if err != nil {
  49. log.Fatal(err)
  50. }
  51. var output []byte
  52. go func(in io.WriteCloser, out io.Reader, output *[]byte) {
  53. var (
  54. line string
  55. r = bufio.NewReader(out)
  56. )
  57. for {
  58. b, err := r.ReadByte()
  59. if err != nil {
  60. break
  61. }
  62. *output = append(*output, b)
  63. if b == byte('\n') {
  64. line = ""
  65. continue
  66. }
  67. line += string(b)
  68. if strings.HasPrefix(line, "[sudo] password for ") && strings.HasSuffix(line, ": ") {
  69. _, err = in.Write([]byte(conn.password + "\n"))
  70. if err != nil {
  71. break
  72. }
  73. }
  74. }
  75. }(in, out, &output)
  76. cmd := strings.Join(cmds, "; ")
  77. _, err = session.Output(cmd)
  78. if err != nil {
  79. return []byte{}, err
  80. }
  81. return output, nil
  82. }

以下是如何使用它的示例。

  1. // ssh是上面自定义的包
  2. conn, err := ssh.Connect("0.0.0.0:22", "username", "password")
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. output, err := conn.SendCommands("sleep 2", "echo Hello!")
  7. if err != nil {
  8. log.Fatal(err)
  9. }
  10. fmt.Println(string(output))
英文:

I managed to fix this issue by making use of the session.StdoutPipe() and session.StdinPipe(). I wrote a go routine which scans each byte and checks if the last written line starts with "[sudo] password for " and ends with ": ". It will write the password + "\n" to the session.StdinPipe() which continues execution of the script.

Here's all of the code I have for this.

  1. package ssh
  2. import (
  3. "bufio"
  4. "io"
  5. "log"
  6. "net"
  7. "strings"
  8. "golang.org/x/crypto/ssh"
  9. )
  10. type Connection struct {
  11. *ssh.Client
  12. password string
  13. }
  14. func Connect(addr, user, password string) (*Connection, error) {
  15. sshConfig := &ssh.ClientConfig{
  16. User: user,
  17. Auth: []ssh.AuthMethod{
  18. ssh.Password(password),
  19. },
  20. HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }),
  21. }
  22. conn, err := ssh.Dial("tcp", addr, sshConfig)
  23. if err != nil {
  24. return nil, err
  25. }
  26. return &Connection{conn, password}, nil
  27. }
  28. func (conn *Connection) SendCommands(cmds ...string) ([]byte, error) {
  29. session, err := conn.NewSession()
  30. if err != nil {
  31. log.Fatal(err)
  32. }
  33. defer session.Close()
  34. modes := ssh.TerminalModes{
  35. ssh.ECHO: 0, // disable echoing
  36. ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
  37. ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
  38. }
  39. err = session.RequestPty("xterm", 80, 40, modes)
  40. if err != nil {
  41. return []byte{}, err
  42. }
  43. in, err := session.StdinPipe()
  44. if err != nil {
  45. log.Fatal(err)
  46. }
  47. out, err := session.StdoutPipe()
  48. if err != nil {
  49. log.Fatal(err)
  50. }
  51. var output []byte
  52. go func(in io.WriteCloser, out io.Reader, output *[]byte) {
  53. var (
  54. line string
  55. r = bufio.NewReader(out)
  56. )
  57. for {
  58. b, err := r.ReadByte()
  59. if err != nil {
  60. break
  61. }
  62. *output = append(*output, b)
  63. if b == byte('\n') {
  64. line = ""
  65. continue
  66. }
  67. line += string(b)
  68. if strings.HasPrefix(line, "[sudo] password for ") && strings.HasSuffix(line, ": ") {
  69. _, err = in.Write([]byte(conn.password + "\n"))
  70. if err != nil {
  71. break
  72. }
  73. }
  74. }
  75. }(in, out, &output)
  76. cmd := strings.Join(cmds, "; ")
  77. _, err = session.Output(cmd)
  78. if err != nil {
  79. return []byte{}, err
  80. }
  81. return output, nil
  82. }

And an example of how you could use it.

  1. // ssh refers to the custom package above
  2. conn, err := ssh.Connect("0.0.0.0:22", "username", "password")
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. output, err := conn.SendCommands("sleep 2", "echo Hello!")
  7. if err != nil {
  8. log.Fatal(err)
  9. }
  10. fmt.Println(string(output))

答案2

得分: 4

这是一个问题,@acidic的代码无法完全捕获输出流。更新后的代码如下:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net"
  8. "strings"
  9. "golang.org/x/crypto/ssh"
  10. )
  11. type Connection struct {
  12. *ssh.Client
  13. password string
  14. }
  15. func Connect(addr, user, password string) (*Connection, error) {
  16. sshConfig := &ssh.ClientConfig{
  17. User: user,
  18. Auth: []ssh.AuthMethod{
  19. ssh.Password(password),
  20. },
  21. HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }),
  22. }
  23. conn, err := ssh.Dial("tcp", addr, sshConfig)
  24. if err != nil {
  25. return nil, err
  26. }
  27. return &Connection{conn, password}, nil
  28. }
  29. func (conn *Connection) SendCommands(cmds string) ([]byte, error) {
  30. session, err := conn.NewSession()
  31. if err != nil {
  32. log.Fatal(err)
  33. }
  34. defer session.Close()
  35. modes := ssh.TerminalModes{
  36. ssh.ECHO: 0, // 禁用回显
  37. ssh.TTY_OP_ISPEED: 14400, // 输入速度 = 14.4kbaud
  38. ssh.TTY_OP_OSPEED: 14400, // 输出速度 = 14.4kbaud
  39. }
  40. err = session.RequestPty("xterm", 80, 40, modes)
  41. if err != nil {
  42. return []byte{}, err
  43. }
  44. stdoutB := new(bytes.Buffer)
  45. session.Stdout = stdoutB
  46. in, _ := session.StdinPipe()
  47. go func(in io.Writer, output *bytes.Buffer) {
  48. for {
  49. if strings.Contains(string(output.Bytes()), "[sudo] password for ") {
  50. _, err = in.Write([]byte(conn.password + "\n"))
  51. if err != nil {
  52. break
  53. }
  54. fmt.Println("输入密码 --- 结束。")
  55. break
  56. }
  57. }
  58. }(in, stdoutB)
  59. err = session.Run(cmds)
  60. if err != nil {
  61. return []byte{}, err
  62. }
  63. return stdoutB.Bytes(), nil
  64. }
  65. func main() {
  66. // ssh 是上面自定义的包
  67. conn, err := Connect("0.0.0.0:22", "username", "password")
  68. if err != nil {
  69. log.Fatal(err)
  70. }
  71. output, err := conn.SendCommands("sudo docker ps")
  72. if err != nil {
  73. log.Fatal(err)
  74. }
  75. fmt.Println(string(output))
  76. }

希望对你有帮助!

英文:

This is an issue that output stream can't be fully captured for @acidic's code.
The updated code is as following

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net"
  8. "strings"
  9. "golang.org/x/crypto/ssh"
  10. )
  11. type Connection struct {
  12. *ssh.Client
  13. password string
  14. }
  15. func Connect(addr, user, password string) (*Connection, error) {
  16. sshConfig := &ssh.ClientConfig{
  17. User: user,
  18. Auth: []ssh.AuthMethod{
  19. ssh.Password(password),
  20. },
  21. HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }),
  22. }
  23. conn, err := ssh.Dial("tcp", addr, sshConfig)
  24. if err != nil {
  25. return nil, err
  26. }
  27. return &Connection{conn, password}, nil
  28. }
  29. func (conn *Connection) SendCommands(cmds string) ([]byte, error) {
  30. session, err := conn.NewSession()
  31. if err != nil {
  32. log.Fatal(err)
  33. }
  34. defer session.Close()
  35. modes := ssh.TerminalModes{
  36. ssh.ECHO: 0, // disable echoing
  37. ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
  38. ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
  39. }
  40. err = session.RequestPty("xterm", 80, 40, modes)
  41. if err != nil {
  42. return []byte{}, err
  43. }
  44. stdoutB := new(bytes.Buffer)
  45. session.Stdout = stdoutB
  46. in, _ := session.StdinPipe()
  47. go func(in io.Writer, output *bytes.Buffer) {
  48. for {
  49. if strings.Contains(string(output.Bytes()), "[sudo] password for ") {
  50. _, err = in.Write([]byte(conn.password + "\n"))
  51. if err != nil {
  52. break
  53. }
  54. fmt.Println("put the password --- end .")
  55. break
  56. }
  57. }
  58. }(in, stdoutB)
  59. err = session.Run(cmds)
  60. if err != nil {
  61. return []byte{}, err
  62. }
  63. return stdoutB.Bytes(), nil
  64. }
  65. func main() {
  66. // ssh refers to the custom package above
  67. conn, err := Connect("0.0.0.0:22", "username", "password")
  68. if err != nil {
  69. log.Fatal(err)
  70. }
  71. output, err := conn.SendCommands("sudo docker ps")
  72. if err != nil {
  73. log.Fatal(err)
  74. }
  75. fmt.Println(string(output))
  76. }

答案3

得分: 0

一个解决方法是将 sudo [cmd] 转换为 echo

输入密码查看隐藏内容

| sudo -S [cmd]
,这不是很好,但对我有效。

英文:

A work around is converting sudo [cmd] to echo

输入密码查看隐藏内容

| sudo -S [cmd]
, it is not good, but working for me.

答案4

得分: 0

另一种解决方法,如果你不想使用ssh库,可以使用pty库创建一个伪终端。以下是一个非常简单的示例:

  1. import (
  2. "io"
  3. "os"
  4. "os/exec"
  5. "time"
  6. "github.com/creack/pty"
  7. )
  8. func main() {
  9. c := exec.Command("ssh", "<user>@<IP>")
  10. f, err := pty.Start(c)
  11. if err != nil {
  12. panic(err)
  13. }
  14. time.Sleep(2 * time.Second)
  15. f.Write([]byte("1234\n"))
  16. io.Copy(os.Stdout, f)
  17. }

这段代码使用pty库创建了一个伪终端,并通过ssh命令连接到指定的用户和IP地址。然后,它等待2秒钟,向伪终端写入"1234\n",并将伪终端的输出复制到标准输出。

英文:

Another workaround if you dont want to use ssh library is to make a pseudo terminal using pty library. An extremely simple example as above

  1. import (
  2. &quot;io&quot;
  3. &quot;os&quot;
  4. &quot;os/exec&quot;
  5. &quot;time&quot;
  6. &quot;github.com/creack/pty&quot;
  7. )
  8. func main() {
  9. c := exec.Command(&quot;ssh&quot;, &quot;&lt;user&gt;@&lt;IP&gt;&quot;)
  10. f, err := pty.Start(c)
  11. if err != nil {
  12. panic(err)
  13. }
  14. time.Sleep(2 * time.Second)
  15. f.Write([]byte(&quot;1234\n&quot;))
  16. io.Copy(os.Stdout, f)
  17. }

huangapple
  • 本文由 发表于 2017年6月10日 17:30:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/44471749.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定