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:
parent
8034079e1d
commit
00bc81c8c7
11 changed files with 468 additions and 68 deletions
|
@ -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`
|
||||
|
|
21
docs/snippets/ibm-external-secret-by-name.yaml
Normal file
21
docs/snippets/ibm-external-secret-by-name.yaml
Normal 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
|
|
@ -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
2
go.mod
|
@ -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
|
||||
|
|
|
@ -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
72
pkg/provider/ibm/cache.go
Normal 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)
|
||||
}
|
92
pkg/provider/ibm/cache_test.go
Normal file
92
pkg/provider/ibm/cache_test.go
Normal 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")
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
75
pkg/provider/ibm/helper.go
Normal file
75
pkg/provider/ibm/helper.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue