简体   繁体   English

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

[英]Golang Enter SSH Sudo Password on Prompt (or exit)

I'm trying to run a script via the SSH package in my Go program (so far I've had success).我正在尝试通过我的 Go 程序中的SSH package运行脚本(到目前为止我已经成功)。

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

For example:例如:

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

In my Go program, I have written something that looks similar to this:在我的 Go 程序中,我编写了类似于以下内容的内容:

// Connect to SSH and retreive session...

out, err := session.StdoutPipe()
if err != nil {
    log.Fatal(err)
}

go func(out io.Reader) {
    r := bufio.NewScanner(out)
    for r.Scan() {
        fmt.Println(r.Text())
    }
}(out)

// 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.而且我收到与上面的示例完全相同的 output,仅在这种情况下,我什至没有看到[sudo] password for guest: ... 它只打印到[ERROR ] Install cs-server: Checking dependencies: missing: lib32gcc1并永远暂停。

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.我的选择是自动输入我的 Go 程序的密码,或者结束 ssh 的执行并只接收 output。

I managed to fix this issue by making use of the session.StdoutPipe() and session.StdinPipe() . 我设法通过使用session.StdoutPipe()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 ": " . 我编写了一个go例程,该例程扫描每个字节并检查最后写入的行是否以"[sudo] password for "开头并以": "结尾。 It will write the password + "\\n" to the session.StdinPipe() which continues execution of the script. 它将password + "\\n"写入session.StdinPipe() ,该脚本继续执行。

Here's all of the code I have for this. 这是我拥有的所有代码。

package ssh

import (
    "bufio"
    "io"
    "log"
    "net"
    "strings"

    "golang.org/x/crypto/ssh"
)

type Connection struct {
    *ssh.Client
    password string
}

func Connect(addr, user, password string) (*Connection, error) {
    sshConfig := &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            ssh.Password(password),
        },
        HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }),
    }

    conn, err := ssh.Dial("tcp", addr, sshConfig)
    if err != nil {
        return nil, err
    }

    return &Connection{conn, password}, nil

}

func (conn *Connection) SendCommands(cmds ...string) ([]byte, error) {
    session, err := conn.NewSession()
    if err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    err = session.RequestPty("xterm", 80, 40, modes)
    if err != nil {
        return []byte{}, err
    }

    in, err := session.StdinPipe()
    if err != nil {
        log.Fatal(err)
    }

    out, err := session.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    var output []byte

    go func(in io.WriteCloser, out io.Reader, output *[]byte) {
        var (
            line string
            r    = bufio.NewReader(out)
        )
        for {
            b, err := r.ReadByte()
            if err != nil {
                break
            }

            *output = append(*output, b)

            if b == byte('\n') {
                line = ""
                continue
            }

            line += string(b)

            if strings.HasPrefix(line, "[sudo] password for ") && strings.HasSuffix(line, ": ") {
                _, err = in.Write([]byte(conn.password + "\n"))
                if err != nil {
                    break
                }
            }
        }
    }(in, out, &output)

    cmd := strings.Join(cmds, "; ")
    _, err = session.Output(cmd)
    if err != nil {
        return []byte{}, err
    }

    return output, nil
}

And an example of how you could use it. 以及如何使用它的示例。

// ssh refers to the custom package above
conn, err := ssh.Connect("0.0.0.0:22", "username", "password")
if err != nil {
    log.Fatal(err)
}

output, err := conn.SendCommands("sleep 2", "echo Hello!")
if err != nil {
    log.Fatal(err)
}

fmt.Println(string(output))

This is an issue that output stream can't be fully captured for @acidic's code. 这是无法为@acidic的代码完全捕获输出流的问题。 The updated code is as following 更新后的代码如下

package main
import (
    "bytes"
    "fmt"
    "io"
    "log"
    "net"
    "strings"

    "golang.org/x/crypto/ssh"
)

type Connection struct {
    *ssh.Client
    password string
}

func Connect(addr, user, password string) (*Connection, error) {
    sshConfig := &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            ssh.Password(password),
        },
        HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }),
    }

    conn, err := ssh.Dial("tcp", addr, sshConfig)
    if err != nil {
        return nil, err
    }

    return &Connection{conn, password}, nil

}

func (conn *Connection) SendCommands(cmds string) ([]byte, error) {
    session, err := conn.NewSession()
    if err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    err = session.RequestPty("xterm", 80, 40, modes)
    if err != nil {
        return []byte{}, err
    }

    stdoutB := new(bytes.Buffer)
    session.Stdout = stdoutB
    in, _ := session.StdinPipe()

    go func(in io.Writer, output *bytes.Buffer) {
        for {
            if strings.Contains(string(output.Bytes()), "[sudo] password for ") {
                _, err = in.Write([]byte(conn.password + "\n"))
                if err != nil {
                    break
                }
                fmt.Println("put the password ---  end .")
                break
            }
        }
    }(in, stdoutB)

    err = session.Run(cmds)
    if err != nil {
        return []byte{}, err
    }
    return stdoutB.Bytes(), nil
}

func main() {
    // ssh refers to the custom package above
    conn, err := Connect("0.0.0.0:22", "username", "password")
    if err != nil {
        log.Fatal(err)
    }

    output, err := conn.SendCommands("sudo docker ps")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(output))

}

A work around is converting sudo [cmd] to echo [password] | sudo -S [cmd] 解决方法是将sudo [cmd]转换为echo [password] | sudo -S [cmd] echo [password] | sudo -S [cmd] , it is not good, but working for me. echo [password] | sudo -S [cmd] ,它不好,但是为我工作。

Another workaround if you dont want to use ssh library is to make a pseudo terminal using pty library.如果您不想使用 ssh 库,另一种解决方法是使用 pty 库制作一个伪终端。 An extremely simple example as above一个极其简单的例子如上

    import (
    "io"
    "os"
    "os/exec"
    "time"

    "github.com/creack/pty"
)

func main() {
    c := exec.Command("ssh", "<user>@<IP>")
    f, err := pty.Start(c)
    if err != nil {
        panic(err)
    }
    time.Sleep(2 * time.Second)
    f.Write([]byte("1234\n"))
    io.Copy(os.Stdout, f)
}


 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM