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 }