From 49cb272de4c81d4ef8115b393e0e5f6047a52d03 Mon Sep 17 00:00:00 2001 From: Soule BA Date: Sun, 6 Feb 2022 00:28:37 +0100 Subject: [PATCH] Mask dockerconfigjson secret types with a json object If implemented, flux diff kustomization will managed correctly sops managed dockerconfigjson secrets. A secret containing stringData has been added to the test cases as well. Signed-off-by: Soule BA --- cmd/flux/diff_kustomization_test.go | 6 ++ .../build-kustomization/podinfo-result.yaml | 25 ++++++ .../podinfo/dockerconfigjson-sops-secret.yaml | 27 +++++++ .../podinfo/kustomization.yaml | 2 + .../podinfo/stringdata-secret.yaml | 8 ++ .../diff-with-deployment.golden | 2 + ...f-with-dockerconfigjson-sops-secret.golden | 6 ++ .../diff-with-drifted-key-sops-secret.golden | 2 + .../diff-with-drifted-secret.golden | 2 + .../diff-with-drifted-service.golden | 2 + ...diff-with-drifted-value-sops-secret.golden | 2 + .../dockerconfigjson-sops-secret.yaml | 11 +++ .../nothing-is-deployed.golden | 2 + internal/build/build.go | 76 +++++++++++++++---- internal/build/build_test.go | 6 +- 15 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 cmd/flux/testdata/build-kustomization/podinfo/dockerconfigjson-sops-secret.yaml create mode 100644 cmd/flux/testdata/build-kustomization/podinfo/stringdata-secret.yaml create mode 100644 cmd/flux/testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden create mode 100644 cmd/flux/testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml diff --git a/cmd/flux/diff_kustomization_test.go b/cmd/flux/diff_kustomization_test.go index e2f62e75f2..a516abdf4d 100644 --- a/cmd/flux/diff_kustomization_test.go +++ b/cmd/flux/diff_kustomization_test.go @@ -79,6 +79,12 @@ func TestDiffKustomization(t *testing.T) { objectFile: "./testdata/diff-kustomization/value-sops-secret.yaml", assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden"), }, + { + name: "diff with a sops dockerconfigjson secret object", + args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo", + objectFile: "./testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml", + assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden"), + }, } tmpl := map[string]string{ diff --git a/cmd/flux/testdata/build-kustomization/podinfo-result.yaml b/cmd/flux/testdata/build-kustomization/podinfo-result.yaml index ce66ff0f58..eae8a9feab 100644 --- a/cmd/flux/testdata/build-kustomization/podinfo-result.yaml +++ b/cmd/flux/testdata/build-kustomization/podinfo-result.yaml @@ -123,6 +123,31 @@ spec: type: ClusterIP --- apiVersion: v1 +data: + .dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ== +kind: Secret +metadata: + labels: + kustomize.toolkit.fluxcd.io/name: podinfo + kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }} + name: docker-secret + namespace: default +type: kubernetes.io/dockerconfigjson +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + kustomize.toolkit.fluxcd.io/name: podinfo + kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }} + name: secret-basic-auth-stringdata + namespace: default +stringData: + password: t0p-Secret + username: admin +type: kubernetes.io/basic-auth +--- +apiVersion: v1 data: token: KipTT1BTKio= kind: Secret diff --git a/cmd/flux/testdata/build-kustomization/podinfo/dockerconfigjson-sops-secret.yaml b/cmd/flux/testdata/build-kustomization/podinfo/dockerconfigjson-sops-secret.yaml new file mode 100644 index 0000000000..c05731bc6e --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/podinfo/dockerconfigjson-sops-secret.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +data: + .dockerconfigjson: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str] +kind: Secret +metadata: + name: docker-secret +type: kubernetes.io/dockerconfigjson +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3eU1CTEJhVXZ4eEVYYkVV + OU90TEcrR2pYckttN0pBanJoSUZWSW1RQXlRCkUydFJ3V1NZUTBuVFF0aC9GUEcw + bUdhNjJWTkoyL1FUVi9Dc1dxUDBkM0UKLS0tIE1sQXkwcWdGaEFuY0RHQTVXM0J6 + dWpJcThEbW15V3dXYXpPZklBdW1Hd1kKoIAdmGNPrEctV8h1w8KuvQ5S+BGmgqN9 + MgpNmUhJjWhgcQpb5BRYpQesBOgU5TBGK7j58A6DMDKlSiYZsdQchQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2022-02-03T16:03:17Z" + mac: ENC[AES256_GCM,data:AHdYSawajwgAFwlmDN1IPNmT9vWaYKzyVIra2d6sPcjTbZ8/p+VRSRpVm4XZFFsaNnW5AUJaouwXnKYDTmJDXKlr/rQcu9kXqsssQgdzcXaA6l5uJlgsnml8ba7J3OK+iEKMax23mwQEx2EUskCd9ENOwFDkunP02sxqDNOz20k=,iv:8F5OamHt3fAVorf6p+SoIrWoqkcATSGWVoM0EK87S4M=,tag:E1mxXnc7wWkEX5BxhpLtng==,type:str] + pgp: [] + encrypted_regex: ^(data|stringData)$ + version: 3.7.1 diff --git a/cmd/flux/testdata/build-kustomization/podinfo/kustomization.yaml b/cmd/flux/testdata/build-kustomization/podinfo/kustomization.yaml index 0ba076685d..435c23c661 100644 --- a/cmd/flux/testdata/build-kustomization/podinfo/kustomization.yaml +++ b/cmd/flux/testdata/build-kustomization/podinfo/kustomization.yaml @@ -4,6 +4,8 @@ resources: - ./deployment.yaml - ./hpa.yaml - ./service.yaml +- ./dockerconfigjson-sops-secret.yaml +- ./stringdata-secret.yaml secretGenerator: - files: - token=token.encrypted diff --git a/cmd/flux/testdata/build-kustomization/podinfo/stringdata-secret.yaml b/cmd/flux/testdata/build-kustomization/podinfo/stringdata-secret.yaml new file mode 100644 index 0000000000..a79cf44a20 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/podinfo/stringdata-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-basic-auth-stringdata +type: kubernetes.io/basic-auth +stringData: + username: admin + password: t0p-Secret diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-deployment.golden b/cmd/flux/testdata/diff-kustomization/diff-with-deployment.golden index 098497fccd..7856dd584c 100644 --- a/cmd/flux/testdata/diff-kustomization/diff-with-deployment.golden +++ b/cmd/flux/testdata/diff-kustomization/diff-with-deployment.golden @@ -1,4 +1,6 @@ ► HorizontalPodAutoscaler/default/podinfo created ► Service/default/podinfo created +► Secret/default/docker-secret created +► Secret/default/secret-basic-auth-stringdata created ► Secret/default/podinfo-token-77t89m9b67 created ► Secret/default/db-user-pass-bkbd782d2c created diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden b/cmd/flux/testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden new file mode 100644 index 0000000000..17972fffd9 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/diff-with-dockerconfigjson-sops-secret.golden @@ -0,0 +1,6 @@ +► Deployment/default/podinfo created +► HorizontalPodAutoscaler/default/podinfo created +► Service/default/podinfo created +► Secret/default/secret-basic-auth-stringdata created +► Secret/default/podinfo-token-77t89m9b67 created +► Secret/default/db-user-pass-bkbd782d2c created diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden index 3826954479..5608c24e2d 100644 --- a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden +++ b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-key-sops-secret.golden @@ -1,6 +1,8 @@ ► Deployment/default/podinfo created ► HorizontalPodAutoscaler/default/podinfo created ► Service/default/podinfo created +► Secret/default/docker-secret created +► Secret/default/secret-basic-auth-stringdata created ► Secret/default/podinfo-token-77t89m9b67 drifted data diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden index ac76978f18..41a65058fd 100644 --- a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden +++ b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-secret.golden @@ -1,6 +1,8 @@ ► Deployment/default/podinfo created ► HorizontalPodAutoscaler/default/podinfo created ► Service/default/podinfo created +► Secret/default/docker-secret created +► Secret/default/secret-basic-auth-stringdata created ► Secret/default/podinfo-token-77t89m9b67 created ► Secret/default/db-user-pass-bkbd782d2c drifted diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-service.golden b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-service.golden index 6ddd393346..a9fe09f2e8 100644 --- a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-service.golden +++ b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-service.golden @@ -7,5 +7,7 @@ spec.ports.http.port - 9899 + 9898 +► Secret/default/docker-secret created +► Secret/default/secret-basic-auth-stringdata created ► Secret/default/podinfo-token-77t89m9b67 created ► Secret/default/db-user-pass-bkbd782d2c created diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden index 033db67e5d..6c5022262b 100644 --- a/cmd/flux/testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden +++ b/cmd/flux/testdata/diff-kustomization/diff-with-drifted-value-sops-secret.golden @@ -1,4 +1,6 @@ ► Deployment/default/podinfo created ► HorizontalPodAutoscaler/default/podinfo created ► Service/default/podinfo created +► Secret/default/docker-secret created +► Secret/default/secret-basic-auth-stringdata created ► Secret/default/db-user-pass-bkbd782d2c created diff --git a/cmd/flux/testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml b/cmd/flux/testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml new file mode 100644 index 0000000000..1a689e9239 --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/dockerconfigjson-sops-secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +data: + .dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ== +kind: Secret +metadata: + labels: + kustomize.toolkit.fluxcd.io/name: podinfo + kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }} + name: docker-secret + namespace: default +type: kubernetes.io/dockerconfigjson diff --git a/cmd/flux/testdata/diff-kustomization/nothing-is-deployed.golden b/cmd/flux/testdata/diff-kustomization/nothing-is-deployed.golden index da1c23dae0..5c320a1b1f 100644 --- a/cmd/flux/testdata/diff-kustomization/nothing-is-deployed.golden +++ b/cmd/flux/testdata/diff-kustomization/nothing-is-deployed.golden @@ -1,5 +1,7 @@ ► Deployment/default/podinfo created ► HorizontalPodAutoscaler/default/podinfo created ► Service/default/podinfo created +► Secret/default/docker-secret created +► Secret/default/secret-basic-auth-stringdata created ► Secret/default/podinfo-token-77t89m9b67 created ► Secret/default/db-user-pass-bkbd782d2c created diff --git a/internal/build/build.go b/internal/build/build.go index 923852d5f5..cef62ec0f8 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "encoding/base64" + "encoding/json" "fmt" "sync" "time" @@ -40,9 +41,11 @@ import ( ) const ( - controllerName = "kustomize-controller" - controllerGroup = "kustomize.toolkit.fluxcd.io" - mask = "**SOPS**" + controllerName = "kustomize-controller" + controllerGroup = "kustomize.toolkit.fluxcd.io" + mask = "**SOPS**" + dockercfgSecretType = "kubernetes.io/dockerconfigjson" + typeField = "type" ) var defaultTimeout = 80 * time.Second @@ -183,7 +186,7 @@ func (b *Builder) build() (m resmap.ResMap, err error) { } // make sure secrets are masked - err = trimSopsData(res) + err = maskSopsData(res) if err != nil { return } @@ -257,7 +260,7 @@ func (b *Builder) setOwnerLabels(res *resource.Resource) error { return nil } -func trimSopsData(res *resource.Resource) error { +func maskSopsData(res *resource.Resource) error { // sopsMess is the base64 encoded mask sopsMess := base64.StdEncoding.EncodeToString([]byte(mask)) @@ -265,29 +268,34 @@ func trimSopsData(res *resource.Resource) error { dataMap := res.GetDataMap() asYaml, err := res.AsYAML() if err != nil { - return fmt.Errorf("failed to decode secret %s data: %w", res.GetName(), err) + return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err) } //delete any sops data as we don't want to expose it if bytes.Contains(asYaml, []byte("sops:")) && bytes.Contains(asYaml, []byte("mac: ENC[")) { + // delete the sops object res.PipeE(yaml.FieldClearer{Name: "sops"}) - for k := range dataMap { - dataMap[k] = sopsMess + secretType, err := res.GetFieldValue(typeField) + if err != nil { + return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err) } - } else { - for k, v := range dataMap { - data, err := base64.StdEncoding.DecodeString(v) + if v, ok := secretType.(string); ok && v == dockercfgSecretType { + // if the secret is a json docker config secret, we need to mask the data with a json object + err := maskDockerconfigjsonSopsData(dataMap) if err != nil { - if _, ok := err.(base64.CorruptInputError); ok { - return fmt.Errorf("failed to decode secret %s data: %w", res.GetName(), err) - } + return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err) } - - if bytes.Contains(data, []byte("sops")) && bytes.Contains(data, []byte("ENC[")) { + } else { + for k := range dataMap { dataMap[k] = sopsMess } } + } else { + err := maskBase64EncryptedSopsData(dataMap, sopsMess) + if err != nil { + return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err) + } } res.SetDataMap(dataMap) @@ -296,6 +304,42 @@ func trimSopsData(res *resource.Resource) error { return nil } +func maskDockerconfigjsonSopsData(dataMap map[string]string) error { + sopsMess := struct { + Mask string `json:"mask"` + }{ + Mask: mask, + } + + maskJson, err := json.Marshal(sopsMess) + if err != nil { + return err + } + + for k := range dataMap { + dataMap[k] = base64.StdEncoding.EncodeToString(maskJson) + } + + return nil +} + +func maskBase64EncryptedSopsData(dataMap map[string]string, mask string) error { + for k, v := range dataMap { + data, err := base64.StdEncoding.DecodeString(v) + if err != nil { + if _, ok := err.(base64.CorruptInputError); ok { + return err + } + } + + if bytes.Contains(data, []byte("sops")) && bytes.Contains(data, []byte("ENC[")) { + dataMap[k] = mask + } + } + + return nil +} + // Cancel cancels the build // It restores a clean reprository func (b *Builder) Cancel() error { diff --git a/internal/build/build_test.go b/internal/build/build_test.go index 17b2cf3962..5e1cfffd1c 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -97,7 +97,7 @@ type: kubernetes.io/basic-auth name: "secret sops secret", yamlStr: `apiVersion: v1 data: - .dockercfg: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str] + .dockerconfigjson: ENC[AES256_GCM,data:KHCFH3hNnc+PMfWLFEPjebf3W4z4WXbGFAANRZyZC+07z7wlrTALJM6rn8YslW4tMAWCoAYxblC5WRCszTy0h9rw0U/RGOv5H0qCgnNg/FILFUqhwo9pNfrUH+MEP4M9qxxbLKZwObpHUE7DUsKx1JYAxsI=,iv:q48lqUbUQD+0cbYcjNMZMJLRdGHi78ZmDhNAT2th9tg=,tag:QRI2SZZXQrAcdql3R5AH2g==,type:str] kind: Secret metadata: name: secret @@ -125,7 +125,7 @@ sops: `, expected: `apiVersion: v1 data: - .dockercfg: KipTT1BTKio= + .dockerconfigjson: eyJtYXNrIjoiKipTT1BTKioifQ== kind: Secret metadata: name: secret @@ -142,7 +142,7 @@ type: kubernetes.io/dockerconfigjson } resource := &resource.Resource{RNode: *r} - err = trimSopsData(resource) + err = maskSopsData(resource) if err != nil { t.Fatalf("unable to trim sops data: %v", err) }