melbahja / goph

🤘 The native golang ssh client to execute your commands over ssh connection. 🚀🚀

Home Page:http://git.io/bfpiw

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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