github.com/bshelton229/agent@v3.5.4+incompatible/bootstrap/ssh.go (about)

     1  package bootstrap
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/buildkite/agent/bootstrap/shell"
    12  	"github.com/buildkite/agent/retry"
    13  )
    14  
    15  var (
    16  	sshKeyscanRetryInterval = 2 * time.Second
    17  )
    18  
    19  func sshKeyScan(sh *shell.Shell, host string) (string, error) {
    20  	toolsDir, err := findPathToSSHTools(sh)
    21  	if err != nil {
    22  		return "", err
    23  	}
    24  
    25  	sshKeyScanPath := filepath.Join(toolsDir, "ssh-keyscan")
    26  	hostParts := strings.Split(host, ":")
    27  	sshKeyScanOutput := ""
    28  
    29  	err = retry.Do(func(s *retry.Stats) error {
    30  		// `ssh-keyscan` needs `-p` when scanning a host with a port
    31  		var sshKeyScanCommand string
    32  		if len(hostParts) == 2 {
    33  			sshKeyScanCommand = fmt.Sprintf("ssh-keyscan -p %q %q", hostParts[1], hostParts[0])
    34  			sshKeyScanOutput, err = sh.RunAndCapture(sshKeyScanPath, "-p", hostParts[1], hostParts[0])
    35  		} else {
    36  			sshKeyScanCommand = fmt.Sprintf("ssh-keyscan %q", host)
    37  			sshKeyScanOutput, err = sh.RunAndCapture(sshKeyScanPath, host)
    38  		}
    39  
    40  		if err != nil {
    41  			keyScanError := fmt.Errorf("`%s` failed", sshKeyScanCommand)
    42  			sh.Warningf("%s (%s)", keyScanError, s)
    43  			return keyScanError
    44  		} else if strings.TrimSpace(sshKeyScanOutput) == "" {
    45  			// Older versions of ssh-keyscan would exit 0 but not
    46  			// return anything, and we've observed newer versions
    47  			// of ssh-keyscan - just sometimes return no data
    48  			// (maybe networking related?). In any case, no
    49  			// response, means an error.
    50  			keyScanError := fmt.Errorf("`%s` returned nothing", sshKeyScanCommand)
    51  			sh.Warningf("%s (%s)", keyScanError, s)
    52  			return keyScanError
    53  		}
    54  
    55  		return nil
    56  	}, &retry.Config{Maximum: 3, Interval: sshKeyscanRetryInterval})
    57  
    58  	return sshKeyScanOutput, err
    59  }
    60  
    61  // On Windows, there are many horrible different versions of the ssh tools. Our
    62  // preference is the one bundled with git for windows which is generally MinGW.
    63  // Often this isn't in the path, so we go looking for it specifically.
    64  //
    65  // Some more details on the relative paths at
    66  // https://stackoverflow.com/a/11771907
    67  func findPathToSSHTools(sh *shell.Shell) (string, error) {
    68  	sshKeyscan, err := sh.AbsolutePath("ssh-keyscan")
    69  	if err == nil {
    70  		return filepath.Dir(sshKeyscan), nil
    71  	}
    72  
    73  	if runtime.GOOS == "windows" {
    74  		execPath, _ := sh.RunAndCapture("git", "--exec-path")
    75  		if len(execPath) > 0 {
    76  			for _, path := range []string{
    77  				filepath.Join(execPath, "..", "..", "..", "usr", "bin", "ssh-keygen.exe"),
    78  				filepath.Join(execPath, "..", "..", "bin", "ssh-keygen.exe"),
    79  			} {
    80  				if _, err := os.Stat(path); err == nil {
    81  					return filepath.Dir(path), nil
    82  				}
    83  			}
    84  		}
    85  	}
    86  
    87  	return "", fmt.Errorf("Unable to find ssh-keyscan: %v", err)
    88  }