1
0
mirror of https://github.com/openshift/installer.git synced 2026-02-06 09:47:02 +01:00
Files
installer/pkg/gather/ssh/ssh.go
2022-12-13 15:40:58 +01:00

144 lines
4.1 KiB
Go

// Package ssh contains utilities that help gather logs, etc. on failures using ssh.
package ssh
import (
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/pkg/sftp"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"github.com/openshift/installer/pkg/lineprinter"
)
// NewClient creates a new SSH client which can be used to SSH to address using user and the keys.
//
// if keys list is empty, it tries to load the keys from the user's environment.
func NewClient(user, address string, keys []string) (*ssh.Client, error) {
ag, agentType, err := getAgent(keys)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize the SSH agent")
}
client, err := ssh.Dial("tcp", address, &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
// Use a callback rather than PublicKeys
// so we only consult the agent once the remote server
// wants it.
ssh.PublicKeysCallback(ag.Signers),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
if err != nil {
if strings.Contains(err.Error(), "ssh: handshake failed: ssh: unable to authenticate") {
if agentType == "agent" {
return nil, errors.Wrap(err, "failed to use pre-existing agent, make sure the appropriate keys exist in the agent for authentication")
}
return nil, errors.Wrap(err, "failed to use the provided keys for authentication")
}
return nil, err
}
if err := agent.ForwardToAgent(client, ag); err != nil {
return nil, errors.Wrap(err, "failed to forward agent")
}
return client, nil
}
// Run uses an SSH client to execute commands.
func Run(client *ssh.Client, command string) error {
sess, err := client.NewSession()
if err != nil {
return err
}
defer sess.Close()
if err := agent.RequestAgentForwarding(sess); err != nil {
return errors.Wrap(err, "failed to setup request agent forwarding")
}
debugW := &lineprinter.LinePrinter{Print: (&lineprinter.Trimmer{WrappedPrint: logrus.Debug}).Print}
defer debugW.Close()
sess.Stdout = debugW
sess.Stderr = debugW
return sess.Run(command)
}
// PullFileTo downloads the file from remote server using SSH connection and writes to localPath.
func PullFileTo(client *ssh.Client, remotePath, localPath string) error {
sc, err := sftp.NewClient(client)
if err != nil {
return errors.Wrap(err, "failed to initialize the sftp client")
}
defer sc.Close()
// Open the source file
rFile, err := sc.Open(remotePath)
if err != nil {
return errors.Wrap(err, "failed to open remote file")
}
defer rFile.Close()
lFile, err := os.Create(localPath)
if err != nil {
return errors.Wrap(err, "failed to create file")
}
defer lFile.Close()
if _, err := rFile.WriteTo(lFile); err != nil {
return err
}
return nil
}
// defaultPrivateSSHKeys returns a list of all the PRIVATE SSH keys from user's home directory.
// It does not return any intermediate errors if at least one private key was loaded.
func defaultPrivateSSHKeys() (map[string]interface{}, error) {
d := filepath.Join(os.Getenv("HOME"), ".ssh")
paths, err := os.ReadDir(d)
if err != nil {
return nil, errors.Wrapf(err, "failed to read directory %q", d)
}
var files []string
for _, path := range paths {
if path.IsDir() {
continue
}
files = append(files, filepath.Join(d, path.Name()))
}
keys, err := LoadPrivateSSHKeys(files)
if len(keys) > 0 {
return keys, nil
}
return nil, err
}
// LoadPrivateSSHKeys try to optimistically load PRIVATE SSH keys from the all paths.
func LoadPrivateSSHKeys(paths []string) (map[string]interface{}, error) {
var errs []error
keys := make(map[string]interface{})
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
errs = append(errs, errors.Wrapf(err, "failed to read %q", path))
continue
}
key, err := ssh.ParseRawPrivateKey(data)
if err != nil {
logrus.Debugf("failed to parse SSH private key from %q", path)
errs = append(errs, errors.Wrapf(err, "failed to parse SSH private key from %q", path))
continue
}
keys[path] = key
}
if err := utilerrors.NewAggregate(errs); err != nil {
return keys, err
}
return keys, nil
}