Moritz Johner 5 лет назад
Родитель
Сommit
802eb79881

+ 1 - 0
go.sum

@@ -923,6 +923,7 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclp
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 3 - 3
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -79,7 +79,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 	log = log.WithValues("SecretStore", store.GetNamespacedName())
 
 	// check if store should be handled by this controller instance
-	if !r.shouldProcessStore(store) {
+	if !shouldProcessStore(store, r.ControllerClass) {
 		log.Info("skippig unmanaged store")
 		return ctrl.Result{}, nil
 	}
@@ -134,8 +134,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 	return ctrl.Result{}, nil
 }
 
-func (r *Reconciler) shouldProcessStore(store esv1alpha1.GenericStore) bool {
-	if store.GetSpec().Controller == "" || store.GetSpec().Controller != r.ControllerClass {
+func shouldProcessStore(store esv1alpha1.GenericStore, class string) bool {
+	if store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
 		return true
 	}
 	return false

+ 78 - 1
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -107,8 +107,8 @@ var _ = Describe("ExternalSecret controller", func() {
 				}
 				return true
 			}, timeout, interval).Should(BeTrue())
-
 		})
+
 	})
 
 	Context("When syncing ExternalSecret value", func() {
@@ -158,6 +158,83 @@ var _ = Describe("ExternalSecret controller", func() {
 
 		})
 	})
+
+	FIt("should not process stores with mismatching controller field", func() {
+		By("creating an ExternalSecret")
+		ctx := context.Background()
+		storeName := "example-ts-foo"
+		Expect(k8sClient.Create(context.Background(), &esv1alpha1.SecretStore{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      storeName,
+				Namespace: ExternalSecretNamespace,
+			},
+			Spec: esv1alpha1.SecretStoreSpec{
+				Controller: "some-other-controller",
+				Provider: &esv1alpha1.SecretStoreProvider{
+					AWSSM: &esv1alpha1.AWSSMProvider{},
+				},
+			},
+		})).To(Succeed())
+		defer func() {
+			Expect(k8sClient.Delete(context.Background(), &esv1alpha1.SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      storeName,
+					Namespace: ExternalSecretNamespace,
+				},
+			})).To(Succeed())
+		}()
+		es := &esv1alpha1.ExternalSecret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      ExternalSecretName,
+				Namespace: ExternalSecretNamespace,
+			},
+			Spec: esv1alpha1.ExternalSecretSpec{
+				SecretStoreRef: esv1alpha1.SecretStoreRef{
+					Name: storeName,
+				},
+				Target: esv1alpha1.ExternalSecretTarget{
+					Name: ExternalSecretTargetSecretName,
+				},
+				Data: []esv1alpha1.ExternalSecretData{
+					{
+						SecretKey: "doesnothing",
+						RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+							Key:      "barz",
+							Property: "bang",
+						},
+					},
+				},
+			},
+		}
+
+		Expect(k8sClient.Create(ctx, es)).Should(Succeed())
+		secretLookupKey := types.NamespacedName{
+			Name:      ExternalSecretName,
+			Namespace: ExternalSecretNamespace,
+		}
+
+		// COND
+		createdES := &esv1alpha1.ExternalSecret{}
+		Consistently(func() bool {
+			err := k8sClient.Get(ctx, secretLookupKey, createdES)
+			if err != nil {
+				return false
+			}
+			cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
+			if cond == nil {
+				return true
+			}
+			return false
+		}, timeout, interval).Should(BeTrue())
+	})
+
+	// TODO:
+	// * do not find store
+	// * missing store provider
+	// * provider client constructor error
+	// * getproviderSecretData error
+	// * check sync failed condition
+
 })
 
 // CreateNamespace creates a new namespace in the cluster.

+ 48 - 0
pkg/provider/aws/secretsmanager/fake/fake.go

@@ -0,0 +1,48 @@
+/*
+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 fake
+
+import (
+	"fmt"
+
+	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
+	"github.com/aws/aws-sdk-go/service/sts"
+	"github.com/google/go-cmp/cmp"
+)
+
+// Client implements the provider interface.
+type Client struct {
+	valFn func(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
+}
+
+func (sm *Client) GetSecretValue(in *awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error) {
+	return sm.valFn(in)
+}
+
+func (sm *Client) WithValue(in *awssm.GetSecretValueInput, val *awssm.GetSecretValueOutput, err error) {
+	sm.valFn = func(paramIn *awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error) {
+		if !cmp.Equal(paramIn, in) {
+			return nil, fmt.Errorf("unexpected test argument")
+		}
+		return val, err
+	}
+}
+
+type AssumeRoler struct {
+	AssumeRoleFunc func(*sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
+}
+
+func (f *AssumeRoler) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
+	return f.AssumeRoleFunc(input)
+}

+ 23 - 13
pkg/provider/aws/secretsmanager/secretsmanager.go

@@ -47,24 +47,34 @@ var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager
 
 // New constructs a SecretsManager Provider that is specific to a store.
 func (sm *SecretsManager) New(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.Provider, error) {
-	spc := store.GetSpec().Provider.AWSSM
+	if store == nil {
+		return nil, fmt.Errorf("found nil store")
+	}
+	spc := store.GetSpec()
 	if spc == nil {
+		return nil, fmt.Errorf("store is missing spec")
+	}
+	if spc.Provider == nil {
+		return nil, fmt.Errorf("storeSpec is missing provider")
+	}
+	smProvider := spc.Provider.AWSSM
+	if smProvider == nil {
 		return nil, fmt.Errorf("invalid provider spec. Missing AWSSM field in store %s", store.GetObjectMeta().String())
 	}
 	var sak, aks string
 	// use provided credentials via secret reference
-	if spc.Auth != nil {
+	if smProvider.Auth != nil {
 		log.V(1).Info("fetching secrets for authentication")
 		ke := client.ObjectKey{
-			Name:      spc.Auth.SecretRef.AccessKeyID.Key,
+			Name:      smProvider.Auth.SecretRef.AccessKeyID.Name,
 			Namespace: namespace, // default to ExternalSecret namespace
 		}
-		// ClusterStore must set namespace
+		// only ClusterStore is allowed to set namespace (and then it's required)
 		if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
-			if spc.Auth.SecretRef.AccessKeyID.Namespace == nil {
+			if smProvider.Auth.SecretRef.AccessKeyID.Namespace == nil {
 				return nil, fmt.Errorf("invalid ClusterSecretStore: missing AWSSM AccessKeyID Namespace")
 			}
-			ke.Namespace = *spc.Auth.SecretRef.AccessKeyID.Namespace
+			ke.Namespace = *smProvider.Auth.SecretRef.AccessKeyID.Namespace
 		}
 		akSecret := v1.Secret{}
 		err := kube.Get(ctx, ke, &akSecret)
@@ -72,23 +82,23 @@ func (sm *SecretsManager) New(ctx context.Context, store esv1alpha1.GenericStore
 			return nil, fmt.Errorf("could not fetch accessKeyID secret: %w", err)
 		}
 		ke = client.ObjectKey{
-			Name:      spc.Auth.SecretRef.SecretAccessKey.Key,
+			Name:      smProvider.Auth.SecretRef.SecretAccessKey.Name,
 			Namespace: namespace, // default to ExternalSecret namespace
 		}
-		// ClusterStore must set namespace
+		// only ClusterStore is allowed to set namespace (and then it's required)
 		if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
-			if spc.Auth.SecretRef.SecretAccessKey.Namespace == nil {
+			if smProvider.Auth.SecretRef.SecretAccessKey.Namespace == nil {
 				return nil, fmt.Errorf("invalid ClusterSecretStore: missing AWSSM SecretAccessKey Namespace")
 			}
-			ke.Namespace = *spc.Auth.SecretRef.SecretAccessKey.Namespace
+			ke.Namespace = *smProvider.Auth.SecretRef.SecretAccessKey.Namespace
 		}
 		sakSecret := v1.Secret{}
 		err = kube.Get(ctx, ke, &sakSecret)
 		if err != nil {
 			return nil, fmt.Errorf("could not fetch SecretAccessKey secret: %w", err)
 		}
-		sak = string(sakSecret.Data[spc.Auth.SecretRef.SecretAccessKey.Key])
-		aks = string(akSecret.Data[spc.Auth.SecretRef.AccessKeyID.Key])
+		sak = string(sakSecret.Data[smProvider.Auth.SecretRef.SecretAccessKey.Key])
+		aks = string(akSecret.Data[smProvider.Auth.SecretRef.AccessKeyID.Key])
 		if sak == "" {
 			return nil, fmt.Errorf("missing SecretAccessKey")
 		}
@@ -99,7 +109,7 @@ func (sm *SecretsManager) New(ctx context.Context, store esv1alpha1.GenericStore
 	if sm.stsProvider == nil {
 		sm.stsProvider = aws.DefaultSTSProvider
 	}
-	sess, err := aws.NewSession(sak, aks, spc.Region, spc.Role, sm.stsProvider)
+	sess, err := aws.NewSession(sak, aks, smProvider.Region, smProvider.Role, sm.stsProvider)
 	if err != nil {
 		return nil, err
 	}

+ 622 - 291
pkg/provider/aws/secretsmanager/secretsmanager_test.go

@@ -1,3 +1,16 @@
+/*
+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 secretsmanager
 
 import (
@@ -14,336 +27,654 @@ import (
 	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
 	"github.com/aws/aws-sdk-go/service/sts"
 	"github.com/google/go-cmp/cmp"
-	. "github.com/onsi/ginkgo"
-	. "github.com/onsi/gomega"
-	"sigs.k8s.io/controller-runtime/pkg/client"
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
-	"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	awsprovider "github.com/external-secrets/external-secrets/pkg/provider/aws"
+	fakesm "github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager/fake"
 )
 
-func TestAPIs(t *testing.T) {
-	RegisterFailHandler(Fail)
-
-	RunSpecsWithDefaultAndCustomReporters(t,
-		"Controller Suite",
-		[]Reporter{printer.NewlineReporter{}})
-}
-
-var _ = Describe("SSM", func() {
-
-	var k8sClient client.Client
-	BeforeEach(func() {
-		k8sClient = clientfake.NewClientBuilder().Build()
-	})
-
-	It("Should create an Client using environment variables", func() {
-		sm := &SecretsManager{}
-		os.Setenv("AWS_SECRET_ACCESS_KEY", "1111")
-		os.Setenv("AWS_ACCESS_KEY_ID", "2222")
-		defer os.Unsetenv("AWS_SECRET_ACCESS_KEY")
-		defer os.Unsetenv("AWS_ACCESS_KEY_ID")
-		smi, err := sm.New(context.Background(), &esv1alpha1.SecretStore{
-			Spec: esv1alpha1.SecretStoreSpec{
-				Provider: &esv1alpha1.SecretStoreProvider{
-					// defaults
-					AWSSM: &esv1alpha1.AWSSMProvider{},
+func TestConstructor(t *testing.T) {
+	rows := []ConstructorRow{
+		{
+			name:      "nil store",
+			expectErr: "found nil store",
+			store:     nil,
+		},
+		{
+			name:      "not store spec",
+			expectErr: "storeSpec is missing provider",
+			store:     &esv1alpha1.SecretStore{},
+		},
+		{
+			name:      "store spec has no provider",
+			expectErr: "storeSpec is missing provider",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{},
+			},
+		},
+		{
+			name:      "spec has no awssm field",
+			expectErr: "Missing AWSSM field",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{},
 				},
 			},
-		}, k8sClient, "example-ns")
-		Expect(err).ToNot(HaveOccurred())
-		Expect(smi).ToNot(BeNil())
-
-		creds, err := sm.session.Config.Credentials.Get()
-		Expect(err).ToNot(HaveOccurred())
-		Expect(creds.AccessKeyID).To(Equal("2222"))
-		Expect(creds.SecretAccessKey).To(Equal("1111"))
-	})
-
-	It("Should create an Client using environment variables and assume a role", func() {
-		sts := &FakeSTS{
-			AssumeRoleFunc: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
-				Expect(*input.RoleArn).To(Equal("my-awesome-role"))
-				return &sts.AssumeRoleOutput{
-					AssumedRoleUser: &sts.AssumedRoleUser{
-						Arn:           aws.String("1123132"),
-						AssumedRoleId: aws.String("xxxxx"),
+		},
+		{
+			name: "configure aws using environment variables",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{},
 					},
-					Credentials: &sts.Credentials{
-						AccessKeyId:     aws.String("3333"),
-						SecretAccessKey: aws.String("4444"),
-						Expiration:      aws.Time(time.Now().Add(time.Hour)),
-						SessionToken:    aws.String("6666"),
-					},
-				}, nil
+				},
 			},
-		}
-		sm := &SecretsManager{
-			stsProvider: func(se *session.Session) stscreds.AssumeRoler {
-				// check if the correct temporary credentials were used
-				creds, err := se.Config.Credentials.Get()
-				Expect(err).ToNot(HaveOccurred())
-				Expect(creds.AccessKeyID).To(Equal("2222"))
-				Expect(creds.SecretAccessKey).To(Equal("1111"))
-				return sts
+			env: map[string]string{
+				"AWS_ACCESS_KEY_ID":     "1111",
+				"AWS_SECRET_ACCESS_KEY": "2222",
 			},
-		}
-		os.Setenv("AWS_SECRET_ACCESS_KEY", "1111")
-		os.Setenv("AWS_ACCESS_KEY_ID", "2222")
-		defer os.Unsetenv("AWS_SECRET_ACCESS_KEY")
-		defer os.Unsetenv("AWS_ACCESS_KEY_ID")
-		smi, err := sm.New(context.Background(), &esv1alpha1.SecretStore{
-			Spec: esv1alpha1.SecretStoreSpec{
-				Provider: &esv1alpha1.SecretStoreProvider{
-					// do assume role!
-					AWSSM: &esv1alpha1.AWSSMProvider{
-						Role: "my-awesome-role",
+			expectProvider:    true,
+			expectedKeyID:     "1111",
+			expectedSecretKey: "2222",
+		},
+		{
+			name: "configure aws using environment variables + assume role",
+			stsProvider: func(*session.Session) stscreds.AssumeRoler {
+				return &fakesm.AssumeRoler{
+					AssumeRoleFunc: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
+						assert.Equal(t, *input.RoleArn, "foo-bar-baz")
+						return &sts.AssumeRoleOutput{
+							AssumedRoleUser: &sts.AssumedRoleUser{
+								Arn:           aws.String("1123132"),
+								AssumedRoleId: aws.String("xxxxx"),
+							},
+							Credentials: &sts.Credentials{
+								AccessKeyId:     aws.String("3333"),
+								SecretAccessKey: aws.String("4444"),
+								Expiration:      aws.Time(time.Now().Add(time.Hour)),
+								SessionToken:    aws.String("6666"),
+							},
+						}, nil
 					},
-				},
+				}
 			},
-		}, k8sClient, "example-ns")
-		Expect(err).ToNot(HaveOccurred())
-		Expect(smi).ToNot(BeNil())
-
-		creds, err := sm.session.Config.Credentials.Get()
-		Expect(err).ToNot(HaveOccurred())
-		Expect(creds.AccessKeyID).To(Equal("3333"))
-		Expect(creds.SecretAccessKey).To(Equal("4444"))
-	})
-
-	It("GetSecret should return the correct values", func() {
-		fake := &FakeSM{}
-		p := &SecretsManager{
-			client: fake,
-		}
-		for i, row := range []struct {
-			apiInput       *awssm.GetSecretValueInput
-			apiOutput      *awssm.GetSecretValueOutput
-			rr             esv1alpha1.ExternalSecretDataRemoteRef
-			apiErr         error
-			expectError    string
-			expectedSecret string
-		}{
-			{
-				// good case: default version is set
-				// key is passed in, output is sent back
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/baz"),
-					VersionStage: aws.String("AWSCURRENT"),
-				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key: "/baz",
-				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String("RRRRR"),
-				},
-				apiErr:         nil,
-				expectError:    "",
-				expectedSecret: "RRRRR",
-			},
-			{
-				// good case: extract property
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/baz"),
-					VersionStage: aws.String("AWSCURRENT"),
-				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key:      "/baz",
-					Property: "/shmoo",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Role: "foo-bar-baz",
+						},
+					},
 				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String(`{"/shmoo": "bang"}`),
+			},
+			env: map[string]string{
+				"AWS_ACCESS_KEY_ID":     "1111",
+				"AWS_SECRET_ACCESS_KEY": "2222",
+			},
+			expectProvider:    true,
+			expectedKeyID:     "3333",
+			expectedSecretKey: "4444",
+		},
+		{
+			name:      "error out when secret with credentials does not exist",
+			namespace: "foo",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Auth: &esv1alpha1.AWSSMAuth{
+								SecretRef: esv1alpha1.AWSSMAuthSecretRef{
+									AccessKeyID: esmeta.SecretKeySelector{
+										Name: "othersecret",
+										Key:  "one",
+									},
+									SecretAccessKey: esmeta.SecretKeySelector{
+										Name: "othersecret",
+										Key:  "two",
+									},
+								},
+							},
+						},
+					},
 				},
-				apiErr:         nil,
-				expectError:    "",
-				expectedSecret: "bang",
-			},
-			{
-				// bad case: missing property
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/baz"),
-					VersionStage: aws.String("AWSCURRENT"),
+			},
+			expectErr: `secrets "othersecret" not found`,
+		},
+		{
+			name:      "use credentials from secret to configure aws",
+			namespace: "foo",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Auth: &esv1alpha1.AWSSMAuth{
+								SecretRef: esv1alpha1.AWSSMAuthSecretRef{
+									AccessKeyID: esmeta.SecretKeySelector{
+										Name: "onesecret",
+										// Namespace is not set
+										Key: "one",
+									},
+									SecretAccessKey: esmeta.SecretKeySelector{
+										Name: "onesecret",
+										// Namespace is not set
+										Key: "two",
+									},
+								},
+							},
+						},
+					},
 				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key:      "/baz",
-					Property: "DOES NOT EXIST",
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "onesecret",
+						Namespace: "foo",
+					},
+					Data: map[string][]byte{
+						"one": []byte("1111"),
+						"two": []byte("2222"),
+					},
 				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String(`{"/shmoo": "bang"}`),
+			},
+			expectProvider:    true,
+			expectedKeyID:     "1111",
+			expectedSecretKey: "2222",
+		},
+		{
+			name:      "error out when secret key does not exist",
+			namespace: "foo",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Auth: &esv1alpha1.AWSSMAuth{
+								SecretRef: esv1alpha1.AWSSMAuthSecretRef{
+									AccessKeyID: esmeta.SecretKeySelector{
+										Name: "brokensecret",
+										Key:  "one",
+									},
+									SecretAccessKey: esmeta.SecretKeySelector{
+										Name: "brokensecret",
+										Key:  "two",
+									},
+								},
+							},
+						},
+					},
 				},
-				apiErr:         nil,
-				expectError:    "has no property",
-				expectedSecret: "",
-			},
-			{
-				// bad case: extract property failure due to invalid json
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/baz"),
-					VersionStage: aws.String("AWSCURRENT"),
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "brokensecret",
+						Namespace: "foo",
+					},
+					Data: map[string][]byte{},
 				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key:      "/baz",
-					Property: "/shmoo",
+			},
+			expectErr: "missing SecretAccessKey",
+		},
+		{
+			name:      "should not be able to access secrets from different namespace",
+			namespace: "foo",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Auth: &esv1alpha1.AWSSMAuth{
+								SecretRef: esv1alpha1.AWSSMAuthSecretRef{
+									AccessKeyID: esmeta.SecretKeySelector{
+										Name:      "onesecret",
+										Namespace: aws.String("evil"), // this should not be possible!
+										Key:       "one",
+									},
+									SecretAccessKey: esmeta.SecretKeySelector{
+										Name:      "onesecret",
+										Namespace: aws.String("evil"),
+										Key:       "two",
+									},
+								},
+							},
+						},
+					},
 				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String(`------`),
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "onesecret",
+						Namespace: "evil",
+					},
+					Data: map[string][]byte{
+						"one": []byte("1111"),
+						"two": []byte("2222"),
+					},
 				},
-				apiErr:         nil,
-				expectError:    "unable to unmarshal secret",
-				expectedSecret: "",
-			},
-			{
-				// should pass version
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/foo/bar"),
-					VersionStage: aws.String("1234"),
+			},
+			expectErr: `secrets "onesecret" not found`,
+		},
+		{
+			name:      "ClusterStore should use credentials from a specific namespace",
+			namespace: "es-namespace",
+			store: &esv1alpha1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
+					Kind:       esv1alpha1.ClusterSecretStoreKind,
 				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key:     "/foo/bar",
-					Version: "1234",
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Auth: &esv1alpha1.AWSSMAuth{
+								SecretRef: esv1alpha1.AWSSMAuthSecretRef{
+									AccessKeyID: esmeta.SecretKeySelector{
+										Name:      "onesecret",
+										Namespace: aws.String("platform-team-ns"),
+										Key:       "one",
+									},
+									SecretAccessKey: esmeta.SecretKeySelector{
+										Name:      "onesecret",
+										Namespace: aws.String("platform-team-ns"),
+										Key:       "two",
+									},
+								},
+							},
+						},
+					},
 				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String("FOOBA!"),
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "onesecret",
+						Namespace: "platform-team-ns",
+					},
+					Data: map[string][]byte{
+						"one": []byte("1111"),
+						"two": []byte("2222"),
+					},
 				},
-				apiErr:         nil,
-				expectError:    "",
-				expectedSecret: "FOOBA!",
-			},
-			{
-				// should return err
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/foo/bar"),
-					VersionStage: aws.String("AWSCURRENT"),
+			},
+			expectProvider:    true,
+			expectedKeyID:     "1111",
+			expectedSecretKey: "2222",
+		},
+		{
+			name:      "namespace is mandatory when using ClusterStore with SecretKeySelector",
+			namespace: "es-namespace",
+			store: &esv1alpha1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
+					Kind:       esv1alpha1.ClusterSecretStoreKind,
 				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key: "/foo/bar",
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Auth: &esv1alpha1.AWSSMAuth{
+								SecretRef: esv1alpha1.AWSSMAuthSecretRef{
+									AccessKeyID: esmeta.SecretKeySelector{
+										Name: "onesecret",
+										Key:  "one",
+									},
+									SecretAccessKey: esmeta.SecretKeySelector{
+										Name: "onesecret",
+										Key:  "two",
+									},
+								},
+							},
+						},
+					},
 				},
-				apiOutput:   &awssm.GetSecretValueOutput{},
-				apiErr:      fmt.Errorf("oh no"),
-				expectError: "oh no",
-			},
-		} {
-			fake.WithValue(row.apiInput, row.apiOutput, row.apiErr)
-			out, err := p.GetSecret(context.Background(), row.rr)
-			if !ErrorContains(err, row.expectError) {
-				GinkgoT().Errorf("[%d] unexpected error: %s, expected: '%s'", i, err.Error(), row.expectError)
-			}
-			if string(out) != row.expectedSecret {
-				GinkgoT().Errorf("[%d] unexpected secret: expected %s, got %s", i, row.expectedSecret, string(out))
-			}
-		}
-	})
+			},
+			expectErr: "invalid ClusterSecretStore: missing AWSSM AccessKeyID Namespace",
+		},
+	}
+	for i := range rows {
+		row := rows[i]
+		t.Run(row.name, func(t *testing.T) {
+			testRow(t, row)
+		})
+	}
+}
 
-	It("GetSecretMap should return correct values", func() {
-		fake := &FakeSM{}
-		p := &SecretsManager{
-			client: fake,
-		}
-		for i, row := range []struct {
-			apiInput     *awssm.GetSecretValueInput
-			apiOutput    *awssm.GetSecretValueOutput
-			rr           esv1alpha1.ExternalSecretDataRemoteRef
-			expectedData map[string]string
-			apiErr       error
-			expectError  string
-		}{
-			{
-				// good case: default version & deserialization
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/baz"),
-					VersionStage: aws.String("AWSCURRENT"),
-				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String(`{"foo":"bar"}`),
-				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key: "/baz",
-				},
-				expectedData: map[string]string{
-					"foo": "bar",
-				},
-				apiErr:      nil,
-				expectError: "",
-			},
-			{
-				// bad case: api error returned
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/baz"),
-					VersionStage: aws.String("AWSCURRENT"),
-				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String(`{"foo":"bar"}`),
-				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key: "/baz",
-				},
-				expectedData: map[string]string{
-					"foo": "bar",
-				},
-				apiErr:      fmt.Errorf("some api err"),
-				expectError: "some api err",
-			},
-			{
-				// bad case: invalid json
-				apiInput: &awssm.GetSecretValueInput{
-					SecretId:     aws.String("/baz"),
-					VersionStage: aws.String("AWSCURRENT"),
-				},
-				apiOutput: &awssm.GetSecretValueOutput{
-					SecretString: aws.String(`-----------------`),
-				},
-				rr: esv1alpha1.ExternalSecretDataRemoteRef{
-					Key: "/baz",
-				},
-				expectedData: map[string]string{},
-				apiErr:       nil,
-				expectError:  "unable to unmarshal secret",
-			},
-		} {
-			fake.WithValue(row.apiInput, row.apiOutput, row.apiErr)
-			out, err := p.GetSecretMap(context.Background(), row.rr)
-			if !ErrorContains(err, row.expectError) {
-				GinkgoT().Errorf("[%d] unexpected error: %s, expected: '%s'", i, err.Error(), row.expectError)
-			}
-			if cmp.Equal(out, row.expectedData) {
-				GinkgoT().Errorf("[%d] unexpected secret data: expected %#v, got %#v", i, row.expectedData, out)
-			}
-		}
-	})
-})
+type ConstructorRow struct {
+	name              string
+	store             esv1alpha1.GenericStore
+	secrets           []v1.Secret
+	namespace         string
+	stsProvider       awsprovider.STSProvider
+	expectProvider    bool
+	expectErr         string
+	expectedKeyID     string
+	expectedSecretKey string
+	env               map[string]string
+}
 
-func ErrorContains(out error, want string) bool {
-	if out == nil {
-		return want == ""
+func testRow(t *testing.T, row ConstructorRow) {
+	kc := clientfake.NewClientBuilder().Build()
+	for i := range row.secrets {
+		err := kc.Create(context.Background(), &row.secrets[i])
+		assert.Nil(t, err)
 	}
-	if want == "" {
-		return false
+	for k, v := range row.env {
+		os.Setenv(k, v)
 	}
-	return strings.Contains(out.Error(), want)
+	defer func() {
+		for k := range row.env {
+			os.Unsetenv(k)
+		}
+	}()
+	sm := SecretsManager{
+		stsProvider: row.stsProvider,
+	}
+	newsm, err := sm.New(context.Background(), row.store, kc, row.namespace)
+	if !ErrorContains(err, row.expectErr) {
+		t.Errorf("expected error %s but found %s", row.expectErr, err.Error())
+	}
+	// pass test on expected error
+	if err != nil {
+		return
+	}
+	if row.expectProvider && newsm == nil {
+		t.Errorf("expected provider object, found nil")
+		return
+	}
+	creds, _ := newsm.(*SecretsManager).session.Config.Credentials.Get()
+	assert.Equal(t, creds.AccessKeyID, row.expectedKeyID)
+	assert.Equal(t, creds.SecretAccessKey, row.expectedSecretKey)
 }
 
-type FakeSM struct {
-	valFn func(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
+func TestSMEnvCredentials(t *testing.T) {
+	k8sClient := clientfake.NewClientBuilder().Build()
+	sm := &SecretsManager{}
+	os.Setenv("AWS_SECRET_ACCESS_KEY", "1111")
+	os.Setenv("AWS_ACCESS_KEY_ID", "2222")
+	defer os.Unsetenv("AWS_SECRET_ACCESS_KEY")
+	defer os.Unsetenv("AWS_ACCESS_KEY_ID")
+	smi, err := sm.New(context.Background(), &esv1alpha1.SecretStore{
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				// defaults
+				AWSSM: &esv1alpha1.AWSSMProvider{},
+			},
+		},
+	}, k8sClient, "example-ns")
+	assert.Nil(t, err)
+	assert.NotNil(t, smi)
+
+	creds, err := sm.session.Config.Credentials.Get()
+	assert.Nil(t, err)
+	assert.Equal(t, creds.AccessKeyID, "2222")
+	assert.Equal(t, creds.SecretAccessKey, "1111")
 }
 
-func (sm *FakeSM) GetSecretValue(in *awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error) {
-	return sm.valFn(in)
+func TestSMAssumeRole(t *testing.T) {
+	k8sClient := clientfake.NewClientBuilder().Build()
+	sts := &fakesm.AssumeRoler{
+		AssumeRoleFunc: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
+			// make sure the correct role is passed in
+			assert.Equal(t, *input.RoleArn, "my-awesome-role")
+			return &sts.AssumeRoleOutput{
+				AssumedRoleUser: &sts.AssumedRoleUser{
+					Arn:           aws.String("1123132"),
+					AssumedRoleId: aws.String("xxxxx"),
+				},
+				Credentials: &sts.Credentials{
+					AccessKeyId:     aws.String("3333"),
+					SecretAccessKey: aws.String("4444"),
+					Expiration:      aws.Time(time.Now().Add(time.Hour)),
+					SessionToken:    aws.String("6666"),
+				},
+			}, nil
+		},
+	}
+	sm := &SecretsManager{
+		stsProvider: func(se *session.Session) stscreds.AssumeRoler {
+			// check if the correct temporary credentials were used
+			creds, err := se.Config.Credentials.Get()
+			assert.Nil(t, err)
+			assert.Equal(t, creds.AccessKeyID, "2222")
+			assert.Equal(t, creds.SecretAccessKey, "1111")
+			return sts
+		},
+	}
+	os.Setenv("AWS_SECRET_ACCESS_KEY", "1111")
+	os.Setenv("AWS_ACCESS_KEY_ID", "2222")
+	defer os.Unsetenv("AWS_SECRET_ACCESS_KEY")
+	defer os.Unsetenv("AWS_ACCESS_KEY_ID")
+	smi, err := sm.New(context.Background(), &esv1alpha1.SecretStore{
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				// do assume role!
+				AWSSM: &esv1alpha1.AWSSMProvider{
+					Role: "my-awesome-role",
+				},
+			},
+		},
+	}, k8sClient, "example-ns")
+	assert.Nil(t, err)
+	assert.NotNil(t, smi)
+
+	creds, err := sm.session.Config.Credentials.Get()
+	assert.Nil(t, err)
+	assert.Equal(t, creds.AccessKeyID, "3333")
+	assert.Equal(t, creds.SecretAccessKey, "4444")
 }
 
-func (sm *FakeSM) WithValue(in *awssm.GetSecretValueInput, val *awssm.GetSecretValueOutput, err error) {
-	sm.valFn = func(paramIn *awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error) {
-		if !cmp.Equal(paramIn, in) {
-			return nil, fmt.Errorf("unexpected test argument")
+// test the sm<->aws interface
+// make sure correct values are passed and errors are handled accordingly.
+func TestGetSecret(t *testing.T) {
+	fake := &fakesm.Client{}
+	p := &SecretsManager{
+		client: fake,
+	}
+	for i, row := range []struct {
+		apiInput       *awssm.GetSecretValueInput
+		apiOutput      *awssm.GetSecretValueOutput
+		rr             esv1alpha1.ExternalSecretDataRemoteRef
+		apiErr         error
+		expectError    string
+		expectedSecret string
+	}{
+		{
+			// good case: default version is set
+			// key is passed in, output is sent back
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/baz"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key: "/baz",
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String("RRRRR"),
+			},
+			apiErr:         nil,
+			expectError:    "",
+			expectedSecret: "RRRRR",
+		},
+		{
+			// good case: extract property
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/baz"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:      "/baz",
+				Property: "/shmoo",
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String(`{"/shmoo": "bang"}`),
+			},
+			apiErr:         nil,
+			expectError:    "",
+			expectedSecret: "bang",
+		},
+		{
+			// bad case: missing property
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/baz"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:      "/baz",
+				Property: "DOES NOT EXIST",
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String(`{"/shmoo": "bang"}`),
+			},
+			apiErr:         nil,
+			expectError:    "has no property",
+			expectedSecret: "",
+		},
+		{
+			// bad case: extract property failure due to invalid json
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/baz"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:      "/baz",
+				Property: "/shmoo",
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String(`------`),
+			},
+			apiErr:         nil,
+			expectError:    "unable to unmarshal secret",
+			expectedSecret: "",
+		},
+		{
+			// should pass version
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/foo/bar"),
+				VersionStage: aws.String("1234"),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:     "/foo/bar",
+				Version: "1234",
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String("FOOBA!"),
+			},
+			apiErr:         nil,
+			expectError:    "",
+			expectedSecret: "FOOBA!",
+		},
+		{
+			// should return err
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/foo/bar"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key: "/foo/bar",
+			},
+			apiOutput:   &awssm.GetSecretValueOutput{},
+			apiErr:      fmt.Errorf("oh no"),
+			expectError: "oh no",
+		},
+	} {
+		fake.WithValue(row.apiInput, row.apiOutput, row.apiErr)
+		out, err := p.GetSecret(context.Background(), row.rr)
+		if !ErrorContains(err, row.expectError) {
+			t.Errorf("[%d] unexpected error: %s, expected: '%s'", i, err.Error(), row.expectError)
+		}
+		if string(out) != row.expectedSecret {
+			t.Errorf("[%d] unexpected secret: expected %s, got %s", i, row.expectedSecret, string(out))
 		}
-		return val, err
 	}
 }
 
-type FakeSTS struct {
-	AssumeRoleFunc func(*sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
+func TestGetSecretMap(t *testing.T) {
+	fake := &fakesm.Client{}
+	p := &SecretsManager{
+		client: fake,
+	}
+	for i, row := range []struct {
+		apiInput     *awssm.GetSecretValueInput
+		apiOutput    *awssm.GetSecretValueOutput
+		rr           esv1alpha1.ExternalSecretDataRemoteRef
+		expectedData map[string]string
+		apiErr       error
+		expectError  string
+	}{
+		{
+			// good case: default version & deserialization
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/baz"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String(`{"foo":"bar"}`),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key: "/baz",
+			},
+			expectedData: map[string]string{
+				"foo": "bar",
+			},
+			apiErr:      nil,
+			expectError: "",
+		},
+		{
+			// bad case: api error returned
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/baz"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String(`{"foo":"bar"}`),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key: "/baz",
+			},
+			expectedData: map[string]string{
+				"foo": "bar",
+			},
+			apiErr:      fmt.Errorf("some api err"),
+			expectError: "some api err",
+		},
+		{
+			// bad case: invalid json
+			apiInput: &awssm.GetSecretValueInput{
+				SecretId:     aws.String("/baz"),
+				VersionStage: aws.String("AWSCURRENT"),
+			},
+			apiOutput: &awssm.GetSecretValueOutput{
+				SecretString: aws.String(`-----------------`),
+			},
+			rr: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key: "/baz",
+			},
+			expectedData: map[string]string{},
+			apiErr:       nil,
+			expectError:  "unable to unmarshal secret",
+		},
+	} {
+		fake.WithValue(row.apiInput, row.apiOutput, row.apiErr)
+		out, err := p.GetSecretMap(context.Background(), row.rr)
+		if !ErrorContains(err, row.expectError) {
+			t.Errorf("[%d] unexpected error: %s, expected: '%s'", i, err.Error(), row.expectError)
+		}
+		if cmp.Equal(out, row.expectedData) {
+			t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", i, row.expectedData, out)
+		}
+	}
 }
 
-func (f *FakeSTS) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
-	return f.AssumeRoleFunc(input)
+func ErrorContains(out error, want string) bool {
+	if out == nil {
+		return want == ""
+	}
+	if want == "" {
+		return false
+	}
+	return strings.Contains(out.Error(), want)
 }