mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-12 01:01:50 +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
|
opts.Debug = true
|
||||||
continue
|
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 {
|
if opts.IsSSH {
|
||||||
// parse SSH opts
|
// parse SSH opts
|
||||||
@ -339,6 +366,9 @@ func handleClient() (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, fmt.Errorf("parsing opts: %w", err)
|
return 1, fmt.Errorf("parsing opts: %w", err)
|
||||||
}
|
}
|
||||||
|
if opts.Debug {
|
||||||
|
packet.GlobalDebug = true
|
||||||
|
}
|
||||||
if !opts.IsSSH {
|
if !opts.IsSSH {
|
||||||
return 1, fmt.Errorf("when running in client mode '--ssh' option must be present")
|
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
|
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) {
|
func (m *Multiplexer) MakeRawFdReader(fdNum int, fd *os.File, shouldClose bool) {
|
||||||
m.Lock.Lock()
|
m.Lock.Lock()
|
||||||
defer m.Lock.Unlock()
|
defer m.Lock.Unlock()
|
||||||
|
@ -385,9 +385,11 @@ type TermSize struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RemoteFd struct {
|
type RemoteFd struct {
|
||||||
FdNum int `json:"fdnum"`
|
FdNum int `json:"fdnum"`
|
||||||
Read bool `json:"read"`
|
Read bool `json:"read"`
|
||||||
Write bool `json:"write"`
|
Write bool `json:"write"`
|
||||||
|
Content string `json:"-"`
|
||||||
|
DupStdin bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunPacketType struct {
|
type RunPacketType struct {
|
||||||
|
@ -30,6 +30,12 @@ const MaxCols = 1024
|
|||||||
const MaxFdNum = 1023
|
const MaxFdNum = 1023
|
||||||
const FirstExtraFilesFdNum = 3
|
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 {
|
type ShExecType struct {
|
||||||
Lock *sync.Mutex
|
Lock *sync.Mutex
|
||||||
StartTs time.Time
|
StartTs time.Time
|
||||||
@ -213,21 +219,87 @@ func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecT
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClientOpts struct {
|
type ClientOpts struct {
|
||||||
IsSSH bool
|
IsSSH bool
|
||||||
SSHOptsTerm bool
|
SSHOptsTerm bool
|
||||||
SSHOpts []string
|
SSHOpts []string
|
||||||
Command string
|
Command string
|
||||||
Fds []packet.RemoteFd
|
Fds []packet.RemoteFd
|
||||||
Cwd string
|
Cwd string
|
||||||
Debug bool
|
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 := packet.MakeRunPacket()
|
||||||
runPacket.Command = opts.Command
|
|
||||||
runPacket.Cwd = opts.Cwd
|
runPacket.Cwd = opts.Cwd
|
||||||
runPacket.Fds = opts.Fds
|
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 {
|
func ValidateRemoteFds(rfds []packet.RemoteFd) error {
|
||||||
@ -261,11 +333,14 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
runPacket, err := opts.MakeRunPacket() // modifies opts
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
cmd := MakeShExec("", "")
|
cmd := MakeShExec("", "")
|
||||||
sshRemoteCommand := `PATH=$PATH:~/.mshell; mshell --remote`
|
|
||||||
var fullSshOpts []string
|
var fullSshOpts []string
|
||||||
fullSshOpts = append(fullSshOpts, opts.SSHOpts...)
|
fullSshOpts = append(fullSshOpts, opts.SSHOpts...)
|
||||||
fullSshOpts = append(fullSshOpts, sshRemoteCommand)
|
fullSshOpts = append(fullSshOpts, SSHRemoteCommand)
|
||||||
ecmd := exec.Command("ssh", fullSshOpts...)
|
ecmd := exec.Command("ssh", fullSshOpts...)
|
||||||
cmd.Cmd = ecmd
|
cmd.Cmd = ecmd
|
||||||
inputWriter, err := ecmd.StdinPipe()
|
inputWriter, err := ecmd.StdinPipe()
|
||||||
@ -280,10 +355,23 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating stderr pipe: %v", err)
|
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(1, os.Stdout, false)
|
||||||
cmd.Multiplexer.MakeRawFdWriter(2, os.Stderr, 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))
|
fd := os.NewFile(uintptr(rfd.FdNum), fmt.Sprintf("/dev/fd/%d", rfd.FdNum))
|
||||||
if fd == nil {
|
if fd == nil {
|
||||||
return nil, fmt.Errorf("cannot open fd %d", rfd.FdNum)
|
return nil, fmt.Errorf("cannot open fd %d", rfd.FdNum)
|
||||||
@ -322,7 +410,6 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
|||||||
if !versionOk {
|
if !versionOk {
|
||||||
return nil, fmt.Errorf("did not receive version from remote mshell")
|
return nil, fmt.Errorf("did not receive version from remote mshell")
|
||||||
}
|
}
|
||||||
runPacket := opts.MakeRunPacket()
|
|
||||||
sender.SendPacket(runPacket)
|
sender.SendPacket(runPacket)
|
||||||
if opts.Debug {
|
if opts.Debug {
|
||||||
cmd.Multiplexer.Debug = true
|
cmd.Multiplexer.Debug = true
|
||||||
|
Loading…
Reference in New Issue
Block a user