From 1d10ca739f3599b9bd746783ad15c8f51ce9f75c Mon Sep 17 00:00:00 2001 From: cdoern Date: Fri, 25 Jun 2021 14:26:33 -0400 Subject: Created scp.go image_scp_test.go and podman-image-scp.1.md added functionality for image secure copying from local to remote. Also moved system connection add code around a bit so functions within that file can be used by scp. Signed-off-by: cdoern --- cmd/podman/system/connection/add.go | 126 ++++++++++++++++++--------------- cmd/podman/system/connection/shared.go | 28 ++++++++ 2 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 cmd/podman/system/connection/shared.go (limited to 'cmd/podman/system') diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 912193d0b..290b3c245 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -1,7 +1,6 @@ package connection import ( - "bytes" "encoding/json" "fmt" "net" @@ -9,6 +8,7 @@ import ( "os" "os/user" "regexp" + "time" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" @@ -83,7 +83,6 @@ func add(cmd *cobra.Command, args []string) error { } else if !match { dest = "ssh://" + dest } - uri, err := url.Parse(dest) if err != nil { return err @@ -96,7 +95,7 @@ func add(cmd *cobra.Command, args []string) error { switch uri.Scheme { case "ssh": if uri.User.Username() == "" { - if uri.User, err = getUserInfo(uri); err != nil { + if uri.User, err = GetUserInfo(uri); err != nil { return err } } @@ -108,9 +107,12 @@ func add(cmd *cobra.Command, args []string) error { if uri.Port() == "" { uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue) } - + iden := "" + if cmd.Flags().Changed("identity") { + iden = cOpts.Identity + } if uri.Path == "" || uri.Path == "/" { - if uri.Path, err = getUDS(cmd, uri); err != nil { + if uri.Path, err = getUDS(cmd, uri, iden); err != nil { return err } } @@ -178,7 +180,7 @@ func add(cmd *cobra.Command, args []string) error { return cfg.Write() } -func getUserInfo(uri *url.URL) (*url.Userinfo, error) { +func GetUserInfo(uri *url.URL) (*url.Userinfo, error) { var ( usr *user.User err error @@ -202,30 +204,74 @@ func getUserInfo(uri *url.URL) (*url.Userinfo, error) { return url.User(usr.Username), nil } -func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { - var signers []ssh.Signer +func getUDS(cmd *cobra.Command, uri *url.URL, iden string) (string, error) { + cfg, err := ValidateAndConfigure(uri, iden) + if err != nil { + return "", errors.Wrapf(err, "failed to validate") + } + dial, err := ssh.Dial("tcp", uri.Host, cfg) + if err != nil { + return "", errors.Wrapf(err, "failed to connect") + } + defer dial.Close() + + session, err := dial.NewSession() + if err != nil { + return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host) + } + defer session.Close() + + // Override podman binary for testing etc + podman := "podman" + if v, found := os.LookupEnv("PODMAN_BINARY"); found { + podman = v + } + run := podman + " info --format=json" + out, err := ExecRemoteCommand(dial, run) + if err != nil { + return "", err + } + infoJSON, err := json.Marshal(out) + if err != nil { + return "", err + } + + var info define.Info + if err := json.Unmarshal(infoJSON, &info); err != nil { + return "", errors.Wrapf(err, "failed to parse 'podman info' results") + } + + if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { + return "", errors.Errorf("remote podman %q failed to report its UDS socket", uri.Host) + } + return info.Host.RemoteSocket.Path, nil +} +// ValidateAndConfigure will take a ssh url and an identity key (rsa and the like) and ensure the information given is valid +// iden iden can be blank to mean no identity key +// once the function validates the information it creates and returns an ssh.ClientConfig +func ValidateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error) { + var signers []ssh.Signer passwd, passwdSet := uri.User.Password() - if cmd.Flags().Changed("identity") { - value := cmd.Flag("identity").Value.String() + if iden != "" { // iden might be blank if coming from image scp or if no validation is needed + value := iden s, err := terminal.PublicKey(value, []byte(passwd)) if err != nil { - return "", errors.Wrapf(err, "failed to read identity %q", value) + return nil, errors.Wrapf(err, "failed to read identity %q", value) } signers = append(signers, s) logrus.Debugf("SSH Ident Key %q %s %s", value, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) } - - if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { + if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { // validate ssh information, specifically the unix file socket used by the ssh agent. logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) c, err := net.Dial("unix", sock) if err != nil { - return "", err + return nil, err } agentSigners, err := agent.NewClient(c).Signers() if err != nil { - return "", err + return nil, err } signers = append(signers, agentSigners...) @@ -236,11 +282,9 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { } } } - - var authMethods []ssh.AuthMethod + var authMethods []ssh.AuthMethod // now we validate and check for the authorization methods, most notaibly public key authorization if len(signers) > 0 { var dedup = make(map[string]ssh.Signer) - // Dedup signers based on fingerprint, ssh-agent keys override CONTAINER_SSHKEY for _, s := range signers { fp := ssh.FingerprintSHA256(s.PublicKey()) if _, found := dedup[fp]; found { @@ -253,60 +297,28 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { for _, s := range dedup { uniq = append(uniq, s) } - authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) { return uniq, nil })) } - - if passwdSet { + if passwdSet { // if password authentication is given and valid, add to the list authMethods = append(authMethods, ssh.Password(passwd)) } - if len(authMethods) == 0 { authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) { pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username())) return string(pass), err })) } - + tick, err := time.ParseDuration("40s") + if err != nil { + return nil, err + } cfg := &ssh.ClientConfig{ User: uri.User.Username(), Auth: authMethods, HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: tick, } - dial, err := ssh.Dial("tcp", uri.Host, cfg) - if err != nil { - return "", errors.Wrapf(err, "failed to connect") - } - defer dial.Close() - - session, err := dial.NewSession() - if err != nil { - return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host) - } - defer session.Close() - - // Override podman binary for testing etc - podman := "podman" - if v, found := os.LookupEnv("PODMAN_BINARY"); found { - podman = v - } - run := podman + " info --format=json" - - var buffer bytes.Buffer - session.Stdout = &buffer - if err := session.Run(run); err != nil { - return "", err - } - - var info define.Info - if err := json.Unmarshal(buffer.Bytes(), &info); err != nil { - return "", errors.Wrapf(err, "failed to parse 'podman info' results") - } - - if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { - return "", errors.Errorf("remote podman %q failed to report its UDS socket", uri.Host) - } - return info.Host.RemoteSocket.Path, nil + return cfg, nil } diff --git a/cmd/podman/system/connection/shared.go b/cmd/podman/system/connection/shared.go new file mode 100644 index 000000000..3fd7c59fb --- /dev/null +++ b/cmd/podman/system/connection/shared.go @@ -0,0 +1,28 @@ +package connection + +import ( + "bytes" + + "github.com/pkg/errors" + "golang.org/x/crypto/ssh" +) + +// ExecRemoteCommand takes a ssh client connection and a command to run and executes the +// command on the specified client. The function returns the Stdout from the client or the Stderr +func ExecRemoteCommand(dial *ssh.Client, run string) (string, error) { + sess, err := dial.NewSession() // new ssh client session + if err != nil { + return "", err + } + defer sess.Close() + + var buffer bytes.Buffer + var bufferErr bytes.Buffer + sess.Stdout = &buffer // output from client funneled into buffer + sess.Stderr = &bufferErr // err form client funneled into buffer + if err := sess.Run(run); err != nil { // run the command on the ssh client + return "", errors.Wrapf(err, bufferErr.String()) + } + out := buffer.String() // output from command + return out, nil +} -- cgit v1.2.3-54-g00ecf