summaryrefslogtreecommitdiff
path: root/ext/git/creds_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'ext/git/creds_test.go')
-rw-r--r--ext/git/creds_test.go369
1 files changed, 369 insertions, 0 deletions
diff --git a/ext/git/creds_test.go b/ext/git/creds_test.go
new file mode 100644
index 0000000..23a705e
--- /dev/null
+++ b/ext/git/creds_test.go
@@ -0,0 +1,369 @@
+package git
+
+import (
+ "encoding/base64"
+ "fmt"
+ "os"
+ "path"
+ "regexp"
+ "strings"
+ "testing"
+
+ "github.com/google/uuid"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+
+ "github.com/argoproj/argo-cd/v2/util/cert"
+ "github.com/argoproj/argo-cd/v2/util/io"
+)
+
+type cred struct {
+ username string
+ password string
+}
+
+type memoryCredsStore struct {
+ creds map[string]cred
+}
+
+func (s *memoryCredsStore) Add(username string, password string) string {
+ id := uuid.New().String()
+ s.creds[id] = cred{
+ username: username,
+ password: password,
+ }
+ return id
+}
+
+func (s *memoryCredsStore) Remove(id string) {
+ delete(s.creds, id)
+}
+
+func TestHTTPSCreds_Environ_no_cert_cleanup(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ creds := NewHTTPSCreds("", "", "", "", true, "", store, false)
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ var nonce string
+ for _, envVar := range env {
+ if strings.HasPrefix(envVar, ASKPASS_NONCE_ENV) {
+ nonce = envVar[len(ASKPASS_NONCE_ENV)+1:]
+ break
+ }
+ }
+ assert.Contains(t, store.creds, nonce)
+ io.Close(closer)
+ assert.NotContains(t, store.creds, nonce)
+}
+
+func TestHTTPSCreds_Environ_insecure_true(t *testing.T) {
+ creds := NewHTTPSCreds("", "", "", "", true, "", &NoopCredsStore{}, false)
+ closer, env, err := creds.Environ()
+ t.Cleanup(func() {
+ io.Close(closer)
+ })
+ require.NoError(t, err)
+ found := false
+ for _, envVar := range env {
+ if envVar == "GIT_SSL_NO_VERIFY=true" {
+ found = true
+ break
+ }
+ }
+ assert.True(t, found)
+}
+
+func TestHTTPSCreds_Environ_insecure_false(t *testing.T) {
+ creds := NewHTTPSCreds("", "", "", "", false, "", &NoopCredsStore{}, false)
+ closer, env, err := creds.Environ()
+ t.Cleanup(func() {
+ io.Close(closer)
+ })
+ require.NoError(t, err)
+ found := false
+ for _, envVar := range env {
+ if envVar == "GIT_SSL_NO_VERIFY=true" {
+ found = true
+ break
+ }
+ }
+ assert.False(t, found)
+}
+
+func TestHTTPSCreds_Environ_forceBasicAuth(t *testing.T) {
+ t.Run("Enabled and credentials set", func(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ creds := NewHTTPSCreds("username", "password", "", "", false, "", store, true)
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ defer closer.Close()
+ var header string
+ for _, envVar := range env {
+ if strings.HasPrefix(envVar, fmt.Sprintf("%s=", forceBasicAuthHeaderEnv)) {
+ header = envVar[len(forceBasicAuthHeaderEnv)+1:]
+ }
+ if header != "" {
+ break
+ }
+ }
+ b64enc := base64.StdEncoding.EncodeToString([]byte("username:password"))
+ assert.Equal(t, "Authorization: Basic "+b64enc, header)
+ })
+ t.Run("Enabled but credentials not set", func(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ creds := NewHTTPSCreds("", "", "", "", false, "", store, true)
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ defer closer.Close()
+ var header string
+ for _, envVar := range env {
+ if strings.HasPrefix(envVar, fmt.Sprintf("%s=", forceBasicAuthHeaderEnv)) {
+ header = envVar[len(forceBasicAuthHeaderEnv)+1:]
+ }
+ if header != "" {
+ break
+ }
+ }
+ assert.Empty(t, header)
+ })
+ t.Run("Disabled with credentials set", func(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ creds := NewHTTPSCreds("username", "password", "", "", false, "", store, false)
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ defer closer.Close()
+ var header string
+ for _, envVar := range env {
+ if strings.HasPrefix(envVar, fmt.Sprintf("%s=", forceBasicAuthHeaderEnv)) {
+ header = envVar[len(forceBasicAuthHeaderEnv)+1:]
+ }
+ if header != "" {
+ break
+ }
+ }
+ assert.Empty(t, header)
+ })
+
+ t.Run("Disabled with credentials not set", func(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ creds := NewHTTPSCreds("", "", "", "", false, "", store, false)
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ defer closer.Close()
+ var header string
+ for _, envVar := range env {
+ if strings.HasPrefix(envVar, fmt.Sprintf("%s=", forceBasicAuthHeaderEnv)) {
+ header = envVar[len(forceBasicAuthHeaderEnv)+1:]
+ }
+ if header != "" {
+ break
+ }
+ }
+ assert.Empty(t, header)
+ })
+}
+
+func TestHTTPSCreds_Environ_clientCert(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ creds := NewHTTPSCreds("", "", "clientCertData", "clientCertKey", false, "", store, false)
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ var cert, key string
+ for _, envVar := range env {
+ if strings.HasPrefix(envVar, "GIT_SSL_CERT=") {
+ cert = envVar[13:]
+ } else if strings.HasPrefix(envVar, "GIT_SSL_KEY=") {
+ key = envVar[12:]
+ }
+ if cert != "" && key != "" {
+ break
+ }
+ }
+ assert.NotEmpty(t, cert)
+ assert.NotEmpty(t, key)
+
+ certBytes, err := os.ReadFile(cert)
+ assert.NoError(t, err)
+ assert.Equal(t, "clientCertData", string(certBytes))
+ keyBytes, err := os.ReadFile(key)
+ assert.Equal(t, "clientCertKey", string(keyBytes))
+ assert.NoError(t, err)
+
+ io.Close(closer)
+
+ _, err = os.Stat(cert)
+ assert.ErrorIs(t, err, os.ErrNotExist)
+ _, err = os.Stat(key)
+ assert.ErrorIs(t, err, os.ErrNotExist)
+}
+
+func Test_SSHCreds_Environ(t *testing.T) {
+ for _, insecureIgnoreHostKey := range []bool{false, true} {
+ tempDir := t.TempDir()
+ caFile := path.Join(tempDir, "caFile")
+ err := os.WriteFile(caFile, []byte(""), os.FileMode(0600))
+ require.NoError(t, err)
+ creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, &NoopCredsStore{}, "")
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ require.Len(t, env, 2)
+
+ assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")
+
+ assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))
+
+ if insecureIgnoreHostKey {
+ assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
+ assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
+ } else {
+ assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
+ hostsPath := cert.GetSSHKnownHostsDataPath()
+ assert.Contains(t, env[1], fmt.Sprintf("-o UserKnownHostsFile=%s", hostsPath))
+ }
+
+ envRegex := regexp.MustCompile("-i ([^ ]+)")
+ assert.Regexp(t, envRegex, env[1])
+ privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
+ assert.FileExists(t, privateKeyFile)
+ io.Close(closer)
+ assert.NoFileExists(t, privateKeyFile)
+ }
+}
+
+func Test_SSHCreds_Environ_WithProxy(t *testing.T) {
+ for _, insecureIgnoreHostKey := range []bool{false, true} {
+ tempDir := t.TempDir()
+ caFile := path.Join(tempDir, "caFile")
+ err := os.WriteFile(caFile, []byte(""), os.FileMode(0600))
+ require.NoError(t, err)
+ creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, &NoopCredsStore{}, "socks5://127.0.0.1:1080")
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ require.Len(t, env, 2)
+
+ assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")
+
+ assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))
+
+ if insecureIgnoreHostKey {
+ assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
+ assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
+ } else {
+ assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
+ hostsPath := cert.GetSSHKnownHostsDataPath()
+ assert.Contains(t, env[1], fmt.Sprintf("-o UserKnownHostsFile=%s", hostsPath))
+ }
+ assert.Contains(t, env[1], "-o ProxyCommand='connect-proxy -S 127.0.0.1:1080 -5 %h %p'")
+
+ envRegex := regexp.MustCompile("-i ([^ ]+)")
+ assert.Regexp(t, envRegex, env[1])
+ privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
+ assert.FileExists(t, privateKeyFile)
+ io.Close(closer)
+ assert.NoFileExists(t, privateKeyFile)
+ }
+}
+
+func Test_SSHCreds_Environ_WithProxyUserNamePassword(t *testing.T) {
+ for _, insecureIgnoreHostKey := range []bool{false, true} {
+ tempDir := t.TempDir()
+ caFile := path.Join(tempDir, "caFile")
+ err := os.WriteFile(caFile, []byte(""), os.FileMode(0600))
+ require.NoError(t, err)
+ creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, &NoopCredsStore{}, "socks5://user:password@127.0.0.1:1080")
+ closer, env, err := creds.Environ()
+ require.NoError(t, err)
+ require.Len(t, env, 4)
+
+ assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")
+
+ assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))
+ assert.Equal(t, "SOCKS5_USER=user", env[2], "SOCKS5 user env var must be set")
+ assert.Equal(t, "SOCKS5_PASSWD=password", env[3], "SOCKS5 password env var must be set")
+
+ if insecureIgnoreHostKey {
+ assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
+ assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
+ } else {
+ assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
+ hostsPath := cert.GetSSHKnownHostsDataPath()
+ assert.Contains(t, env[1], fmt.Sprintf("-o UserKnownHostsFile=%s", hostsPath))
+ }
+ assert.Contains(t, env[1], "-o ProxyCommand='connect-proxy -S 127.0.0.1:1080 -5 %h %p'")
+
+ envRegex := regexp.MustCompile("-i ([^ ]+)")
+ assert.Regexp(t, envRegex, env[1])
+ privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
+ assert.FileExists(t, privateKeyFile)
+ io.Close(closer)
+ assert.NoFileExists(t, privateKeyFile)
+ }
+}
+
+const gcpServiceAccountKeyJSON = `{
+ "type": "service_account",
+ "project_id": "my-google-project",
+ "private_key_id": "REDACTED",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
+ "client_email": "argocd-service-account@my-google-project.iam.gserviceaccount.com",
+ "client_id": "REDACTED",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/argocd-service-account%40my-google-project.iam.gserviceaccount.com"
+}`
+
+const invalidJSON = `{
+ "type": "service_account",
+ "project_id": "my-google-project",
+`
+
+func TestNewGoogleCloudCreds(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ googleCloudCreds := NewGoogleCloudCreds(gcpServiceAccountKeyJSON, store)
+ assert.NotNil(t, googleCloudCreds)
+}
+
+func TestNewGoogleCloudCreds_invalidJSON(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ googleCloudCreds := NewGoogleCloudCreds(invalidJSON, store)
+ assert.Nil(t, googleCloudCreds.creds)
+
+ token, err := googleCloudCreds.getAccessToken()
+ assert.Equal(t, "", token)
+ assert.NotNil(t, err)
+
+ username, err := googleCloudCreds.getUsername()
+ assert.Equal(t, "", username)
+ assert.NotNil(t, err)
+
+ closer, envStringSlice, err := googleCloudCreds.Environ()
+ assert.Equal(t, NopCloser{}, closer)
+ assert.Equal(t, []string(nil), envStringSlice)
+ assert.NotNil(t, err)
+}
+
+func TestGoogleCloudCreds_Environ_cleanup(t *testing.T) {
+ store := &memoryCredsStore{creds: make(map[string]cred)}
+ staticToken := &oauth2.Token{AccessToken: "token"}
+ googleCloudCreds := GoogleCloudCreds{&google.Credentials{
+ ProjectID: "my-google-project",
+ TokenSource: oauth2.StaticTokenSource(staticToken),
+ JSON: []byte(gcpServiceAccountKeyJSON),
+ }, store}
+
+ closer, env, err := googleCloudCreds.Environ()
+ assert.NoError(t, err)
+ var nonce string
+ for _, envVar := range env {
+ if strings.HasPrefix(envVar, ASKPASS_NONCE_ENV) {
+ nonce = envVar[len(ASKPASS_NONCE_ENV)+1:]
+ break
+ }
+ }
+ assert.Contains(t, store.creds, nonce)
+ io.Close(closer)
+ assert.NotContains(t, store.creds, nonce)
+}