Browse Source

Enhance IBM SM provider for SecretMap

Hafid.Haddouti 4 years ago
parent
commit
763019d1ff

+ 20 - 2
docs/provider-ibm-secrets-manager.md

@@ -86,7 +86,7 @@ The behavior for the different secret types is as following:
 
 #### kv
 * `remoteRef` could has a `property` to select requested key from the KV secret, otherwise the entire secret will be returned
-* `dataFrom` retrieves a string from secrets manager and tries to parse it as JSON object and determining the key (using gjson path) in resulting Kubernetes secret if successful
+* `dataFrom` retrieves a string from secrets manager and tries to parse it as JSON object setting the key:values pairs in resulting Kubernetes secret if successful
 
 ```json
 {
@@ -101,6 +101,7 @@ The behavior for the different secret types is as following:
 ```
 
 ```yaml
+data:
 - secretKey: key3_keyB
   remoteRef:
     key: 'kv/aaaaa-bbbb-cccc-dddd-eeeeee'
@@ -112,9 +113,26 @@ The behavior for the different secret types is as following:
 - secretKey: key_all
   remoteRef:
     key: 'kv/aaaaa-bbbb-cccc-dddd-eeeeee'
+
+dataFrom:
+  - key: 'kv/aaaaa-bbbb-cccc-dddd-eeeeee'
+    property: 'key3'
+```
+
+results in
+
+```yaml
+data:
+  # secrets from data
+  key3_keyB: ... #valB
+  special_key: ... #special-content
+  key_all: ... #{"key1":"val1","key2":"val2", ..."special.key":"special-content"}
+  
+  # secrets from dataFrom
+  keyA: ... #valA
+  keyB: ... #valB
 ```
 
-results in `key3_keyB: valB` and `special_key: special-content` and the `key_all` will contain the **entire** payload of the secret `{"key1":"val1","key2":"val2", ..."special.key":"special-content"}`.
 
 ### Creating external secret
 

+ 7 - 2
docs/snippets/ibm-es-types.yaml

@@ -24,11 +24,16 @@ spec:
     remoteRef:
       key: public_cert/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
       property: certificate
-  - secretKey: bakv
+  - secretKey: kv_without_key
+    remoteRef:
+      key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
+  - secretKey: kv_key
     remoteRef:
       key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
       property: 'keyid'
-  - secretKey: key_with_path
+  - secretKey: kv_key_with_path
     remoteRef:
       key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
       property: 'key.path'
+  dataFrom:
+  

+ 34 - 11
pkg/provider/ibm/provider.go

@@ -17,6 +17,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"strconv"
 	"strings"
 	"time"
 
@@ -252,7 +253,6 @@ func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSec
 		return nil, err
 	}
 
-	log.Info("getKVSecret", "secretName", secretName)
 	secretData := secret.SecretData.(map[string]interface{})
 
 	payload, ok := secretData["payload"]
@@ -432,21 +432,17 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1beta1.External
 		return secretMap, nil
 
 	case sm.CreateSecretOptionsSecretTypeKvConst:
-		secret, err := getSecretByType(ibm, &secretName, sm.CreateSecretOptionsSecretTypeKvConst)
+		secret, err := getKVSecret(ibm, &secretName, ref)
 		if err != nil {
 			return nil, err
 		}
-
-		secretData := secret.SecretData.(map[string]interface{})
-		secretPayload := secretData["payload"].(string)
-
-		kv := make(map[string]interface{})
-		err = json.Unmarshal([]byte(secretPayload), &kv)
+		m := make(map[string]interface{})
+		err = json.Unmarshal(secret, &m)
 		if err != nil {
-			return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+			return nil, err
 		}
 
-		secretMap := byteArrayMap(kv)
+		secretMap := byteArrayMap(m)
 
 		return secretMap, nil
 
@@ -456,13 +452,40 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1beta1.External
 }
 
 func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
+	var err error
 	secretMap := make(map[string][]byte)
 	for k, v := range secretData {
-		secretMap[k] = []byte(v.(string))
+		secretMap[k], err = getTypedKey(v)
+		if err != nil {
+			return nil
+		}
 	}
 	return secretMap
 }
 
+// kudos Vault Provider - convert from various types.
+func getTypedKey(v interface{}) ([]byte, error) {
+	switch t := v.(type) {
+	case string:
+		return []byte(t), nil
+	case map[string]interface{}:
+		return json.Marshal(t)
+	case map[string]string:
+		return json.Marshal(t)
+	case []byte:
+		return t, nil
+		// also covers int and float32 due to json.Marshal
+	case float64:
+		return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil
+	case bool:
+		return []byte(strconv.FormatBool(t)), nil
+	case nil:
+		return []byte(nil), nil
+	default:
+		return nil, fmt.Errorf("secret not in expected format")
+	}
+}
+
 func (ibm *providerIBM) Close(ctx context.Context) error {
 	return nil
 }

+ 76 - 2
pkg/provider/ibm/provider_test.go

@@ -411,6 +411,17 @@ func TestGetSecretMap(t *testing.T) {
 	secretPrivateKey := "private_key_value"
 	secretIntermediate := "intermediate_value"
 
+	secretComplex := map[string]interface{}{
+		"key1": "val1",
+		"key2": "val2",
+		"keyC": map[string]interface{}{
+			"keyC1": map[string]string{
+				"keyA": "valA",
+				"keyB": "valB",
+			},
+		},
+	}
+
 	// good case: default version & deserialization
 	setDeserialization := func(smtc *secretManagerTestCase) {
 		secretData := make(map[string]interface{})
@@ -521,10 +532,11 @@ func TestGetSecretMap(t *testing.T) {
 		smtc.expectedData["intermediate"] = []byte(secretIntermediate)
 	}
 
-	// good case: kv
+	// good case: kv, no property, return entire payload as key:value pairs
 	setSecretKV := func(smtc *secretManagerTestCase) {
 		secretData := make(map[string]interface{})
-		secretData["payload"] = `{"key1":"val1", "key2":"val2"}`
+		// secretData["payload"] = `{"key1":"val1", "key2":"val2"}`
+		secretData["payload"] = secretComplex
 
 		resources := []sm.SecretResourceIntf{
 			&sm.SecretResource{
@@ -538,6 +550,65 @@ func TestGetSecretMap(t *testing.T) {
 		smtc.ref.Key = "kv/test-secret"
 		smtc.expectedData["key1"] = []byte("val1")
 		smtc.expectedData["key2"] = []byte("val2")
+		smtc.expectedData["keyC"] = []byte(`{"keyC1":{"keyA":"valA","keyB":"valB"}}`)
+	}
+
+	// good case: kv, with property
+	setSecretKVWithProperty := func(smtc *secretManagerTestCase) {
+		secretData := make(map[string]interface{})
+		secretData["payload"] = secretComplex
+
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
+		smtc.ref.Property = "keyC"
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = "kv/test-secret"
+		smtc.expectedData["keyC1"] = []byte(`{"keyA":"valA","keyB":"valB"}`)
+	}
+
+	// good case: kv, with property and path
+	setSecretKVWithPathAndProperty := func(smtc *secretManagerTestCase) {
+		secretData := make(map[string]interface{})
+		secretData["payload"] = secretComplex
+
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
+		smtc.ref.Property = "keyC.keyC1"
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = "kv/test-secret"
+		smtc.expectedData["keyA"] = []byte("valA")
+		smtc.expectedData["keyB"] = []byte("valB")
+	}
+
+	// bad case: kv, with property and path
+	badSecretKVWithUnknownProperty := func(smtc *secretManagerTestCase) {
+		secretData := make(map[string]interface{})
+		secretData["payload"] = secretComplex
+
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
+		smtc.ref.Property = "unknown.property"
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = "kv/test-secret"
+		smtc.expectError = "key unknown.property does not exist in secret kv/test-secret"
 	}
 
 	successCases := []*secretManagerTestCase{
@@ -549,6 +620,9 @@ func TestGetSecretMap(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setSecretIam),
 		makeValidSecretManagerTestCaseCustom(setSecretCert),
 		makeValidSecretManagerTestCaseCustom(setSecretKV),
+		makeValidSecretManagerTestCaseCustom(setSecretKVWithProperty),
+		makeValidSecretManagerTestCaseCustom(setSecretKVWithPathAndProperty),
+		makeValidSecretManagerTestCaseCustom(badSecretKVWithUnknownProperty),
 		makeValidSecretManagerTestCaseCustom(setSecretPublicCert),
 	}