Browse Source

fix(tpl): fail on parse/execute error

fixes #126
Moritz Johner 5 years ago
parent
commit
5f719a3dce
2 changed files with 101 additions and 52 deletions
  1. 40 37
      pkg/template/template.go
  2. 61 15
      pkg/template/template_test.go

+ 40 - 37
pkg/template/template.go

@@ -18,13 +18,13 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"encoding/pem"
+	"fmt"
 	"strings"
 	tpl "text/template"
 
 	"github.com/youmark/pkcs8"
 	"golang.org/x/crypto/pkcs12"
 	corev1 "k8s.io/api/core/v1"
-	ctrl "sigs.k8s.io/controller-runtime"
 )
 
 var tplFuncs = tpl.FuncMap{
@@ -46,88 +46,91 @@ var tplFuncs = tpl.FuncMap{
 	"lower":    strings.ToLower,
 }
 
-var log = ctrl.Log.WithName("template")
+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"
+	errDecodeBase64         = "unable to decode base64: %s"
+	errUnmarshalJSON        = "unable to unmarshal json: %s"
+	errMarshalJSON          = "unable to marshal json: %s"
+)
 
-// Execute uses an best-effort approach to render the secret data as template.
+// Execute renders the secret data as template. If an error occurs processing is stopped immediately.
 func Execute(secret *corev1.Secret, data map[string][]byte) error {
 	for k, v := range secret.Data {
 		t, err := tpl.New(k).
 			Funcs(tplFuncs).
 			Parse(string(v))
 		if err != nil {
-			log.Error(err, "unable to parse template at key", "key", k)
-			continue
+			return fmt.Errorf(errParse, k, err)
 		}
 		buf := bytes.NewBuffer(nil)
 		err = t.Execute(buf, data)
 		if err != nil {
-			log.Error(err, "unable to execute template at key", "key", k)
-			continue
+			return fmt.Errorf(errExecute, k, err)
 		}
 		secret.Data[k] = buf.Bytes()
 	}
 	return nil
 }
 
-func pkcs12keyPass(pass string, input []byte) []byte {
+func pkcs12keyPass(pass string, input []byte) ([]byte, error) {
 	key, _, err := pkcs12.Decode(input, pass)
 	if err != nil {
-		log.Error(err, "unable to decode pkcs12 with password")
-		return nil
+		return nil, fmt.Errorf(errDecodePKCS12WithPass, err)
 	}
 	kb, err := pkcs8.ConvertPrivateKeyToPKCS8(key)
 	if err != nil {
-		log.Error(err, "unable to convert pkcs12 private key")
-		return nil
+		return nil, fmt.Errorf(errConvertPrivKey, err)
 	}
-	return kb
+	return kb, nil
 }
 
-func pkcs12key(input []byte) []byte {
+func pkcs12key(input []byte) ([]byte, error) {
 	return pkcs12keyPass("", input)
 }
 
-func pkcs12certPass(pass string, input []byte) []byte {
+func pkcs12certPass(pass string, input []byte) ([]byte, error) {
 	_, cert, err := pkcs12.Decode(input, pass)
 	if err != nil {
-		log.Error(err, "unable to decode pkcs12 certificate with password")
-		return nil
+		return nil, fmt.Errorf(errDecodeCertWithPass, err)
 	}
-	return cert.Raw
+	return cert.Raw, nil
 }
 
-func pkcs12cert(input []byte) []byte {
+func pkcs12cert(input []byte) ([]byte, error) {
 	return pkcs12certPass("", input)
 }
 
-func pemPrivateKey(key []byte) string {
+func pemPrivateKey(key []byte) (string, error) {
 	buf := bytes.NewBuffer(nil)
 	err := pem.Encode(buf, &pem.Block{Type: "PRIVATE KEY", Bytes: key})
 	if err != nil {
-		log.Error(err, "unable to encode pem private key")
-		return ""
+		return "", fmt.Errorf(errEncodePEMKey, err)
 	}
-	return buf.String()
+	return buf.String(), err
 }
 
-func pemCertificate(cert []byte) string {
+func pemCertificate(cert []byte) (string, error) {
 	buf := bytes.NewBuffer(nil)
 	err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
 	if err != nil {
-		log.Error(err, "unable to encode pem certificate")
-		return ""
+		return "", fmt.Errorf(errEncodePEMCert, err)
 	}
-	return buf.String()
+	return buf.String(), nil
 }
 
-func base64decode(in []byte) []byte {
+func base64decode(in []byte) ([]byte, error) {
 	out := make([]byte, len(in))
 	l, err := base64.StdEncoding.Decode(out, in)
 	if err != nil {
-		log.Error(err, "unable to encode base64")
-		return []byte("")
+		return nil, fmt.Errorf(errDecodeBase64, err)
 	}
-	return out[:l]
+	return out[:l], nil
 }
 
 func base64encode(in []byte) []byte {
@@ -136,21 +139,21 @@ func base64encode(in []byte) []byte {
 	return out
 }
 
-func fromJSON(in []byte) interface{} {
+func fromJSON(in []byte) (interface{}, error) {
 	var out interface{}
 	err := json.Unmarshal(in, &out)
 	if err != nil {
-		log.Error(err, "unable to unmarshal json")
+		return nil, fmt.Errorf(errUnmarshalJSON, err)
 	}
-	return out
+	return out, nil
 }
 
-func toJSON(in interface{}) string {
+func toJSON(in interface{}) (string, error) {
 	output, err := json.Marshal(in)
 	if err != nil {
-		log.Error(err, "unable to marshal json")
+		return "", fmt.Errorf(errMarshalJSON, err)
 	}
-	return string(output)
+	return string(output), nil
 }
 
 func toString(in []byte) string {

+ 61 - 15
pkg/template/template_test.go

@@ -128,13 +128,13 @@ func TestExecute(t *testing.T) {
 			name: "multiline template",
 			secret: map[string][]byte{
 				"cfg": []byte(`
-datasources:
-- name: Graphite
-	type: graphite
-	access: proxy
-	url: http://localhost:8080
-	password: "{{ .password | toString }}"
-	user: "{{ .user | toString }}"`),
+		datasources:
+		- name: Graphite
+			type: graphite
+			access: proxy
+			url: http://localhost:8080
+			password: "{{ .password | toString }}"
+			user: "{{ .user | toString }}"`),
 			},
 			data: map[string][]byte{
 				"user":     []byte(`foobert`),
@@ -142,13 +142,13 @@ datasources:
 			},
 			outSecret: map[string][]byte{
 				"cfg": []byte(`
-datasources:
-- name: Graphite
-	type: graphite
-	access: proxy
-	url: http://localhost:8080
-	password: "harharhar"
-	user: "foobert"`),
+		datasources:
+		- name: Graphite
+			type: graphite
+			access: proxy
+			url: http://localhost:8080
+			password: "harharhar"
+			user: "foobert"`),
 			},
 		},
 		{
@@ -189,6 +189,52 @@ datasources:
 				"cert": []byte(pkcs12Cert),
 			},
 		},
+		{
+			name: "base64 decode error",
+			secret: map[string][]byte{
+				"key": []byte(`{{ .example | base64decode }}`),
+			},
+			data: map[string][]byte{
+				"example": []byte("iam_no_base64"),
+			},
+			expErr: "unable to decode base64",
+		},
+		{
+			name: "pkcs12 key wrong password",
+			secret: map[string][]byte{
+				"key": []byte(`{{ .secret | base64decode | pkcs12keyPass "wrong" | pemPrivateKey }}`),
+			},
+			data: map[string][]byte{
+				"secret": []byte(pkcs12ContentWithPass),
+			},
+			expErr: "unable to decode pkcs12",
+		},
+		{
+			name: "pkcs12 cert wrong password",
+			secret: map[string][]byte{
+				"cert": []byte(`{{ .secret | base64decode | pkcs12certPass "wrong" | pemCertificate }}`),
+			},
+			data: map[string][]byte{
+				"secret": []byte(pkcs12ContentWithPass),
+			},
+			expErr: "unable to decode pkcs12",
+		},
+		{
+			name: "fromJSON error",
+			secret: map[string][]byte{
+				"key": []byte(`{{ "{ # no json # }" | toBytes | fromJSON }}`),
+			},
+			data:   map[string][]byte{},
+			expErr: "unable to unmarshal json",
+		},
+		{
+			name: "template syntax error",
+			secret: map[string][]byte{
+				"key": []byte(`{{ #xx }}`),
+			},
+			data:   map[string][]byte{},
+			expErr: "unable to parse template",
+		},
 	}
 
 	for i := range tbl {
@@ -198,7 +244,7 @@ datasources:
 				Data: row.secret,
 			}, row.data)
 			if !ErrorContains(err, row.expErr) {
-				t.Errorf("unexpected error: %s", err)
+				t.Errorf("unexpected error: %s, expected: %s", err, row.expErr)
 			}
 			if row.outSecret == nil {
 				return