podman/pkg/machine/ssh.go

160 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package machine
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
)
// LocalhostSSH 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 LocalhostSSH(username, identityPath, name string, sshPort int, inputArgs []string) error {
return localhostBuiltinSSH(username, identityPath, name, sshPort, inputArgs, true, os.Stdin)
}
func LocalhostSSHShell(username, identityPath, name string, sshPort int, inputArgs []string) error {
return localhostNativeSSH(username, identityPath, name, sshPort, inputArgs, os.Stdin)
}
func LocalhostSSHSilent(username, identityPath, name string, sshPort int, inputArgs []string) error {
return localhostBuiltinSSH(username, identityPath, name, sshPort, inputArgs, false, nil)
}
func LocalhostSSHWithStdin(username, identityPath, name string, sshPort int, inputArgs []string, stdin io.Reader) error {
return localhostBuiltinSSH(username, identityPath, name, sshPort, inputArgs, true, stdin)
}
func localhostBuiltinSSH(username, identityPath, name string, sshPort int, inputArgs []string, passOutput bool, stdin io.Reader) error {
config, err := createLocalhostConfig(username, identityPath) // WARNING: This MUST NOT be generalized to allow communication over untrusted networks.
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()
}
// createLocalhostConfig returns a *ssh.ClientConfig for authenticating a user using a private key
//
// WARNING: This MUST NOT be used to communicate over untrusted networks.
func createLocalhostConfig(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{
// Not specifying ciphers / MACs seems to allow fairly weak ciphers. This config is restricted
// to connecting to localhost: where we rely on the kernels process isolation, not primarily on cryptography.
User: user,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
// This config is restricted to connecting to localhost (and to a VM we manage),
// we rely on the kernels process isolation, not on cryptography,
// This would be UNACCEPTABLE for most other uses.
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}, nil
}
func localhostNativeSSH(username, identityPath, name string, sshPort int, inputArgs []string, stdin io.Reader) error {
sshDestination := username + "@localhost"
port := strconv.Itoa(sshPort)
interactive := true
args := append([]string{"-i", identityPath, "-p", port, sshDestination}, LocalhostSSHArgs()...) // WARNING: This MUST NOT be generalized to allow communication over untrusted networks.
if len(inputArgs) > 0 {
interactive = false
args = append(args, inputArgs...)
} else {
// ensure we have a tty
args = append(args, "-t")
fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", name)
}
cmd := exec.Command("ssh", args...)
logrus.Debugf("Executing: ssh %v\n", args)
if err := setupIOPassthrough(cmd, interactive, stdin); err != nil {
return err
}
return cmd.Run()
}
// LocalhostSSHArgs returns OpenSSH command-line options for connecting with no host key identity checks.
//
// WARNING: This MUST NOT be used to communicate over untrusted networks.
func LocalhostSSHArgs() []string {
// This config is restricted to connecting to localhost (and to a VM we manage),
// we rely on the kernels process isolation, not on cryptography,
// This would be UNACCEPTABLE for most other uses.
return []string{
"-o", "IdentitiesOnly=yes",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=" + os.DevNull,
"-o", "CheckHostIP=no",
"-o", "LogLevel=ERROR",
"-o", "SetEnv=LC_ALL="}
}