From 3c08926150a622db19f65ccc58ea8481f5160fdf Mon Sep 17 00:00:00 2001 From: Boris Kreitchman Date: Sat, 15 Feb 2025 14:40:37 +0200 Subject: [PATCH 1/3] Fix diffKsArgs reset Signed-off-by: Boris Kreitchman --- cmd/flux/main_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index 30673b1f..9d0e23a5 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -435,7 +435,9 @@ func resetCmdArgs() { checkArgs = checkFlags{} createArgs = createFlags{} deleteArgs = deleteFlags{} - diffKsArgs = diffKsFlags{} + diffKsArgs = diffKsFlags{ + localSources: map[string]string{}, + } exportArgs = exportFlags{} getArgs = GetFlags{} gitArgs = gitFlags{} From 3226043088a8daec42fd535070c164a0c2679559 Mon Sep 17 00:00:00 2001 From: Boris Kreitchman Date: Sat, 8 Feb 2025 12:32:55 +0200 Subject: [PATCH 2/3] Combine builder options Signed-off-by: Boris Kreitchman --- cmd/flux/build_kustomization.go | 34 +++++++++++++----------------- cmd/flux/diff_kustomization.go | 37 +++++++++++++-------------------- 2 files changed, 29 insertions(+), 42 deletions(-) diff --git a/cmd/flux/build_kustomization.go b/cmd/flux/build_kustomization.go index 5d29c001..6aea4944 100644 --- a/cmd/flux/build_kustomization.go +++ b/cmd/flux/build_kustomization.go @@ -111,30 +111,24 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) { } } + var options []build.BuilderOptionFunc var builder *build.Builder + + options = append(options, + build.WithClientConfig(kubeconfigArgs, kubeclientOptions), + build.WithTimeout(rootArgs.timeout), + build.WithKustomizationFile(buildKsArgs.kustomizationFile), + build.WithIgnore(buildKsArgs.ignorePaths), + build.WithStrictSubstitute(buildKsArgs.strictSubst), + build.WithRecursive(buildKsArgs.recursive), + build.WithLocalSources(buildKsArgs.localSources), + ) + if buildKsArgs.dryRun { - builder, err = build.NewBuilder(name, buildKsArgs.path, - build.WithTimeout(rootArgs.timeout), - build.WithKustomizationFile(buildKsArgs.kustomizationFile), - build.WithDryRun(buildKsArgs.dryRun), - build.WithNamespace(*kubeconfigArgs.Namespace), - build.WithIgnore(buildKsArgs.ignorePaths), - build.WithStrictSubstitute(buildKsArgs.strictSubst), - build.WithRecursive(buildKsArgs.recursive), - build.WithLocalSources(buildKsArgs.localSources), - ) - } else { - builder, err = build.NewBuilder(name, buildKsArgs.path, - build.WithClientConfig(kubeconfigArgs, kubeclientOptions), - build.WithTimeout(rootArgs.timeout), - build.WithKustomizationFile(buildKsArgs.kustomizationFile), - build.WithIgnore(buildKsArgs.ignorePaths), - build.WithStrictSubstitute(buildKsArgs.strictSubst), - build.WithRecursive(buildKsArgs.recursive), - build.WithLocalSources(buildKsArgs.localSources), - ) + options = append(options, build.WithDryRun(buildKsArgs.dryRun)) } + builder, err = build.NewBuilder(name, buildKsArgs.path, options...) if err != nil { return err } diff --git a/cmd/flux/diff_kustomization.go b/cmd/flux/diff_kustomization.go index 0480e293..efec44cf 100644 --- a/cmd/flux/diff_kustomization.go +++ b/cmd/flux/diff_kustomization.go @@ -99,34 +99,27 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { } var ( + options []build.BuilderOptionFunc builder *build.Builder err error ) + + options = append(options, + build.WithClientConfig(kubeconfigArgs, kubeclientOptions), + build.WithTimeout(rootArgs.timeout), + build.WithKustomizationFile(diffKsArgs.kustomizationFile), + build.WithIgnore(diffKsArgs.ignorePaths), + build.WithStrictSubstitute(diffKsArgs.strictSubst), + build.WithRecursive(diffKsArgs.recursive), + build.WithLocalSources(diffKsArgs.localSources), + build.WithSingleKustomization(), + ) + if diffKsArgs.progressBar { - builder, err = build.NewBuilder(name, diffKsArgs.path, - build.WithClientConfig(kubeconfigArgs, kubeclientOptions), - build.WithTimeout(rootArgs.timeout), - build.WithKustomizationFile(diffKsArgs.kustomizationFile), - build.WithProgressBar(), - build.WithIgnore(diffKsArgs.ignorePaths), - build.WithStrictSubstitute(diffKsArgs.strictSubst), - build.WithRecursive(diffKsArgs.recursive), - build.WithLocalSources(diffKsArgs.localSources), - build.WithSingleKustomization(), - ) - } else { - builder, err = build.NewBuilder(name, diffKsArgs.path, - build.WithClientConfig(kubeconfigArgs, kubeclientOptions), - build.WithTimeout(rootArgs.timeout), - build.WithKustomizationFile(diffKsArgs.kustomizationFile), - build.WithIgnore(diffKsArgs.ignorePaths), - build.WithStrictSubstitute(diffKsArgs.strictSubst), - build.WithRecursive(diffKsArgs.recursive), - build.WithLocalSources(diffKsArgs.localSources), - build.WithSingleKustomization(), - ) + options = append(options, build.WithProgressBar()) } + builder, err = build.NewBuilder(name, diffKsArgs.path, options...) if err != nil { return &RequestError{StatusCode: 2, Err: err} } From 8dce48ae061d0485e640a2eef1880e95d392099e Mon Sep 17 00:00:00 2001 From: Boris Kreitchman Date: Fri, 7 Feb 2025 20:55:47 +0200 Subject: [PATCH 3/3] Decrypt SOPS secrets for diff Signed-off-by: Boris Kreitchman --- cmd/flux/build_kustomization_test.go | 1 + cmd/flux/diff_kustomization.go | 3 + cmd/flux/diff_kustomization_test.go | 6 ++ .../podinfo-token-sops-secret.yaml | 27 +++++++++ .../podinfo-kustomization.yaml | 4 ++ .../build-kustomization/sops-age.yaml | 10 ++++ .../diff-with-decryption.golden | 7 +++ internal/build/build.go | 57 ++++++++++++++++--- internal/build/diff.go | 1 + 9 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 cmd/flux/testdata/build-kustomization/decrypt-secret/podinfo-token-sops-secret.yaml create mode 100644 cmd/flux/testdata/build-kustomization/sops-age.yaml create mode 100644 cmd/flux/testdata/diff-kustomization/diff-with-decryption.golden diff --git a/cmd/flux/build_kustomization_test.go b/cmd/flux/build_kustomization_test.go index f5635977..5f42be40 100644 --- a/cmd/flux/build_kustomization_test.go +++ b/cmd/flux/build_kustomization_test.go @@ -31,6 +31,7 @@ func setup(t *testing.T, tmpl map[string]string) { t.Helper() testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t) testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-kustomization.yaml", tmpl, t) + testEnv.CreateObjectFile("./testdata/build-kustomization/sops-age.yaml", tmpl, t) } func TestBuildKustomization(t *testing.T) { diff --git a/cmd/flux/diff_kustomization.go b/cmd/flux/diff_kustomization.go index efec44cf..63b1291f 100644 --- a/cmd/flux/diff_kustomization.go +++ b/cmd/flux/diff_kustomization.go @@ -61,6 +61,7 @@ type diffKsFlags struct { progressBar bool strictSubst bool recursive bool + decryptSecrets bool localSources map[string]string } @@ -74,6 +75,7 @@ func init() { diffKsCmd.Flags().BoolVar(&diffKsArgs.strictSubst, "strict-substitute", false, "When enabled, the post build substitutions will fail if a var without a default value is declared in files but is missing from the input vars.") diffKsCmd.Flags().BoolVarP(&diffKsArgs.recursive, "recursive", "r", false, "Recursively diff Kustomizations") + diffKsCmd.Flags().BoolVar(&diffKsArgs.decryptSecrets, "decrypt-secrets", false, "Decrypt SOPS-encrypted secrets for comparison") diffKsCmd.Flags().StringToStringVar(&diffKsArgs.localSources, "local-sources", nil, "Comma-separated list of repositories in format: Kind/namespace/name=path") diffCmd.AddCommand(diffKsCmd) } @@ -111,6 +113,7 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error { build.WithIgnore(diffKsArgs.ignorePaths), build.WithStrictSubstitute(diffKsArgs.strictSubst), build.WithRecursive(diffKsArgs.recursive), + build.WithDecryptSecrets(diffKsArgs.decryptSecrets), build.WithLocalSources(diffKsArgs.localSources), build.WithSingleKustomization(), ) diff --git a/cmd/flux/diff_kustomization_test.go b/cmd/flux/diff_kustomization_test.go index b6d4e9af..da8df7c2 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 drifted value in decrypted sops secret object", + args: "diff kustomization podinfo --path ./testdata/build-kustomization/decrypt-secret --progress-bar=false --decrypt-secrets", + objectFile: "./testdata/diff-kustomization/value-sops-secret.yaml", + assert: assertGoldenFile("./testdata/diff-kustomization/diff-with-decryption.golden"), + }, { name: "diff with a sops dockerconfigjson secret object", args: "diff kustomization podinfo --path ./testdata/build-kustomization/podinfo --progress-bar=false", diff --git a/cmd/flux/testdata/build-kustomization/decrypt-secret/podinfo-token-sops-secret.yaml b/cmd/flux/testdata/build-kustomization/decrypt-secret/podinfo-token-sops-secret.yaml new file mode 100644 index 00000000..dae57b44 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/decrypt-secret/podinfo-token-sops-secret.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: Secret +metadata: + name: podinfo-token-77t89m9b67 +stringData: + token: ENC[AES256_GCM,data:ut7THDa7SJMTIn26orb2,iv:jBqKk4f8jzOZLpoH7pMnryHRAwwvjaycKwBryEBO3oQ=,tag:193UGSrkhQJzs4pDg5u2mQ==,type:str] +type: Opaque +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1yqval9atdcnzjwhmutcjwdukfe5pk9tsa3lqya90u08grp03zgyss93knx + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxeGdXSVAyMGUzSFpwRGNF + bUV5bU9scVJsRUVwbERFYWVjSVpJNFlYREJRCm5xaGxzZGRic0ZwY3hJSmJOcWk3 + UmYzUDNIU29zd3orYlFlemNGUDhWZVEKLS0tIFJCcEsrdmlZcHBFWFE3SUlCaUNZ + ZkJuMm83a0VNODdXMkxUeDRmemJ2blkKebY+krevnla3Rxhrm3T4mmao8NUishwl + W4sV4fM2m5gjpiz72MVPjUrakqo9lA9ZLUkSue95YzFe09o7uqglRQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-02-16T20:34:53Z" + mac: ENC[AES256_GCM,data:C6Sv7iAoMztDGMDTYEW5KFUGSdey6O9zLzNdaEHxQL1oTrQB/hSBOjA4jaEBHovdCL/58w9jDTq8G30IGqUEFEu4HM0YUrSDA1gdTrbtvOfza0hQC8CtmCBWgol3tsWBwcLAeFOlE95perdvKkJx10t/r8yb8biCpLtJcxa/WZE=,iv:2CswtWAATMPZ7BHzWSUNhvT9xfmSSGPdOMvG1jHi3Nk=,tag:FZeCX+EuAlsOGaZbErpfJQ==,type:str] + pgp: [] + encrypted_regex: ^(data|stringData)$ + version: 3.9.1 diff --git a/cmd/flux/testdata/build-kustomization/podinfo-kustomization.yaml b/cmd/flux/testdata/build-kustomization/podinfo-kustomization.yaml index 9f5e3ef3..3393fbfd 100644 --- a/cmd/flux/testdata/build-kustomization/podinfo-kustomization.yaml +++ b/cmd/flux/testdata/build-kustomization/podinfo-kustomization.yaml @@ -13,6 +13,10 @@ spec: kind: GitRepository name: podinfo targetNamespace: default + decryption: + provider: sops + secretRef: + name: sops-age postBuild: substitute: cluster_env: "prod" diff --git a/cmd/flux/testdata/build-kustomization/sops-age.yaml b/cmd/flux/testdata/build-kustomization/sops-age.yaml new file mode 100644 index 00000000..d6b751b4 --- /dev/null +++ b/cmd/flux/testdata/build-kustomization/sops-age.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: sops-age + namespace: {{ .fluxns }} +type: Opaque +stringData: + identity.agekey: | + # public key: age1yqval9atdcnzjwhmutcjwdukfe5pk9tsa3lqya90u08grp03zgyss93knx + AGE-SECRET-KEY-1JYSQLQ4QM6GZHDF4F5JLA3HZD2DFJFCMAA2ASCN2USTC02KC4V6SSZNLA7 diff --git a/cmd/flux/testdata/diff-kustomization/diff-with-decryption.golden b/cmd/flux/testdata/diff-kustomization/diff-with-decryption.golden new file mode 100644 index 00000000..2b3e6adb --- /dev/null +++ b/cmd/flux/testdata/diff-kustomization/diff-with-decryption.golden @@ -0,0 +1,7 @@ +► Secret/default/podinfo-token-77t89m9b67 drifted + +data.token + ± value change + - *** (before) + + *** (after) + diff --git a/internal/build/build.go b/internal/build/build.go index 7328a573..89db69ca 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -44,6 +44,7 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/kustomize-controller/decryptor" "github.com/fluxcd/pkg/kustomize" runclient "github.com/fluxcd/pkg/runtime/client" ssautil "github.com/fluxcd/pkg/ssa/utils" @@ -77,15 +78,16 @@ type Builder struct { kustomizationFile string ignore []string // mu is used to synchronize access to the kustomization file - mu sync.Mutex - action kustomize.Action - kustomization *kustomizev1.Kustomization - timeout time.Duration - spinner *yacspin.Spinner - dryRun bool - strictSubst bool - recursive bool - localSources map[string]string + mu sync.Mutex + action kustomize.Action + kustomization *kustomizev1.Kustomization + timeout time.Duration + spinner *yacspin.Spinner + dryRun bool + strictSubst bool + recursive bool + decryptSecrets bool + localSources map[string]string // diff needs to handle kustomizations one by one singleKustomization bool } @@ -190,6 +192,14 @@ func WithRecursive(recursive bool) BuilderOptionFunc { } } +// WithDecryptSecrets sets the decrypt secrets field +func WithDecryptSecrets(decryptSecrets bool) BuilderOptionFunc { + return func(b *Builder) error { + b.decryptSecrets = decryptSecrets + return nil + } +} + // WithLocalSources sets the local sources field func WithLocalSources(localSources map[string]string) BuilderOptionFunc { return func(b *Builder) error { @@ -514,7 +524,36 @@ func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomizatio return nil, fmt.Errorf("kustomize build failed: %w", err) } + var dec *decryptor.Decryptor + var cleanup func() + if b.decryptSecrets { + dec, cleanup, err = decryptor.NewTempDecryptor(b.resourcesPath, b.client, b.kustomization) + if err != nil { + return nil, err + } + defer cleanup() + + // Import decryption keys + if err := dec.ImportKeys(ctx); err != nil { + return nil, err + } + } + for _, res := range m.Resources() { + if res.GetKind() == "Secret" && b.decryptSecrets { + outRes, err := dec.DecryptResource(res) + if err != nil { + return nil, fmt.Errorf("decryption failed for '%s': %w", res.GetName(), err) + } + + if outRes != nil { + _, err = m.Replace(res) + if err != nil { + return nil, err + } + } + } + // run variable substitutions if kustomization.Spec.PostBuild != nil { data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization) diff --git a/internal/build/diff.go b/internal/build/diff.go index b6636641..23c69ea4 100644 --- a/internal/build/diff.go +++ b/internal/build/diff.go @@ -221,6 +221,7 @@ func (b *Builder) kustomizationDiff(kustomization *kustomizev1.Kustomization) (s WithIgnore(b.ignore), WithStrictSubstitute(b.strictSubst), WithRecursive(b.recursive), + WithDecryptSecrets(b.decryptSecrets), WithLocalSources(b.localSources), WithSingleKustomization(), )