Skip to content

Commit

Permalink
SSH Authentication behaviour update
Browse files Browse the repository at this point in the history
Following Issue 83 on the CLI, the behaviour of ssh authentication was changed to:

1. Try authenticating via ssh-agent.
If failed due to any reason (No agent / connection failed / no key in agent):
2. Try authenticating via ssh-key.
If the key is encrypted, passphrase has to be passed via the --ssh-passphrase flag.
  • Loading branch information
RobiNino committed Nov 13, 2018
1 parent 3b3e007 commit 4350385
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 13 deletions.
6 changes: 3 additions & 3 deletions artifactory/auth/rtdetails.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type ArtifactoryDetails interface {
SetApiKey(apiKey string)
SetSshAuthHeaders(sshAuthHeaders map[string]string)

AuthenticateSsh(sshKey, sshPassphrase []byte) error
AuthenticateSsh(sshKey, sshPassphrase string) error

CreateHttpClientDetails() httputils.HttpClientDetails
}
Expand Down Expand Up @@ -83,8 +83,8 @@ func (rt *artifactoryDetails) SetSshAuthHeaders(sshAuthHeaders map[string]string
rt.SshAuthHeaders = sshAuthHeaders
}

func (rt *artifactoryDetails) AuthenticateSsh(sshKey, sshPassphrase []byte) error {
sshHeaders, baseUrl, err := sshAuthentication(rt.Url, sshKey, sshPassphrase)
func (rt *artifactoryDetails) AuthenticateSsh(sshKeyPath, sshPassphrase string) error {
sshHeaders, baseUrl, err := sshAuthentication(rt.Url, sshKeyPath, sshPassphrase)
if err != nil {
return err
}
Expand Down
83 changes: 73 additions & 10 deletions artifactory/auth/sshlogin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,71 @@ package auth
import (
"bytes"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/jfrog/jfrog-client-go/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/log"
"github.com/xanzy/ssh-agent"
"golang.org/x/crypto/ssh"
"io"
"io/ioutil"
"regexp"
"strconv"
"strings"
)

func sshAuthentication(url string, sshKey, sshPassphrase []byte) (map[string]string, string, error) {
func sshAuthentication(url, sshKeyPath, sshPassphrase string) (sshAuthHeaders map[string]string, newUrl string, err error) {
_, host, port, err := parseUrl(url)
if err != nil {
return nil, "", err
}

log.Info("Performing SSH authentication...")
var sshAuth ssh.AuthMethod
if len(sshKey) == 0 {
sshAuth, err = sshAuthAgent()
} else {
sshAuth, err = sshAuthPublicKey(sshKey, sshPassphrase)
log.Info("Performing SSH authentication...")
log.Info("Trying to authenticate via SSH-Agent...")

// Try authenticating via agent. If failed, try authenticating via key.
sshAuth, err = sshAuthAgent()
if err == nil {
sshAuthHeaders, newUrl, err = getSshHeaders(sshAuth, host, port)
}
if err != nil {
return nil, "", err
log.Info("Authentication via SSH-Agent failed. Error:\n", err)
log.Info("Trying to authenticate via SSH Key...")

// Check if key specified
if len(sshKeyPath) <= 0 {
log.Info("Authentication via SSH key failed.")
return nil, "", fmt.Errorf("SSH key not specified.")
}

// Read key and passphrase
var sshKey, sshPassphraseBytes []byte
sshKey, sshPassphraseBytes, err = readSshKeyAndPassphrase(sshKeyPath, sshPassphrase)
if err != nil {
log.Info("Authentication via SSH key failed.")
return nil, "", err
}

// Verify key and get ssh headers
sshAuth, err = sshAuthPublicKey(sshKey, sshPassphraseBytes)
if err == nil {
sshAuthHeaders, newUrl, err = getSshHeaders(sshAuth, host, port)
}
if err != nil {
log.Info("Authentication via SSH Key failed.")
return nil, "", err
}
}

// If successful, return headers
log.Info("SSH authentication successful.")
return sshAuthHeaders, newUrl, nil
}

func getSshHeaders(sshAuth ssh.AuthMethod, host string, port int) (map[string]string, string, error) {
sshConfig := &ssh.ClientConfig{
User: "admin",
Auth: []ssh.AuthMethod{
Expand Down Expand Up @@ -65,12 +103,38 @@ func sshAuthentication(url string, sshKey, sshPassphrase []byte) (map[string]str
if err = json.Unmarshal(buf.Bytes(), &result); errorutils.CheckError(err) != nil {
return nil, "", err
}
url = utils.AddTrailingSlashIfNeeded(result.Href)
url := utils.AddTrailingSlashIfNeeded(result.Href)
sshAuthHeaders := result.Headers
log.Info("SSH authentication successful.")
return sshAuthHeaders, url, nil
}

func readSshKeyAndPassphrase(sshKeyPath, sshPassphrase string) ([]byte, []byte, error) {
sshKey, err := ioutil.ReadFile(utils.ReplaceTildeWithUserHome(sshKeyPath))
if err != nil {
return nil, nil, errorutils.CheckError(err)
}
if len(sshPassphrase) == 0 {
encryptedKey, err := IsEncrypted(sshKey)
if err != nil {
return nil, nil, errorutils.CheckError(err)
}
// If key is encrypted but no passphrase specified
if encryptedKey {
return nil, nil, errorutils.CheckError(errors.New("SSH Key is encrypted but no passphrase was specified."))
}
}

return sshKey, []byte(sshPassphrase), err
}

func IsEncrypted(buffer []byte) (bool, error) {
block, _ := pem.Decode(buffer)
if block == nil {
return false, errors.New("SSH: no key found")
}
return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED"), nil
}

func parseUrl(url string) (protocol, host string, port int, err error) {
pattern1 := "^(.+)://(.+):([0-9].+)/$"
pattern2 := "^(.+)://(.+)$"
Expand Down Expand Up @@ -114,7 +178,6 @@ func sshAuthPublicKey(sshKey, sshPassphrase []byte) (ssh.AuthMethod, error) {
}

func sshAuthAgent() (ssh.AuthMethod, error) {
log.Info("Authenticating Using SSH agent")
sshAgent, _, err := sshagent.New()
if errorutils.CheckError(err) != nil {
return nil, err
Expand Down
19 changes: 19 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/log"
"os"
"regexp"
"runtime"
Expand Down Expand Up @@ -216,6 +217,24 @@ func GetUserHomeDir() string {
return os.Getenv("HOME")
}

func GetBoolEnvValue(flagName string, defValue bool) (bool, error) {
envVarValue := os.Getenv(flagName)
if envVarValue == "" {
return defValue, nil
}
val, err := strconv.ParseBool(envVarValue)
err = CheckErrorWithMessage(err, "can't parse environment variable "+flagName)
return val, err
}

func CheckErrorWithMessage(err error, message string) error {
if err != nil {
log.Error(message)
err = errorutils.CheckError(err)
}
return err
}

func GetMapFromStringSlice(slice []string, sep string) map[string]string {
mapFromSlice := make(map[string]string)
for _, value := range slice {
Expand Down

0 comments on commit 4350385

Please sign in to comment.