diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index 7c9346b47e..94bc7449b8 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -120,7 +120,7 @@ func ssh(cmd *cobra.Command, args []string) error { username = mc.SSH.RemoteUsername } - err = machine.CommonSSH(username, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, sshOpts.Args) + err = machine.CommonSSHShell(username, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, sshOpts.Args) return utils.HandleOSExecError(err) } diff --git a/go.mod b/go.mod index 92665a66de..e6a4c3c1d6 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,7 @@ require ( github.com/vbauerster/mpb/v8 v8.7.2 github.com/vishvananda/netlink v1.2.1-beta.2 go.etcd.io/bbolt v1.3.9 + golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/net v0.22.0 golang.org/x/sync v0.6.0 @@ -210,7 +211,6 @@ require ( go.opentelemetry.io/otel/sdk v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/pkg/machine/hyperv/volumes.go b/pkg/machine/hyperv/volumes.go index a34ea4bc83..ea94147dbd 100644 --- a/pkg/machine/hyperv/volumes.go +++ b/pkg/machine/hyperv/volumes.go @@ -42,8 +42,7 @@ func removeShares(mc *vmconfigs.MachineConfig) error { func startShares(mc *vmconfigs.MachineConfig) error { for _, mount := range mc.Mounts { - args := []string{"-q", "--"} - + var args []string cleanTarget := path.Clean(mount.Target) requiresChattr := !strings.HasPrefix(cleanTarget, "/home") && !strings.HasPrefix(cleanTarget, "/mnt") if requiresChattr { diff --git a/pkg/machine/qemu/stubber.go b/pkg/machine/qemu/stubber.go index 750e444a49..dbd762a0d5 100644 --- a/pkg/machine/qemu/stubber.go +++ b/pkg/machine/qemu/stubber.go @@ -313,7 +313,7 @@ func (q *QEMUStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool) // create mountpoint directory if it doesn't exist // because / is immutable, we have to monkey around with permissions // if we dont mount in /home or /mnt - args := []string{"-q", "--"} + var args []string if !strings.HasPrefix(mount.Target, "/home") && !strings.HasPrefix(mount.Target, "/mnt") { args = append(args, "sudo", "chattr", "-i", "/", ";") } @@ -333,7 +333,7 @@ func (q *QEMUStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool) if mount.ReadOnly { mountOptions = append(mountOptions, []string{"-o", "ro"}...) } - err = machine.CommonSSH(mc.SSH.RemoteUsername, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, append([]string{"-q", "--", "sudo", "mount"}, mountOptions...)) + err = machine.CommonSSH(mc.SSH.RemoteUsername, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, append([]string{"sudo", "mount"}, mountOptions...)) if err != nil { return err } diff --git a/pkg/machine/ssh.go b/pkg/machine/ssh.go index de5fd9aee8..74b811b984 100644 --- a/pkg/machine/ssh.go +++ b/pkg/machine/ssh.go @@ -1,31 +1,115 @@ package machine import ( + "bufio" "fmt" "io" "os" "os/exec" "strconv" + "strings" "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" ) // CommonSSH is a common function for ssh'ing to a podman machine using system-connections // and a port // TODO This should probably be taught about an machineconfig to reduce input func CommonSSH(username, identityPath, name string, sshPort int, inputArgs []string) error { - return commonSSH(username, identityPath, name, sshPort, inputArgs, false, os.Stdin) + return commonBuiltinSSH(username, identityPath, name, sshPort, inputArgs, true, os.Stdin) +} + +func CommonSSHShell(username, identityPath, name string, sshPort int, inputArgs []string) error { + return commonNativeSSH(username, identityPath, name, sshPort, inputArgs, os.Stdin) } func CommonSSHSilent(username, identityPath, name string, sshPort int, inputArgs []string) error { - return commonSSH(username, identityPath, name, sshPort, inputArgs, true, os.Stdin) + return commonBuiltinSSH(username, identityPath, name, sshPort, inputArgs, false, nil) } func CommonSSHWithStdin(username, identityPath, name string, sshPort int, inputArgs []string, stdin io.Reader) error { - return commonSSH(username, identityPath, name, sshPort, inputArgs, false, stdin) + return commonBuiltinSSH(username, identityPath, name, sshPort, inputArgs, true, stdin) +} + +func commonBuiltinSSH(username, identityPath, name string, sshPort int, inputArgs []string, passOutput bool, stdin io.Reader) error { + config, err := createConfig(username, identityPath) + if err != nil { + return err + } + + client, err := ssh.Dial("tcp", fmt.Sprintf("localhost:%d", sshPort), config) + if err != nil { + return err + } + defer client.Close() + + session, err := client.NewSession() + if err != nil { + return err + } + defer session.Close() + + cmd := strings.Join(inputArgs, " ") + logrus.Debugf("Running ssh command on machine %q: %s", name, cmd) + session.Stdin = stdin + if passOutput { + session.Stdout = os.Stdout + session.Stderr = os.Stderr + } else if logrus.IsLevelEnabled(logrus.DebugLevel) { + return runSessionWithDebug(session, cmd) + } + + return session.Run(cmd) } -func commonSSH(username, identityPath, name string, sshPort int, inputArgs []string, silent bool, stdin io.Reader) error { +func runSessionWithDebug(session *ssh.Session, cmd string) error { + outPipe, err := session.StdoutPipe() + if err != nil { + return err + } + errPipe, err := session.StderrPipe() + if err != nil { + return err + } + logOuput := func(pipe io.Reader, done chan struct{}) { + scanner := bufio.NewScanner(pipe) + for scanner.Scan() { + logrus.Debugf("ssh output: %s", scanner.Text()) + } + done <- struct{}{} + } + if err := session.Start(cmd); err != nil { + return err + } + completed := make(chan struct{}, 2) + go logOuput(outPipe, completed) + go logOuput(errPipe, completed) + <-completed + <-completed + + return session.Wait() +} + +func createConfig(user string, identityPath string) (*ssh.ClientConfig, error) { + key, err := os.ReadFile(identityPath) + if err != nil { + return nil, err + } + + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + return nil, err + } + + return &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }, nil +} + +func commonNativeSSH(username, identityPath, name string, sshPort int, inputArgs []string, stdin io.Reader) error { sshDestination := username + "@localhost" port := strconv.Itoa(sshPort) interactive := true @@ -45,10 +129,8 @@ func commonSSH(username, identityPath, name string, sshPort int, inputArgs []str cmd := exec.Command("ssh", args...) logrus.Debugf("Executing: ssh %v\n", args) - if !silent { - if err := setupIOPassthrough(cmd, interactive, stdin); err != nil { - return err - } + if err := setupIOPassthrough(cmd, interactive, stdin); err != nil { + return err } return cmd.Run()