Files
tomsweeneyredhat b234bb55e4 Bump Buildah to v1.35.0
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>
2024-03-07 11:51:09 -05:00

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
}
}