diff options
Diffstat (limited to 'registry-scanner/pkg/image/credentials_test.go')
| -rw-r--r-- | registry-scanner/pkg/image/credentials_test.go | 410 |
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) + }) +} |
