From fa6ba128a7d1b282ec2045d7c3cdef4c21f81e39 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 10 Feb 2023 08:55:29 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Manage=20encoding=20when=20replacin?= =?UTF-8?q?g=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is an unused option in kustomize `ReplacementTransformer` named `encoding`. We make this option usable an provide 3 encodings: - base64 - hex - bcrypt This is useful to have values in plain text in replacement sources and encode them in the destination. The `bcrypt` option may provide some issues has the value would change for each run. --- main.go | 2 +- pkg/extras/encoding.go | 85 +++++++++++++++++++ pkg/extras/encodingtype_string.go | 26 ++++++ pkg/extras/replacement.go | 23 +++-- .../expected/argocd-cm.yaml | 1 + .../functions/argocd-values-replacements.yaml | 11 +++ .../original/argocd-cm.yaml | 1 + .../values/properties.yaml | 1 + 8 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 pkg/extras/encoding.go create mode 100644 pkg/extras/encodingtype_string.go diff --git a/main.go b/main.go index f825fc9..e3847c7 100644 --- a/main.go +++ b/main.go @@ -114,7 +114,7 @@ func main() { cmd := command.Build(processor, command.StandaloneDisabled, false) command.AddGenerateDockerfile(cmd) - cmd.Version = "v0.4.1" // <---VERSION---> + cmd.Version = "v0.4.2" // <---VERSION---> if err := cmd.Execute(); err != nil { os.Exit(1) diff --git a/pkg/extras/encoding.go b/pkg/extras/encoding.go new file mode 100644 index 0000000..0353069 --- /dev/null +++ b/pkg/extras/encoding.go @@ -0,0 +1,85 @@ +package extras + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "strings" + + "golang.org/x/crypto/bcrypt" +) + +// EncodingType enumerates the existing encoding types. +// +//go:generate go run golang.org/x/tools/cmd/stringer -type=EncodingType +type EncodingType int + +const ( + UnknownEncoding EncodingType = iota + Base64Encoding + BCryptEncoding + HexEncoding +) + +var stringToEncodingTypeMap map[string]EncodingType + +func init() { //nolint:gochecknoinits + stringToEncodingTypeMap = makeStringToEncodingTypeMap() +} + +// makeStringToEncodingTypeMap makes a map to get the appropriate +// [EncodingType] given its name. +func makeStringToEncodingTypeMap() (result map[string]EncodingType) { + result = make(map[string]EncodingType, 3) + for k := range EncoderFactories { + result[strings.Replace(strings.ToLower(k.String()), "encoding", "", 1)] = k + } + return +} + +// getEncodingType returns the appropriate [EncodingType] for the passed +// encoding type name +func getEncodingType(n string) EncodingType { + result, ok := stringToEncodingTypeMap[strings.ToLower(n)] + if ok { + return result + } + return UnknownEncoding +} + +// Encoder is an encoder function +type Encoder func(value string) (string, error) + +// EncodeBase64 encodes value in base64 +func EncodeBase64(value string) (string, error) { + return base64.StdEncoding.EncodeToString([]byte(value)), nil +} + +// EncodeBcrypt generates the bcrypt hash of value. +func EncodeBcrypt(value string) (string, error) { + encoded, err := bcrypt.GenerateFromPassword([]byte(value), 10) + + return string(encoded), err +} + +// EncodeHex returns the hex string of value +func EncodeHex(value string) (string, error) { + return hex.EncodeToString([]byte(value)), nil +} + +// EncoderFactories register the [Encoder] factory functions for each +// [EncoderType]. +var EncoderFactories = map[EncodingType]Encoder{ + Base64Encoding: EncodeBase64, + BCryptEncoding: EncodeBcrypt, + HexEncoding: EncodeHex, +} + +func GetEncodedValue(value string, encoding string) (string, error) { + et := getEncodingType(encoding) + if f, ok := EncoderFactories[et]; ok { + return f(value) + } + + return "", fmt.Errorf("encoding %s is unknown (%d)", encoding, et) +} diff --git a/pkg/extras/encodingtype_string.go b/pkg/extras/encodingtype_string.go new file mode 100644 index 0000000..954a38c --- /dev/null +++ b/pkg/extras/encodingtype_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=EncodingType"; DO NOT EDIT. + +package extras + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[UnknownEncoding-0] + _ = x[Base64Encoding-1] + _ = x[BCryptEncoding-2] + _ = x[HexEncoding-3] +} + +const _EncodingType_name = "UnknownEncodingBase64EncodingBCryptEncodingHexEncoding" + +var _EncodingType_index = [...]uint8{0, 15, 29, 43, 54} + +func (i EncodingType) String() string { + if i < 0 || i >= EncodingType(len(_EncodingType_index)-1) { + return "EncodingType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EncodingType_name[_EncodingType_index[i]:_EncodingType_index[i+1]] +} diff --git a/pkg/extras/replacement.go b/pkg/extras/replacement.go index 62a192e..54b32aa 100644 --- a/pkg/extras/replacement.go +++ b/pkg/extras/replacement.go @@ -96,18 +96,27 @@ func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yam } func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) { - if options == nil || options.Delimiter == "" { + if options == nil || (options.Delimiter == "" && options.Encoding == "") { return rn, nil } if rn.YNode().Kind != yaml.ScalarNode { - return nil, fmt.Errorf("delimiter option can only be used with scalar nodes") - } - value := strings.Split(yaml.GetValue(rn), options.Delimiter) - if options.Index >= len(value) || options.Index < 0 { - return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn)) + return nil, fmt.Errorf("delimiter or encoding option can only be used with scalar nodes") } n := rn.Copy() - n.YNode().Value = value[options.Index] + if options.Delimiter != "" { + value := strings.Split(yaml.GetValue(rn), options.Delimiter) + if options.Index >= len(value) || options.Index < 0 { + return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn)) + } + + n.YNode().Value = value[options.Index] + } else { + value, err := GetEncodedValue(yaml.GetValue(rn), options.Encoding) + if err != nil { + return nil, errors.Wrapf(err, "while encoding value") + } + n.YNode().Value = value + } return n, nil } diff --git a/tests/sourced-replacement/expected/argocd-cm.yaml b/tests/sourced-replacement/expected/argocd-cm.yaml index 019c5b3..57f6843 100644 --- a/tests/sourced-replacement/expected/argocd-cm.yaml +++ b/tests/sourced-replacement/expected/argocd-cm.yaml @@ -34,3 +34,4 @@ data: teamNameField: slug orgs: - name: thisisfakeorganization + b64encoded: VGhpcyBpcyB0aGUgdmFsdWUgdG8gZW5jb2RlIGluIGJhc2U2NA== diff --git a/tests/sourced-replacement/functions/argocd-values-replacements.yaml b/tests/sourced-replacement/functions/argocd-values-replacements.yaml index 49d16d0..0baf960 100644 --- a/tests/sourced-replacement/functions/argocd-values-replacements.yaml +++ b/tests/sourced-replacement/functions/argocd-values-replacements.yaml @@ -26,3 +26,14 @@ replacements: name: argocd-cm fieldPaths: - data.dex\.config.!!yaml.connectors.[id=github].config.orgs.0.name + - source: + name: autocloud-values + fieldPath: data.to_encode + options: + encoding: base64 + targets: + - select: + kind: ConfigMap + name: argocd-cm + fieldPaths: + - data.b64encoded diff --git a/tests/sourced-replacement/original/argocd-cm.yaml b/tests/sourced-replacement/original/argocd-cm.yaml index 5131578..e980d47 100644 --- a/tests/sourced-replacement/original/argocd-cm.yaml +++ b/tests/sourced-replacement/original/argocd-cm.yaml @@ -34,3 +34,4 @@ data: teamNameField: slug orgs: - name: johndoe + b64encoded: dGhpc2lzZmFrZXZhbHVl diff --git a/tests/sourced-replacement/values/properties.yaml b/tests/sourced-replacement/values/properties.yaml index 82fe1a8..5903bb2 100644 --- a/tests/sourced-replacement/values/properties.yaml +++ b/tests/sourced-replacement/values/properties.yaml @@ -9,3 +9,4 @@ data: repo: antoinemartin/autocloud email: john@doe.me clientID: thisisfakeclientid + to_encode: This is the value to encode in base64