No support for commands with sudo
agentram opened this issue · comments
When trying to execute command with sudo, e.g. sudo sleep 3
, I receive the error:
sudo: no tty present and no askpass program specified
2021/08/20 17:52:03 Process exited with status 1
exit status 1
Example code:
package main
import (
"log"
"fmt"
"github.com/melbahja/goph"
)
func main() {
// Start new ssh connection with password
auth := goph.Password("12345")
client, err := goph.New("testssh", "172.16.1.10", auth)
if err != nil {
log.Fatal(err)
}
// Defer closing the network connection.
defer client.Close()
// Execute your command.
out, err := client.Run("sudo sleep 3")
if err != nil {
fmt.Println(string(out))
log.Fatal(err)
}
// Get your output as []byte.
fmt.Println(string(out))
}
This because sudo asks for root password and never gets one, you can connect via root if you want to run commands with root privileges.
It should be possible to check if the stdout response is prefixed with "[sudo] password for ". If this is recognized, the password should just be sent again. Terraform is doing it so for example.
Another approach would be to use KeyBoardInteractive.
The problem isn't the library per se, but how sudo works. When running a command with sudo, the output returns an error about sudo needing a terminal or whatever to take the input for password. To solve this I just simply asked the user to input the password via the term package and a function I modified from SO:
// Taken from here: https://stackoverflow.com/a/32768479
func credentials() (string, error) {
fmt.Print("Enter Password: ")
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", err
}
password := string(bytePassword)
return strings.TrimSpace(password), nil
}
for my commands that require sudo I just did:
out, err := client.Run("echo " + password + "| sudo -S apt upgrade --assume-yes")
if err != nil {
panic(err)
}
// Get your output as []byte.
fmt.Println("Output of command: \n\n", string(out))
Haven't fully tested it, and I'm tired, but it works for my test program.
sudo -S
helps prevent the command from being added to history, including the echo.
Looks a little sloppy, but it works for what I need. I do think a function to at least specify a PTY properly with stdin and stdout would be nice to have, or at least some sort of example added to handle I/O going to and from the session.
EDIT: SSH by default doesn't allow root login, and it isn't recommended. Also NOPASSWD in sudoers isn't recommended either.
I solved my use case by wrapping the client struct like this.
type SudoClient struct {
*goph.Client
sudoPassword string
}
func (s *SudoClient) SetPassword(password string) {
s.sudoPassword = password
}
// RunContext runs the provided command and if a non empty password is provided it
// prefixes the command with sudo.
func (g *SudoClient) RunContext(ctx context.Context, name string) ([]byte, error) {
if g.sudoPassword != "" {
name = fmt.Sprintf(`echo %s | sudo --prompt="" -S %s`, g.sudoPassword, name)
}
return g.Client.RunContext(ctx, name)
}