diff --git a/CHANGELOG.md b/CHANGELOG.md index ce08f518..f20331e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Adds support for tracing with OpenTelemetry. [cyberark/secrets-provider-for-k8s#398](https://github.com/cyberark/secrets-provider-for-k8s/pull/398) +- Adds support for Base64 encode/decode functions in custom templates. [cyberark/secrets-provider-for-k8s#409](https://github.com/cyberark/secrets-provider-for-k8s/pull/409) ### Fixed - If the Secrets Provider is run in Push-to-File mode, it no longer errors out diff --git a/PUSH_TO_FILE.md b/PUSH_TO_FILE.md index f0c47821..8992bf4f 100644 --- a/PUSH_TO_FILE.md +++ b/PUSH_TO_FILE.md @@ -515,6 +515,24 @@ respectively: For a full list of global Go text template functions, reference the official [`text/template` documentation](https://pkg.go.dev/text/template#hdr-Functions). +### Additional Template Functions + +Custom templates also support a limited number of additional functions. Currently +supported functions are: + +- `b64enc`: Base64 encode a value. +- `b64dec`: Base64 decode a value. + +These can be used as follows: + +``` +{{ secret "alias" | b64enc }} +{{ secret "alias" | b64dec }} +``` + +When using the `b64dec` function, an error will occur if the value retrieved from +Conjur is not a valid Base64 encoded string. + ### Execution "Double-Pass" To avoid leaking sensitive secret data to logs, and to ensure that a diff --git a/pkg/secrets/pushtofile/push_to_writer.go b/pkg/secrets/pushtofile/push_to_writer.go index 486e8c95..55fdfb1b 100644 --- a/pkg/secrets/pushtofile/push_to_writer.go +++ b/pkg/secrets/pushtofile/push_to_writer.go @@ -73,6 +73,8 @@ func pushToWriter( // when the template is executed. panic(fmt.Sprintf("secret alias %q not present in specified secrets for group", alias)) }, + "b64enc": b64encTemplateFunc, + "b64dec": b64decTemplateFunc, }).Parse(groupTemplate) if err != nil { return err diff --git a/pkg/secrets/pushtofile/push_to_writer_test.go b/pkg/secrets/pushtofile/push_to_writer_test.go index a62a83a8..7998bf22 100644 --- a/pkg/secrets/pushtofile/push_to_writer_test.go +++ b/pkg/secrets/pushtofile/push_to_writer_test.go @@ -65,6 +65,29 @@ var writeToFileTestCases = []pushToWriterTestCase{ secrets: []*Secret{{Alias: "alias", Value: "\" ' & < > \000"}}, assert: assertGoodOutput("" ' & < > \uFFFD"), }, + { + description: "base64 encoding", + template: `{{secret "alias" | b64enc}}`, + secrets: []*Secret{{Alias: "alias", Value: "secret value"}}, + assert: assertGoodOutput("c2VjcmV0IHZhbHVl"), + }, + { + description: "base64 decoding", + template: `{{secret "alias" | b64dec}}`, + secrets: []*Secret{{Alias: "alias", Value: "c2VjcmV0IHZhbHVl"}}, + assert: assertGoodOutput("secret value"), + }, + { + description: "base64 decoding invalid input", + template: `{{secret "alias" | b64dec}}`, + secrets: []*Secret{{Alias: "alias", Value: "c2VjcmV0IHZhbHVl_invalid"}}, + assert: func(t *testing.T, s string, err error) { + assert.Error(t, err) + assert.Contains(t, err.Error(), "value could not be base64 decoded") + // Ensure the error doesn't contain the actual secret + assert.NotContains(t, err.Error(), "c2VjcmV0IHZhbHVl_invalid") + }, + }, { description: "iterate over secret key-value pairs", template: `{{- range $index, $secret := .SecretsArray -}} diff --git a/pkg/secrets/pushtofile/secret_group_test.go b/pkg/secrets/pushtofile/secret_group_test.go index d780f408..7a2da865 100644 --- a/pkg/secrets/pushtofile/secret_group_test.go +++ b/pkg/secrets/pushtofile/secret_group_test.go @@ -404,6 +404,18 @@ func TestNewSecretGroups(t *testing.T) { assert.Contains(t, errs[0].Error(), `secret alias "x" not present in specified secrets`) }) + t.Run("pass custom format first-pass at execution with base64 decoding", func(t *testing.T) { + // The string "REDACTED" is valid Base64 so no error is produced in the first-pass. + _, errs := NewSecretGroups("/basepath", "", map[string]string{ + "conjur.org/conjur-secrets.first": "- path/to/secret/first1\n", + "conjur.org/secret-file-path.first": "firstfilepath", + "conjur.org/secret-file-format.first": "template", + "conjur.org/secret-file-template.first": `{{ secret "first1" | b64dec }}`, + }) + + assert.Len(t, errs, 0) + }) + t.Run("custom template - happy case from template file", func(t *testing.T) { // Setup mocks closableBuf := new(ClosableBuffer) diff --git a/pkg/secrets/pushtofile/template_functions.go b/pkg/secrets/pushtofile/template_functions.go new file mode 100644 index 00000000..3a1c7a8f --- /dev/null +++ b/pkg/secrets/pushtofile/template_functions.go @@ -0,0 +1,26 @@ +package pushtofile + +import "encoding/base64" + +// Define template functions that don't need access to secrets in this file +// to keep the push_to_writer.go file cleaner with only the functions that +// require access to secrets. + +// b64enc is a custom template function for performing a base64 encode +// on a secret value. +func b64encTemplateFunc(value string) string { + return base64.StdEncoding.EncodeToString([]byte(value)) +} + +// b64dec is a custom template function for performing a base64 decode +// on a secret value. +func b64decTemplateFunc(encValue string) string { + decValue, err := base64.StdEncoding.DecodeString(encValue) + if err == nil { + return string(decValue) + } + + // Panic in a template function is captured as an error + // when the template is executed. + panic("value could not be base64 decoded") +}