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

IBM Provider: enable ESO to pull secrets by name (#2326)

* IBM Provider: enable ESO to pull secrets by name

Signed-off-by: tanishg6@gmail.com <tanishg6@gmail.com>

* document ESO's capability to pull by secret name for IBM provider

Signed-off-by: tanishg6@gmail.com <tanishg6@gmail.com>

* correct the metrics instrumentation

Signed-off-by: tanishg6@gmail.com <tanishg6@gmail.com>

---------

Signed-off-by: tanishg6@gmail.com <tanishg6@gmail.com>
This commit is contained in:
Shanti G 2023-05-19 00:32:40 +05:30 committed by GitHub
parent 8034079e1d
commit 00bc81c8c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 468 additions and 68 deletions

View file

@ -191,12 +191,17 @@ data:
### Creating external secret
To create a kubernetes secret from the IBM Secrets Manager, a `Kind=ExternalSecret` is needed.
Below example creates a kubernetes secret based on ID of the secret in Secrets Manager.
```yaml
{% include 'ibm-external-secret.yaml' %}
```
Currently we can only get the secret by its id and not its name, so something like `565287ce-578f-8d96-a746-9409d531fe2a`.
Alternatively, secret name can be specified instead of secret ID.
```yaml
{% include 'ibm-external-secret-by-name.yaml' %}
```
### Getting the Kubernetes secret
The operator will fetch the IBM Secret Manager secret and inject it as a `Kind=Secret`

View file

@ -0,0 +1,21 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 60m
secretStoreRef:
name: ibm-store
kind: SecretStore
target:
name: database-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: username_password/<SECRET_NAME>
property: username
- secretKey: password
remoteRef:
key: username_password/<SECRET_NAME>
property: password

View file

@ -13,7 +13,9 @@ spec:
data:
- secretKey: username
remoteRef:
key: database_user
key: username_password/<SECRET_ID>
property: username
- secretKey: password
remoteRef:
key: database_password
key: username_password/<SECRET_ID>
property: password

2
go.mod
View file

@ -135,7 +135,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect

View file

@ -67,8 +67,9 @@ const (
CallKubernetesUpdateSecret = "UpdateSecret"
CallKubernetesCreateSelfSubjectRulesReview = "CreateSelfSubjectRulesReview"
ProviderIBMSM = "IBM/SecretsManager"
CallIBMSMGetSecret = "GetSecret"
ProviderIBMSM = "IBM/SecretsManager"
CallIBMSMGetSecret = "GetSecret"
CallIBMSMListSecrets = "ListSecrets"
ProviderWebhook = "Webhook"
CallWebhookHTTPReq = "HTTPRequest"

72
pkg/provider/ibm/cache.go Normal file
View file

@ -0,0 +1,72 @@
/*
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 ibm
import (
"time"
"github.com/golang/groupcache/lru"
)
type (
cacheIntf interface {
GetData(key string) (bool, []byte)
PutData(key string, value []byte)
DeleteData(key string)
}
lruCache struct {
lru *lru.Cache
ttl time.Duration
}
cacheObject struct {
timeExpires time.Time
value []byte
}
)
func NewCache(maxEntries int, ttl time.Duration) cacheIntf {
lruCache := &lruCache{
lru: lru.New(maxEntries),
ttl: ttl,
}
return lruCache
}
func (c *lruCache) GetData(key string) (bool, []byte) {
v, ok := c.lru.Get(key)
if !ok {
return false, nil
}
returnedObj := v.(cacheObject)
if time.Now().After(returnedObj.timeExpires) && c.ttl > 0 {
c.DeleteData(key)
return false, nil
}
return true, returnedObj.value
}
func (c *lruCache) PutData(key string, value []byte) {
obj := cacheObject{
timeExpires: time.Now().Add(c.ttl),
value: value,
}
c.lru.Add(key, obj)
}
func (c *lruCache) DeleteData(key string) {
c.lru.Remove(key)
}

View file

@ -0,0 +1,92 @@
/*
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 ibm
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const maxEntries = 10
func dataCache(t *testing.T, ttl time.Duration, maxEntries int) cacheIntf {
t.Helper()
return NewCache(maxEntries, ttl)
}
func TestGetData(t *testing.T) {
tests := []struct {
name string
ttl time.Duration
key string
value []byte
wantValue []byte
wantFound bool
}{
{
name: "object exists in cache and has not expired",
ttl: 30 * time.Second,
key: "testObject",
value: []byte("testValue"),
wantValue: []byte("testValue"),
wantFound: true,
},
{
name: "object exists in cache and will never expire",
ttl: 0 * time.Second,
key: "testObject",
value: []byte("testValue"),
wantValue: []byte("testValue"),
wantFound: true,
},
{
name: "object exists in cache but has expired",
ttl: 1 * time.Nanosecond,
key: "testObject",
value: []byte("testValue"),
wantFound: false,
},
{
name: "object not in cache",
ttl: 30 * time.Second,
key: "testObject",
wantFound: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewCache(10, tt.ttl)
if tt.value != nil {
c.PutData(tt.key, tt.value)
}
gotFound, gotValue := c.GetData(tt.key)
assert.Equal(t, tt.wantFound, gotFound)
assert.Equal(t, string(gotValue), string(tt.wantValue))
})
}
}
func TestPutData(t *testing.T) {
t.Parallel()
d := dataCache(t, time.Minute, maxEntries)
d.PutData("test-key", []byte("test-value"))
}
func TestDeleteData(t *testing.T) {
t.Parallel()
d := dataCache(t, time.Minute, maxEntries)
d.DeleteData("test-key")
}

View file

@ -24,19 +24,27 @@ import (
)
type IBMMockClient struct {
getSecretWithContext func(ctx context.Context, getSecretOptions *sm.GetSecretOptions) (result sm.SecretIntf, response *core.DetailedResponse, err error)
getSecretWithContext func(ctx context.Context, getSecretOptions *sm.GetSecretOptions) (result sm.SecretIntf, response *core.DetailedResponse, err error)
listSecretsWithContext func(ctx context.Context, listSecretsOptions *sm.ListSecretsOptions) (result *sm.SecretMetadataPaginatedCollection, response *core.DetailedResponse, err error)
}
type IBMMockClientParams struct {
GetSecretOptions *sm.GetSecretOptions
GetSecretOutput sm.SecretIntf
GetSecretErr error
GetSecretOptions *sm.GetSecretOptions
GetSecretOutput sm.SecretIntf
GetSecretErr error
ListSecretsOptions *sm.ListSecretsOptions
ListSecretsOutput *sm.SecretMetadataPaginatedCollection
ListSecretsErr error
}
func (mc *IBMMockClient) GetSecretWithContext(ctx context.Context, getSecretOptions *sm.GetSecretOptions) (result sm.SecretIntf, response *core.DetailedResponse, err error) {
return mc.getSecretWithContext(ctx, getSecretOptions)
}
func (mc *IBMMockClient) ListSecretsWithContext(ctx context.Context, listSecretsOptions *sm.ListSecretsOptions) (result *sm.SecretMetadataPaginatedCollection, response *core.DetailedResponse, err error) {
return mc.listSecretsWithContext(ctx, listSecretsOptions)
}
func (mc *IBMMockClient) WithValue(params IBMMockClientParams) {
if mc != nil {
mc.getSecretWithContext = func(ctx context.Context, paramReq *sm.GetSecretOptions) (sm.SecretIntf, *core.DetailedResponse, error) {
@ -47,5 +55,13 @@ func (mc *IBMMockClient) WithValue(params IBMMockClientParams) {
}
return params.GetSecretOutput, nil, params.GetSecretErr
}
mc.listSecretsWithContext = func(ctx context.Context, paramReq *sm.ListSecretsOptions) (result *sm.SecretMetadataPaginatedCollection, response *core.DetailedResponse, err error) {
// type secretmanagerpb.AccessSecretVersionRequest contains unexported fields
// use cmpopts.IgnoreUnexported to ignore all the unexported fields in the cmp.
if !cmp.Equal(paramReq, params.ListSecretsOptions, cmpopts.IgnoreUnexported(sm.SecretMetadataPaginatedCollection{})) {
return nil, nil, fmt.Errorf("unexpected test argument for ListSecrets: %s, %s", *paramReq.Search, *params.ListSecretsOptions.Search)
}
return params.ListSecretsOutput, nil, params.ListSecretsErr
}
}
}

View file

@ -0,0 +1,75 @@
/*
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 ibm
import (
"fmt"
sm "github.com/IBM/secrets-manager-go-sdk/v2/secretsmanagerv2"
)
func extractSecretMetadata(response sm.SecretMetadataIntf, givenName *string, secretType string) (*string, *string, error) {
switch secretType {
case sm.Secret_SecretType_Arbitrary:
metadata, ok := response.(*sm.ArbitrarySecretMetadata)
if !ok {
return nil, nil, fmt.Errorf(errExtractingSecret, *givenName, sm.Secret_SecretType_Arbitrary, "extractSecretMetadata")
}
return metadata.ID, metadata.Name, nil
case sm.Secret_SecretType_UsernamePassword:
metadata, ok := response.(*sm.UsernamePasswordSecretMetadata)
if !ok {
return nil, nil, fmt.Errorf(errExtractingSecret, *givenName, sm.Secret_SecretType_UsernamePassword, "extractSecretMetadata")
}
return metadata.ID, metadata.Name, nil
case sm.Secret_SecretType_IamCredentials:
metadata, ok := response.(*sm.IAMCredentialsSecretMetadata)
if !ok {
return nil, nil, fmt.Errorf(errExtractingSecret, *givenName, sm.Secret_SecretType_IamCredentials, "extractSecretMetadata")
}
return metadata.ID, metadata.Name, nil
case sm.Secret_SecretType_ImportedCert:
metadata, ok := response.(*sm.ImportedCertificateMetadata)
if !ok {
return nil, nil, fmt.Errorf(errExtractingSecret, *givenName, sm.Secret_SecretType_ImportedCert, "extractSecretMetadata")
}
return metadata.ID, metadata.Name, nil
case sm.Secret_SecretType_PublicCert:
metadata, ok := response.(*sm.PublicCertificateMetadata)
if !ok {
return nil, nil, fmt.Errorf(errExtractingSecret, *givenName, sm.Secret_SecretType_PublicCert, "extractSecretMetadata")
}
return metadata.ID, metadata.Name, nil
case sm.Secret_SecretType_PrivateCert:
metadata, ok := response.(*sm.PrivateCertificateMetadata)
if !ok {
return nil, nil, fmt.Errorf(errExtractingSecret, *givenName, sm.Secret_SecretType_PrivateCert, "extractSecretMetadata")
}
return metadata.ID, metadata.Name, nil
case sm.Secret_SecretType_Kv:
metadata, ok := response.(*sm.KVSecretMetadata)
if !ok {
return nil, nil, fmt.Errorf(errExtractingSecret, *givenName, sm.Secret_SecretType_Kv, "extractSecretMetadata")
}
return metadata.ID, metadata.Name, nil
default:
return nil, nil, fmt.Errorf("unknown secret type %s", secretType)
}
}

View file

@ -24,6 +24,7 @@ import (
core "github.com/IBM/go-sdk-core/v5/core"
sm "github.com/IBM/secrets-manager-go-sdk/v2/secretsmanagerv2"
"github.com/google/uuid"
gjson "github.com/tidwall/gjson"
corev1 "k8s.io/api/core/v1"
types "k8s.io/apimachinery/pkg/types"
@ -55,7 +56,10 @@ const (
errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
errMissingSAK = "missing SecretAccessKey"
errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
errExtractingSecret = "unable to extract the fetched secret %s of type %s"
errExtractingSecret = "unable to extract the fetched secret %s of type %s while performing %s"
defaultCacheSize = 100
defaultCacheExpiry = 1 * time.Hour
)
var contextTimeout = time.Minute * 2
@ -66,10 +70,12 @@ var _ esv1beta1.Provider = &providerIBM{}
type SecretManagerClient interface {
GetSecretWithContext(ctx context.Context, getSecretOptions *sm.GetSecretOptions) (result sm.SecretIntf, response *core.DetailedResponse, err error)
ListSecretsWithContext(ctx context.Context, listSecretsOptions *sm.ListSecretsOptions) (result *sm.SecretMetadataPaginatedCollection, response *core.DetailedResponse, err error)
}
type providerIBM struct {
IBMClient SecretManagerClient
cache cacheIntf
}
type client struct {
@ -189,26 +195,26 @@ func (ibm *providerIBM) GetSecret(_ context.Context, ref esv1beta1.ExternalSecre
}
func getArbitrarySecret(ibm *providerIBM, secretName *string) ([]byte, error) {
response, err := getSecretData(ibm, secretName)
response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_Arbitrary)
if err != nil {
return nil, err
}
secret, ok := response.(*sm.ArbitrarySecret)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_Arbitrary)
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_Arbitrary, "getArbitrarySecret")
}
return []byte(*secret.Payload), nil
}
func getImportCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
response, err := getSecretData(ibm, secretName)
response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_ImportedCert)
if err != nil {
return nil, err
}
secret, ok := response.(*sm.ImportedCertificate)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_ImportedCert)
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_ImportedCert, "getImportCertSecret")
}
switch ref.Property {
case certificateConst:
@ -223,13 +229,13 @@ func getImportCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.Ext
}
func getPublicCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
response, err := getSecretData(ibm, secretName)
response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_PublicCert)
if err != nil {
return nil, err
}
secret, ok := response.(*sm.PublicCertificate)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_PublicCert)
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_PublicCert, "getPublicCertSecret")
}
switch ref.Property {
@ -245,13 +251,13 @@ func getPublicCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.Ext
}
func getPrivateCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
response, err := getSecretData(ibm, secretName)
response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_PrivateCert)
if err != nil {
return nil, err
}
secret, ok := response.(*sm.PrivateCertificate)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_PrivateCert)
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_PrivateCert, "getPrivateCertSecret")
}
switch ref.Property {
case certificateConst:
@ -264,25 +270,25 @@ func getPrivateCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.Ex
}
func getIamCredentialsSecret(ibm *providerIBM, secretName *string) ([]byte, error) {
response, err := getSecretData(ibm, secretName)
response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_IamCredentials)
if err != nil {
return nil, err
}
secret, ok := response.(*sm.IAMCredentialsSecret)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_IamCredentials)
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_IamCredentials, "getIamCredentialsSecret")
}
return []byte(*secret.ApiKey), nil
}
func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
response, err := getSecretData(ibm, secretName)
response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_UsernamePassword)
if err != nil {
return nil, err
}
secret, ok := response.(*sm.UsernamePasswordSecret)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_UsernamePassword)
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_UsernamePassword, "getUsernamePasswordSecret")
}
switch ref.Property {
case "username":
@ -296,13 +302,13 @@ 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)
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)
return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_Kv, "getKVSecret")
}
payloadJSONByte, err := json.Marshal(secret.Data)
if err != nil {
@ -346,7 +352,29 @@ func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSec
return nil, fmt.Errorf("no property provided for secret %s", ref.Key)
}
func getSecretData(ibm *providerIBM, secretName *string) (sm.SecretIntf, error) {
func getSecretData(ibm *providerIBM, secretName *string, secretType string) (sm.SecretIntf, error) {
var givenName *string
var cachedKey string
// parse given secretName for a uuid or a secret name
_, err := uuid.Parse(*secretName)
if err != nil {
givenName = secretName
cachedKey = fmt.Sprintf("%s/%s", secretType, *givenName)
isCached, cacheData := ibm.cache.GetData(cachedKey)
tmp := string(cacheData)
cachedName := &tmp
if isCached && *cachedName != "" {
secretName = cachedName
} else {
secretName, err = findSecretByName(ibm, givenName, secretType)
if err != nil {
return nil, err
}
ibm.cache.PutData(cachedKey, []byte(*secretName))
}
}
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
defer cancel()
response, _, err := ibm.IBMClient.GetSecretWithContext(
@ -361,6 +389,38 @@ func getSecretData(ibm *providerIBM, secretName *string) (sm.SecretIntf, error)
return response, nil
}
func findSecretByName(ibm *providerIBM, secretName *string, secretType string) (*string, error) {
var secretID *string
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
defer cancel()
response, _, err := ibm.IBMClient.ListSecretsWithContext(ctx,
&sm.ListSecretsOptions{
Search: secretName,
})
metrics.ObserveAPICall(constants.ProviderIBMSM, constants.CallIBMSMListSecrets, err)
if err != nil {
return nil, err
}
found := 0
for _, r := range response.Secrets {
foundsecretID, foundSecretName, err := extractSecretMetadata(r, secretName, secretType)
if err == nil {
if *foundSecretName == *secretName {
found++
secretID = foundsecretID
}
}
}
if found == 0 {
return nil, fmt.Errorf("failed to find a secret for the given secretName %s", *secretName)
}
if found > 1 {
return nil, fmt.Errorf("found more than one secret matching for the given secretName %s, cannot proceed further", *secretName)
}
return secretID, nil
}
func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
if utils.IsNil(ibm.IBMClient) {
return nil, fmt.Errorf(errUninitalizedIBMProvider)
@ -376,7 +436,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
}
secretMap := make(map[string][]byte)
response, err := getSecretData(ibm, &secretName)
response, err := getSecretData(ibm, &secretName, secretType)
if err != nil {
return nil, err
}
@ -385,7 +445,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
case sm.Secret_SecretType_Arbitrary:
secretData, ok := response.(*sm.ArbitrarySecret)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_Arbitrary)
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_Arbitrary, "GetSecretMap")
}
secretMap[arbitraryConst] = []byte(*secretData.Payload)
return secretMap, nil
@ -393,7 +453,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
case sm.Secret_SecretType_UsernamePassword:
secretData, ok := response.(*sm.UsernamePasswordSecret)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_UsernamePassword)
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_UsernamePassword, "GetSecretMap")
}
secretMap[usernameConst] = []byte(*secretData.Username)
secretMap[passwordConst] = []byte(*secretData.Password)
@ -403,7 +463,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
case sm.Secret_SecretType_IamCredentials:
secretData, ok := response.(*sm.IAMCredentialsSecret)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_IamCredentials)
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_IamCredentials, "GetSecretMap")
}
secretMap[apikeyConst] = []byte(*secretData.ApiKey)
@ -412,7 +472,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
case sm.Secret_SecretType_ImportedCert:
secretData, ok := response.(*sm.ImportedCertificate)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_ImportedCert)
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_ImportedCert, "GetSecretMap")
}
secretMap[certificateConst] = []byte(*secretData.Certificate)
secretMap[intermediateConst] = []byte(*secretData.Intermediate)
@ -423,7 +483,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
case sm.Secret_SecretType_PublicCert:
secretData, ok := response.(*sm.PublicCertificate)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_PublicCert)
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_PublicCert, "GetSecretMap")
}
secretMap[certificateConst] = []byte(*secretData.Certificate)
secretMap[intermediateConst] = []byte(*secretData.Intermediate)
@ -434,7 +494,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
case sm.Secret_SecretType_PrivateCert:
secretData, ok := response.(*sm.PrivateCertificate)
if !ok {
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_PrivateCert)
return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_PrivateCert, "GetSecretMap")
}
secretMap[certificateConst] = []byte(*secretData.Certificate)
secretMap[privateKeyConst] = []byte(*secretData.PrivateKey)
@ -626,6 +686,7 @@ func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericSt
}
ibm.IBMClient = secretsManager
ibm.cache = NewCache(defaultCacheSize, defaultCacheExpiry)
return ibm, nil
}

View file

@ -20,6 +20,7 @@ import (
"reflect"
"strings"
"testing"
"time"
"github.com/IBM/go-sdk-core/v5/core"
sm "github.com/IBM/secrets-manager-go-sdk/v2/secretsmanagerv2"
@ -44,6 +45,9 @@ type secretManagerTestCase struct {
mockClient *fakesm.IBMMockClient
apiInput *sm.GetSecretOptions
apiOutput sm.SecretIntf
listInput *sm.ListSecretsOptions
listOutput *sm.SecretMetadataPaginatedCollection
listError error
ref *esv1beta1.ExternalSecretDataRemoteRef
serviceURL *string
apiErr error
@ -59,6 +63,9 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
apiInput: makeValidAPIInput(),
ref: makeValidRef(),
apiOutput: makeValidAPIOutput(),
listInput: makeValidListInput(),
listOutput: makeValidListSecretsOutput(),
listError: nil,
serviceURL: nil,
apiErr: nil,
expectError: "",
@ -66,9 +73,12 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
expectedData: map[string][]byte{},
}
mcParams := fakesm.IBMMockClientParams{
GetSecretOptions: smtc.apiInput,
GetSecretOutput: smtc.apiOutput,
GetSecretErr: smtc.apiErr,
GetSecretOptions: smtc.apiInput,
GetSecretOutput: smtc.apiOutput,
GetSecretErr: smtc.apiErr,
ListSecretsOptions: smtc.listInput,
ListSecretsOutput: smtc.listOutput,
ListSecretsErr: smtc.listError,
}
smtc.mockClient.WithValue(mcParams)
return &smtc
@ -97,15 +107,28 @@ func makeValidAPIOutput() sm.SecretIntf {
return i
}
func makeValidListSecretsOutput() *sm.SecretMetadataPaginatedCollection {
list := sm.SecretMetadataPaginatedCollection{}
return &list
}
func makeValidListInput() *sm.ListSecretsOptions {
listOpt := sm.ListSecretsOptions{}
return &listOpt
}
func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
smtc := makeValidSecretManagerTestCase()
for _, fn := range tweaks {
fn(smtc)
}
mcParams := fakesm.IBMMockClientParams{
GetSecretOptions: smtc.apiInput,
GetSecretOutput: smtc.apiOutput,
GetSecretErr: smtc.apiErr,
GetSecretOptions: smtc.apiInput,
GetSecretOutput: smtc.apiOutput,
GetSecretErr: smtc.apiErr,
ListSecretsOptions: smtc.listInput,
ListSecretsOutput: smtc.listOutput,
ListSecretsErr: smtc.listError,
}
smtc.mockClient.WithValue(mcParams)
return smtc
@ -229,36 +252,64 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
}
// good case: username_password type with property
setSecretUserPass := func(smtc *secretManagerTestCase) {
secret := &sm.UsernamePasswordSecret{
SecretType: utilpointer.String(sm.Secret_SecretType_UsernamePassword),
Name: utilpointer.String("testyname"),
ID: utilpointer.String(secretUUID),
Username: &secretUsername,
Password: &secretPassword,
funcSetUserPass := func(secretName, property, name string) func(smtc *secretManagerTestCase) {
return func(smtc *secretManagerTestCase) {
secret := &sm.UsernamePasswordSecret{
SecretType: utilpointer.String(sm.Secret_SecretType_UsernamePassword),
Name: utilpointer.String("testyname"),
ID: utilpointer.String(secretUUID),
Username: &secretUsername,
Password: &secretPassword,
}
secretMetadata := &sm.UsernamePasswordSecretMetadata{
Name: utilpointer.String("testyname"),
ID: utilpointer.String(secretUUID),
}
smtc.name = name
smtc.apiInput.ID = utilpointer.String(secretUUID)
smtc.apiOutput = secret
smtc.listInput.Search = utilpointer.String("testyname")
smtc.listOutput.Secrets = make([]sm.SecretMetadataIntf, 1)
smtc.listOutput.Secrets[0] = secretMetadata
smtc.ref.Key = "username_password/" + secretName
smtc.ref.Property = property
if property == "username" {
smtc.expectedSecret = secretUsername
} else {
smtc.expectedSecret = secretPassword
}
}
}
setSecretUserPassByID := funcSetUserPass(secretUUID, "username", "good case: username_password type - get username by ID")
setSecretUserPassUsername := funcSetUserPass("testyname", "username", "good case: username_password type - get username by secret name")
setSecretUserPassPassword := funcSetUserPass("testyname", "password", "good case: username_password type - get password by secret name")
// good case: iam_credentials type
funcSetSecretIam := func(secretName, name string) func(*secretManagerTestCase) {
return func(smtc *secretManagerTestCase) {
secret := &sm.IAMCredentialsSecret{
SecretType: utilpointer.String(sm.Secret_SecretType_IamCredentials),
Name: utilpointer.String("testyname"),
ID: utilpointer.String(secretUUID),
ApiKey: utilpointer.String(secretAPIKey),
}
secretMetadata := &sm.IAMCredentialsSecretMetadata{
Name: utilpointer.String("testyname"),
ID: utilpointer.String(secretUUID),
}
smtc.apiInput.ID = utilpointer.String(secretUUID)
smtc.name = name
smtc.apiOutput = secret
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.expectedSecret = secretAPIKey
}
smtc.name = "good case: username_password type with property"
smtc.apiInput.ID = utilpointer.String(secretUUID)
smtc.apiOutput = secret
smtc.ref.Key = secretUserPass
smtc.ref.Property = "password"
smtc.expectedSecret = secretPassword
}
// good case: iam_credenatials type
setSecretIam := func(smtc *secretManagerTestCase) {
secret := &sm.IAMCredentialsSecret{
SecretType: utilpointer.String(sm.Secret_SecretType_IamCredentials),
Name: utilpointer.String("testyname"),
ID: utilpointer.String(secretUUID),
ApiKey: utilpointer.String(secretAPIKey),
}
smtc.apiInput.ID = utilpointer.String(secretUUID)
smtc.name = "good case: iam_credenatials type"
smtc.apiOutput = secret
smtc.ref.Key = "iam_credentials/" + secretUUID
smtc.expectedSecret = secretAPIKey
}
setSecretIamByID := funcSetSecretIam(secretUUID, "good case: iam_credenatials type - get API Key by ID")
setSecretIamByName := funcSetSecretIam("testyname", "good case: iam_credenatials type - get API Key by name")
funcSetCertSecretTest := func(secret sm.SecretIntf, name, certType string, good bool) func(*secretManagerTestCase) {
return func(smtc *secretManagerTestCase) {
@ -426,8 +477,11 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
makeValidSecretManagerTestCaseCustom(setAPIErr),
makeValidSecretManagerTestCaseCustom(setNilMockClient),
makeValidSecretManagerTestCaseCustom(badSecretUserPass),
makeValidSecretManagerTestCaseCustom(setSecretUserPass),
makeValidSecretManagerTestCaseCustom(setSecretIam),
makeValidSecretManagerTestCaseCustom(setSecretUserPassByID),
makeValidSecretManagerTestCaseCustom(setSecretUserPassUsername),
makeValidSecretManagerTestCaseCustom(setSecretUserPassPassword),
makeValidSecretManagerTestCaseCustom(setSecretIamByID),
makeValidSecretManagerTestCaseCustom(setSecretIamByName),
makeValidSecretManagerTestCaseCustom(setSecretCert),
makeValidSecretManagerTestCaseCustom(setSecretKV),
makeValidSecretManagerTestCaseCustom(setSecretKVWithKey),
@ -446,6 +500,7 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
for k, v := range successCases {
t.Run(v.name, func(t *testing.T) {
sm.IBMClient = v.mockClient
sm.cache = NewCache(10, 1*time.Minute)
out, err := sm.GetSecret(context.Background(), *v.ref)
if !ErrorContains(err, v.expectError) {
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)