github.com/openshift/installer@v1.4.17/pkg/gather/ssh/ssh.go (about)

     1  // Package ssh contains utilities that help gather logs, etc. on failures using ssh.
     2  package ssh
     3  
     4  import (
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  	"github.com/pkg/sftp"
    11  	"github.com/sirupsen/logrus"
    12  	"golang.org/x/crypto/ssh"
    13  	"golang.org/x/crypto/ssh/agent"
    14  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    15  
    16  	"github.com/openshift/installer/pkg/lineprinter"
    17  )
    18  
    19  // NewClient creates a new SSH client which can be used to SSH to address using user and the keys.
    20  //
    21  // if keys list is empty, it tries to load the keys from the user's environment.
    22  func NewClient(user, address string, keys []string) (*ssh.Client, error) {
    23  	ag, agentType, err := getAgent(keys)
    24  	if err != nil {
    25  		return nil, errors.Wrap(err, "failed to initialize the SSH agent")
    26  	}
    27  
    28  	client, err := ssh.Dial("tcp", address, &ssh.ClientConfig{
    29  		User: user,
    30  		Auth: []ssh.AuthMethod{
    31  			// Use a callback rather than PublicKeys
    32  			// so we only consult the agent once the remote server
    33  			// wants it.
    34  			ssh.PublicKeysCallback(ag.Signers),
    35  		},
    36  		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    37  	})
    38  	if err != nil {
    39  		if strings.Contains(err.Error(), "ssh: handshake failed: ssh: unable to authenticate") {
    40  			if agentType == "agent" {
    41  				return nil, errors.Wrap(err, "failed to use pre-existing agent, make sure the appropriate keys exist in the agent for authentication")
    42  			}
    43  			return nil, errors.Wrap(err, "failed to use the provided keys for authentication")
    44  		}
    45  		return nil, err
    46  	}
    47  	if err := agent.ForwardToAgent(client, ag); err != nil {
    48  		return nil, errors.Wrap(err, "failed to forward agent")
    49  	}
    50  	return client, nil
    51  }
    52  
    53  // Run uses an SSH client to execute commands.
    54  func Run(client *ssh.Client, command string) error {
    55  	sess, err := client.NewSession()
    56  	if err != nil {
    57  		return err
    58  	}
    59  	defer sess.Close()
    60  	if err := agent.RequestAgentForwarding(sess); err != nil {
    61  		return errors.Wrap(err, "failed to setup request agent forwarding")
    62  	}
    63  
    64  	debugW := &lineprinter.LinePrinter{Print: (&lineprinter.Trimmer{WrappedPrint: logrus.Debug}).Print}
    65  	defer debugW.Close()
    66  	sess.Stdout = debugW
    67  	sess.Stderr = debugW
    68  	return sess.Run(command)
    69  }
    70  
    71  // PullFileTo downloads the file from remote server using SSH connection and writes to localPath.
    72  func PullFileTo(client *ssh.Client, remotePath, localPath string) error {
    73  	sc, err := sftp.NewClient(client)
    74  	if err != nil {
    75  		return errors.Wrap(err, "failed to initialize the sftp client")
    76  	}
    77  	defer sc.Close()
    78  
    79  	// Open the source file
    80  	rFile, err := sc.Open(remotePath)
    81  	if err != nil {
    82  		return errors.Wrap(err, "failed to open remote file")
    83  	}
    84  	defer rFile.Close()
    85  
    86  	lFile, err := os.Create(localPath)
    87  	if err != nil {
    88  		return errors.Wrap(err, "failed to create file")
    89  	}
    90  	defer lFile.Close()
    91  
    92  	if _, err := rFile.WriteTo(lFile); err != nil {
    93  		return err
    94  	}
    95  	return nil
    96  }
    97  
    98  // defaultPrivateSSHKeys returns a list of all the PRIVATE SSH keys from user's home directory.
    99  // It does not return any intermediate errors if at least one private key was loaded.
   100  func defaultPrivateSSHKeys() (map[string]interface{}, error) {
   101  	d := filepath.Join(os.Getenv("HOME"), ".ssh")
   102  	paths, err := os.ReadDir(d)
   103  	if err != nil {
   104  		return nil, errors.Wrapf(err, "failed to read directory %q", d)
   105  	}
   106  
   107  	var files []string
   108  	for _, path := range paths {
   109  		if path.IsDir() {
   110  			continue
   111  		}
   112  		files = append(files, filepath.Join(d, path.Name()))
   113  	}
   114  	keys, err := LoadPrivateSSHKeys(files)
   115  	if len(keys) > 0 {
   116  		return keys, nil
   117  	}
   118  	return nil, err
   119  }
   120  
   121  // LoadPrivateSSHKeys try to optimistically load PRIVATE SSH keys from the all paths.
   122  func LoadPrivateSSHKeys(paths []string) (map[string]interface{}, error) {
   123  	var errs []error
   124  	keys := make(map[string]interface{})
   125  	for _, path := range paths {
   126  		data, err := os.ReadFile(path)
   127  		if err != nil {
   128  			errs = append(errs, errors.Wrapf(err, "failed to read %q", path))
   129  			continue
   130  		}
   131  		key, err := ssh.ParseRawPrivateKey(data)
   132  		if err != nil {
   133  			logrus.Debugf("failed to parse SSH private key from %q", path)
   134  			errs = append(errs, errors.Wrapf(err, "failed to parse SSH private key from %q", path))
   135  			continue
   136  		}
   137  		keys[path] = key
   138  	}
   139  	if err := utilerrors.NewAggregate(errs); err != nil {
   140  		return keys, err
   141  	}
   142  	return keys, nil
   143  }