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

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

Golang Enter SSH Sudo Password on Prompt (or exit)

问题

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

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

例如:

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

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

// 连接到SSH并获取会话...

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)

// 执行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:

[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:

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

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(),以继续执行脚本。

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

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
}

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

// ssh是上面自定义的包
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))
英文:

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.

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

答案2

得分: 4

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

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,     // 禁用回显
		ssh.TTY_OP_ISPEED: 14400, // 输入速度 = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // 输出速度 = 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("输入密码 --- 结束。")
				break
			}
		}
	}(in, stdoutB)

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

func main() {
	// ssh 是上面自定义的包
	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))

}

希望对你有帮助!

英文:

This is an issue that output stream can't be fully captured for @acidic's code.
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))
}

答案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库创建一个伪终端。以下是一个非常简单的示例:

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)
}

这段代码使用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

    import (
&quot;io&quot;
&quot;os&quot;
&quot;os/exec&quot;
&quot;time&quot;
&quot;github.com/creack/pty&quot;
)
func main() {
c := exec.Command(&quot;ssh&quot;, &quot;&lt;user&gt;@&lt;IP&gt;&quot;)
f, err := pty.Start(c)
if err != nil {
panic(err)
}
time.Sleep(2 * time.Second)
f.Write([]byte(&quot;1234\n&quot;))
io.Copy(os.Stdout, f)
}

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:

确定