goph icon indicating copy to clipboard operation
goph copied to clipboard

No support for commands with sudo

Open agentram opened this issue 4 years ago • 4 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))
}

agentram avatar Aug 20 '21 14:08 agentram

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.

melbahja avatar Aug 20 '21 16:08 melbahja

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.

vexvec avatar Oct 20 '21 09:10 vexvec

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.

f0rg-02 avatar Jul 16 '23 02:07 f0rg-02

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

zapling avatar Mar 13 '24 15:03 zapling