英文:
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 (
"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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论