mirror of
https://github.com/fluxcd/flux2.git
synced 2026-05-23 09:55:55 +00:00
build/diff: strip SOPS metadata on non-Secrets
Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca> Assisted-by: GitHub Copilot/GPT-5.3-Codex
This commit is contained in:
parent
4e78a9d7e0
commit
a485d0ec60
11 changed files with 266 additions and 0 deletions
|
|
@ -211,6 +211,18 @@ spec:
|
|||
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build helmrelease with sops metadata",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/sops-helmrelease",
|
||||
resultFile: "./testdata/build-kustomization/sops-helmrelease-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
{
|
||||
name: "build configmap with sops metadata",
|
||||
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/sops-configmap",
|
||||
resultFile: "./testdata/build-kustomization/sops-configmap-result.yaml",
|
||||
assertFunc: "assertGoldenTemplateFile",
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := map[string]string{
|
||||
|
|
|
|||
|
|
@ -34,6 +34,20 @@ func TestCreateKustomization(t *testing.T) {
|
|||
args: "create kustomization my-app --path=./deploy --export",
|
||||
assert: assertError("source is required"),
|
||||
},
|
||||
{
|
||||
// Verify that --decryption-provider and --decryption-secret produce the
|
||||
// expected Kustomization YAML with a spec.decryption block.
|
||||
name: "with sops decryption",
|
||||
args: "create kustomization mysql " +
|
||||
"--source=GitRepository/apps " +
|
||||
"--path=./apps " +
|
||||
"--decryption-provider=sops " +
|
||||
"--decryption-secret=sops-age " +
|
||||
"--namespace=flux-system " +
|
||||
"--interval=1m " +
|
||||
"--export",
|
||||
assert: assertGoldenFile("testdata/create_kustomization/with-sops-decryption.yaml"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
11
cmd/flux/testdata/build-kustomization/sops-configmap-result.yaml
vendored
Normal file
11
cmd/flux/testdata/build-kustomization/sops-configmap-result.yaml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
api-key: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
kustomize.toolkit.fluxcd.io/name: podinfo
|
||||
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
|
||||
name: app-config
|
||||
namespace: default
|
||||
---
|
||||
22
cmd/flux/testdata/build-kustomization/sops-configmap/configmap.yaml
vendored
Normal file
22
cmd/flux/testdata/build-kustomization/sops-configmap/configmap.yaml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: app-config
|
||||
namespace: default
|
||||
data:
|
||||
api-key: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
abc
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-07-15T00:00:00Z"
|
||||
mac: ENC[AES256_GCM,data:mac,iv:iv,tag:tag,type:str]
|
||||
encrypted_regex: ^(data)$
|
||||
version: 3.7.3
|
||||
4
cmd/flux/testdata/build-kustomization/sops-configmap/kustomization.yaml
vendored
Normal file
4
cmd/flux/testdata/build-kustomization/sops-configmap/kustomization.yaml
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ./configmap.yaml
|
||||
19
cmd/flux/testdata/build-kustomization/sops-helmrelease-result.yaml
vendored
Normal file
19
cmd/flux/testdata/build-kustomization/sops-helmrelease-result.yaml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
labels:
|
||||
kustomize.toolkit.fluxcd.io/name: podinfo
|
||||
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
|
||||
name: mysql
|
||||
namespace: default
|
||||
spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: mysql
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bitnami
|
||||
values:
|
||||
mysql:
|
||||
rootPassword: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
|
||||
---
|
||||
30
cmd/flux/testdata/build-kustomization/sops-helmrelease/helmrelease.yaml
vendored
Normal file
30
cmd/flux/testdata/build-kustomization/sops-helmrelease/helmrelease.yaml
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: mysql
|
||||
namespace: default
|
||||
spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: mysql
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bitnami
|
||||
values:
|
||||
mysql:
|
||||
rootPassword: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
abc
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-07-15T00:00:00Z"
|
||||
mac: ENC[AES256_GCM,data:mac,iv:iv,tag:tag,type:str]
|
||||
encrypted_regex: ^(values)$
|
||||
version: 3.7.3
|
||||
4
cmd/flux/testdata/build-kustomization/sops-helmrelease/kustomization.yaml
vendored
Normal file
4
cmd/flux/testdata/build-kustomization/sops-helmrelease/kustomization.yaml
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ./helmrelease.yaml
|
||||
17
cmd/flux/testdata/create_kustomization/with-sops-decryption.yaml
vendored
Normal file
17
cmd/flux/testdata/create_kustomization/with-sops-decryption.yaml
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: mysql
|
||||
namespace: flux-system
|
||||
spec:
|
||||
decryption:
|
||||
provider: sops
|
||||
secretRef:
|
||||
name: sops-age
|
||||
interval: 1m0s
|
||||
path: ./apps
|
||||
prune: false
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: apps
|
||||
|
|
@ -742,6 +742,19 @@ func maskSopsData(res *resource.Resource) error {
|
|||
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For non-Secret resources (e.g. HelmRelease), strip the top-level .sops metadata
|
||||
// block so it is not persisted in the cluster or exposed in build/diff output.
|
||||
// The kustomize-controller decrypts these resources before apply when
|
||||
// spec.decryption.provider is set; the .sops field is not part of the CRD schema
|
||||
// and would cause a server-side apply dry-run failure if left in place.
|
||||
asYaml, err := res.AsYAML()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %s %s for sops check: %w", res.GetKind(), res.GetName(), err)
|
||||
}
|
||||
if bytes.Contains(asYaml, []byte("sops:")) && bytes.Contains(asYaml, []byte("mac: ENC[")) {
|
||||
res.PipeE(yaml.FieldClearer{Name: "sops"})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -168,6 +168,126 @@ type: kubernetes.io/dockerconfigjson
|
|||
}
|
||||
}
|
||||
|
||||
func TestMaskSopsDataNonSecret(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
yamlStr string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// A SOPS-encrypted HelmRelease (values block encrypted) must have its
|
||||
// .sops metadata stripped so it is safe for build/diff output and does
|
||||
// not cause a server-side apply schema error.
|
||||
name: "HelmRelease with sops metadata",
|
||||
yamlStr: `apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: mysql
|
||||
namespace: default
|
||||
spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: mysql
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bitnami
|
||||
values:
|
||||
mysql:
|
||||
rootPassword: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
|
||||
replicationPassword: ENC[AES256_GCM,data:def456,iv:xyz,tag:tag,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
abc
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-07-15T00:00:00Z"
|
||||
mac: ENC[AES256_GCM,data:mac,iv:iv,tag:tag,type:str]
|
||||
encrypted_regex: ^(values)$
|
||||
version: 3.7.3
|
||||
`,
|
||||
expected: `apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: mysql
|
||||
namespace: default
|
||||
spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: mysql
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bitnami
|
||||
values:
|
||||
mysql:
|
||||
replicationPassword: ENC[AES256_GCM,data:def456,iv:xyz,tag:tag,type:str]
|
||||
rootPassword: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
|
||||
`,
|
||||
},
|
||||
{
|
||||
// A HelmRelease without any SOPS metadata must pass through unchanged.
|
||||
name: "HelmRelease without sops metadata",
|
||||
yamlStr: `apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: default
|
||||
spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: podinfo
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: podinfo
|
||||
values:
|
||||
replicaCount: 2
|
||||
`,
|
||||
expected: `apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: default
|
||||
spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: podinfo
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: podinfo
|
||||
values:
|
||||
replicaCount: 2
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r, err := yaml.Parse(tc.yamlStr)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse yaml: %v", err)
|
||||
}
|
||||
|
||||
res := &resource.Resource{RNode: *r}
|
||||
if err := maskSopsData(res); err != nil {
|
||||
t.Fatalf("maskSopsData returned unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got, err := res.AsYAML()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to convert resource to yaml: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(string(got), tc.expected); diff != "" {
|
||||
t.Errorf("unexpected output (-got +want):\n%v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_unMarshallKustomization(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue