Use built-in ssh impl for all non-pty operations

Windows is not guaranteed to have the SSH feature installed, so prefer the use
of the built-in ssh client for all operations other than podman machine ssh,
which requires terminal pty logic. This restores previous behavior in 4.x.

Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
Jason T. Greene
2024-03-24 19:54:02 -05:00
committed by openshift-cherrypick-robot
parent e852b970f6
commit 3123138729
5 changed files with 95 additions and 14 deletions

View File

@ -120,7 +120,7 @@ func ssh(cmd *cobra.Command, args []string) error {
username = mc.SSH.RemoteUsername 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) return utils.HandleOSExecError(err)
} }

2
go.mod
View File

@ -68,6 +68,7 @@ require (
github.com/vbauerster/mpb/v8 v8.7.2 github.com/vbauerster/mpb/v8 v8.7.2
github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vishvananda/netlink v1.2.1-beta.2
go.etcd.io/bbolt v1.3.9 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/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/net v0.22.0 golang.org/x/net v0.22.0
golang.org/x/sync v0.6.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/sdk v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.22.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect
golang.org/x/arch v0.7.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/mod v0.15.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect

View File

@ -42,8 +42,7 @@ func removeShares(mc *vmconfigs.MachineConfig) error {
func startShares(mc *vmconfigs.MachineConfig) error { func startShares(mc *vmconfigs.MachineConfig) error {
for _, mount := range mc.Mounts { for _, mount := range mc.Mounts {
args := []string{"-q", "--"} var args []string
cleanTarget := path.Clean(mount.Target) cleanTarget := path.Clean(mount.Target)
requiresChattr := !strings.HasPrefix(cleanTarget, "/home") && !strings.HasPrefix(cleanTarget, "/mnt") requiresChattr := !strings.HasPrefix(cleanTarget, "/home") && !strings.HasPrefix(cleanTarget, "/mnt")
if requiresChattr { if requiresChattr {

View File

@ -313,7 +313,7 @@ func (q *QEMUStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool)
// create mountpoint directory if it doesn't exist // create mountpoint directory if it doesn't exist
// because / is immutable, we have to monkey around with permissions // because / is immutable, we have to monkey around with permissions
// if we dont mount in /home or /mnt // 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") { if !strings.HasPrefix(mount.Target, "/home") && !strings.HasPrefix(mount.Target, "/mnt") {
args = append(args, "sudo", "chattr", "-i", "/", ";") args = append(args, "sudo", "chattr", "-i", "/", ";")
} }
@ -333,7 +333,7 @@ func (q *QEMUStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool)
if mount.ReadOnly { if mount.ReadOnly {
mountOptions = append(mountOptions, []string{"-o", "ro"}...) 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 { if err != nil {
return err return err
} }

View File

@ -1,31 +1,115 @@
package machine package machine
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
"strings"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
) )
// CommonSSH is a common function for ssh'ing to a podman machine using system-connections // CommonSSH is a common function for ssh'ing to a podman machine using system-connections
// and a port // and a port
// TODO This should probably be taught about an machineconfig to reduce input // TODO This should probably be taught about an machineconfig to reduce input
func CommonSSH(username, identityPath, name string, sshPort int, inputArgs []string) error { 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 { 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 { 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 commonSSH(username, identityPath, name string, sshPort int, inputArgs []string, silent bool, stdin io.Reader) error { 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 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" sshDestination := username + "@localhost"
port := strconv.Itoa(sshPort) port := strconv.Itoa(sshPort)
interactive := true interactive := true
@ -45,10 +129,8 @@ func commonSSH(username, identityPath, name string, sshPort int, inputArgs []str
cmd := exec.Command("ssh", args...) cmd := exec.Command("ssh", args...)
logrus.Debugf("Executing: ssh %v\n", args) logrus.Debugf("Executing: ssh %v\n", args)
if !silent { if err := setupIOPassthrough(cmd, interactive, stdin); err != nil {
if err := setupIOPassthrough(cmd, interactive, stdin); err != nil { return err
return err
}
} }
return cmd.Run() return cmd.Run()