From d82e04ea5e6f5933403961735c3205114ff7aad2 Mon Sep 17 00:00:00 2001 From: Sirisha Gopigiri Date: Mon, 12 Oct 2020 19:01:07 +0530 Subject: [PATCH] Adding decoding and encoding feature to Replacement Transformer This PS adds decoding features to ReplacementTransformer to decode the source objectRef when source is of `kind: Secret` and has `data` field. It also encodes the value in the target fieldRefs before replacement if the target is `kind: Secret` and has `data` fields. Throws an error if the target fieldRefs have both `data` and `stringData` for replacement. Change-Id: I1d918058409b3511faa9a99512d25574027bda86 --- pkg/document/plugin/replacement/errors.go | 9 + .../plugin/replacement/transformer.go | 58 ++++- .../plugin/replacement/transformer_test.go | 228 +++++++++++++++++- 3 files changed, 283 insertions(+), 12 deletions(-) diff --git a/pkg/document/plugin/replacement/errors.go b/pkg/document/plugin/replacement/errors.go index 449fcd1c6..9d56c7574 100644 --- a/pkg/document/plugin/replacement/errors.go +++ b/pkg/document/plugin/replacement/errors.go @@ -67,6 +67,15 @@ func (e ErrPatternSubstring) Error() string { return e.Msg } +// ErrBase64Decoding returned incorrect base64 encoded string received for `kind: Secret` +type ErrBase64Decoding struct { + Msg string +} + +func (e ErrBase64Decoding) Error() string { + return e.Msg +} + // ErrIndexOutOfBound returned if JSON path points to a wrong index of a list type ErrIndexOutOfBound struct { Index int diff --git a/pkg/document/plugin/replacement/transformer.go b/pkg/document/plugin/replacement/transformer.go index fa7d45611..4db053aa8 100644 --- a/pkg/document/plugin/replacement/transformer.go +++ b/pkg/document/plugin/replacement/transformer.go @@ -15,9 +15,11 @@ package replacement import ( + "encoding/base64" "fmt" "io" "regexp" + "strings" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/kustomize/api/types" @@ -34,6 +36,11 @@ var ( substringPatternRegex = regexp.MustCompile(`(.+)%(\S+)%$`) ) +const ( + secret = "Secret" + secretData = "data" +) + var _ plugtypes.Plugin = &plugin{} type plugin struct { @@ -105,7 +112,17 @@ func getValue(items []*yaml.RNode, source *types.ReplSource) (*yaml.RNode, error if source.FieldRef != "" { path = source.FieldRef } - return sources[0].Pipe(kyamlutils.JSONPathFilter{Path: path}) + sourceNode, err := sources[0].Pipe(kyamlutils.JSONPathFilter{Path: path}) + if err != nil { + return nil, err + } + + // Decoding value if source is `kind: Secret` and + // has fieldRef `data` + if source.ObjRef.Gvk.Kind == secret && strings.Split(source.FieldRef, ".")[0] == secretData { + return decodeValue(sourceNode) + } + return sourceNode, nil } func mutateField(rnSource *yaml.RNode) func([]*yaml.RNode) error { @@ -131,18 +148,24 @@ func replace(items []*yaml.RNode, target *types.ReplTarget, value *yaml.RNode) e } for _, tgt := range targets { for _, fieldRef := range target.FieldRefs { + val := value + // Encoding value before replacement if target is `kind: Secret` + // and has fieldRef `data` + if target.ObjRef.Gvk.Kind == secret && strings.Split(fieldRef, ".")[0] == secretData { + val = encodeValue(val) + } // fieldref can contain substring pattern for regexp - we need to get it groups := substringPatternRegex.FindStringSubmatch(fieldRef) // if there is no substring pattern if len(groups) != 3 { - filter := kyamlutils.JSONPathFilter{Path: fieldRef, Mutator: mutateField(value), Create: true} + filter := kyamlutils.JSONPathFilter{Path: fieldRef, Mutator: mutateField(val), Create: true} if _, err := tgt.Pipe(filter); err != nil { return err } continue } - if err := substituteSubstring(tgt, groups[1], groups[2], value); err != nil { + if err := substituteSubstring(tgt, groups[1], groups[2], val); err != nil { return err } } @@ -180,3 +203,32 @@ func substituteSubstring(tgt *yaml.RNode, fieldRef, substringPattern string, val } return nil } + +func decodeValue(source *yaml.RNode) (*yaml.RNode, error) { + //decoding replacement source if source reference has data field + decodeValue, err := decodeString(yaml.GetValue(source)) + if err != nil { + return nil, ErrBase64Decoding{Msg: fmt.Sprintf("Error while decoding base64"+ + " encoded string: %s", yaml.GetValue(source))} + } + node := yaml.NewScalarRNode(decodeValue) + return node, nil +} + +func encodeValue(value *yaml.RNode) *yaml.RNode { + encodeValue := encodeString(yaml.GetValue(value)) + node := yaml.NewScalarRNode(encodeValue) + return node +} + +func decodeString(value interface{}) (string, error) { + decodedValue, err := base64.StdEncoding.DecodeString(value.(string)) + if err != nil { + return "", err + } + return string(decodedValue), nil +} + +func encodeString(value string) string { + return base64.StdEncoding.EncodeToString([]byte(value)) +} diff --git a/pkg/document/plugin/replacement/transformer_test.go b/pkg/document/plugin/replacement/transformer_test.go index 7726ced99..a7ea9b461 100644 --- a/pkg/document/plugin/replacement/transformer_test.go +++ b/pkg/document/plugin/replacement/transformer_test.go @@ -644,6 +644,188 @@ kind: ReplacementTransformer metadata: name: Test_Case_10 replacements: +- source: + objref: + kind: Secret + name: secret + fieldref: data.imagename + target: + objref: + kind: Deployment + fieldrefs: + - spec.template.spec.containers[name=myapp-container].image`, + in: ` +apiVersion: v1 +kind: Secret +metadata: + name: secret +data: + imagename: c2VjcmV0ZGVjb2Rpbmc= +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deploy2 +spec: + template: + spec: + containers: + - image: busybox:TAG + name: myapp-container +`, + expectedOut: `apiVersion: v1 +kind: Secret +metadata: + name: secret +data: + imagename: c2VjcmV0ZGVjb2Rpbmc= +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deploy2 +spec: + template: + spec: + containers: + - image: secretdecoding + name: myapp-container +`, + }, + { + cfg: ` +apiVersion: airshipit.org/v1alpha1 +kind: ReplacementTransformer +metadata: + name: Test_Case_11 +replacements: +- source: + objref: + kind: Secret + name: secret1 + fieldref: stringData.username + target: + objref: + kind: Secret + name: secret2 + fieldrefs: + - data.username`, + in: ` +apiVersion: v1 +kind: Secret +metadata: + name: secret1 +stringData: + username: secretencoding +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret2 +`, + expectedOut: `apiVersion: v1 +kind: Secret +metadata: + name: secret1 +stringData: + username: secretencoding +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret2 +data: + username: c2VjcmV0ZW5jb2Rpbmc= +`, + }, + { + cfg: ` +apiVersion: airshipit.org/v1alpha1 +kind: ReplacementTransformer +metadata: + name: Test_Case_12 +replacements: +- source: + value: testing + target: + objref: + kind: Secret + name: secret1 + fieldrefs: + - data.username + - stringData.username`, + in: ` +apiVersion: v1 +kind: Secret +metadata: + name: secret1 +`, + expectedOut: `apiVersion: v1 +kind: Secret +metadata: + name: secret1 +data: + username: dGVzdGluZw== +stringData: + username: testing +`, + }, + { + cfg: ` +apiVersion: airshipit.org/v1alpha1 +kind: ReplacementTransformer +metadata: + name: Test_Case_13 +replacements: +- source: + objref: + kind: Secret + name: secret1 + fieldref: data.username + target: + objref: + kind: Secret + name: secret2 + fieldrefs: + - data.password + - stringData.password`, + in: ` +apiVersion: v1 +kind: Secret +metadata: + name: secret1 +data: + username: dGVzdGluZw== +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret2 +`, + expectedOut: `apiVersion: v1 +kind: Secret +metadata: + name: secret1 +data: + username: dGVzdGluZw== +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret2 +data: + password: dGVzdGluZw== +stringData: + password: testing +`, + }, + { + cfg: ` +apiVersion: airshipit.org/v1alpha1 +kind: ReplacementTransformer +metadata: + name: Test_Case_14 +replacements: - source: objref: kind: Pod @@ -677,7 +859,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_11 + name: Test_Case_15 replacements: - source: objref: @@ -701,7 +883,7 @@ metadata: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_12 + name: Test_Case_16 replacements: - source: objref: @@ -728,7 +910,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_13 + name: Test_Case_17 replacements: - source: objref: @@ -768,7 +950,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_14 + name: Test_Case_18 replacements: - source: objref: @@ -808,7 +990,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_15 + name: Test_Case_19 replacements: - source: objref: @@ -848,7 +1030,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_16 + name: Test_Case_20 replacements: - source: objref: @@ -887,7 +1069,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_17 + name: Test_Case_21 replacements: - source: objref: @@ -927,7 +1109,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_18 + name: Test_Case_22 replacements: - source: objref: @@ -967,7 +1149,7 @@ spec: apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: - name: Test_Case_19 + name: Test_Case_23 replacements: - source: value: "12345678" @@ -1015,6 +1197,34 @@ spec: permissions: "0640" `, }, + { + cfg: ` +apiVersion: airshipit.org/v1alpha1 +kind: ReplacementTransformer +metadata: + name: Test_Case_24 +replacements: +- source: + objref: + kind: Secret + name: secret1 + fieldref: data.username + target: + objref: + kind: Secret + name: secret1 + fieldrefs: + - data.password`, + in: ` +apiVersion: v1 +kind: Secret +metadata: + name: secret1 +data: + username: abc +`, + expectedErr: "Error while decoding base64 encoded string: abc", + }, } func TestExec(t *testing.T) {