github.com/jfrog/jfrog-client-go@v1.40.2/auth/sshlogin.go (about) 1 package auth 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "os" 8 "regexp" 9 "strconv" 10 11 "github.com/jfrog/jfrog-client-go/utils" 12 "github.com/jfrog/jfrog-client-go/utils/errorutils" 13 "github.com/jfrog/jfrog-client-go/utils/log" 14 sshagent "github.com/xanzy/ssh-agent" 15 "golang.org/x/crypto/ssh" 16 ) 17 18 func SshAuthentication(url, sshKeyPath, sshPassphrase string) (sshAuthHeaders map[string]string, newUrl string, err error) { 19 _, host, port, err := parseUrl(url) 20 if err != nil { 21 return nil, "", err 22 } 23 24 var sshAuth ssh.AuthMethod 25 log.Debug("Performing SSH authentication...") 26 log.Debug("Trying to authenticate via SSH-Agent...") 27 28 // Try authenticating via agent. If failed, try authenticating via key. 29 sshAuth, err = sshAuthAgent() 30 if err == nil { 31 sshAuthHeaders, newUrl, err = getSshHeaders(sshAuth, host, port) 32 } 33 if err != nil { 34 log.Debug("Authentication via SSH-Agent failed. Error:\n", err) 35 log.Debug("Trying to authenticate via SSH Key...") 36 37 // Check if key specified 38 if len(sshKeyPath) == 0 { 39 log.Error("Authentication via SSH key failed.") 40 return nil, "", errorutils.CheckErrorf("SSH key not specified.") 41 } 42 43 // Read key and passphrase 44 var sshKey, sshPassphraseBytes []byte 45 sshKey, sshPassphraseBytes, err = readSshKeyAndPassphrase(sshKeyPath, sshPassphrase) 46 if err != nil { 47 log.Error("Authentication via SSH key failed.") 48 return nil, "", err 49 } 50 51 // Verify key and get ssh headers 52 sshAuth, err = sshAuthPublicKey(sshKey, sshPassphraseBytes) 53 if err == nil { 54 sshAuthHeaders, newUrl, err = getSshHeaders(sshAuth, host, port) 55 } 56 if err != nil { 57 log.Error("Authentication via SSH Key failed.") 58 return nil, "", err 59 } 60 } 61 62 // If successful, return headers 63 log.Debug("SSH authentication successful.") 64 return sshAuthHeaders, newUrl, nil 65 } 66 67 func getSshHeaders(sshAuth ssh.AuthMethod, host string, port int) (map[string]string, string, error) { 68 sshConfig := &ssh.ClientConfig{ 69 User: "admin", 70 Auth: []ssh.AuthMethod{ 71 sshAuth, 72 }, 73 //#nosec G106 -- Used to get ssh headers only. 74 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 75 } 76 77 hostAndPort := host + ":" + strconv.Itoa(port) 78 connection, err := ssh.Dial("tcp", hostAndPort, sshConfig) 79 if errorutils.CheckError(err) != nil { 80 return nil, "", err 81 } 82 defer connection.Close() 83 session, err := connection.NewSession() 84 if errorutils.CheckError(err) != nil { 85 return nil, "", err 86 } 87 defer session.Close() 88 89 stdout, err := session.StdoutPipe() 90 if errorutils.CheckError(err) != nil { 91 return nil, "", err 92 } 93 94 if err = session.Run("jfrog-authenticate"); err != nil && err != io.EOF { 95 return nil, "", errorutils.CheckError(err) 96 } 97 var buf bytes.Buffer 98 _, err = io.Copy(&buf, stdout) 99 if errorutils.CheckError(err) != nil { 100 return nil, "", err 101 } 102 var result SshAuthResult 103 if err = json.Unmarshal(buf.Bytes(), &result); errorutils.CheckError(err) != nil { 104 return nil, "", err 105 } 106 url := utils.AddTrailingSlashIfNeeded(result.Href) 107 sshAuthHeaders := result.Headers 108 return sshAuthHeaders, url, nil 109 } 110 111 func readSshKeyAndPassphrase(sshKeyPath, sshPassphrase string) ([]byte, []byte, error) { 112 sshKey, err := os.ReadFile(utils.ReplaceTildeWithUserHome(sshKeyPath)) 113 if err != nil { 114 return nil, nil, errorutils.CheckError(err) 115 } 116 if len(sshPassphrase) == 0 { 117 encryptedKey, err := IsEncrypted(sshKey) 118 if err != nil { 119 return nil, nil, errorutils.CheckError(err) 120 } 121 // If key is encrypted but no passphrase specified 122 if encryptedKey { 123 return nil, nil, errorutils.CheckErrorf("SSH Key is encrypted but no passphrase was specified.") 124 } 125 } 126 127 return sshKey, []byte(sshPassphrase), err 128 } 129 130 func IsEncrypted(buffer []byte) (bool, error) { 131 _, err := ssh.ParsePrivateKey(buffer) 132 if _, ok := err.(*ssh.PassphraseMissingError); ok { 133 // Key is encrypted 134 return true, nil 135 } 136 // Key is not encrypted or an error occurred 137 return false, err 138 } 139 140 func parseUrl(url string) (protocol, host string, port int, err error) { 141 pattern1 := "^(.+)://(.+):([0-9].+)/$" 142 pattern2 := "^(.+)://(.+)$" 143 144 var r *regexp.Regexp 145 r, err = regexp.Compile(pattern1) 146 if errorutils.CheckError(err) != nil { 147 return 148 } 149 groups := r.FindStringSubmatch(url) 150 if len(groups) == 4 { 151 protocol = groups[1] 152 host = groups[2] 153 port, err = strconv.Atoi(groups[3]) 154 if err != nil { 155 err = errorutils.CheckErrorf("URL: " + url + " is invalid. Expecting ssh://<host>:<port> or http(s)://...") 156 } 157 return 158 } 159 160 r, err = regexp.Compile(pattern2) 161 err = errorutils.CheckError(err) 162 if err != nil { 163 return 164 } 165 groups = r.FindStringSubmatch(url) 166 if len(groups) == 3 { 167 protocol = groups[1] 168 host = groups[2] 169 port = 80 170 } 171 return 172 } 173 174 func sshAuthPublicKey(sshKey, sshPassphrase []byte) (ssh.AuthMethod, error) { 175 var key ssh.Signer 176 var err error 177 if len(sshPassphrase) == 0 { 178 key, err = ssh.ParsePrivateKey(sshKey) 179 } else { 180 key, err = ssh.ParsePrivateKeyWithPassphrase(sshKey, sshPassphrase) 181 } 182 if errorutils.CheckError(err) != nil { 183 return nil, err 184 } 185 return ssh.PublicKeys(key), nil 186 } 187 188 func sshAuthAgent() (ssh.AuthMethod, error) { 189 sshAgent, _, err := sshagent.New() 190 if errorutils.CheckError(err) != nil { 191 return nil, err 192 } 193 cbk := sshAgent.Signers 194 authMethod := ssh.PublicKeysCallback(cbk) 195 return authMethod, nil 196 } 197 198 type SshAuthResult struct { 199 Href string 200 Headers map[string]string 201 }