From b877834c2daa6aaf578302cf6d1804747d0badcc Mon Sep 17 00:00:00 2001 From: John Riendeau Date: Thu, 2 Mar 2023 13:56:11 -0600 Subject: [PATCH] Add base64-decode operator --- doc/operators.md | 17 ++++++++ op_base64_decode.go | 98 +++++++++++++++++++++++++++++++++++++++++++++ operator_test.go | 34 ++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 op_base64_decode.go diff --git a/doc/operators.md b/doc/operators.md index 572b6b0a..a3c9276b 100644 --- a/doc/operators.md +++ b/doc/operators.md @@ -23,6 +23,7 @@ - [awsparam](#-awsparam-) - [awssecret](#-awssecret-) - [base64](#-base64-) +- [base64-decode](#-base64-decode-) Additionally, there are operators that are specific to merging arrays. For more detail see the [array merging documentation][array-merging]: @@ -391,6 +392,22 @@ time base64 encoding of string literals specified directly, or by reference. [Example][base64-example] +## (( base64-decode )) + +Usage: `(( base64-decode LITERAL|REFERENCE ... ))` + +The `(( base64-decode ))` operator allows for merge-time decoding of base64-encoded string literals specified +directly, or by reference. + +Example: +```yaml +encoded_string_1: Zm9v +decoded_string_1: (( base64-decode encoded_string_1 )) +decoded_string_2: (( base64-decode "YmFy" )) +decoded_properties_file_contents: (( concat "fookey=" decoded_string_1 "\nbarkey=" decoded_string_2 "\n" )) +encoded_properties_file_contents: (( base64 decoded_properties_file_contents )) +``` + [array-merging]: https://github.com/geofffranks/spruce/blob/master/doc/array-merging.md [env-var]: https://github.com/geofffranks/spruce/blob/master/doc/environment-variables-and-defaults.md [vault]: https://vaultproject.io diff --git a/op_base64_decode.go b/op_base64_decode.go new file mode 100644 index 00000000..1b0b8dbe --- /dev/null +++ b/op_base64_decode.go @@ -0,0 +1,98 @@ +package spruce + +import ( + "encoding/base64" + "fmt" + + "github.com/starkandwayne/goutils/ansi" + + "github.com/starkandwayne/goutils/tree" + + . "github.com/geofffranks/spruce/log" +) + +// Base64DecodeOperator ... +type Base64DecodeOperator struct{} + +// Setup ... +func (Base64DecodeOperator) Setup() error { + return nil +} + +// Phase ... +func (Base64DecodeOperator) Phase() OperatorPhase { + return EvalPhase +} + +// Dependencies ... +func (Base64DecodeOperator) Dependencies(_ *Evaluator, _ []*Expr, _ []*tree.Cursor, auto []*tree.Cursor) []*tree.Cursor { + return auto +} + +// Run ... +func (Base64DecodeOperator) Run(ev *Evaluator, args []*Expr) (*Response, error) { + DEBUG("running (( base64-decode ... )) operation at $.%s", ev.Here) + defer DEBUG("done with (( base64-decode ... )) operation at $%s\n", ev.Here) + + if len(args) != 1 { + return nil, fmt.Errorf("base64-decode operator requires exactly one string or reference argument") + } + + var contents string + + arg := args[0] + i := 0 + v, err := arg.Resolve(ev.Tree) + if err != nil { + DEBUG(" arg[%d]: failed to resolve expression to a concrete value", i) + DEBUG(" [%d]: error was: %s", i, err) + return nil, err + } + + switch v.Type { + case Literal: + DEBUG(" arg[%d]: using string literal '%v'", i, v.Literal) + DEBUG(" [%d]: appending '%v' to resultant string", i, v.Literal) + if fmt.Sprintf("%T", v.Literal) != "string" { + return nil, ansi.Errorf("@R{tried to base64 decode} @c{%v}@R{, which is not a string scalar}", v.Literal) + } + contents = fmt.Sprintf("%v", v.Literal) + + case Reference: + DEBUG(" arg[%d]: trying to resolve reference $.%s", i, v.Reference) + s, err := v.Reference.Resolve(ev.Tree) + if err != nil { + DEBUG(" [%d]: resolution failed\n error: %s", i, err) + return nil, fmt.Errorf("unable to resolve `%s`: %s", v.Reference, err) + } + + switch s.(type) { + case string: + DEBUG(" [%d]: appending '%s' to resultant string", i, s) + contents = fmt.Sprintf("%v", s) + + default: + DEBUG(" arg[%d]: %v is not a string scalar", i, s) + return nil, ansi.Errorf("@R{tried to base64 decode} @c{%v}@R{, which is not a string scalar}", v.Reference) + } + + default: + DEBUG(" arg[%d]: I don't know what to do with '%v'", i, arg) + return nil, fmt.Errorf("base64-decode operator only accepts string literals and key reference argument") + } + DEBUG("") + + if decoded, err := base64.StdEncoding.DecodeString(contents); err == nil { + DEBUG(" resolved (( base64-decode ... )) operation to the string:\n \"%s\"", string(decoded)) + return &Response{ + Type: Replace, + Value: string(decoded), + }, nil + } else { + return nil, fmt.Errorf("unable to base64 decode string %s: %s", contents, err) + } +} + +func init() { + RegisterOp("base64-decode", Base64DecodeOperator{}) +} diff --git a/operator_test.go b/operator_test.go index 90160df3..29c31703 100644 --- a/operator_test.go +++ b/operator_test.go @@ -1963,6 +1963,40 @@ meta: }) + Convey("Base64Decode Operator", t, func() { + op := Base64DecodeOperator{} + ev := &Evaluator{ + Tree: YAML( + `meta: + sample: "U2FtcGxlIFRleHQgVG8gQmFzZTY0IEVuY29kZSBGcm9tIFJlZmVyZW5jZQ==" +`), + } + + Convey("can decode from a string literal", func() { + r, err := op.Run(ev, []*Expr{ + str("U2FtcGxlIFRleHQgVG8gQmFzZTY0IEVuY29kZQ=="), + }) + So(err, ShouldBeNil) + So(r, ShouldNotBeNil) + + So(r.Type, ShouldEqual, Replace) + So(r.Value.(string), ShouldEqual, "Sample Text To Base64 Encode") + }) + + Convey("can decode from a reference", func() { + r, err := op.Run(ev, []*Expr{ + ref("meta.sample"), + }) + + So(err, ShouldBeNil) + So(r, ShouldNotBeNil) + + So(r.Type, ShouldEqual, Replace) + + So(r.Value.(string), ShouldEqual, "Sample Text To Base64 Encode From Reference") + }) + }) + Convey("awsparam/awssecret operator", t, func() { op := AwsOperator{variant: "awsparam"} ev := &Evaluator{