Conversation
Agent-Logs-Url: https://github.com/SebTardif/flux2/sessions/dac56475-250d-4fa6-add6-9e7559d90d2b Co-authored-by: SebTardif <1413412+SebTardif@users.noreply.github.com>
- Strip .sops metadata from non-Secret resources (e.g. HelmRelease) in maskSopsData so flux build/diff kustomization does not expose SOPS metadata blocks for CRD objects managed via kustomize-controller with spec.decryption.provider=sops - Add TestMaskSopsDataNonSecret covering HelmRelease with and without .sops metadata - Fix duplicate loop left by previous partial edit in build_test.go - Add golden-file unit test for create kustomization --decryption-provider sops --decryption-secret sops-age --export Assisted-by: copilot/claude-sonnet-4 Agent-Logs-Url: https://github.com/SebTardif/flux2/sessions/1e97b1f8-2194-4033-8c76-4c23dfb62d6b Co-authored-by: SebTardif <1413412+SebTardif@users.noreply.github.com>
Agent-Logs-Url: https://github.com/SebTardif/flux2/sessions/652bdb9c-a738-4041-8fe3-601443c05375 Co-authored-by: SebTardif <1413412+SebTardif@users.noreply.github.com>
Agent-Logs-Url: https://github.com/SebTardif/flux2/sessions/953896d5-3a14-4b12-807b-4699524ce24f Co-authored-by: SebTardif <1413412+SebTardif@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates flux build kustomization / flux diff kustomization masking behavior to properly handle SOPS-encrypted non-Secret resources by stripping the top-level .sops metadata block, preventing CRD schema validation failures and avoiding SOPS metadata leakage in CLI output.
Changes:
- Extend
maskSopsDatato strip.sopsmetadata from non-Secret resources when SOPS-encrypted content is detected. - Add unit + end-to-end build pipeline tests covering HelmRelease/ConfigMap SOPS metadata stripping.
- Add a golden-file test for
flux create kustomizationdecryption flags and corresponding testdata fixtures.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| plan.md | Documents investigation, approach, and remaining follow-ups. |
| internal/build/build.go | Adds non-Secret .sops stripping logic to maskSopsData. |
| internal/build/build_test.go | Adds unit tests for non-Secret .sops stripping behavior. |
| cmd/flux/create_kustomization_test.go | Adds golden test for --decryption-provider/--decryption-secret. |
| cmd/flux/build_kustomization_test.go | Adds integration test cases ensuring .sops is stripped in build output. |
| cmd/flux/testdata/create_kustomization/with-sops-decryption.yaml | Golden output for create kustomization decryption flags. |
| cmd/flux/testdata/build-kustomization/sops-helmrelease/kustomization.yaml | Fixture kustomization referencing a SOPS-encrypted HelmRelease. |
| cmd/flux/testdata/build-kustomization/sops-helmrelease/helmrelease.yaml | SOPS-encrypted HelmRelease input fixture (includes .sops). |
| cmd/flux/testdata/build-kustomization/sops-helmrelease-result.yaml | Expected build output with .sops removed. |
| cmd/flux/testdata/build-kustomization/sops-configmap/kustomization.yaml | Fixture kustomization referencing a SOPS-encrypted ConfigMap. |
| cmd/flux/testdata/build-kustomization/sops-configmap/configmap.yaml | SOPS-encrypted ConfigMap input fixture (includes .sops). |
| cmd/flux/testdata/build-kustomization/sops-configmap-result.yaml | Expected build output with .sops removed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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"}) |
There was a problem hiding this comment.
The non-Secret branch detects SOPS by serializing the whole resource to YAML and doing substring checks. This adds per-resource overhead during build/diff and is also brittle (it depends on YAML rendering details like mac: ENC[ formatting). Consider doing a structured lookup on the root node instead (e.g., check for a top-level sops mapping and optionally sops.mac), then clear the sops field based on that, avoiding AsYAML()/bytes.Contains entirely.
| 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"}) | |
| sopsNode, err := res.Pipe(yaml.Lookup("sops")) | |
| if err != nil { | |
| return fmt.Errorf("failed to read %s %s sops field: %w", res.GetKind(), res.GetName(), err) | |
| } | |
| if sopsNode != nil && sopsNode.YNode() != nil && sopsNode.YNode().Kind == yaml.MappingNode { | |
| macNode, err := res.Pipe(yaml.Lookup("sops", "mac")) | |
| if err != nil { | |
| return fmt.Errorf("failed to read %s %s sops.mac field: %w", res.GetKind(), res.GetName(), err) | |
| } | |
| if macNode != nil && strings.Contains(yaml.GetValue(macNode), "ENC[") { | |
| if err := res.PipeE(yaml.FieldClearer{Name: "sops"}); err != nil { | |
| return fmt.Errorf("failed to clear %s %s sops field: %w", res.GetKind(), res.GetName(), err) | |
| } | |
| } |
End goal
kustomize-controller supports encrypting any Kubernetes resource (not just Secrets)
with SOPS — for example a
HelmReleasewhosespec.valuesblock contains credentials,or a
ConfigMapwith sensitive data. When such a resource is committed to git it carriesa top-level
.sopsmetadata block containing key fingerprints, recipients, and anencrypted MAC. The controller strips this block before applying the resource to the
cluster, but the Flux CLI (
flux build kustomization,flux diff kustomization) was notdoing the same.
The end goal is for
flux build kustomizationandflux diff kustomizationto handleSOPS-encrypted non-Secret resources correctly:
.sopsfield is not part of any CRD schema; leaving it inthe output causes
flux diff(server-side apply dry-run) to fail with a fieldvalidation error.
not appear in CLI output or be sent to the API server.
The root-cause analysis and all findings from the investigation are captured in
plan.mdon this branch — the single source of truth for what was discovered, what was
implemented, and what remains.
What this PR does
Delivers the core fix plus test coverage across four changes:
1.
internal/build/build.go— extendmaskSopsDatafor non-Secret resourcesmaskSopsDatapreviously handled onlySecretresources. Anelsebranch is added forevery other kind: when a
.sopsblock containingmac: ENC[…]is detected, it isstripped via
yaml.FieldClearer. TheENC[…]ciphertext field values are left intact —they are already opaque, not plaintext.
2.
internal/build/build_test.go—TestMaskSopsDataNonSecretTable-driven unit test:
.sopsblock → block stripped,ENC[…]values preserved.sopsblock → resource passes through unchanged3.
cmd/flux/create_kustomization_test.go— golden-file test for--decryption-providerExercises
flux create kustomization --decryption-provider sops --decryption-secret …and asserts the generated
spec.decryptionblock. Adds--interval=1mexplicitly toavoid Cobra flag-state pollution from
resetCmdArgs()zeroingcreateArgs.interval.4.
cmd/flux/build_kustomization_test.go— full pipeline integration testsTwo new cases in
TestBuildLocalKustomizationthat runBuilder.Build()end-to-end:build helmrelease with sops metadata— asserts.sopsabsent,ENC[…]preservedbuild configmap with sops metadata— same for a SOPS-encrypted ConfigMapFixtures:
cmd/flux/testdata/build-kustomization/sops-helmrelease/andsops-configmap/.What remains (tracked in
plan.md)ENC[…]ciphertext with**SOPS**in non-Secret output (designdecision; ciphertext is already opaque, so this is lower priority)
flux build kustomizationdocs/examples to mention SOPS HelmRelease handlingtests/integration/