From 54e68399eca425bb0e976a19892805a9e58d489c Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Tue, 18 Jan 2022 10:48:43 +0100 Subject: [PATCH] feat: implement template engine v2 Signed-off-by: Moritz Johner --- .../v1alpha1/externalsecret_types.go | 13 + .../external-secrets.io_externalsecrets.yaml | 6 + e2e/framework/framework.go | 5 +- e2e/suite/import.go | 1 + e2e/suite/template/provider.go | 89 ++++ e2e/suite/template/template.go | 101 +++++ go.mod | 6 +- go.sum | 14 +- .../externalsecret_controller_template.go | 6 +- .../externalsecret_controller_test.go | 21 + pkg/provider/webhook/webhook.go | 4 +- pkg/template/engine.go | 36 ++ pkg/template/{ => v1}/template.go | 2 - pkg/template/{ => v1}/template_test.go | 0 pkg/template/v2/template.go | 188 +++++++++ pkg/template/v2/template_test.go | 384 ++++++++++++++++++ 16 files changed, 863 insertions(+), 13 deletions(-) create mode 100644 e2e/suite/template/provider.go create mode 100644 e2e/suite/template/template.go create mode 100644 pkg/template/engine.go rename pkg/template/{ => v1}/template.go (99%) rename pkg/template/{ => v1}/template_test.go (100%) create mode 100644 pkg/template/v2/template.go create mode 100644 pkg/template/v2/template_test.go diff --git a/apis/externalsecrets/v1alpha1/externalsecret_types.go b/apis/externalsecrets/v1alpha1/externalsecret_types.go index 78a534011..ab6ee51c1 100644 --- a/apis/externalsecrets/v1alpha1/externalsecret_types.go +++ b/apis/externalsecrets/v1alpha1/externalsecret_types.go @@ -59,6 +59,12 @@ type ExternalSecretTemplate struct { // +optional Type corev1.SecretType `json:"type,omitempty"` + // EngineVersion specifies the template engine version + // that should be used to compile/execute the + // template specified in .data and .templateFrom[]. + // +kubebuilder:default="v1" + EngineVersion TemplateEngineVersion `json:"engineVersion,omitempty"` + // +optional Metadata ExternalSecretTemplateMetadata `json:"metadata,omitempty"` @@ -69,6 +75,13 @@ type ExternalSecretTemplate struct { TemplateFrom []TemplateFrom `json:"templateFrom,omitempty"` } +type TemplateEngineVersion string + +const ( + TemplateEngineV1 TemplateEngineVersion = "v1" + TemplateEngineV2 TemplateEngineVersion = "v2" +) + // +kubebuilder:validation:MinProperties=1 // +kubebuilder:validation:MaxProperties=1 type TemplateFrom struct { diff --git a/deploy/crds/external-secrets.io_externalsecrets.yaml b/deploy/crds/external-secrets.io_externalsecrets.yaml index 3f560f1cd..475e1822a 100644 --- a/deploy/crds/external-secrets.io_externalsecrets.yaml +++ b/deploy/crds/external-secrets.io_externalsecrets.yaml @@ -148,6 +148,12 @@ spec: additionalProperties: type: string type: object + engineVersion: + default: v1 + description: EngineVersion specifies the template engine version + that should be used to compile/execute the template specified + in .data and .templateFrom[]. + type: string metadata: description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint. diff --git a/e2e/framework/framework.go b/e2e/framework/framework.go index 04943d11f..663bd9c32 100644 --- a/e2e/framework/framework.go +++ b/e2e/framework/framework.go @@ -110,8 +110,9 @@ func (f *Framework) Install(a addon.Addon) { // Compose helps define multiple testcases with same/different auth methods. func Compose(descAppend string, f *Framework, fn func(f *Framework) (string, func(*TestCase)), tweaks ...func(*TestCase)) TableEntry { - desc, tfn := fn(f) - tweaks = append(tweaks, tfn) + // prepend common fn to tweaks + desc, cfn := fn(f) + tweaks = append([]func(*TestCase){cfn}, tweaks...) // need to convert []func to []interface{} ifs := make([]interface{}, len(tweaks)) diff --git a/e2e/suite/import.go b/e2e/suite/import.go index 6e5d5fdb6..d6c55d768 100644 --- a/e2e/suite/import.go +++ b/e2e/suite/import.go @@ -20,5 +20,6 @@ import ( _ "github.com/external-secrets/external-secrets/e2e/suite/aws/secretsmanager" _ "github.com/external-secrets/external-secrets/e2e/suite/azure" _ "github.com/external-secrets/external-secrets/e2e/suite/gcp" + _ "github.com/external-secrets/external-secrets/e2e/suite/template" _ "github.com/external-secrets/external-secrets/e2e/suite/vault" ) diff --git a/e2e/suite/template/provider.go b/e2e/suite/template/provider.go new file mode 100644 index 000000000..816e5690e --- /dev/null +++ b/e2e/suite/template/provider.go @@ -0,0 +1,89 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package template + +import ( + "context" + + // nolint + . "github.com/onsi/ginkgo/v2" + + // nolint + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" + "github.com/external-secrets/external-secrets/e2e/framework" +) + +type templateProvider struct { + framework *framework.Framework +} + +func newProvider(f *framework.Framework) *templateProvider { + prov := &templateProvider{ + framework: f, + } + BeforeEach(prov.BeforeEach) + return prov +} + +func (s *templateProvider) CreateSecret(key, val string) { + // noop: this provider implements static key/value pairs +} + +func (s *templateProvider) DeleteSecret(key string) { + // noop: this provider implements static key/value pairs +} + +func (s *templateProvider) BeforeEach() { + // Create a secret store - change these values to match YAML + By("creating a secret store for credentials") + secretStore := &esv1alpha1.SecretStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.framework.Namespace.Name, + Namespace: s.framework.Namespace.Name, + }, + Spec: esv1alpha1.SecretStoreSpec{ + Provider: &esv1alpha1.SecretStoreProvider{ + Fake: &esv1alpha1.FakeProvider{ + Data: []esv1alpha1.FakeProviderData{ + { + Key: "foo", + Value: "bar", + }, + { + Key: "baz", + Value: "bang", + }, + { + Key: "map", + ValueMap: map[string]string{ + "foo": "barmap", + "bar": "bangmap", + }, + }, + { + Key: "json", + Value: `{"foo":{"bar":"baz"}}`, + }, + }, + }, + }, + }, + } + + err := s.framework.CRClient.Create(context.Background(), secretStore) + Expect(err).ToNot(HaveOccurred()) +} diff --git a/e2e/suite/template/template.go b/e2e/suite/template/template.go new file mode 100644 index 000000000..b318fac82 --- /dev/null +++ b/e2e/suite/template/template.go @@ -0,0 +1,101 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +limitations under the License. +*/ +package template + +import ( + + // nolint + . "github.com/onsi/ginkgo/v2" + v1 "k8s.io/api/core/v1" + + esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" + "github.com/external-secrets/external-secrets/e2e/framework" +) + +var _ = Describe("[template]", Label("template"), func() { + f := framework.New("eso-template") + prov := newProvider(f) + + DescribeTable("sync secrets", framework.TableFunc(f, prov), + framework.Compose("template v1", f, genericTemplate, useTemplateV1), + framework.Compose("template v2", f, genericTemplate, useTemplateV2), + ) +}) + +// useTemplateV1 specifies a test case which uses the template engine v1. +func useTemplateV1(tc *framework.TestCase) { + tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ + EngineVersion: esv1alpha1.TemplateEngineV1, + Data: map[string]string{ + "my-data": "executed: {{ .singlefoo | toString }}|{{ .singlebaz | toString }}", + "other": `{{ .foo | toString }}|{{ .bar | toString }}`, + }, + } + tc.ExpectedSecret.Data = map[string][]byte{ + "my-data": []byte(`executed: bar|bang`), + "other": []byte(`barmap|bangmap`), + } +} + +// useTemplateV2 specifies a test case which uses the template engine v2. +func useTemplateV2(tc *framework.TestCase) { + tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ + EngineVersion: esv1alpha1.TemplateEngineV2, + Data: map[string]string{ + "my-data": "executed: {{ .singlefoo }}|{{ .singlebaz }}", + "other": `{{ .foo }}|{{ .bar }}`, + "sprig-str": `{{ .foo | upper }}`, + "json-ex": `{{ $var := .singlejson | fromJson }}{{ $var.foo | toJson }}`, + }, + } + tc.ExpectedSecret.Data = map[string][]byte{ + "my-data": []byte(`executed: bar|bang`), + "other": []byte(`barmap|bangmap`), + "sprig-str": []byte(`BARMAP`), + "json-ex": []byte(`{"bar":"baz"}`), + } +} + +// This case uses template engine v1. +func genericTemplate(f *framework.Framework) (string, func(*framework.TestCase)) { + return "[template] should execute template v1", func(tc *framework.TestCase) { + tc.ExpectedSecret = &v1.Secret{ + Type: v1.SecretTypeOpaque, + } + tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{ + { + SecretKey: "singlefoo", + RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{ + Key: "foo", + }, + }, + { + SecretKey: "singlebaz", + RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{ + Key: "baz", + }, + }, + { + SecretKey: "singlejson", + RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{ + Key: "json", + }, + }, + } + tc.ExternalSecret.Spec.DataFrom = []esv1alpha1.ExternalSecretDataRemoteRef{ + { + Key: "map", + }, + } + } +} diff --git a/go.mod b/go.mod index f94dedee9..b35cb0f10 100644 --- a/go.mod +++ b/go.mod @@ -40,8 +40,7 @@ require ( github.com/IBM/go-sdk-core/v5 v5.9.1 github.com/IBM/secrets-manager-go-sdk v1.0.31 github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/sprig v2.22.0+incompatible + github.com/Masterminds/sprig/v3 v3.2.2 github.com/PaesslerAG/jsonpath v0.1.1 github.com/ahmetb/gen-crd-api-reference-docs v0.3.0 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2 @@ -98,6 +97,7 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/PaesslerAG/gval v1.0.0 // indirect github.com/armon/go-metrics v0.3.10 // indirect github.com/armon/go-radix v1.0.0 // indirect @@ -180,8 +180,10 @@ require ( github.com/prometheus/procfs v0.7.3 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b // indirect + github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.2.0 // indirect diff --git a/go.sum b/go.sum index 74dc95578..ba9298d39 100644 --- a/go.sum +++ b/go.sum @@ -94,10 +94,10 @@ github.com/IBM/secrets-manager-go-sdk v1.0.31 h1:KRRyeEvlKkkZb90njgReOrK92+IyS6L github.com/IBM/secrets-manager-go-sdk v1.0.31/go.mod h1:0Juj6ER/LpDqJ49nw705MNyXSHsHodgztFdkXz5ttxs= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -517,11 +517,13 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -759,6 +761,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -779,6 +783,7 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -898,6 +903,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= diff --git a/pkg/controllers/externalsecret/externalsecret_controller_template.go b/pkg/controllers/externalsecret/externalsecret_controller_template.go index a3d9463d8..7ad1baa0f 100644 --- a/pkg/controllers/externalsecret/externalsecret_controller_template.go +++ b/pkg/controllers/externalsecret/externalsecret_controller_template.go @@ -56,7 +56,11 @@ func (r *Reconciler) applyTemplate(ctx context.Context, es *esv1alpha1.ExternalS } r.Log.V(1).Info("found template data", "tpl_data", tplMap) - err = template.Execute(tplMap, dataMap, secret) + execute, err := template.EngineForVersion(es.Spec.Target.Template.EngineVersion) + if err != nil { + return err + } + err = execute(tplMap, dataMap, secret) if err != nil { return fmt.Errorf(errExecTpl, err) } diff --git a/pkg/controllers/externalsecret/externalsecret_controller_test.go b/pkg/controllers/externalsecret/externalsecret_controller_test.go index 64e9a6b60..0cb8d7947 100644 --- a/pkg/controllers/externalsecret/externalsecret_controller_test.go +++ b/pkg/controllers/externalsecret/externalsecret_controller_test.go @@ -433,6 +433,9 @@ var _ = Describe("ExternalSecret controller", func() { "hihi": "ga", }, }, + // We do not specify the engine version + // it should default to v1 for alpha1 + // EngineVersion: esv1alpha1.TemplateEngineV1, Type: v1.SecretTypeOpaque, Data: map[string]string{ targetProp: targetPropObj, @@ -453,6 +456,23 @@ var _ = Describe("ExternalSecret controller", func() { } } + // when using a v2 template it should use the v2 engine version + syncWithTemplateV2 := func(tc *testCase) { + const secretVal = "someValue" + tc.externalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ + Type: v1.SecretTypeOpaque, + EngineVersion: esv1alpha1.TemplateEngineV2, + Data: map[string]string{ + targetProp: "{{ .targetProperty | upper }} was templated", + }, + } + fakeProvider.WithGetSecret([]byte(secretVal), nil) + tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) { + // check values + Expect(string(secret.Data[targetProp])).To(Equal(expectedSecretVal)) + } + } + // secret should be synced with correct value precedence: // * template // * templateFrom @@ -1046,6 +1066,7 @@ var _ = Describe("ExternalSecret controller", func() { Entry("should not resolve conflicts with creationPolicy=Merge", mergeWithConflict), Entry("should not update unchanged secret using creationPolicy=Merge", mergeWithSecretNoChange), Entry("should sync with template", syncWithTemplate), + Entry("should sync with template engine v2", syncWithTemplateV2), Entry("should sync template with correct value precedence", syncWithTemplatePrecedence), Entry("should refresh secret from template", refreshWithTemplate), Entry("should be able to use only metadata from template", onlyMetadataFromTemplate), diff --git a/pkg/provider/webhook/webhook.go b/pkg/provider/webhook/webhook.go index 7df3acbc3..875197a91 100644 --- a/pkg/provider/webhook/webhook.go +++ b/pkg/provider/webhook/webhook.go @@ -26,7 +26,7 @@ import ( "strings" tpl "text/template" - "github.com/Masterminds/sprig" + "github.com/Masterminds/sprig/v3" "github.com/PaesslerAG/jsonpath" "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" @@ -36,7 +36,7 @@ import ( esmeta "github.com/external-secrets/external-secrets/apis/meta/v1" "github.com/external-secrets/external-secrets/pkg/provider" "github.com/external-secrets/external-secrets/pkg/provider/schema" - "github.com/external-secrets/external-secrets/pkg/template" + "github.com/external-secrets/external-secrets/pkg/template/v1" ) // Provider satisfies the provider interface. diff --git a/pkg/template/engine.go b/pkg/template/engine.go new file mode 100644 index 000000000..779d42f64 --- /dev/null +++ b/pkg/template/engine.go @@ -0,0 +1,36 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +limitations under the License. +*/ +package template + +import ( + corev1 "k8s.io/api/core/v1" + + esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" + v1 "github.com/external-secrets/external-secrets/pkg/template/v1" + v2 "github.com/external-secrets/external-secrets/pkg/template/v2" +) + +type ExecFunc func(tpl, data map[string][]byte, secret *corev1.Secret) error + +func EngineForVersion(version esapi.TemplateEngineVersion) (ExecFunc, error) { + switch version { + case esapi.TemplateEngineV1: + return v1.Execute, nil + case esapi.TemplateEngineV2: + return v2.Execute, nil + } + + // in case we run with a old v1alpha1 CRD + // we must return v1 as default + return v1.Execute, nil +} diff --git a/pkg/template/template.go b/pkg/template/v1/template.go similarity index 99% rename from pkg/template/template.go rename to pkg/template/v1/template.go index 2eb60b2ab..6d566749b 100644 --- a/pkg/template/template.go +++ b/pkg/template/v1/template.go @@ -2,9 +2,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/pkg/template/template_test.go b/pkg/template/v1/template_test.go similarity index 100% rename from pkg/template/template_test.go rename to pkg/template/v1/template_test.go diff --git a/pkg/template/v2/template.go b/pkg/template/v2/template.go new file mode 100644 index 000000000..0a281c3d0 --- /dev/null +++ b/pkg/template/v2/template.go @@ -0,0 +1,188 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package template + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "fmt" + tpl "text/template" + + "github.com/Masterminds/sprig/v3" + "github.com/lestrrat-go/jwx/jwk" + "github.com/youmark/pkcs8" + "golang.org/x/crypto/pkcs12" + corev1 "k8s.io/api/core/v1" +) + +var tplFuncs = tpl.FuncMap{ + "pkcs12key": pkcs12key, + "pkcs12keyPass": pkcs12keyPass, + "pkcs12cert": pkcs12cert, + "pkcs12certPass": pkcs12certPass, + + "pemPrivateKey": pemPrivateKey, + "pemCertificate": pemCertificate, + + "jwkPublicKeyPem": jwkPublicKeyPem, + "jwkPrivateKeyPem": jwkPrivateKeyPem, +} + +// So other templating calls can use the same extra functions. +func FuncMap() tpl.FuncMap { + return tplFuncs +} + +const ( + errParse = "unable to parse template at key %s: %s" + errExecute = "unable to execute template at key %s: %s" + errDecodePKCS12WithPass = "unable to decode pkcs12 with password: %s" + errConvertPrivKey = "unable to convert pkcs12 private key: %s" + errDecodeCertWithPass = "unable to decode pkcs12 certificate with password: %s" + errEncodePEMKey = "unable to encode pem private key: %s" + errEncodePEMCert = "unable to encode pem certificate: %s" +) + +func init() { + fmt.Printf("calling init in v2 pkg") + sprigFuncs := sprig.TxtFuncMap() + delete(sprigFuncs, "env") + delete(sprigFuncs, "expandenv") + + for k, v := range sprigFuncs { + fmt.Printf("adding func %s\n", k) + tplFuncs[k] = v + } +} + +// Execute renders the secret data as template. If an error occurs processing is stopped immediately. +func Execute(tpl, data map[string][]byte, secret *corev1.Secret) error { + if tpl == nil { + return nil + } + for k, v := range tpl { + val, err := execute(k, string(v), data) + if err != nil { + return fmt.Errorf(errExecute, k, err) + } + secret.Data[k] = val + } + return nil +} + +func execute(k, val string, data map[string][]byte) ([]byte, error) { + strValData := make(map[string]string, len(data)) + for k := range data { + strValData[k] = string(data[k]) + } + + t, err := tpl.New(k). + Funcs(tplFuncs). + Parse(val) + if err != nil { + return nil, fmt.Errorf(errParse, k, err) + } + buf := bytes.NewBuffer(nil) + err = t.Execute(buf, strValData) + if err != nil { + return nil, fmt.Errorf(errExecute, k, err) + } + return buf.Bytes(), nil +} + +func pkcs12keyPass(pass, input string) (string, error) { + key, _, err := pkcs12.Decode([]byte(input), pass) + if err != nil { + return "", fmt.Errorf(errDecodePKCS12WithPass, err) + } + kb, err := pkcs8.ConvertPrivateKeyToPKCS8(key) + if err != nil { + return "", fmt.Errorf(errConvertPrivKey, err) + } + return string(kb), nil +} + +func pkcs12key(input string) (string, error) { + return pkcs12keyPass("", input) +} + +func pkcs12certPass(pass, input string) (string, error) { + _, cert, err := pkcs12.Decode([]byte(input), pass) + if err != nil { + return "", fmt.Errorf(errDecodeCertWithPass, err) + } + return string(cert.Raw), nil +} + +func pkcs12cert(input string) (string, error) { + return pkcs12certPass("", input) +} + +func jwkPublicKeyPem(jwkjson string) (string, error) { + k, err := jwk.ParseKey([]byte(jwkjson)) + if err != nil { + return "", err + } + var rawkey interface{} + err = k.Raw(&rawkey) + if err != nil { + return "", err + } + mpk, err := x509.MarshalPKIXPublicKey(rawkey) + if err != nil { + return "", err + } + return pemEncode(string(mpk), "PUBLIC KEY") +} + +func jwkPrivateKeyPem(jwkjson string) (string, error) { + k, err := jwk.ParseKey([]byte(jwkjson)) + if err != nil { + return "", err + } + var mpk []byte + var pk interface{} + err = k.Raw(&pk) + if err != nil { + return "", err + } + mpk, err = x509.MarshalPKCS8PrivateKey(pk) + if err != nil { + return "", err + } + return pemEncode(string(mpk), "PRIVATE KEY") +} + +func pemEncode(thing, kind string) (string, error) { + buf := bytes.NewBuffer(nil) + err := pem.Encode(buf, &pem.Block{Type: kind, Bytes: []byte(thing)}) + return buf.String(), err +} + +func pemPrivateKey(key string) (string, error) { + res, err := pemEncode(key, "PRIVATE KEY") + if err != nil { + return res, fmt.Errorf(errEncodePEMKey, err) + } + return res, nil +} + +func pemCertificate(cert string) (string, error) { + res, err := pemEncode(cert, "CERTIFICATE") + if err != nil { + return res, fmt.Errorf(errEncodePEMCert, err) + } + return res, nil +} diff --git a/pkg/template/v2/template_test.go b/pkg/template/v2/template_test.go new file mode 100644 index 000000000..6e06b63c1 --- /dev/null +++ b/pkg/template/v2/template_test.go @@ -0,0 +1,384 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package template_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + + "github.com/external-secrets/external-secrets/pkg/template/v2" +) + +const ( + pkcs12ContentNoPass = `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` + pkcs12ContentWithPass = `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` + pkcs12Cert = `-----BEGIN CERTIFICATE----- +MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw +EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4 +MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj +aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW +Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE +1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e +ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa +YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC +pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G +CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN +ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa +lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0 +mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d +9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC +QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u +-----END CERTIFICATE----- +` + pkcs12Key = `-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3o6/JdZEqNbqN +RkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4NzjaG15owr92/11W0pxPUliRLti +3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvWY8jh8A0LQALZiV/9QsrJdXZd +S47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE1gEDqnKfRxXI8DEQKXr+CKPU +wCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4eugHe52vXHdh/HJ9VjNp0xOH1 +waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJaYOOonQSEswveSv6PcG9AHvpN +Pot2Xs6hAgMBAAECggEACTGPrmVNZDCWa1Y2hkJ0J7SoNcw+9O4M/jwMp4l/PD6P +I98S78LYLCZhPLK17SmjUcnFO1AXKW1JeFS2D/fjfP256guvcqQNjLFoioxcOhVb +ZGyd1Mi8JPqP5wfOj16gBeYDwTkjz9wqldcfiZaL9XoXetkZecbzR2JwC2FtIVuC +0njTjMNYpaBKnoLb8OTR0EQz7lYEo2MkQiWryz8wseONnFmdfh18p+p10YgCbuCH +qesrWfDLLxaxZelNtDhDngg9LoCLmarYy7BgShacmUEgJTZ/x3xFC75thK3ln0OY ++ktTgvVotYYaZi7qAjQiEsTvkTAPg5RMpQLd2UIWsQKBgQDCBp+1vURbwGzmTNUg +HMipD6WDFdLc9DCacx6+ZqsEPTMWQbCpVZrDKiY0Rjt5F+xOCyMr00J5RDJXRC0G ++L7NcJdywOFutT7vB+cmETg7l/6PHweNYBnE66706eTL/KVYZMi4tEinarPWhHmL +jasfdLANtpDjdWkRt299TkPRbQKBgQDyS8Rr7KZdv04Csqkf+ASmiJpT5R6Y72kc +3XYpKETyB2FyPZkuh/zInMut9SkkSI9O/jA3zf956jj6sF1DHvp7T8KkIp5OAQeD +J9AF65m2MnZfHFUeJ6ZQsggwMWqrD0ycIWP7YWtiBHH+D1wGkjYrssq+bvG/yNpA +LtqdKq9lhQKBgQCZA2hIhy61vRckuEsLvCdzTGeW7UsR/XGnHEqOlaEhArKbRsrv +gBdA+qiOaSTV5svw8E+YbE7sG6AnuhhYeyreEYEeeoZOLJmpIG5mUwYp2UBj1nC6 +SaOI7OVZOGu7g09SWokBQQxbG4cgEfFY4Sym7fs5lVTGTP3Dfwppo6NQMQKBgQCo +J5NDP3Lafwk58BpV+H/pv8YzUUDh7M2rXbtCpxLqUdr8OOnVlEUISWFF8m5CIyVq +MhjuscWLK9Wtjba7/YTjDaDM3sW05xv6lyfU5ATCoNTr/zLHgcb4HAZ4w+L+otiN +RtMnxB2NYf5mzuwUF2cG/secUEzwyAlIH/xStSwTLQKBgQCRvqF+rqxnegoOgwVW +qrWPv06wXD8dW2FlPpY5GXqA0l6erSK3YsQQToRmbem9ibPD7bd5P4gNbWfxwK4C +Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd +BixHvI/EJ8YK3ta5WdJWKC6hnA== +-----END PRIVATE KEY----- +` + jwkPubRSA = `{"kid":"ex","kty":"RSA","key_ops":["sign","verify","wrapKey","unwrapKey","encrypt","decrypt"],"n":"p2VQo8qCfWAZmdWBVaYuYb-a-tWWm78K6Sr9poCvNcmv8rUPSLACxitQWR8gZaSH1DklVkqz-Ed8Cdlf8lkDg4Ex5tkB64jRdC1Uvn4CDpOH6cp-N2s8hTFLqy9_YaDmyQS7HiqthOi9oVjil1VMeWfaAbClGtFt6UnKD0Vb_DvLoWYQSqlhgBArFJi966b4E1pOq5Ad02K8pHBDThlIIx7unibLehhDU6q3DCwNH_OOLx6bgNtmvGYJDd1cywpkLQ3YzNCUPWnfMBJRP3iQP_WI21uP6cvo0DqBPBM4wvVzHbCT0vnIflwkbgEWkq1FprqAitZlop9KjLqzjp9vyQ","e":"AQAB"}` + jwkPubRSAPKIX = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp2VQo8qCfWAZmdWBVaYu +Yb+a+tWWm78K6Sr9poCvNcmv8rUPSLACxitQWR8gZaSH1DklVkqz+Ed8Cdlf8lkD +g4Ex5tkB64jRdC1Uvn4CDpOH6cp+N2s8hTFLqy9/YaDmyQS7HiqthOi9oVjil1VM +eWfaAbClGtFt6UnKD0Vb/DvLoWYQSqlhgBArFJi966b4E1pOq5Ad02K8pHBDThlI +Ix7unibLehhDU6q3DCwNH/OOLx6bgNtmvGYJDd1cywpkLQ3YzNCUPWnfMBJRP3iQ +P/WI21uP6cvo0DqBPBM4wvVzHbCT0vnIflwkbgEWkq1FprqAitZlop9KjLqzjp9v +yQIDAQAB +-----END PUBLIC KEY----- +` + jwkPrivRSA = `{"kty" : "RSA","kid" : "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df","use" : "sig","n" : "pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w","e" : "AQAB","d" : "ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q","p" : "4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0","q" : "ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8","dp" : "lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE","dq" : "mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk","qi" : "ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg"}` + jwkPrivRSAPKCS8 = `-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCmN2yzxloN8Qfo +rpTsZ5bafEOpHgg/Tj1+TV8rSWd2KZswxUF0+/+FKmbxPwS0EPGtR2LU4dl8yFSL +EZq637edDgYb2czbj2jGEK3Gqo28ReuZBEapzPIvG6H58qf0WD76FL1SlrMel9UA +WcHloJ9eg2E+4jygHLIUowpo5WAc2o/k0ESppuIt+1kPdb+WwUI8a7OvhWnRhLvN +LaENhJwLag4y7isZTUtwxl/f2nfXncKrttLZeHpj6/DmnDMVhl2NDEOfzHwEbd8n +qPxMYtdCxsofXbXz8dxQlG8zB2ltRAbme8DYZdWoup3CnTngvOT38H9/WVWuY4q4 +eNM0erjzAgMBAAECggEBAJLA5rnHTCV5BRmcYqJjR566DmcXvAJgywxjtb4bPjzm +uT2TO5rVD6J8cI1ZrYZqW2c5WvpIOeThXzu2HF4YPh5tjlkysJu9/6y4dyWr2h47 +warFSrqK191d0WJEq6Oh8mCMxSdRJO7C8W4w0XAzo+Inr0l9KDfZfiWYWg2JT5XI +ubibKKq6P2KxND0UVlYbRsp3fv2loEL9WM5H2bjA/oSbQ4tSJtobpjlsQOHmaxbP +XhvsIV3Dr2ksDuLEhm0vfXnEGRzNk3HV3gLNT741YEP3Sp2ZRjd5U1qFn0D+eWe0 +4LfDX9auGQCnfjZTHvu4qghX7JxcF40omjmtgkRmZ/kCgYEA4A5nU4ahEww7B65y +uzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ++wwf +pRwHvSxtNU9qXb8ewo+BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3In +KF4JvIlchyqs0RQ8wx7lULqwnn0CgYEAven83GM6SfrmO+TBHbjTk6JhP/3CMsIv +mSdo4KrbQNvp4vHO3w1/0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEB +pxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA+k4UoH/eQmGKGK44TRz +Yj5hZYGWIC8CgYEAlmmU/AG5SGxBhJqb8wxfNXDPJjf//i92BgJT2Vp4pskBbr5P +GoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ+m0/XSWx13v9t9DIbheA +tgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpECgYEA +mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe//EjuCBbwHfcT8OG3hWOv8vpzo +kQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p+AF2p6Yfahscjtq+GY9cB85Nx +Ly2IXCC0PF++Sq9LOrTE9QV988SJy/yUrAjcZ5MmECkCgYEAldHXIrEmMZVaNwGz +DF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uY +iqewXfCKw/UngrJt8Xwfq1Zruz0YY869zPN4GiE9+9rzdZB33RBw8kIOquY3MK74 +FMwCihYx/LiU2YTHkaoJ3ncvtvg= +-----END PRIVATE KEY----- +` + jwkPubEC = `{"kid":"https://kv-test-mj.vault.azure.net/keys/ec-p-521/e3d0e9c179b54988860c69c6ae172c65","kty":"EC","key_ops":["sign","verify"],"crv":"P-521","x":"AedOAtb7H7Oz1C_cPKI_R4CN_eai5nteY6KFW07FOoaqgQfVCSkQDK22fCOiMT_28c8LZYJRsiIFz_IIbQUW7bXj","y":"AOnchHnmBphIWXvanmMAmcCDkaED6ycW8GsAl9fQ43BMVZTqcTkJYn6vGnhn7MObizmkNSmgZYTwG-vZkIg03HHs"}` + jwkPubECPKIX = `-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB504C1vsfs7PUL9w8oj9HgI395qLm +e15jooVbTsU6hqqBB9UJKRAMrbZ8I6IxP/bxzwtlglGyIgXP8ghtBRbtteMA6dyE +eeYGmEhZe9qeYwCZwIORoQPrJxbwawCX19DjcExVlOpxOQlifq8aeGfsw5uLOaQ1 +KaBlhPAb69mQiDTccew= +-----END PUBLIC KEY----- +` + jwkPrivEC = `{"kty": "EC","kid": "rie3pHe8u8gjSa0IaJfqk7_iEfHeYfDYx-Bqi7vQc0s","crv": "P-256","x": "fDjg3Nq4jPf8IOZ0277aPVal_8iXySnzLUJAZghUzZM","y": "d863PeyBOK_Q4duiSmWwgIRzi1RPlFZTR-vACMlPg-Q","d": "jJs5xsoHUetdMabtt8H2KyX5T92nGul1chFeMT5hlr0"}` + jwkPrivECPKCS8 = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjJs5xsoHUetdMabt +t8H2KyX5T92nGul1chFeMT5hlr2hRANCAAR8OODc2riM9/wg5nTbvto9VqX/yJfJ +KfMtQkBmCFTNk3fOtz3sgTiv0OHbokplsICEc4tUT5RWU0frwAjJT4Pk +-----END PRIVATE KEY----- +` +) + +func TestExecute(t *testing.T) { + tbl := []struct { + name string + tpl map[string][]byte + data map[string][]byte + expetedData map[string][]byte + expErr string + }{ + { + name: "test empty", + tpl: nil, + data: nil, + }, + { + name: "b64dec func", + tpl: map[string][]byte{ + "foo": []byte("{{ .secret | b64dec }}"), + }, + data: map[string][]byte{ + "secret": []byte("MTIzNA=="), + }, + expetedData: map[string][]byte{ + "foo": []byte("1234"), + }, + }, + { + name: "fromJSON func", + tpl: map[string][]byte{ + "foo": []byte("{{ $var := .secret | fromJson }}{{ $var.foo }}"), + }, + data: map[string][]byte{ + "secret": []byte(`{"foo": "bar"}`), + }, + expetedData: map[string][]byte{ + "foo": []byte("bar"), + }, + }, + { + name: "from & toJSON func", + tpl: map[string][]byte{ + "foo": []byte("{{ $var := .secret | fromJson }}{{ $var.foo | toJson }}"), + }, + data: map[string][]byte{ + "secret": []byte(`{"foo": {"baz":"bang"}}`), + }, + expetedData: map[string][]byte{ + "foo": []byte(`{"baz":"bang"}`), + }, + }, + { + name: "use sprig functions", + tpl: map[string][]byte{ + "foo": []byte(`{{ .path | ext }}`), // TODO: this is a breaking change + }, + data: map[string][]byte{ + "path": []byte(`foo/bar/baz.exe`), + }, + expetedData: map[string][]byte{ + "foo": []byte(`.exe`), + }, + }, + { + name: "multiline template", + tpl: map[string][]byte{ + "cfg": []byte(` + datasources: + - name: Graphite + type: graphite + access: proxy + url: http://localhost:8080 + password: "{{ .password }}" + user: "{{ .user }}"`), + }, + data: map[string][]byte{ + "user": []byte(`foobert`), + "password": []byte("harharhar"), + }, + expetedData: map[string][]byte{ + "cfg": []byte(` + datasources: + - name: Graphite + type: graphite + access: proxy + url: http://localhost:8080 + password: "harharhar" + user: "foobert"`), + }, + }, + { + name: "base64 pipeline", + tpl: map[string][]byte{ + "foo": []byte(`{{ "123412341234" | b64enc | b64dec }}`), + }, + data: map[string][]byte{}, + expetedData: map[string][]byte{ + "foo": []byte("123412341234"), + }, + }, + { + name: "base64 pkcs12 extract", + tpl: map[string][]byte{ + "key": []byte(`{{ .secret | b64dec | pkcs12key | pemPrivateKey }}`), + "cert": []byte(`{{ .secret | b64dec | pkcs12cert | pemCertificate }}`), + }, + data: map[string][]byte{ + "secret": []byte(pkcs12ContentNoPass), + }, + expetedData: map[string][]byte{ + "key": []byte(pkcs12Key), + "cert": []byte(pkcs12Cert), + }, + }, + { + name: "base64 pkcs12 extract with password", + tpl: map[string][]byte{ + "key": []byte(`{{ .secret | b64dec | pkcs12keyPass "123456" | pemPrivateKey }}`), + "cert": []byte(`{{ .secret | b64dec | pkcs12certPass "123456" | pemCertificate }}`), + }, + data: map[string][]byte{ + "secret": []byte(pkcs12ContentWithPass), + }, + expetedData: map[string][]byte{ + "key": []byte(pkcs12Key), + "cert": []byte(pkcs12Cert), + }, + }, + { + name: "base64 decode error", + tpl: map[string][]byte{ + "key": []byte(`{{ .example | b64dec }}`), + }, + data: map[string][]byte{ + "example": []byte("iam_no_base64"), + }, + expErr: "", // silent error + }, + { + name: "pkcs12 key wrong password", + tpl: map[string][]byte{ + "key": []byte(`{{ .secret | b64dec | pkcs12keyPass "wrong" | pemPrivateKey }}`), + }, + data: map[string][]byte{ + "secret": []byte(pkcs12ContentWithPass), + }, + expErr: "unable to decode pkcs12", + }, + { + name: "pkcs12 cert wrong password", + tpl: map[string][]byte{ + "cert": []byte(`{{ .secret | b64dec | pkcs12certPass "wrong" | pemCertificate }}`), + }, + data: map[string][]byte{ + "secret": []byte(pkcs12ContentWithPass), + }, + expErr: "unable to decode pkcs12", + }, + { + name: "fromJSON error", + tpl: map[string][]byte{ + "key": []byte(`{{ "{ # no json # }" | fromJson }}`), + }, + data: map[string][]byte{}, + expErr: "", // silent error + }, + { + name: "template syntax error", + tpl: map[string][]byte{ + "key": []byte(`{{ #xx }}`), + }, + data: map[string][]byte{}, + expErr: "unable to parse template", + }, + { + name: "jwk rsa pub pem", + tpl: map[string][]byte{ + "fn": []byte(`{{ .secret | jwkPublicKeyPem }}`), + }, + data: map[string][]byte{ + "secret": []byte(jwkPubRSA), + }, + expetedData: map[string][]byte{ + "fn": []byte(jwkPubRSAPKIX), + }, + }, + { + name: "jwk rsa priv pem", + tpl: map[string][]byte{ + "fn": []byte(`{{ .secret | jwkPrivateKeyPem }}`), + }, + data: map[string][]byte{ + "secret": []byte(jwkPrivRSA), + }, + expetedData: map[string][]byte{ + "fn": []byte(jwkPrivRSAPKCS8), + }, + }, + { + name: "jwk ecdsa pub pem", + tpl: map[string][]byte{ + "fn": []byte(`{{ .secret | jwkPublicKeyPem }}`), + }, + data: map[string][]byte{ + "secret": []byte(jwkPubEC), + }, + expetedData: map[string][]byte{ + "fn": []byte(jwkPubECPKIX), + }, + }, + { + name: "jwk ecdsa priv pem", + tpl: map[string][]byte{ + "fn": []byte(`{{ .secret | jwkPrivateKeyPem }}`), + }, + data: map[string][]byte{ + "secret": []byte(jwkPrivEC), + }, + expetedData: map[string][]byte{ + "fn": []byte(jwkPrivECPKCS8), + }, + }, + } + + for i := range tbl { + row := tbl[i] + t.Run(row.name, func(t *testing.T) { + sec := &corev1.Secret{ + Data: make(map[string][]byte), + } + err := template.Execute(row.tpl, row.data, sec) + if !ErrorContains(err, row.expErr) { + t.Errorf("unexpected error: %s, expected: %s", err, row.expErr) + } + if row.expetedData == nil { + return + } + assert.EqualValues(t, row.expetedData, sec.Data) + }) + } +} + +func ErrorContains(out error, want string) bool { + if out == nil { + return want == "" + } + if want == "" { + return false + } + return strings.Contains(out.Error(), want) +}