1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

feat: implement template engine v2

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
Moritz Johner 2022-01-18 10:48:43 +01:00
parent 9fa3a965bc
commit 54e68399ec
16 changed files with 863 additions and 13 deletions

View file

@ -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 {

View file

@ -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.

View file

@ -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))

View file

@ -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"
)

View file

@ -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())
}

View file

@ -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",
},
}
}
}

6
go.mod
View file

@ -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

14
go.sum
View file

@ -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=

View file

@ -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)
}

View file

@ -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),

View file

@ -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.

36
pkg/template/engine.go Normal file
View file

@ -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
}

View file

@ -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.

188
pkg/template/v2/template.go Normal file
View file

@ -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
}

View file

@ -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)
}