1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-15 17:51:01 +00:00
external-secrets/pkg/provider/ibm/provider_test.go

531 lines
18 KiB
Go

/*
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 (
"context"
"fmt"
"reflect"
"strings"
"testing"
"github.com/IBM/go-sdk-core/v5/core"
sm "github.com/IBM/secrets-manager-go-sdk/secretsmanagerv1"
"github.com/crossplane/crossplane-runtime/pkg/test"
corev1 "k8s.io/api/core/v1"
utilpointer "k8s.io/utils/pointer"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
fakesm "github.com/external-secrets/external-secrets/pkg/provider/ibm/fake"
)
type secretManagerTestCase struct {
mockClient *fakesm.IBMMockClient
apiInput *sm.GetSecretOptions
apiOutput *sm.GetSecret
ref *esv1beta1.ExternalSecretDataRemoteRef
serviceURL *string
apiErr error
expectError string
expectedSecret string
// for testing secretmap
expectedData map[string][]byte
}
func makeValidSecretManagerTestCase() *secretManagerTestCase {
smtc := secretManagerTestCase{
mockClient: &fakesm.IBMMockClient{},
apiInput: makeValidAPIInput(),
ref: makeValidRef(),
apiOutput: makeValidAPIOutput(),
serviceURL: nil,
apiErr: nil,
expectError: "",
expectedSecret: "",
expectedData: map[string][]byte{},
}
smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput, smtc.apiErr)
return &smtc
}
func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
return &esv1beta1.ExternalSecretDataRemoteRef{
Key: "test-secret",
Version: "default",
}
}
func makeValidAPIInput() *sm.GetSecretOptions {
return &sm.GetSecretOptions{
SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
ID: utilpointer.StringPtr("test-secret"),
}
}
func makeValidAPIOutput() *sm.GetSecret {
secretData := make(map[string]interface{})
secretData["payload"] = ""
return &sm.GetSecret{
Resources: []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr("testytype"),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
},
},
}
}
func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
smtc := makeValidSecretManagerTestCase()
for _, fn := range tweaks {
fn(smtc)
}
smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput, smtc.apiErr)
return smtc
}
// This case can be shared by both GetSecret and GetSecretMap tests.
// bad case: set apiErr.
var setAPIErr = func(smtc *secretManagerTestCase) {
smtc.apiErr = fmt.Errorf("oh no")
smtc.expectError = "oh no"
}
var setNilMockClient = func(smtc *secretManagerTestCase) {
smtc.mockClient = nil
smtc.expectError = errUninitalizedIBMProvider
}
// test the sm<->gcp interface
// make sure correct values are passed and errors are handled accordingly.
func TestIBMSecretManagerGetSecret(t *testing.T) {
secretData := make(map[string]interface{})
secretString := "changedvalue"
secretPassword := "P@ssw0rd"
secretAPIKey := "01234567890"
secretCertificate := "certificate_value"
secretData["payload"] = secretString
secretData["password"] = secretPassword
secretData["certificate"] = secretCertificate
// good case: default version is set
// key is passed in, output is sent back
setSecretString := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr("testytype"),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiOutput.Resources = resources
smtc.expectedSecret = secretString
}
// good case: custom version set
setCustomKey := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr("testytype"),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.ref.Key = "testyname"
smtc.apiInput.ID = utilpointer.StringPtr("testyname")
smtc.apiOutput.Resources = resources
smtc.expectedSecret = secretString
}
// bad case: username_password type without property
secretUserPass := "username_password/test-secret"
badSecretUserPass := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretUserPass
smtc.expectError = "remoteRef.property required for secret type username_password"
}
// good case: username_password type with property
setSecretUserPass := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretUserPass
smtc.ref.Property = "password"
smtc.expectedSecret = secretPassword
}
// good case: iam_credenatials type
setSecretIam := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
Name: utilpointer.StringPtr("testyname"),
APIKey: utilpointer.StringPtr(secretAPIKey),
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = "iam_credentials/test-secret"
smtc.expectedSecret = secretAPIKey
}
// good case: imported_cert type with property
secretCert := "imported_cert/test-secret"
setSecretCert := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretCert
smtc.ref.Property = "certificate"
smtc.expectedSecret = secretCertificate
}
// bad case: imported_cert type without property
badSecretCert := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretCert
smtc.expectError = "remoteRef.property required for secret type imported_cert"
}
secretDataKV := make(map[string]interface{})
secretKVPayload := `{"key1":"val1"}`
secretDataKV["payload"] = secretKVPayload
secretDataKVComplex := make(map[string]interface{})
secretKVPayloadComplex := `{"key1":"val1","key2":"val2"}`
secretDataKVComplex["payload"] = secretKVPayloadComplex
secretKV := "kv/test-secret"
// bad case: kv type with key which is not in payload
badSecretKV := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretDataKV,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretKV
smtc.ref.Property = "other-key"
smtc.expectError = "key other-key does not exist in secret kv/test-secret"
}
// good case: kv type with property
setSecretKV := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretDataKV,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretKV
smtc.ref.Property = "key1"
smtc.expectedSecret = "val1"
}
// good case: kv type without property, returns all
setSecretKVWithOutKey := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretDataKV,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretKV
smtc.expectedSecret = secretKVPayload
}
// good case: kv type without property, returns all
setSecretKVWithKey := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretDataKVComplex,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = secretKV
smtc.ref.Property = "key2"
smtc.expectedSecret = "val2"
}
successCases := []*secretManagerTestCase{
makeValidSecretManagerTestCase(),
makeValidSecretManagerTestCaseCustom(setSecretString),
makeValidSecretManagerTestCaseCustom(setCustomKey),
makeValidSecretManagerTestCaseCustom(setAPIErr),
makeValidSecretManagerTestCaseCustom(setNilMockClient),
makeValidSecretManagerTestCaseCustom(badSecretUserPass),
makeValidSecretManagerTestCaseCustom(setSecretUserPass),
makeValidSecretManagerTestCaseCustom(setSecretIam),
makeValidSecretManagerTestCaseCustom(setSecretCert),
makeValidSecretManagerTestCaseCustom(badSecretCert),
makeValidSecretManagerTestCaseCustom(setSecretKV),
makeValidSecretManagerTestCaseCustom(setSecretKVWithOutKey),
makeValidSecretManagerTestCaseCustom(setSecretKVWithKey),
makeValidSecretManagerTestCaseCustom(badSecretKV),
}
sm := providerIBM{}
for k, v := range successCases {
sm.IBMClient = v.mockClient
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)
}
if string(out) != v.expectedSecret {
t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
}
}
}
func TestGetSecretMap(t *testing.T) {
secretUsername := "user1"
secretPassword := "P@ssw0rd"
secretAPIKey := "01234567890"
secretCertificate := "certificate_value"
secretPrivateKey := "private_key_value"
secretIntermediate := "intermediate_value"
// good case: default version & deserialization
setDeserialization := func(smtc *secretManagerTestCase) {
secretData := make(map[string]interface{})
secretData["payload"] = `{"foo":"bar"}`
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr("testytype"),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiOutput.Resources = resources
smtc.expectedData["foo"] = []byte("bar")
}
// bad case: invalid json
setInvalidJSON := func(smtc *secretManagerTestCase) {
secretData := make(map[string]interface{})
secretData["payload"] = `-----------------`
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr("testytype"),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiOutput.Resources = resources
smtc.expectError = "unable to unmarshal secret: invalid character '-' in numeric literal"
}
// good case: username_password
setSecretUserPass := func(smtc *secretManagerTestCase) {
secretData := make(map[string]interface{})
secretData["username"] = secretUsername
secretData["password"] = secretPassword
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = "username_password/test-secret"
smtc.expectedData["username"] = []byte(secretUsername)
smtc.expectedData["password"] = []byte(secretPassword)
}
// good case: iam_credentials
setSecretIam := func(smtc *secretManagerTestCase) {
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
Name: utilpointer.StringPtr("testyname"),
APIKey: utilpointer.StringPtr(secretAPIKey),
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = "iam_credentials/test-secret"
smtc.expectedData["apikey"] = []byte(secretAPIKey)
}
// good case: imported_cert
setSecretCert := func(smtc *secretManagerTestCase) {
secretData := make(map[string]interface{})
secretData["certificate"] = secretCertificate
secretData["private_key"] = secretPrivateKey
secretData["intermediate"] = secretIntermediate
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = "imported_cert/test-secret"
smtc.expectedData["certificate"] = []byte(secretCertificate)
smtc.expectedData["private_key"] = []byte(secretPrivateKey)
smtc.expectedData["intermediate"] = []byte(secretIntermediate)
}
// good case: kv
setSecretKV := func(smtc *secretManagerTestCase) {
secretData := make(map[string]interface{})
secretData["payload"] = `{"key1":"val1", "key2":"val2"}`
resources := []sm.SecretResourceIntf{
&sm.SecretResource{
SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst),
Name: utilpointer.StringPtr("testyname"),
SecretData: secretData,
}}
smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeKvConst)
smtc.apiOutput.Resources = resources
smtc.ref.Key = "kv/test-secret"
smtc.expectedData["key1"] = []byte("val1")
smtc.expectedData["key2"] = []byte("val2")
}
successCases := []*secretManagerTestCase{
makeValidSecretManagerTestCaseCustom(setDeserialization),
makeValidSecretManagerTestCaseCustom(setInvalidJSON),
makeValidSecretManagerTestCaseCustom(setNilMockClient),
makeValidSecretManagerTestCaseCustom(setAPIErr),
makeValidSecretManagerTestCaseCustom(setSecretUserPass),
makeValidSecretManagerTestCaseCustom(setSecretIam),
makeValidSecretManagerTestCaseCustom(setSecretCert),
makeValidSecretManagerTestCaseCustom(setSecretKV),
}
sm := providerIBM{}
for k, v := range successCases {
sm.IBMClient = v.mockClient
out, err := sm.GetSecretMap(context.Background(), *v.ref)
if !ErrorContains(err, v.expectError) {
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
}
if err == nil && !reflect.DeepEqual(out, v.expectedData) {
t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
}
}
}
func TestValidRetryInput(t *testing.T) {
sm := providerIBM{}
invalid := "Invalid"
serviceURL := "http://fake-service-url.cool"
spec := &esv1beta1.SecretStore{
Spec: esv1beta1.SecretStoreSpec{
Provider: &esv1beta1.SecretStoreProvider{
IBM: &esv1beta1.IBMProvider{
Auth: esv1beta1.IBMAuth{
SecretRef: esv1beta1.IBMAuthSecretRef{
SecretAPIKey: v1.SecretKeySelector{
Name: "fake-secret",
Key: "fake-key",
},
},
},
ServiceURL: &serviceURL,
},
},
RetrySettings: &esv1beta1.SecretStoreRetrySettings{
RetryInterval: &invalid,
},
},
}
expected := fmt.Sprintf("cannot setup new ibm client: time: invalid duration %q", invalid)
ctx := context.TODO()
kube := &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
if o, ok := obj.(*corev1.Secret); ok {
o.Data = map[string][]byte{
"fake-key": []byte("ImAFakeApiKey"),
}
return nil
}
return nil
}),
}
_, err := sm.NewClient(ctx, spec, kube, "default")
if !ErrorContains(err, expected) {
t.Errorf("CheckValidRetryInput unexpected error: %s, expected: '%s'", err.Error(), expected)
}
}
func ErrorContains(out error, want string) bool {
if out == nil {
return want == ""
}
if want == "" {
return false
}
return strings.Contains(out.Error(), want)
}