k0sproject / rig

A go package for multi-protocol and multi-os remote host communication.
Apache License 2.0
39 stars 24 forks source link

sudo with password support #210

Open kaplan-michael opened 2 weeks ago

kaplan-michael commented 2 weeks ago

Split up from a closed/related issue for better tracking. I do plan to attempt an implementation of this.

I do have a question in terms of sudo, do you plan/want to support sudo with a password? probably injected through stdin?

Originally posted by @kaplan-michael in https://github.com/k0sproject/rig/issues/195#issuecomment-2111167471

twz123 commented 2 weeks ago

This might get quite cumbersome and brittle. Many of the commands k0sctl runs already read input from stdin. I'm not sure if there is a reliable way to split this into a stdin for sudo and a stdin for the command itself.

kaplan-michael commented 2 weeks ago

Yeah, My theory is that we could try catch the stdout and only if we find a sudo prompt, we inject the password, but from what I have been playing with it. it seems quite impossible to make it reliable... in a non interactive Exec. As of now, I assume Interactive Exec(when we have a pty) should be better.

I also wonder if we could perhaps have another command decorator(similar to sudo) that would handle that for us on the remote side?

Maybe someone has some better ideas how to make it work reliably?

kke commented 2 weeks ago

Looking for sudo prompts would require analyzing all output (different locales and distros may make this very difficult to do reliably) and maybe buffer/gatekeep stdin input. And it's slightly suspicious security-wise to send the sudo password to any command that renders output that looks like a sudo prompt.

Perhaps the best option would be to spawn a sudo shell after connect that would be then used as the launcher for all subsequent commands, this is possible by returning a cmd.Executor() that wraps all commands and sends them to the background sudo shell for running. Managing concurrency may require some kind of subshell-pooling. It would be possible to do this by registering this custom sudo method to the sudo.DefaultProvider() or by creating a new SudoProvider and passing that to each client via WithSudoProvider() client-option.

kaplan-michael commented 2 weeks ago

Looking for sudo prompts would require analyzing all output (different locales and distros may make this very difficult to do reliably) and maybe buffer/gatekeep stdin input. And it's slightly suspicious security-wise to send the sudo password to any command that renders output that looks like a sudo prompt.

Yeah, that's true.

I went with the route of doing a new provider with a decorator and calling it good on my end. I wouldn't call it safe(mostly bcs sudo pass will show up in the process table), but we want to deprecate sudo with pass and ssh passwords anyways and just need to support the current behavior for a while(the same)

Thanks a lot for help so far.

// NewSudoProviderWithPass creates a new sudo provider configured with a sudo password.
func NewSudoProviderWithPass(password string) *sudo.Provider {
    provider := plumbing.NewProvider[cmd.Runner, cmd.Runner](ErrNoSudo)
    provider.Register(func(c cmd.Runner) (cmd.Runner, bool) {
        if c.IsWindows() {
            return nil, false
        }
        decorator := func(command string) string {
            return SudoPass(command, password)
        }
        return cmd.NewExecutor(c, decorator), true
    })
    return provider
}

// SudoPass is a DecorateFunc that will wrap the given command in a sudo call.
func SudoPass(cmd string, pass string) string {
    return fmt.Sprintf(`echo %s | sudo -S -- "${SHELL-sh}" -c %s`, shellescape.Quote(pass), shellescape.Quote(cmd))

}

and

rig.WithSudoProvider(sudo.NewSudoProviderWithPass(hostConfig.SudoPassword)
twz123 commented 2 weeks ago
func SudoPass(cmd string, pass string) string {
  return fmt.Sprintf(`echo %s | sudo -S -- "${SHELL-sh}" -c %s`, shellescape.Quote(pass), shellescape.Quote(cmd))

}

That will leak the password and make it visible via /proc to all users (if /proc isn't mounted with hidepid>0). You really need to pass the password via stdin.

kaplan-michael commented 2 weeks ago

That will leak the password and make it visible via /proc to all users (if /proc isn't mounted with hidepid>0). You really need to pass the password via stdin.

Yes, I point to that in my comment as well.

I wouldn't call it safe(mostly bcs sudo pass will show up in the process table), but we want to deprecate sudo with pass and ssh passwords anyways and just need to support the current behavior for a while(the same)

The thing is that we currently do it in a similar way, so It doesn't change the security posture much.(and we use it on a single user system 99.99% of the time, so it fits within our risks)

I would like to point out, that I wouldn't say this is a good way to do this in rig.