From 68004bac16ab6bf9817fc99154b62771a79659ab Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Thu, 1 Aug 2024 07:33:08 -0600 Subject: [PATCH 1/2] support generic secret in docker auth currently, we only support unwrapping the auth key if its value supports a specific format. this change will fallback to generic unwrapping of the auth value when it doesn't match the user:pass format. Signed-off-by: Devin Christensen --- e2e/deploy/vault/vault.yaml | 5 +++ e2e/test/secret-docker-json-key.yaml | 20 +++++++++++ e2e/webhook_test.go | 50 +++++++++++++++++++++++++- pkg/webhook/secret.go | 54 +++++++++++++++++----------- 4 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 e2e/test/secret-docker-json-key.yaml diff --git a/e2e/deploy/vault/vault.yaml b/e2e/deploy/vault/vault.yaml index 811369fe..4309086e 100644 --- a/e2e/deploy/vault/vault.yaml +++ b/e2e/deploy/vault/vault.yaml @@ -146,6 +146,11 @@ spec: data: DOCKER_REPO_USER: dockerrepouser DOCKER_REPO_PASSWORD: dockerrepopassword + DOCKER_REPO_JSON_KEY: | + _json_key: { + "type": "service_account", + "project_id": "test" + } - type: kv path: secret/data/mysql data: diff --git a/e2e/test/secret-docker-json-key.yaml b/e2e/test/secret-docker-json-key.yaml new file mode 100644 index 00000000..48169dae --- /dev/null +++ b/e2e/test/secret-docker-json-key.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Secret +metadata: + name: test-secret-docker-json-key + annotations: + vault.security.banzaicloud.io/vault-addr: "https://vault.default.svc.cluster.local:8200" + vault.security.banzaicloud.io/vault-role: "default" + vault.security.banzaicloud.io/vault-tls-secret: vault-tls + # vault.security.banzaicloud.io/vault-skip-verify: "true" + vault.security.banzaicloud.io/vault-path: "kubernetes" +type: kubernetes.io/dockerconfigjson +stringData: + .dockerconfigjson: | + { + "auths": { + "https://index.docker.io/v1/": { + "auth": "dmF1bHQ6c2VjcmV0L2RhdGEvZG9ja2VycmVwbyNET0NLRVJfUkVQT19KU09OX0tFWQ==" + } + } + } diff --git a/e2e/webhook_test.go b/e2e/webhook_test.go index 9e85f1a1..b7e8ae7d 100644 --- a/e2e/webhook_test.go +++ b/e2e/webhook_test.go @@ -81,14 +81,62 @@ func TestSecretValueInjection(t *testing.T) { err = json.Unmarshal(secret.Data[".dockerconfigjson"], &dockerconfigjson) require.NoError(t, err) + dockerrepoauth := base64.StdEncoding.EncodeToString([]byte("dockerrepouser:dockerrepopassword")) assert.Equal(t, "dockerrepouser", dockerconfigjson.Auths.V1.Username) assert.Equal(t, "dockerrepopassword", dockerconfigjson.Auths.V1.Password) + assert.Equal(t, dockerrepoauth, dockerconfigjson.Auths.V1.Auth) assert.Equal(t, "Inline: secretId AWS_ACCESS_KEY_ID", string(secret.Data["inline"])) return ctx }). Feature() + secretDockerJsonKey := applyResource(features.New("secret"), "secret-docker-json-key.yaml"). + Assess("object created", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + secrets := &v1.SecretList{ + Items: []v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{Name: "test-secret-docker-json-key", Namespace: cfg.Namespace()}, + }, + }, + } + + // wait for the secret to become available + err := wait.For(conditions.New(cfg.Client().Resources()).ResourcesFound(secrets), wait.WithTimeout(1*time.Minute)) + require.NoError(t, err) + + return ctx + }). + Assess("secret values are injected", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + var secret v1.Secret + + err := cfg.Client().Resources(cfg.Namespace()).Get(ctx, "test-secret-docker-json-key", cfg.Namespace(), &secret) + require.NoError(t, err) + + type v1 struct { + Auth string `json:"auth"` + } + + type auths struct { + V1 v1 `json:"https://index.docker.io/v1/"` + } + + type dockerconfig struct { + Auths auths `json:"auths"` + } + + var dockerconfigjson dockerconfig + + err = json.Unmarshal(secret.Data[".dockerconfigjson"], &dockerconfigjson) + require.NoError(t, err) + + dockerrepoauth := base64.StdEncoding.EncodeToString([]byte("_json_key: {\n \"type\": \"service_account\",\n \"project_id\": \"test\"\n}\n")) + assert.Equal(t, dockerrepoauth, dockerconfigjson.Auths.V1.Auth) + + return ctx + }). + Feature() + configMap := applyResource(features.New("configmap"), "configmap.yaml"). Assess("object created", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { configMaps := &v1.ConfigMapList{ @@ -120,7 +168,7 @@ func TestSecretValueInjection(t *testing.T) { }). Feature() - testenv.Test(t, secret, configMap) + testenv.Test(t, secret, secretDockerJsonKey, configMap) } func TestPodMutation(t *testing.T) { diff --git a/pkg/webhook/secret.go b/pkg/webhook/secret.go index 240ffd63..78a991bf 100644 --- a/pkg/webhook/secret.go +++ b/pkg/webhook/secret.go @@ -138,30 +138,42 @@ func (mw *MutatingWebhook) mutateDockerCreds(secret *corev1.Secret, dc *dockerCr auth := string(authBytes) if common.HasVaultPrefix(auth) { split := strings.Split(auth, ":") - if len(split) != 4 { - return errors.New("splitting auth credentials failed") - } - username := fmt.Sprintf("%s:%s", split[0], split[1]) - password := fmt.Sprintf("%s:%s", split[2], split[3]) + if len(split) == 4 { + username := fmt.Sprintf("%s:%s", split[0], split[1]) + password := fmt.Sprintf("%s:%s", split[2], split[3]) - credentialData := map[string]string{ - "username": username, - "password": password, - } + credentialData := map[string]string{ + "username": username, + "password": password, + } - dcCreds, err := secretInjector.GetDataFromVault(credentialData) - if err != nil { - return err - } - auth = fmt.Sprintf("%s:%s", dcCreds["username"], dcCreds["password"]) - dockerAuth := dockerAuthConfig{ - Auth: base64.StdEncoding.EncodeToString([]byte(auth)), - } - if creds.Username != "" && creds.Password != "" { - dockerAuth.Username = dcCreds["username"] - dockerAuth.Password = dcCreds["password"] + dcCreds, err := secretInjector.GetDataFromVault(credentialData) + if err != nil { + return err + } + auth = fmt.Sprintf("%s:%s", dcCreds["username"], dcCreds["password"]) + dockerAuth := dockerAuthConfig{ + Auth: base64.StdEncoding.EncodeToString([]byte(auth)), + } + if creds.Username != "" && creds.Password != "" { + dockerAuth.Username = dcCreds["username"] + dockerAuth.Password = dcCreds["password"] + } + assembled.Auths[key] = dockerAuth + } else { + credentialData := map[string]string{ + "auth": auth, + } + + dcCreds, err := secretInjector.GetDataFromVault(credentialData) + if err != nil { + return err + } + dockerAuth := dockerAuthConfig{ + Auth: base64.StdEncoding.EncodeToString([]byte(dcCreds["auth"])), + } + assembled.Auths[key] = dockerAuth } - assembled.Auths[key] = dockerAuth } } From 4ce88b67452514453ef21d9a69b21263f7b7a6a9 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Thu, 8 Aug 2024 08:56:01 -0600 Subject: [PATCH 2/2] simplify testing types Signed-off-by: Devin Christensen --- e2e/webhook_test.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/e2e/webhook_test.go b/e2e/webhook_test.go index b7e8ae7d..8c14b738 100644 --- a/e2e/webhook_test.go +++ b/e2e/webhook_test.go @@ -113,16 +113,12 @@ func TestSecretValueInjection(t *testing.T) { err := cfg.Client().Resources(cfg.Namespace()).Get(ctx, "test-secret-docker-json-key", cfg.Namespace(), &secret) require.NoError(t, err) - type v1 struct { + type auth struct { Auth string `json:"auth"` } - type auths struct { - V1 v1 `json:"https://index.docker.io/v1/"` - } - type dockerconfig struct { - Auths auths `json:"auths"` + Auths map[string]auth `json:"auths"` } var dockerconfigjson dockerconfig @@ -131,7 +127,7 @@ func TestSecretValueInjection(t *testing.T) { require.NoError(t, err) dockerrepoauth := base64.StdEncoding.EncodeToString([]byte("_json_key: {\n \"type\": \"service_account\",\n \"project_id\": \"test\"\n}\n")) - assert.Equal(t, dockerrepoauth, dockerconfigjson.Auths.V1.Auth) + assert.Equal(t, dockerrepoauth, dockerconfigjson.Auths["https://index.docker.io/v1/"].Auth) return ctx }).