mirror of https://github.com/containers/podman.git
160 lines
5.0 KiB
Go
160 lines
5.0 KiB
Go
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 kernel’s 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 kernel’s 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 kernel’s 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="}
|
||
}
|