Browse Source

Finalize kv secretType support for IBM Cloud SM

Hafid.Haddouti 4 years ago
parent
commit
03da4458af

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

@@ -84,9 +84,28 @@ The behavior for the different secret types is as following:
 * `dataFrom` retrieves all `certificate`, `private_key` and `intermediate` fields from the secrets manager secret and sets appropriate key:value pairs in the resulting Kubernetes secret
 * `dataFrom` retrieves all `certificate`, `private_key` and `intermediate` fields from the secrets manager secret and sets appropriate key:value pairs in the resulting Kubernetes secret
 
 
 #### kv
 #### kv
-* `remoteRef` could has a `property` to select requested key from the KV secret
-* `dataFrom` retrieves a string from secrets manager and tries to parse it as JSON object and setting all key:value pairs or only the requested key in the resulting Kubernetes secret if successful
+* `remoteRef` requires a `property` to select requested key from the KV secret
+* `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
+
+```json
+{
+  "key1": "val1",
+  "key2": "val2",
+  "key3": {
+    "keyA": "valA",
+    "keyB": "valB"
+  }
+}
+```
+
+```yaml
+- secretKey: key3_keyB
+  remoteRef:
+    key: 'kv/aaaaa-bbbb-cccc-dddd-eeeeee'
+    property: 'key3.keyB'
+```
 
 
+results in `key3_keyB: valB`
 
 
 ### Creating external secret
 ### Creating external secret
 
 

+ 5 - 0
docs/snippets/ibm-es-types.yaml

@@ -21,3 +21,8 @@ spec:
   - secretKey: bakv
   - secretKey: bakv
     remoteRef:
     remoteRef:
       key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
       key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
+      property: 'keyid'
+  - secretKey: key_with_path
+    remoteRef:
+      key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
+      property: 'key.path'

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

@@ -28,6 +28,10 @@ import (
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
+
+	ctrl "sigs.k8s.io/controller-runtime"
+
+	"github.com/tidwall/gjson"
 )
 )
 
 
 const (
 const (
@@ -60,6 +64,8 @@ type client struct {
 	credentials []byte
 	credentials []byte
 }
 }
 
 
+var log = ctrl.Log.WithName("provider").WithName("ibm").WithName("secretsmanager")
+
 func (c *client) setAuth(ctx context.Context) error {
 func (c *client) setAuth(ctx context.Context) error {
 	credentialsSecret := &corev1.Secret{}
 	credentialsSecret := &corev1.Secret{}
 	credentialsSecretName := c.store.Auth.SecretRef.SecretAPIKey.Name
 	credentialsSecretName := c.store.Auth.SecretRef.SecretAPIKey.Name
@@ -137,6 +143,10 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSec
 
 
 	case sm.CreateSecretOptionsSecretTypeKvConst:
 	case sm.CreateSecretOptionsSecretTypeKvConst:
 
 
+		if ref.Property == "" {
+			return nil, fmt.Errorf("remoteRef.property required for secret type kv")
+		}
+
 		return getKVSecret(ibm, &secretName, ref)
 		return getKVSecret(ibm, &secretName, ref)
 
 
 	default:
 	default:
@@ -214,30 +224,43 @@ func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1bet
 	return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
 	return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
 }
 }
 
 
+// Returns a secret of type kv and supports json path
 func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	secret, err := getSecretByType(ibm, secretName, sm.CreateSecretOptionsSecretTypeKvConst)
 	secret, err := getSecretByType(ibm, secretName, sm.CreateSecretOptionsSecretTypeKvConst)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	log.Info("getKVSecret", "secretName", secretName)
 	secretData := secret.SecretData.(map[string]interface{})
 	secretData := secret.SecretData.(map[string]interface{})
-	secretPayload := secretData["payload"].(string)
 
 
-	kv := make(map[string]interface{})
-	err = json.Unmarshal([]byte(secretPayload), &kv)
-	if err != nil {
-		return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+	payload, ok := secretData["payload"]
+	if !ok {
+		return nil, fmt.Errorf("no payload returned for secret %s", ref.Key)
+	}
+
+	var payloadJson string
+
+	switch payload.(type) {
+	case string:
+		payloadJson = payload.(string)
+	case map[string]interface{}:
+		var payloadJsonByte []byte
+		payloadJsonByte, err = json.Marshal(payload.(map[string]interface{}))
+		payloadJson = string(payloadJsonByte)
+	default:
+		return nil, fmt.Errorf("payload type %T not supported yet for secret %s", payload, ref.Key)
 	}
 	}
 
 
-	// returns only the value of the requested key, otherwise the entire payload
 	if ref.Property != "" {
 	if ref.Property != "" {
-		if val, ok := kv[ref.Property]; ok {
-			return []byte(val.(string)), nil
+		val := gjson.Get(payloadJson, ref.Property)
+		if !val.Exists() {
+			return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
 		}
 		}
-		return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
+		return []byte(val.String()), nil
+	} else {
+		return nil, fmt.Errorf("no property provided for secret %s", ref.Key)
 	}
 	}
-
-	return []byte(secretPayload), nil
 }
 }
 
 
 func getSecretByType(ibm *providerIBM, secretName *string, secretType string) (*sm.SecretResource, error) {
 func getSecretByType(ibm *providerIBM, secretName *string, secretType string) (*sm.SecretResource, error) {

+ 13 - 10
pkg/provider/ibm/provider_test.go

@@ -232,12 +232,14 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	}
 	}
 
 
 	secretDataKV := make(map[string]interface{})
 	secretDataKV := make(map[string]interface{})
-	secretKVPayload := `{"key1":"val1"}`
+	secretKVPayload := make(map[string]interface{})
+	secretKVPayload["key1"] = "val1"
 	secretDataKV["payload"] = secretKVPayload
 	secretDataKV["payload"] = secretKVPayload
 
 
 	secretDataKVComplex := make(map[string]interface{})
 	secretDataKVComplex := make(map[string]interface{})
-	secretKVPayloadComplex := `{"key1":"val1","key2":"val2"}`
-	secretDataKVComplex["payload"] = secretKVPayloadComplex
+	secretKVComplex := `{"key1":"val1","key2":"val2","key3":"val3","keyC":{"keyC1":"valC1", "keyC2":"valC2"}}`
+
+	secretDataKVComplex["payload"] = secretKVComplex
 
 
 	secretKV := "kv/test-secret"
 	secretKV := "kv/test-secret"
 	// bad case: kv type with key which is not in payload
 	// bad case: kv type with key which is not in payload
@@ -273,22 +275,23 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	}
 	}
 
 
 	// good case: kv type without property, returns all
 	// good case: kv type without property, returns all
-	setSecretKVWithOutKey := func(smtc *secretManagerTestCase) {
+	setSecretKVWithKey := func(smtc *secretManagerTestCase) {
 		resources := []sm.SecretResourceIntf{
 		resources := []sm.SecretResourceIntf{
 			&sm.SecretResource{
 			&sm.SecretResource{
 				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
 				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
 				Name:       utilpointer.StringPtr("testyname"),
 				Name:       utilpointer.StringPtr("testyname"),
-				SecretData: secretDataKV,
+				SecretData: secretDataKVComplex,
 			}}
 			}}
 
 
 		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
 		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
 		smtc.apiOutput.Resources = resources
 		smtc.apiOutput.Resources = resources
 		smtc.ref.Key = secretKV
 		smtc.ref.Key = secretKV
-		smtc.expectedSecret = secretKVPayload
+		smtc.ref.Property = "key2"
+		smtc.expectedSecret = "val2"
 	}
 	}
 
 
 	// good case: kv type without property, returns all
 	// good case: kv type without property, returns all
-	setSecretKVWithKey := func(smtc *secretManagerTestCase) {
+	setSecretKVWithKeyPath := func(smtc *secretManagerTestCase) {
 		resources := []sm.SecretResourceIntf{
 		resources := []sm.SecretResourceIntf{
 			&sm.SecretResource{
 			&sm.SecretResource{
 				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
 				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
@@ -299,8 +302,8 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
 		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
 		smtc.apiOutput.Resources = resources
 		smtc.apiOutput.Resources = resources
 		smtc.ref.Key = secretKV
 		smtc.ref.Key = secretKV
-		smtc.ref.Property = "key2"
-		smtc.expectedSecret = "val2"
+		smtc.ref.Property = "keyC.keyC2"
+		smtc.expectedSecret = "valC2"
 	}
 	}
 
 
 	successCases := []*secretManagerTestCase{
 	successCases := []*secretManagerTestCase{
@@ -315,8 +318,8 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setSecretCert),
 		makeValidSecretManagerTestCaseCustom(setSecretCert),
 		makeValidSecretManagerTestCaseCustom(badSecretCert),
 		makeValidSecretManagerTestCaseCustom(badSecretCert),
 		makeValidSecretManagerTestCaseCustom(setSecretKV),
 		makeValidSecretManagerTestCaseCustom(setSecretKV),
-		makeValidSecretManagerTestCaseCustom(setSecretKVWithOutKey),
 		makeValidSecretManagerTestCaseCustom(setSecretKVWithKey),
 		makeValidSecretManagerTestCaseCustom(setSecretKVWithKey),
+		makeValidSecretManagerTestCaseCustom(setSecretKVWithKeyPath),
 		makeValidSecretManagerTestCaseCustom(badSecretKV),
 		makeValidSecretManagerTestCaseCustom(badSecretKV),
 	}
 	}