summaryrefslogtreecommitdiff
path: root/registry-scanner/pkg/image/credentials_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'registry-scanner/pkg/image/credentials_test.go')
-rw-r--r--registry-scanner/pkg/image/credentials_test.go410
1 files changed, 410 insertions, 0 deletions
diff --git a/registry-scanner/pkg/image/credentials_test.go b/registry-scanner/pkg/image/credentials_test.go
new file mode 100644
index 0000000..6d53bf4
--- /dev/null
+++ b/registry-scanner/pkg/image/credentials_test.go
@@ -0,0 +1,410 @@
+package image
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/kube"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/test/fake"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/test/fixture"
+)
+
+func Test_ParseCredentialAnnotation(t *testing.T) {
+ t.Run("Parse valid credentials definition of type secret", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=secret:mynamespace/mysecret#anyfield", true)
+ assert.NoError(t, err)
+ assert.Equal(t, "gcr.io", src.Registry)
+ assert.Equal(t, "mynamespace", src.SecretNamespace)
+ assert.Equal(t, "mysecret", src.SecretName)
+ assert.Equal(t, "anyfield", src.SecretField)
+ })
+
+ t.Run("Parse valid credentials definition of type pullsecret", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=pullsecret:mynamespace/mysecret", true)
+ assert.NoError(t, err)
+ assert.Equal(t, "gcr.io", src.Registry)
+ assert.Equal(t, "mynamespace", src.SecretNamespace)
+ assert.Equal(t, "mysecret", src.SecretName)
+ assert.Equal(t, ".dockerconfigjson", src.SecretField)
+ })
+
+ t.Run("Parse invalid secret definition - missing registry", func(t *testing.T) {
+ src, err := ParseCredentialSource("secret:mynamespace/mysecret#anyfield", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid secret definition - empty registry", func(t *testing.T) {
+ src, err := ParseCredentialSource("=secret:mynamespace/mysecret#anyfield", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid secret definition - unknown credential type", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=secrets:mynamespace/mysecret#anyfield", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid secret definition - missing field", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=secret:mynamespace/mysecret#", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid secret definition - missing namespace", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=secret:/mysecret#anyfield", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid credential definition - missing name", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=secret:mynamespace/#anyfield", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid credential definition - missing most", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=secret:", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid pullsecret definition - missing namespace", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=pullsecret:/mysecret", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse invalid credential definition - missing name", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=pullsecret:mynamespace", true)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+ t.Run("Parse valid credentials definition from environment", func(t *testing.T) {
+ src, err := ParseCredentialSource("env:DUMMY_SECRET", false)
+ require.NoError(t, err)
+ require.NotNil(t, src)
+ assert.Equal(t, "DUMMY_SECRET", src.EnvName)
+ })
+
+ t.Run("Parse valid credentials definition from environment", func(t *testing.T) {
+ src, err := ParseCredentialSource("env:DUMMY_SECRET", false)
+ require.NoError(t, err)
+ require.NotNil(t, src)
+ assert.Equal(t, "DUMMY_SECRET", src.EnvName)
+ })
+
+ t.Run("Parse external script credentials", func(t *testing.T) {
+ src, err := ParseCredentialSource("ext:/tmp/a.sh", false)
+ require.NoError(t, err)
+ assert.Equal(t, CredentialSourceExt, src.Type)
+ assert.Equal(t, "/tmp/a.sh", src.ScriptPath)
+ })
+}
+
+func Test_ParseCredentialReference(t *testing.T) {
+ t.Run("Parse valid credentials definition of type secret", func(t *testing.T) {
+ src, err := ParseCredentialSource("secret:mynamespace/mysecret#anyfield", false)
+ assert.NoError(t, err)
+ assert.Equal(t, "", src.Registry)
+ assert.Equal(t, "mynamespace", src.SecretNamespace)
+ assert.Equal(t, "mysecret", src.SecretName)
+ assert.Equal(t, "anyfield", src.SecretField)
+ })
+
+ t.Run("Parse valid credentials definition of type pullsecret", func(t *testing.T) {
+ src, err := ParseCredentialSource("gcr.io=pullsecret:mynamespace/mysecret", false)
+ assert.NoError(t, err)
+ assert.Equal(t, "gcr.io", src.Registry)
+ assert.Equal(t, "mynamespace", src.SecretNamespace)
+ assert.Equal(t, "mysecret", src.SecretName)
+ assert.Equal(t, ".dockerconfigjson", src.SecretField)
+ })
+
+ t.Run("Parse invalid secret definition - empty registry", func(t *testing.T) {
+ src, err := ParseCredentialSource("=secret:mynamespace/mysecret#anyfield", false)
+ assert.Error(t, err)
+ assert.Nil(t, src)
+ })
+
+}
+
+func Test_FetchCredentialsFromSecret(t *testing.T) {
+ t.Run("Fetch credentials from secret", func(t *testing.T) {
+ secretData := make(map[string][]byte)
+ secretData["username_password"] = []byte(fmt.Sprintf("%s:%s", "foo", "bar"))
+ secret := fixture.NewSecret("test", "test", secretData)
+ clientset := fake.NewFakeClientsetWithResources(secret)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceSecret,
+ SecretNamespace: "test",
+ SecretName: "test",
+ SecretField: "username_password",
+ }
+ creds, err := credSrc.FetchCredentials("NA", &kube.KubernetesClient{Clientset: clientset})
+ require.NoError(t, err)
+ require.NotNil(t, creds)
+ assert.Equal(t, "foo", creds.Username)
+ assert.Equal(t, "bar", creds.Password)
+
+ credSrc.SecretNamespace = "test1" // test with a wrong SecretNamespace
+ creds, err = credSrc.FetchCredentials("NA", &kube.KubernetesClient{Clientset: clientset})
+ require.Error(t, err)
+ require.Nil(t, creds)
+ })
+
+ t.Run("Fetch credentials from secret with invalid config", func(t *testing.T) {
+ secretData := make(map[string][]byte)
+ secretData["username_password"] = []byte(fmt.Sprintf("%s:%s", "foo", "bar"))
+ secret := fixture.NewSecret("test", "test", secretData)
+ clientset := fake.NewFakeClientsetWithResources(secret)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceSecret,
+ SecretNamespace: "test",
+ SecretName: "test",
+ SecretField: "username_password",
+ }
+ creds, err := credSrc.FetchCredentials("NA", nil)
+ require.Error(t, err) // should fail with "could not fetch credentials: no Kubernetes client given"
+ require.Nil(t, creds)
+
+ credSrc.SecretField = "BAD" // test with a wrong SecretField
+ creds, err = credSrc.FetchCredentials("NA", &kube.KubernetesClient{Clientset: clientset})
+ require.Error(t, err)
+ require.Nil(t, creds)
+
+ })
+}
+
+func Test_FetchCredentialsFromPullSecret(t *testing.T) {
+ t.Run("Fetch credentials from pull secret", func(t *testing.T) {
+ dockerJson := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
+ secretData := make(map[string][]byte)
+ secretData[pullSecretField] = []byte(dockerJson)
+ pullSecret := fixture.NewSecret("test", "test", secretData)
+ clientset := fake.NewFakeClientsetWithResources(pullSecret)
+ credSrc := &CredentialSource{
+ Type: CredentialSourcePullSecret,
+ Registry: "https://registry-1.docker.io/v2",
+ SecretNamespace: "test",
+ SecretName: "test",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
+ require.NoError(t, err)
+ require.NotNil(t, creds)
+ assert.Equal(t, "foo", creds.Username)
+ assert.Equal(t, "bar", creds.Password)
+
+ credSrc.SecretNamespace = "test1" // test with a wrong SecretNamespace
+ creds, err = credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
+ require.Error(t, err)
+ require.Nil(t, creds)
+ })
+
+ t.Run("Fetch credentials from pull secret with invalid config", func(t *testing.T) {
+ dockerJson := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
+ dockerJson = strings.ReplaceAll(dockerJson, "auths", "BAD-KEY")
+ secretData := make(map[string][]byte)
+ secretData[pullSecretField] = []byte(dockerJson)
+ pullSecret := fixture.NewSecret("test", "test", secretData)
+ clientset := fake.NewFakeClientsetWithResources(pullSecret)
+ credSrc := &CredentialSource{
+ Type: CredentialSourcePullSecret,
+ Registry: "https://registry-1.docker.io/v2",
+ SecretNamespace: "test",
+ SecretName: "test",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
+ require.Error(t, err) // should fail with "no credentials in image pull secret"
+ require.Nil(t, creds)
+
+ creds, err = credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.Error(t, err) // should fail with "could not fetch credentials: no Kubernetes client given"
+ require.Nil(t, creds)
+ })
+
+ t.Run("Fetch credentials from pull secret with protocol stripped", func(t *testing.T) {
+ dockerJson := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
+ secretData := make(map[string][]byte)
+ secretData[pullSecretField] = []byte(dockerJson)
+ pullSecret := fixture.NewSecret("test", "test", secretData)
+ clientset := fake.NewFakeClientsetWithResources(pullSecret)
+ credSrc := &CredentialSource{
+ Type: CredentialSourcePullSecret,
+ Registry: "https://registry-1.docker.io/v2",
+ SecretNamespace: "test",
+ SecretName: "test",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
+ require.NoError(t, err)
+ require.NotNil(t, creds)
+ assert.Equal(t, "foo", creds.Username)
+ assert.Equal(t, "bar", creds.Password)
+ })
+}
+
+func Test_FetchCredentialsFromEnv(t *testing.T) {
+ t.Run("Fetch credentials from environment", func(t *testing.T) {
+ err := os.Setenv("MY_SECRET_ENV", "foo:bar")
+ require.NoError(t, err)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceEnv,
+ Registry: "https://registry-1.docker.io/v2",
+ EnvName: "MY_SECRET_ENV",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.NoError(t, err)
+ require.NotNil(t, creds)
+ assert.Equal(t, "foo", creds.Username)
+ assert.Equal(t, "bar", creds.Password)
+ })
+
+ t.Run("Fetch credentials from environment with missing env var", func(t *testing.T) {
+ err := os.Setenv("MY_SECRET_ENV", "")
+ require.NoError(t, err)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceEnv,
+ Registry: "https://registry-1.docker.io/v2",
+ EnvName: "MY_SECRET_ENV",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.Error(t, err)
+ require.Nil(t, creds)
+ })
+
+ t.Run("Fetch credentials from environment with invalid value in env var", func(t *testing.T) {
+ for _, value := range []string{"babayaga", "foo:", "bar:", ":"} {
+ err := os.Setenv("MY_SECRET_ENV", value)
+ require.NoError(t, err)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceEnv,
+ Registry: "https://registry-1.docker.io/v2",
+ EnvName: "MY_SECRET_ENV",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.Error(t, err)
+ require.Nil(t, creds)
+ }
+ })
+}
+
+func Test_FetchCredentialsFromExt(t *testing.T) {
+ t.Run("Fetch credentials from external script - valid output", func(t *testing.T) {
+ pwd, err := os.Getwd()
+ require.NoError(t, err)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceExt,
+ Registry: "https://registry-1.docker.io/v2",
+ ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-valid.sh"),
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.NoError(t, err)
+ require.NotNil(t, creds)
+ assert.Equal(t, "username", creds.Username)
+ assert.Equal(t, "password", creds.Password)
+ })
+ t.Run("Fetch credentials from external script - invalid script output", func(t *testing.T) {
+ pwd, err := os.Getwd()
+ require.NoError(t, err)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceExt,
+ Registry: "https://registry-1.docker.io/v2",
+ ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-invalid.sh"),
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.Errorf(t, err, "invalid script output")
+ require.Nil(t, creds)
+ })
+ t.Run("Fetch credentials from external script - script does not exist", func(t *testing.T) {
+ pwd, err := os.Getwd()
+ require.NoError(t, err)
+ credSrc := &CredentialSource{
+ Type: CredentialSourceExt,
+ Registry: "https://registry-1.docker.io/v2",
+ ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-notexist.sh"),
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.Errorf(t, err, "no such file or directory")
+ require.Nil(t, creds)
+ })
+ t.Run("Fetch credentials from external script - relative path", func(t *testing.T) {
+ credSrc := &CredentialSource{
+ Type: CredentialSourceExt,
+ Registry: "https://registry-1.docker.io/v2",
+ ScriptPath: "get-credentials-notexist.sh",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.Errorf(t, err, "path to script must be absolute")
+ require.Nil(t, creds)
+ })
+}
+
+func Test_FetchCredentialsFromUnknown(t *testing.T) {
+ t.Run("Fetch credentials from unknown type", func(t *testing.T) {
+ credSrc := &CredentialSource{
+ Type: CredentialSourceType(-1),
+ Registry: "https://registry-1.docker.io/v2",
+ }
+ creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
+ require.Error(t, err) // should fail with "unknown credential type"
+ require.Nil(t, creds)
+ })
+}
+
+func Test_ParseDockerConfig(t *testing.T) {
+ t.Run("Parse valid Docker configuration with matching registry", func(t *testing.T) {
+ config := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
+ username, password, err := parseDockerConfigJson("https://registry-1.docker.io", config)
+ require.NoError(t, err)
+ assert.Equal(t, "foo", username)
+ assert.Equal(t, "bar", password)
+ })
+
+ t.Run("Parse valid Docker configuration with matching registry as prefix", func(t *testing.T) {
+ config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
+ username, password, err := parseDockerConfigJson("https://registry-1.docker.io", config)
+ require.NoError(t, err)
+ assert.Equal(t, "foo", username)
+ assert.Equal(t, "bar", password)
+ })
+
+ t.Run("Parse valid Docker configuration with matching http registry as prefix", func(t *testing.T) {
+ config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
+ username, password, err := parseDockerConfigJson("http://registry-1.docker.io", config)
+ require.NoError(t, err)
+ assert.Equal(t, "foo", username)
+ assert.Equal(t, "bar", password)
+ })
+
+ t.Run("Parse valid Docker configuration with matching no-protocol registry as prefix", func(t *testing.T) {
+ config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
+ username, password, err := parseDockerConfigJson("registry-1.docker.io", config)
+ require.NoError(t, err)
+ assert.Equal(t, "foo", username)
+ assert.Equal(t, "bar", password)
+ })
+
+ t.Run("Parse valid Docker configuration with matching registry as prefix with / in the end", func(t *testing.T) {
+ config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
+ username, password, err := parseDockerConfigJson("https://registry-1.docker.io/", config)
+ require.NoError(t, err)
+ assert.Equal(t, "foo", username)
+ assert.Equal(t, "bar", password)
+ })
+
+ t.Run("Parse valid Docker configuration without matching registry", func(t *testing.T) {
+ config := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
+ username, password, err := parseDockerConfigJson("https://gcr.io", config)
+ assert.Error(t, err)
+ assert.Empty(t, username)
+ assert.Empty(t, password)
+ })
+}