Browse Source

Add IBM provider metadata to secret labels and/or annotations (#2429)

* Set metadata to external secrets managed by cluster external secrets (#2413)

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>
Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

* Pull secret metadata from IBM Secrets Manager

Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

* Add lower-kebab name transformer to Doppler provider (#2418)

Signed-off-by: Joel Watson <joel.watson@doppler.com>
Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

* Fix E2E test setup on non-linux machines (#2414)

Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com>
Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

* Removing IncludeSecretMetadata from externalsecret_types.go

Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

* Changes to call IBM Secrets Manager once in case of KV Secret

Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

* Removing extra parameters to getKVSecret() is not required

Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

* Removing linting errors

Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>

---------

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>
Signed-off-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>
Signed-off-by: Joel Watson <joel.watson@doppler.com>
Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com>
Co-authored-by: Shuhei Kitagawa <shuheiktgw@users.noreply.github.com>
Co-authored-by: Vishal Singha Roy <vishal.singha.roy@ibm.com>
Co-authored-by: Joel Watson <joel@watsonian.net>
Co-authored-by: Michael Sauter <mail@michaelsauter.net>
Vishal Singha Roy 2 years ago
parent
commit
a755a49422
4 changed files with 346 additions and 25 deletions
  1. 2 2
      go.mod
  2. 10 2
      go.sum
  3. 39 16
      pkg/provider/ibm/provider.go
  4. 295 5
      pkg/provider/ibm/provider_test.go

+ 2 - 2
go.mod

@@ -72,6 +72,7 @@ require (
 	github.com/aliyun/credentials-go v1.3.0
 	github.com/avast/retry-go/v4 v4.3.4
 	github.com/cyberark/conjur-api-go v0.11.1
+	github.com/go-openapi/strfmt v0.21.5
 	github.com/golang-jwt/jwt/v5 v5.0.0
 	github.com/hashicorp/golang-lru v0.5.4
 	github.com/hashicorp/vault/api/auth/aws v0.4.1
@@ -136,8 +137,7 @@ require (
 	github.com/go-logr/zapr v1.2.4 // indirect
 	github.com/go-openapi/errors v0.20.4 // indirect
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
-	github.com/go-openapi/jsonreference v0.20.2 // indirect
-	github.com/go-openapi/strfmt v0.21.7 // indirect
+	github.com/go-openapi/jsonreference v0.20.2 // indirect; indirectgithub.com/go-openapi/strfmt v0.21.7 // indirect
 	github.com/go-openapi/swag v0.22.4 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect

+ 10 - 2
go.sum

@@ -151,6 +151,7 @@ github.com/aliyun/credentials-go v1.3.0 h1:wfBNojfNJJyuHK3YUIIjRPwnlQIdmy/YMkia1
 github.com/aliyun/credentials-go v1.3.0/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/avast/retry-go/v4 v4.3.4 h1:pHLkL7jvCvP317I8Ge+Km2Yhntv3SdkJm7uekkqbKhM=
@@ -247,14 +248,15 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
 github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
 github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
+github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
 github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
 github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
 github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
 github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
 github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
-github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
+github.com/go-openapi/strfmt v0.21.5 h1:Z/algjpXIZpbvdN+6KbVTkpO75RuedMrqpn1GN529h4=
+github.com/go-openapi/strfmt v0.21.5/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
 github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
 github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
 github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
@@ -512,6 +514,7 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
 github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
 github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -616,6 +619,7 @@ github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
 github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
@@ -629,7 +633,9 @@ github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6
 github.com/xanzy/go-gitlab v0.86.0 h1:jR8V9cK9jXRQDb46KOB20NCF3ksY09luaG0IfXE6p7w=
 github.com/xanzy/go-gitlab v0.86.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
@@ -650,6 +656,7 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
 github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
+go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
 go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
 go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -1105,6 +1112,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 39 - 16
pkg/provider/ibm/provider.go

@@ -56,6 +56,7 @@ const (
 	errFetchSAKSecret                        = "could not fetch SecretAccessKey secret: %w"
 	errMissingSAK                            = "missing SecretAccessKey"
 	errJSONSecretUnmarshal                   = "unable to unmarshal secret: %w"
+	errJSONSecretMarshal                     = "unable to marshal secret: %w"
 	errExtractingSecret                      = "unable to extract the fetched secret %s of type %s while performing %s"
 
 	defaultCacheSize   = 100
@@ -187,7 +188,15 @@ func (ibm *providerIBM) GetSecret(_ context.Context, ref esv1beta1.ExternalSecre
 
 	case sm.Secret_SecretType_Kv:
 
-		return getKVSecret(ibm, &secretName, ref)
+		response, err := getSecretData(ibm, &secretName, sm.Secret_SecretType_Kv)
+		if err != nil {
+			return nil, err
+		}
+		secret, ok := response.(*sm.KVSecret)
+		if !ok {
+			return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_Kv, "GetSecret")
+		}
+		return getKVSecret(ref, secret)
 
 	default:
 		return nil, fmt.Errorf("unknown secret type %s", secretType)
@@ -301,15 +310,7 @@ func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1bet
 }
 
 // Returns a secret of type kv and supports json path.
-func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_Kv)
-	if err != nil {
-		return nil, err
-	}
-	secret, ok := response.(*sm.KVSecret)
-	if !ok {
-		return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_Kv, "getKVSecret")
-	}
+func getKVSecret(ref esv1beta1.ExternalSecretDataRemoteRef, secret *sm.KVSecret) ([]byte, error) {
 	payloadJSONByte, err := json.Marshal(secret.Data)
 	if err != nil {
 		return nil, fmt.Errorf("marshaling payload from secret failed. %w", err)
@@ -440,6 +441,11 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 	if err != nil {
 		return nil, err
 	}
+	if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+		if err := populateSecretMap(secretMap, response); err != nil {
+			return nil, err
+		}
+	}
 
 	switch secretType {
 	case sm.Secret_SecretType_Arbitrary:
@@ -502,7 +508,11 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 		return secretMap, nil
 
 	case sm.Secret_SecretType_Kv:
-		secret, err := getKVSecret(ibm, &secretName, ref)
+		secretData, ok := response.(*sm.KVSecret)
+		if !ok {
+			return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_Kv, "GetSecretMap")
+		}
+		secret, err := getKVSecret(ref, secretData)
 		if err != nil {
 			return nil, err
 		}
@@ -511,9 +521,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 		if err != nil {
 			return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
 		}
-
-		secretMap := byteArrayMap(m)
-
+		secretMap = byteArrayMap(m, secretMap)
 		return secretMap, nil
 
 	default:
@@ -521,9 +529,8 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 	}
 }
 
-func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
+func byteArrayMap(secretData map[string]interface{}, secretMap map[string][]byte) map[string][]byte {
 	var err error
-	secretMap := make(map[string][]byte)
 	for k, v := range secretData {
 		secretMap[k], err = getTypedKey(v)
 		if err != nil {
@@ -695,3 +702,19 @@ func init() {
 		IBM: &esv1beta1.IBMProvider{},
 	})
 }
+
+// populateSecretMap populates the secretMap with metadata information that is pulled from IBM provider.
+func populateSecretMap(secretMap map[string][]byte, secretData interface{}) error {
+	secretDataMap := make(map[string]interface{})
+	data, err := json.Marshal(secretData)
+	if err != nil {
+		return fmt.Errorf(errJSONSecretMarshal, err)
+	}
+	if err := json.Unmarshal(data, &secretDataMap); err != nil {
+		return fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+	for key, value := range secretDataMap {
+		secretMap[key] = []byte(fmt.Sprintf("%v", value))
+	}
+	return nil
+}

+ 295 - 5
pkg/provider/ibm/provider_test.go

@@ -18,12 +18,14 @@ import (
 	"encoding/json"
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
 
 	"github.com/IBM/go-sdk-core/v5/core"
 	sm "github.com/IBM/secrets-manager-go-sdk/v2/secretsmanagerv2"
+	"github.com/go-openapi/strfmt"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	utilpointer "k8s.io/utils/pointer"
@@ -35,9 +37,10 @@ import (
 )
 
 const (
-	errExpectedErr = "wanted error got nil"
-	secretKey      = "test-secret"
-	secretUUID     = "d5deb37a-7883-4fe2-a5e7-3c15420adc76"
+	errExpectedErr       = "wanted error got nil"
+	secretKey            = "test-secret"
+	secretUUID           = "d5deb37a-7883-4fe2-a5e7-3c15420adc76"
+	iamCredentialsSecret = "iam_credentials/"
 )
 
 type secretManagerTestCase struct {
@@ -303,7 +306,7 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 			smtc.listInput.Search = utilpointer.String("testyname")
 			smtc.listOutput.Secrets = make([]sm.SecretMetadataIntf, 1)
 			smtc.listOutput.Secrets[0] = secretMetadata
-			smtc.ref.Key = "iam_credentials/" + secretName
+			smtc.ref.Key = iamCredentialsSecret + secretName
 			smtc.expectedSecret = secretAPIKey
 		}
 	}
@@ -516,9 +519,11 @@ func TestGetSecretMap(t *testing.T) {
 	secretUsername := "user1"
 	secretPassword := "P@ssw0rd"
 	secretAPIKey := "01234567890"
+	nilValue := "<nil>"
 	secretCertificate := "certificate_value"
 	secretPrivateKey := "private_key_value"
 	secretIntermediate := "intermediate_value"
+	timeValue := "0001-01-01T00:00:00.000Z"
 
 	secretComplex := map[string]interface{}{
 		"key1": "val1",
@@ -575,7 +580,7 @@ func TestGetSecretMap(t *testing.T) {
 		smtc.name = "good case: iam_credentials"
 		smtc.apiInput.ID = utilpointer.String(secretUUID)
 		smtc.apiOutput = secret
-		smtc.ref.Key = "iam_credentials/" + secretUUID
+		smtc.ref.Key = iamCredentialsSecret + secretUUID
 		smtc.expectedData["apikey"] = []byte(secretAPIKey)
 	}
 
@@ -630,6 +635,283 @@ func TestGetSecretMap(t *testing.T) {
 		smtc.expectedData["private_key"] = []byte(secretPrivateKey)
 	}
 
+	// good case: arbitrary with metadata
+	setArbitraryWithMetadata := func(smtc *secretManagerTestCase) {
+		payload := `{"foo":"bar"}`
+		secret := &sm.ArbitrarySecret{
+			CreatedBy:  utilpointer.String("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.Bool(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.Int64(20),
+			Payload:    &payload,
+		}
+		smtc.name = "good case: arbitrary with metadata"
+		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{"arbitrary": []byte(payload),
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"payload":         []byte(payload),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: iam_credentials with metadata
+	setSecretIamWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.IAMCredentialsSecret{
+			CreatedBy:  utilpointer.String("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.Bool(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.Int64(20),
+			ApiKey:     utilpointer.String(secretAPIKey),
+		}
+		smtc.name = "good case: iam_credentials with metadata"
+		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = iamCredentialsSecret + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{"api_key": []byte(secretAPIKey),
+			"apikey":          []byte(secretAPIKey),
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"reuse_api_key":   []byte(nilValue),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"ttl":             []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// "good case: username_password with metadata
+	setSecretUserPassWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.UsernamePasswordSecret{
+			CreatedBy:  utilpointer.String("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.Bool(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.Int64(20),
+			Username:   &secretUsername,
+			Password:   &secretPassword,
+		}
+		smtc.name = "good case: username_password with metadata"
+		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "username_password/" + secretUUID
+		smtc.expectedData["username"] = []byte(secretUsername)
+		smtc.expectedData["password"] = []byte(secretPassword)
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"password":        []byte(secretPassword),
+			"rotation":        []byte(nilValue),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"username":        []byte(secretUsername),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: imported_cert with metadata
+	setimportedCertWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.ImportedCertificate{
+			CreatedBy:    utilpointer.String("testCreatedBy"),
+			CreatedAt:    &strfmt.DateTime{},
+			Downloaded:   utilpointer.Bool(false),
+			Labels:       []string{"abc", "def", "xyz"},
+			LocksTotal:   utilpointer.Int64(20),
+			Certificate:  utilpointer.String(secretCertificate),
+			Intermediate: utilpointer.String(secretIntermediate),
+			PrivateKey:   utilpointer.String(secretPrivateKey),
+		}
+		smtc.name = "good case: imported_cert with metadata"
+		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "imported_cert" + "/" + secretUUID
+
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"certificate":           []byte(secretCertificate),
+			"created_at":            []byte(timeValue),
+			"created_by":            []byte(*secret.CreatedBy),
+			"crn":                   []byte(nilValue),
+			"downloaded":            []byte(strconv.FormatBool(*secret.Downloaded)),
+			"expiration_date":       []byte(nilValue),
+			"id":                    []byte(nilValue),
+			"intermediate":          []byte(secretIntermediate),
+			"intermediate_included": []byte(nilValue),
+			"issuer":                []byte(nilValue),
+			"labels":                []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":           []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"private_key":           []byte(secretPrivateKey),
+			"private_key_included":  []byte(nilValue),
+			"secret_group_id":       []byte(nilValue),
+			"secret_type":           []byte(nilValue),
+			"serial_number":         []byte(nilValue),
+			"signing_algorithm":     []byte(nilValue),
+			"updated_at":            []byte(nilValue),
+			"validity":              []byte(nilValue),
+			"versions_total":        []byte(nilValue),
+		}
+	}
+
+	// good case: public_cert with metadata
+	setPublicCertWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.PublicCertificate{
+			CreatedBy:    utilpointer.String("testCreatedBy"),
+			CreatedAt:    &strfmt.DateTime{},
+			Downloaded:   utilpointer.Bool(false),
+			Labels:       []string{"abc", "def", "xyz"},
+			LocksTotal:   utilpointer.Int64(20),
+			Certificate:  utilpointer.String(secretCertificate),
+			Intermediate: utilpointer.String(secretIntermediate),
+			PrivateKey:   utilpointer.String(secretPrivateKey),
+		}
+		smtc.name = "good case: public_cert with metadata"
+		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "public_cert" + "/" + secretUUID
+
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"certificate":     []byte(secretCertificate),
+			"common_name":     []byte(nilValue),
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"intermediate":    []byte(secretIntermediate),
+			"key_algorithm":   []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"private_key":     []byte(secretPrivateKey),
+			"rotation":        []byte(nilValue),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: private_cert with metadata
+	setPrivateCertWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.PrivateCertificate{
+			CreatedBy:   utilpointer.String("testCreatedBy"),
+			CreatedAt:   &strfmt.DateTime{},
+			Downloaded:  utilpointer.Bool(false),
+			Labels:      []string{"abc", "def", "xyz"},
+			LocksTotal:  utilpointer.Int64(20),
+			Certificate: utilpointer.String(secretCertificate),
+			PrivateKey:  utilpointer.String(secretPrivateKey),
+		}
+		smtc.name = "good case: private_cert with metadata"
+		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "private_cert" + "/" + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"certificate":          []byte(secretCertificate),
+			"certificate_template": []byte(nilValue),
+			"common_name":          []byte(nilValue),
+			"created_at":           []byte(timeValue),
+			"created_by":           []byte(*secret.CreatedBy),
+			"crn":                  []byte(nilValue),
+			"downloaded":           []byte(strconv.FormatBool(*secret.Downloaded)),
+			"expiration_date":      []byte(nilValue),
+			"id":                   []byte(nilValue),
+			"issuer":               []byte(nilValue),
+			"labels":               []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":          []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"private_key":          []byte(secretPrivateKey),
+			"secret_group_id":      []byte(nilValue),
+			"secret_type":          []byte(nilValue),
+			"serial_number":        []byte(nilValue),
+			"signing_algorithm":    []byte(nilValue),
+			"updated_at":           []byte(nilValue),
+			"validity":             []byte(nilValue),
+			"versions_total":       []byte(nilValue),
+		}
+	}
+
+	// good case: kv with property and metadata
+	setSecretKVWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.KVSecret{
+			CreatedBy:  utilpointer.String("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.Bool(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.Int64(20),
+			Data:       secretComplex,
+		}
+		smtc.name = "good case: kv, with property and with metadata"
+		smtc.apiInput.ID = core.StringPtr(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "kv/" + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"data":            []byte("map[key1:val1 key2:val2 keyC:map[keyC1:map[keyA:valA keyB:valB]]]"),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"key1":            []byte("val1"),
+			"key2":            []byte("val2"),
+			"keyC":            []byte(`{"keyC1":{"keyA":"valA","keyB":"valB"}}`),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: iam_credentials without metadata
+	setSecretIamWithoutMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.IAMCredentialsSecret{
+			CreatedBy:  utilpointer.String("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.Bool(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.Int64(20),
+			ApiKey:     utilpointer.String(secretAPIKey),
+		}
+		smtc.name = "good case: iam_credentials without metadata"
+		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = iamCredentialsSecret + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyNone
+		smtc.expectedData = map[string][]byte{
+			"apikey": []byte(secretAPIKey),
+		}
+	}
+
 	secretKeyKV := "kv/" + secretUUID
 	// good case: kv, no property, return entire payload as key:value pairs
 	setSecretKV := func(smtc *secretManagerTestCase) {
@@ -710,6 +992,14 @@ func TestGetSecretMap(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(badSecretKVWithUnknownProperty),
 		makeValidSecretManagerTestCaseCustom(setSecretPublicCert),
 		makeValidSecretManagerTestCaseCustom(setSecretPrivateCert),
+		makeValidSecretManagerTestCaseCustom(setSecretIamWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setArbitraryWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setSecretUserPassWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setimportedCertWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setPublicCertWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setPrivateCertWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setSecretKVWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setSecretIamWithoutMetadata),
 	}
 
 	sm := providerIBM{}