mirror of
https://github.com/containers/podman.git
synced 2025-05-28 05:08:18 +08:00

As the title says. This is the last step in the vendor dance for Podman v5.0. [NO NEW TESTS NEEDED] Signed-off-by: tomsweeneyredhat <tsweeney@redhat.com>
194 lines
4.2 KiB
Go
194 lines
4.2 KiB
Go
package ssh
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/containers/common/pkg/config"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
const sshdPort = 22
|
|
|
|
func Validate(user *url.Userinfo, path string, port int, identity string) (*config.Destination, *url.URL, error) {
|
|
// url.Parse NEEDS ssh://, if this ever fails or returns some nonsense, that is why.
|
|
uri, err := url.Parse(path)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// sometimes we are not going to have a path, this breaks uri.Hostname()
|
|
if uri.Host == "" && strings.Contains(uri.String(), "@") {
|
|
uri.Host = strings.Split(uri.String(), "@")[1]
|
|
}
|
|
|
|
if uri.Port() == "" {
|
|
if port == 0 {
|
|
port = sshdPort
|
|
}
|
|
uri.Host = net.JoinHostPort(uri.Host, strconv.Itoa(port))
|
|
}
|
|
|
|
if user != nil {
|
|
uri.User = user
|
|
}
|
|
|
|
dst := config.Destination{
|
|
URI: uri.String(),
|
|
}
|
|
|
|
if len(identity) > 0 {
|
|
dst.Identity = identity
|
|
}
|
|
return &dst, uri, err
|
|
}
|
|
|
|
var (
|
|
passPhrase []byte
|
|
phraseSync sync.Once
|
|
password []byte
|
|
passwordSync sync.Once
|
|
)
|
|
|
|
// ReadPassword prompts for a secret and returns value input by user from stdin
|
|
// Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported.
|
|
// Additionally, all input after `<secret>/n` is queued to podman command.
|
|
func ReadPassword(prompt string) (pw []byte, err error) {
|
|
fd := int(os.Stdin.Fd())
|
|
if term.IsTerminal(fd) {
|
|
fmt.Fprint(os.Stderr, prompt)
|
|
pw, err = term.ReadPassword(fd)
|
|
fmt.Fprintln(os.Stderr)
|
|
return
|
|
}
|
|
|
|
var b [1]byte
|
|
for {
|
|
n, err := os.Stdin.Read(b[:])
|
|
// terminal.ReadPassword discards any '\r', so we do the same
|
|
if n > 0 && b[0] != '\r' {
|
|
if b[0] == '\n' {
|
|
return pw, nil
|
|
}
|
|
pw = append(pw, b[0])
|
|
// limit size, so that a wrong input won't fill up the memory
|
|
if len(pw) > 1024 {
|
|
err = errors.New("password too long, 1024 byte limit")
|
|
}
|
|
}
|
|
if err != nil {
|
|
// terminal.ReadPassword accepts EOF-terminated passwords
|
|
// if non-empty, so we do the same
|
|
if err == io.EOF && len(pw) > 0 {
|
|
err = nil
|
|
}
|
|
return pw, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func PublicKey(path string, passphrase []byte) (ssh.Signer, error) {
|
|
key, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
signer, err := ssh.ParsePrivateKey(key)
|
|
if err != nil {
|
|
if _, ok := err.(*ssh.PassphraseMissingError); !ok {
|
|
return nil, err
|
|
}
|
|
if len(passphrase) == 0 {
|
|
passphrase = ReadPassphrase()
|
|
}
|
|
return ssh.ParsePrivateKeyWithPassphrase(key, passphrase)
|
|
}
|
|
return signer, nil
|
|
}
|
|
|
|
func ReadPassphrase() []byte {
|
|
phraseSync.Do(func() {
|
|
secret, err := ReadPassword("Key Passphrase: ")
|
|
if err != nil {
|
|
secret = []byte{}
|
|
}
|
|
passPhrase = secret
|
|
})
|
|
return passPhrase
|
|
}
|
|
|
|
func ReadLogin() []byte {
|
|
passwordSync.Do(func() {
|
|
secret, err := ReadPassword("Login password: ")
|
|
if err != nil {
|
|
secret = []byte{}
|
|
}
|
|
password = secret
|
|
})
|
|
return password
|
|
}
|
|
|
|
func ParseScpArgs(options ConnectionScpOptions) (string, string, string, bool, error) {
|
|
// assume load to remote
|
|
host := options.Destination
|
|
if strings.Contains(host, "ssh://") {
|
|
host = strings.Split(host, "ssh://")[1]
|
|
}
|
|
localPath := options.Source
|
|
if strings.Contains(localPath, "ssh://") {
|
|
localPath = strings.Split(localPath, "ssh://")[1]
|
|
}
|
|
var remotePath string
|
|
swap := false
|
|
if split := strings.Split(localPath, ":"); len(split) == 2 {
|
|
// save to remote, load to local
|
|
host = split[0]
|
|
remotePath = split[1]
|
|
localPath = options.Destination
|
|
swap = true
|
|
} else {
|
|
split = strings.Split(host, ":")
|
|
if len(split) != 2 {
|
|
return "", "", "", false, errors.New("no remote destination provided")
|
|
}
|
|
host = split[0]
|
|
remotePath = split[1]
|
|
}
|
|
remotePath = strings.TrimSuffix(remotePath, "\n")
|
|
return host, remotePath, localPath, swap, nil
|
|
}
|
|
|
|
func DialNet(sshClient *ssh.Client, mode string, url *url.URL) (net.Conn, error) {
|
|
port := sshdPort
|
|
if url.Port() != "" {
|
|
p, err := strconv.Atoi(url.Port())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
port = p
|
|
}
|
|
if _, _, err := Validate(url.User, url.Hostname(), port, ""); err != nil {
|
|
return nil, err
|
|
}
|
|
return sshClient.Dial(mode, url.Path)
|
|
}
|
|
|
|
func DefineMode(flag string) EngineMode {
|
|
switch flag {
|
|
case "native":
|
|
return NativeMode
|
|
case "golang":
|
|
return GolangMode
|
|
default:
|
|
return InvalidMode
|
|
}
|
|
}
|