mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-07 00:12:21 +01:00
implement sudo dance allowing passing the sudo password on stdin with sudo -S, and passing a different stdin fd to the command
This commit is contained in:
parent
e8ae01efae
commit
222deff0db
@ -306,6 +306,33 @@ func parseClientOpts() (*shexec.ClientOpts, error) {
|
||||
opts.Debug = true
|
||||
continue
|
||||
}
|
||||
if argStr == "--sudo" {
|
||||
opts.Sudo = true
|
||||
continue
|
||||
}
|
||||
if argStr == "--sudo-with-password" {
|
||||
if !iter.HasNext() {
|
||||
return nil, fmt.Errorf("'--sudo-with-password [pw]', missing password")
|
||||
}
|
||||
opts.Sudo = true
|
||||
opts.SudoWithPass = true
|
||||
opts.SudoPw = iter.Next()
|
||||
continue
|
||||
}
|
||||
if argStr == "--sudo-with-passfile" {
|
||||
if !iter.HasNext() {
|
||||
return nil, fmt.Errorf("'--sudo-with-passfile [file]', missing file")
|
||||
}
|
||||
opts.Sudo = true
|
||||
opts.SudoWithPass = true
|
||||
fileName := iter.Next()
|
||||
contents, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read --sudo-with-passfile file '%s': %w", fileName, err)
|
||||
}
|
||||
opts.SudoPw = string(contents)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if opts.IsSSH {
|
||||
// parse SSH opts
|
||||
@ -339,6 +366,9 @@ func handleClient() (int, error) {
|
||||
if err != nil {
|
||||
return 1, fmt.Errorf("parsing opts: %w", err)
|
||||
}
|
||||
if opts.Debug {
|
||||
packet.GlobalDebug = true
|
||||
}
|
||||
if !opts.IsSSH {
|
||||
return 1, fmt.Errorf("when running in client mode '--ssh' option must be present")
|
||||
}
|
||||
|
@ -100,6 +100,18 @@ func (m *Multiplexer) MakeWriterPipe(fdNum int) (*os.File, error) {
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func (m *Multiplexer) MakeStringFdReader(fdNum int, contents string) error {
|
||||
pw, err := m.MakeReaderPipe(fdNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
pw.Write([]byte(contents))
|
||||
pw.Close()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Multiplexer) MakeRawFdReader(fdNum int, fd *os.File, shouldClose bool) {
|
||||
m.Lock.Lock()
|
||||
defer m.Lock.Unlock()
|
||||
|
@ -385,9 +385,11 @@ type TermSize struct {
|
||||
}
|
||||
|
||||
type RemoteFd struct {
|
||||
FdNum int `json:"fdnum"`
|
||||
Read bool `json:"read"`
|
||||
Write bool `json:"write"`
|
||||
FdNum int `json:"fdnum"`
|
||||
Read bool `json:"read"`
|
||||
Write bool `json:"write"`
|
||||
Content string `json:"-"`
|
||||
DupStdin bool `json:"-"`
|
||||
}
|
||||
|
||||
type RunPacketType struct {
|
||||
|
@ -30,6 +30,12 @@ const MaxCols = 1024
|
||||
const MaxFdNum = 1023
|
||||
const FirstExtraFilesFdNum = 3
|
||||
|
||||
const SSHRemoteCommand = `PATH=$PATH:~/.mshell; mshell --remote`
|
||||
|
||||
const RemoteCommandFmt = `%s`
|
||||
const RemoteSudoCommandFmt = `sudo -C %d bash /dev/fd/%d`
|
||||
const RemoteSudoPasswordCommandFmt = `cat /dev/fd/%d | sudo -S -C %d bash -c "echo '[from-mshell]'; bash /dev/fd/%d < /dev/fd/%d"`
|
||||
|
||||
type ShExecType struct {
|
||||
Lock *sync.Mutex
|
||||
StartTs time.Time
|
||||
@ -213,21 +219,87 @@ func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecT
|
||||
}
|
||||
|
||||
type ClientOpts struct {
|
||||
IsSSH bool
|
||||
SSHOptsTerm bool
|
||||
SSHOpts []string
|
||||
Command string
|
||||
Fds []packet.RemoteFd
|
||||
Cwd string
|
||||
Debug bool
|
||||
IsSSH bool
|
||||
SSHOptsTerm bool
|
||||
SSHOpts []string
|
||||
Command string
|
||||
Fds []packet.RemoteFd
|
||||
Cwd string
|
||||
Debug bool
|
||||
Sudo bool
|
||||
SudoWithPass bool
|
||||
SudoPw string
|
||||
CommandStdinFdNum int
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MakeRunPacket() *packet.RunPacketType {
|
||||
func (opts *ClientOpts) MakeRunPacket() (*packet.RunPacketType, error) {
|
||||
runPacket := packet.MakeRunPacket()
|
||||
runPacket.Command = opts.Command
|
||||
runPacket.Cwd = opts.Cwd
|
||||
runPacket.Fds = opts.Fds
|
||||
return runPacket
|
||||
if !opts.Sudo {
|
||||
// normal, non-sudo command
|
||||
runPacket.Command = opts.Command
|
||||
return runPacket, nil
|
||||
}
|
||||
if opts.SudoWithPass {
|
||||
pwFdNum, err := opts.NextFreeFdNum()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pwRfd := packet.RemoteFd{FdNum: pwFdNum, Read: true, Content: opts.SudoPw}
|
||||
opts.Fds = append(opts.Fds, pwRfd)
|
||||
commandFdNum, err := opts.NextFreeFdNum()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commandRfd := packet.RemoteFd{FdNum: commandFdNum, Read: true, Content: opts.Command}
|
||||
opts.Fds = append(opts.Fds, commandRfd)
|
||||
commandStdinFdNum, err := opts.NextFreeFdNum()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commandStdinRfd := packet.RemoteFd{FdNum: commandStdinFdNum, Read: true, DupStdin: true}
|
||||
opts.Fds = append(opts.Fds, commandStdinRfd)
|
||||
opts.CommandStdinFdNum = commandStdinFdNum
|
||||
maxFdNum := opts.MaxFdNum()
|
||||
runPacket.Command = fmt.Sprintf(RemoteSudoPasswordCommandFmt, pwFdNum, maxFdNum+1, commandFdNum, commandStdinFdNum)
|
||||
runPacket.Fds = opts.Fds
|
||||
return runPacket, nil
|
||||
} else {
|
||||
commandFdNum, err := opts.NextFreeFdNum()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rfd := packet.RemoteFd{FdNum: commandFdNum, Read: true, Content: opts.Command}
|
||||
opts.Fds = append(opts.Fds, rfd)
|
||||
maxFdNum := opts.MaxFdNum()
|
||||
runPacket.Command = fmt.Sprintf(RemoteSudoCommandFmt, maxFdNum+1, commandFdNum)
|
||||
runPacket.Fds = opts.Fds
|
||||
return runPacket, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) NextFreeFdNum() (int, error) {
|
||||
fdMap := make(map[int]bool)
|
||||
for _, fd := range opts.Fds {
|
||||
fdMap[fd.FdNum] = true
|
||||
}
|
||||
for i := 3; i <= MaxFdNum; i++ {
|
||||
if !fdMap[i] {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("reached maximum number of fds, all fds between 3-%d are in use", MaxFdNum)
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MaxFdNum() int {
|
||||
maxFdNum := 3
|
||||
for _, fd := range opts.Fds {
|
||||
if fd.FdNum > maxFdNum {
|
||||
maxFdNum = fd.FdNum
|
||||
}
|
||||
}
|
||||
return maxFdNum
|
||||
}
|
||||
|
||||
func ValidateRemoteFds(rfds []packet.RemoteFd) error {
|
||||
@ -261,11 +333,14 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runPacket, err := opts.MakeRunPacket() // modifies opts
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := MakeShExec("", "")
|
||||
sshRemoteCommand := `PATH=$PATH:~/.mshell; mshell --remote`
|
||||
var fullSshOpts []string
|
||||
fullSshOpts = append(fullSshOpts, opts.SSHOpts...)
|
||||
fullSshOpts = append(fullSshOpts, sshRemoteCommand)
|
||||
fullSshOpts = append(fullSshOpts, SSHRemoteCommand)
|
||||
ecmd := exec.Command("ssh", fullSshOpts...)
|
||||
cmd.Cmd = ecmd
|
||||
inputWriter, err := ecmd.StdinPipe()
|
||||
@ -280,10 +355,23 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating stderr pipe: %v", err)
|
||||
}
|
||||
cmd.Multiplexer.MakeRawFdReader(0, os.Stdin, false)
|
||||
if !opts.SudoWithPass {
|
||||
cmd.Multiplexer.MakeRawFdReader(0, os.Stdin, false)
|
||||
}
|
||||
cmd.Multiplexer.MakeRawFdWriter(1, os.Stdout, false)
|
||||
cmd.Multiplexer.MakeRawFdWriter(2, os.Stderr, false)
|
||||
for _, rfd := range opts.Fds {
|
||||
for _, rfd := range runPacket.Fds {
|
||||
if rfd.Read && rfd.Content != "" {
|
||||
err = cmd.Multiplexer.MakeStringFdReader(rfd.FdNum, rfd.Content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating content fd %d", rfd.FdNum)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if rfd.Read && rfd.DupStdin {
|
||||
cmd.Multiplexer.MakeRawFdReader(rfd.FdNum, os.Stdin, false)
|
||||
continue
|
||||
}
|
||||
fd := os.NewFile(uintptr(rfd.FdNum), fmt.Sprintf("/dev/fd/%d", rfd.FdNum))
|
||||
if fd == nil {
|
||||
return nil, fmt.Errorf("cannot open fd %d", rfd.FdNum)
|
||||
@ -322,7 +410,6 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
||||
if !versionOk {
|
||||
return nil, fmt.Errorf("did not receive version from remote mshell")
|
||||
}
|
||||
runPacket := opts.MakeRunPacket()
|
||||
sender.SendPacket(runPacket)
|
||||
if opts.Debug {
|
||||
cmd.Multiplexer.Debug = true
|
||||
|
Loading…
Reference in New Issue
Block a user