Skip to content

Commit a485d0e

Browse files
committed
build/diff: strip SOPS metadata on non-Secrets
Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca> Assisted-by: GitHub Copilot/GPT-5.3-Codex
1 parent 4e78a9d commit a485d0e

11 files changed

Lines changed: 266 additions & 0 deletions

File tree

cmd/flux/build_kustomization_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ spec:
211211
resultFile: "./testdata/build-kustomization/podinfo-with-my-app-result.yaml",
212212
assertFunc: "assertGoldenTemplateFile",
213213
},
214+
{
215+
name: "build helmrelease with sops metadata",
216+
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/sops-helmrelease",
217+
resultFile: "./testdata/build-kustomization/sops-helmrelease-result.yaml",
218+
assertFunc: "assertGoldenTemplateFile",
219+
},
220+
{
221+
name: "build configmap with sops metadata",
222+
args: "build kustomization podinfo --kustomization-file " + tmpFile + " --path ./testdata/build-kustomization/sops-configmap",
223+
resultFile: "./testdata/build-kustomization/sops-configmap-result.yaml",
224+
assertFunc: "assertGoldenTemplateFile",
225+
},
214226
}
215227

216228
tmpl := map[string]string{

cmd/flux/create_kustomization_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ func TestCreateKustomization(t *testing.T) {
3434
args: "create kustomization my-app --path=./deploy --export",
3535
assert: assertError("source is required"),
3636
},
37+
{
38+
// Verify that --decryption-provider and --decryption-secret produce the
39+
// expected Kustomization YAML with a spec.decryption block.
40+
name: "with sops decryption",
41+
args: "create kustomization mysql " +
42+
"--source=GitRepository/apps " +
43+
"--path=./apps " +
44+
"--decryption-provider=sops " +
45+
"--decryption-secret=sops-age " +
46+
"--namespace=flux-system " +
47+
"--interval=1m " +
48+
"--export",
49+
assert: assertGoldenFile("testdata/create_kustomization/with-sops-decryption.yaml"),
50+
},
3751
}
3852

3953
for _, tt := range tests {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: v1
2+
data:
3+
api-key: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
4+
kind: ConfigMap
5+
metadata:
6+
labels:
7+
kustomize.toolkit.fluxcd.io/name: podinfo
8+
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
9+
name: app-config
10+
namespace: default
11+
---
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: app-config
5+
namespace: default
6+
data:
7+
api-key: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
8+
sops:
9+
kms: []
10+
gcp_kms: []
11+
azure_kv: []
12+
hc_vault: []
13+
age:
14+
- recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce
15+
enc: |
16+
-----BEGIN AGE ENCRYPTED FILE-----
17+
abc
18+
-----END AGE ENCRYPTED FILE-----
19+
lastmodified: "2023-07-15T00:00:00Z"
20+
mac: ENC[AES256_GCM,data:mac,iv:iv,tag:tag,type:str]
21+
encrypted_regex: ^(data)$
22+
version: 3.7.3
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
resources:
4+
- ./configmap.yaml
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: helm.toolkit.fluxcd.io/v2
2+
kind: HelmRelease
3+
metadata:
4+
labels:
5+
kustomize.toolkit.fluxcd.io/name: podinfo
6+
kustomize.toolkit.fluxcd.io/namespace: {{ .fluxns }}
7+
name: mysql
8+
namespace: default
9+
spec:
10+
chart:
11+
spec:
12+
chart: mysql
13+
sourceRef:
14+
kind: HelmRepository
15+
name: bitnami
16+
values:
17+
mysql:
18+
rootPassword: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
19+
---
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
apiVersion: helm.toolkit.fluxcd.io/v2
2+
kind: HelmRelease
3+
metadata:
4+
name: mysql
5+
namespace: default
6+
spec:
7+
chart:
8+
spec:
9+
chart: mysql
10+
sourceRef:
11+
kind: HelmRepository
12+
name: bitnami
13+
values:
14+
mysql:
15+
rootPassword: ENC[AES256_GCM,data:abc123,iv:xyz,tag:tag,type:str]
16+
sops:
17+
kms: []
18+
gcp_kms: []
19+
azure_kv: []
20+
hc_vault: []
21+
age:
22+
- recipient: age10la2ge0wtvx3qr7datqf7rs4yngxszdal927fs9rukamr8u2pshsvtz7ce
23+
enc: |
24+
-----BEGIN AGE ENCRYPTED FILE-----
25+
abc
26+
-----END AGE ENCRYPTED FILE-----
27+
lastmodified: "2023-07-15T00:00:00Z"
28+
mac: ENC[AES256_GCM,data:mac,iv:iv,tag:tag,type:str]
29+
encrypted_regex: ^(values)$
30+
version: 3.7.3
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
resources:
4+
- ./helmrelease.yaml
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
apiVersion: kustomize.toolkit.fluxcd.io/v1
3+
kind: Kustomization
4+
metadata:
5+
name: mysql
6+
namespace: flux-system
7+
spec:
8+
decryption:
9+
provider: sops
10+
secretRef:
11+
name: sops-age
12+
interval: 1m0s
13+
path: ./apps
14+
prune: false
15+
sourceRef:
16+
kind: GitRepository
17+
name: apps

internal/build/build.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,19 @@ func maskSopsData(res *resource.Resource) error {
742742
return fmt.Errorf("failed to mask secret %s sops data: %w", res.GetName(), err)
743743
}
744744
}
745+
} else {
746+
// For non-Secret resources (e.g. HelmRelease), strip the top-level .sops metadata
747+
// block so it is not persisted in the cluster or exposed in build/diff output.
748+
// The kustomize-controller decrypts these resources before apply when
749+
// spec.decryption.provider is set; the .sops field is not part of the CRD schema
750+
// and would cause a server-side apply dry-run failure if left in place.
751+
asYaml, err := res.AsYAML()
752+
if err != nil {
753+
return fmt.Errorf("failed to read %s %s for sops check: %w", res.GetKind(), res.GetName(), err)
754+
}
755+
if bytes.Contains(asYaml, []byte("sops:")) && bytes.Contains(asYaml, []byte("mac: ENC[")) {
756+
res.PipeE(yaml.FieldClearer{Name: "sops"})
757+
}
745758
}
746759

747760
return nil

0 commit comments

Comments
 (0)