Browse Source

add awssm support (#34)

* feat: add awssm

fixes #26
Moritz Johner 5 years ago
parent
commit
92be45df6a

+ 8 - 4
apis/externalsecrets/v1alpha1/secretstore_awssm_types.go

@@ -18,24 +18,28 @@ import (
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 )
 
+// AWSSMAuth contains a secretRef for credentials.
 type AWSSMAuth struct {
 	SecretRef AWSSMAuthSecretRef `json:"secretRef"`
 }
 
+// AWSSMAuthSecretRef holds secret references for aws credentials
+// both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
 type AWSSMAuthSecretRef struct {
 	// The AccessKeyID is used for authentication
-	// +optional
 	AccessKeyID esmeta.SecretKeySelector `json:"accessKeyIDSecretRef,omitempty"`
 
 	// The SecretAccessKey is used for authentication
-	// +optional
 	SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
 }
 
-// Configures a store to sync secrets using the AWS Secret Manager provider.
+// AWSSMProvider configures a store to sync secrets using the AWS Secret Manager provider.
 type AWSSMProvider struct {
 	// Auth defines the information necessary to authenticate against AWS
-	Auth AWSSMAuth `json:"auth"`
+	// if not set aws sdk will infer credentials from your environment
+	// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
+	// +nullable
+	Auth *AWSSMAuth `json:"auth"`
 
 	// Role is a Role ARN which the SecretManager provider will assume
 	// +optional

+ 5 - 1
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -58,7 +58,11 @@ func (in *AWSSMAuthSecretRef) DeepCopy() *AWSSMAuthSecretRef {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AWSSMProvider) DeepCopyInto(out *AWSSMProvider) {
 	*out = *in
-	in.Auth.DeepCopyInto(&out.Auth)
+	if in.Auth != nil {
+		in, out := &in.Auth, &out.Auth
+		*out = new(AWSSMAuth)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSSMProvider.

+ 7 - 2
config/crd/bases/external-secrets.io_clustersecretstores.yaml

@@ -59,10 +59,15 @@ spec:
                       AWS Secret Manager provider
                     properties:
                       auth:
-                        description: Auth defines the information necessary to authenticate
-                          against AWS
+                        description: 'Auth defines the information necessary to authenticate
+                          against AWS if not set aws sdk will infer credentials from
+                          your environment see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials'
+                        nullable: true
                         properties:
                           secretRef:
+                            description: AWSSMAuthSecretRef holds secret references
+                              for aws credentials both AccessKeyID and SecretAccessKey
+                              must be defined in order to properly authenticate.
                             properties:
                               accessKeyIDSecretRef:
                                 description: The AccessKeyID is used for authentication

+ 7 - 2
config/crd/bases/external-secrets.io_secretstores.yaml

@@ -59,10 +59,15 @@ spec:
                       AWS Secret Manager provider
                     properties:
                       auth:
-                        description: Auth defines the information necessary to authenticate
-                          against AWS
+                        description: 'Auth defines the information necessary to authenticate
+                          against AWS if not set aws sdk will infer credentials from
+                          your environment see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials'
+                        nullable: true
                         properties:
                           secretRef:
+                            description: AWSSMAuthSecretRef holds secret references
+                              for aws credentials both AccessKeyID and SecretAccessKey
+                              must be defined in order to properly authenticate.
                             properties:
                               accessKeyIDSecretRef:
                                 description: The AccessKeyID is used for authentication

+ 29 - 1
go.mod

@@ -2,10 +2,38 @@ module github.com/external-secrets/external-secrets
 
 go 1.13
 
+replace (
+	k8s.io/api => k8s.io/api v0.20.2
+	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.20.2
+	k8s.io/apimachinery => k8s.io/apimachinery v0.20.2
+	k8s.io/apiserver => k8s.io/apiserver v0.20.2
+	k8s.io/cli-runtime => k8s.io/cli-runtime v0.20.2
+	k8s.io/client-go => k8s.io/client-go v0.20.2
+	k8s.io/cloud-provider => k8s.io/cloud-provider v0.20.2
+	k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.20.2
+	k8s.io/code-generator => k8s.io/code-generator v0.20.2
+	k8s.io/component-base => k8s.io/component-base v0.20.2
+	k8s.io/component-helpers => k8s.io/component-helpers v0.20.2
+	k8s.io/controller-manager => k8s.io/controller-manager v0.20.2
+	k8s.io/cri-api => k8s.io/cri-api v0.20.2
+	k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.20.2
+	k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.20.2
+	k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.20.2
+	k8s.io/kube-proxy => k8s.io/kube-proxy v0.20.2
+	k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.20.2
+	k8s.io/kubectl => k8s.io/kubectl v0.20.2
+	k8s.io/kubelet => k8s.io/kubelet v0.20.2
+	k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.20.2
+	k8s.io/metrics => k8s.io/metrics v0.20.2
+	k8s.io/mount-utils => k8s.io/mount-utils v0.20.2
+	k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.20.2
+)
+
 require (
+	github.com/aws/aws-sdk-go v1.35.24
 	github.com/go-logr/logr v0.4.0
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/google/go-cmp v0.5.4 // indirect
+	github.com/google/go-cmp v0.5.4
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/uuid v1.2.0 // indirect
 	github.com/googleapis/gnostic v0.5.4 // indirect

+ 9 - 8
go.sum

@@ -69,7 +69,10 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.35.24 h1:U3GNTg8+7xSM6OAJ8zksiSM4bRqxBWmVwwehvOSNG3A=
+github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
@@ -301,7 +304,12 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -915,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=
@@ -926,24 +935,16 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
 k8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw=
 k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8=
-k8s.io/apiextensions-apiserver v0.20.1 h1:ZrXQeslal+6zKM/HjDXLzThlz/vPSxrfK3OqL8txgVQ=
-k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk=
 k8s.io/apiextensions-apiserver v0.20.2 h1:rfrMWQ87lhd8EzQWRnbQ4gXrniL/yTRBgYH1x1+BLlo=
 k8s.io/apiextensions-apiserver v0.20.2/go.mod h1:F6TXp389Xntt+LUq3vw6HFOLttPa0V8821ogLGwb6Zs=
-k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
 k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg=
 k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
 k8s.io/apiserver v0.20.2/go.mod h1:2nKd93WyMhZx4Hp3RfgH2K5PhwyTrprrkWYnI7id7jA=
-k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
 k8s.io/client-go v0.20.2 h1:uuf+iIAbfnCSw8IGAv/Rg0giM+2bOzHLOsbbrwrdhNQ=
 k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE=
-k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
 k8s.io/code-generator v0.20.2/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
-k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
 k8s.io/component-base v0.20.2 h1:LMmu5I0pLtwjpp5009KLuMGFqSc2S2isGw8t1hpYKLE=
 k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=

+ 10 - 6
main.go

@@ -44,8 +44,10 @@ func init() {
 
 func main() {
 	var metricsAddr string
+	var controllerClass string
 	var enableLeaderElection bool
 	flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+	flag.StringVar(&controllerClass, "controller-class", "default", "the controller is instantiated with a specific controller name and filters ES based on this property")
 	flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
 		"Enable leader election for controller manager. "+
 			"Enabling this will ensure there is only one active controller manager.")
@@ -66,17 +68,19 @@ func main() {
 	}
 
 	if err = (&secretstore.Reconciler{
-		Client: mgr.GetClient(),
-		Log:    ctrl.Log.WithName("controllers").WithName("SecretStore"),
-		Scheme: mgr.GetScheme(),
+		Client:          mgr.GetClient(),
+		Log:             ctrl.Log.WithName("controllers").WithName("SecretStore"),
+		Scheme:          mgr.GetScheme(),
+		ControllerClass: controllerClass,
 	}).SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "SecretStore")
 		os.Exit(1)
 	}
 	if err = (&externalsecret.Reconciler{
-		Client: mgr.GetClient(),
-		Log:    ctrl.Log.WithName("controllers").WithName("ExternalSecret"),
-		Scheme: mgr.GetScheme(),
+		Client:          mgr.GetClient(),
+		Log:             ctrl.Log.WithName("controllers").WithName("ExternalSecret"),
+		Scheme:          mgr.GetScheme(),
+		ControllerClass: controllerClass,
 	}).SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "ExternalSecret")
 		os.Exit(1)

+ 24 - 4
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -41,11 +41,12 @@ const (
 	requeueAfter = time.Second * 30
 )
 
-// ExternalSecretReconciler reconciles a ExternalSecret object.
+// Reconciler reconciles a ExternalSecret object.
 type Reconciler struct {
 	client.Client
-	Log    logr.Logger
-	Scheme *runtime.Scheme
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	ControllerClass string
 }
 
 // +kubebuilder:rbac:groups=external-secrets.io,resources=externalsecrets,verbs=get;list;watch;create;update;patch;delete
@@ -72,11 +73,20 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 	store, err := r.getStore(ctx, &externalSecret)
 	if err != nil {
 		log.Error(err, "could not get store reference")
+		conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionFalse, esv1alpha1.ConditionReasonSecretSyncedError, err.Error())
+		SetExternalSecretCondition(&externalSecret.Status, *conditionSynced)
+		err = r.Status().Update(ctx, &externalSecret)
 		return ctrl.Result{RequeueAfter: requeueAfter}, nil
 	}
 
 	log = log.WithValues("SecretStore", store.GetNamespacedName())
 
+	// check if store should be handled by this controller instance
+	if !shouldProcessStore(store, r.ControllerClass) {
+		log.Info("skippig unmanaged store")
+		return ctrl.Result{}, nil
+	}
+
 	storeProvider, err := schema.GetProvider(store)
 	if err != nil {
 		log.Error(err, "could not get store provider")
@@ -86,6 +96,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 	providerClient, err := storeProvider.New(ctx, store, r.Client, req.Namespace)
 	if err != nil {
 		log.Error(err, "could not get provider client")
+		conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionFalse, esv1alpha1.ConditionReasonSecretSyncedError, err.Error())
+		SetExternalSecretCondition(&externalSecret.Status, *conditionSynced)
+		err = r.Status().Update(ctx, &externalSecret)
 		return ctrl.Result{RequeueAfter: requeueAfter}, nil
 	}
 
@@ -108,7 +121,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 
 	if err != nil {
 		log.Error(err, "could not reconcile ExternalSecret")
-		conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionFalse, esv1alpha1.ConditionReasonSecretSynced, err.Error())
+		conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionFalse, esv1alpha1.ConditionReasonSecretSyncedError, err.Error())
 		SetExternalSecretCondition(&externalSecret.Status, *conditionSynced)
 		err = r.Status().Update(ctx, &externalSecret)
 		if err != nil {
@@ -127,6 +140,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 	return ctrl.Result{}, nil
 }
 
+func shouldProcessStore(store esv1alpha1.GenericStore, class string) bool {
+	if store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
+		return true
+	}
+	return false
+}
+
 func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1alpha1.ExternalSecret) (esv1alpha1.GenericStore, error) {
 	// TODO: Implement getting ClusterSecretStore
 	var secretStore esv1alpha1.SecretStore

+ 270 - 2
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -27,6 +27,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	"github.com/external-secrets/external-secrets/pkg/provider"
 	"github.com/external-secrets/external-secrets/pkg/provider/fake"
 	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
@@ -107,12 +108,12 @@ var _ = Describe("ExternalSecret controller", func() {
 				}
 				return true
 			}, timeout, interval).Should(BeTrue())
-
 		})
+
 	})
 
 	Context("When syncing ExternalSecret value", func() {
-		It("should set the secret value", func() {
+		It("should set the secret value and sync labels/annotations", func() {
 			By("creating an ExternalSecret")
 			ctx := context.Background()
 			const targetProp = "targetProperty"
@@ -121,6 +122,14 @@ var _ = Describe("ExternalSecret controller", func() {
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      ExternalSecretName,
 					Namespace: ExternalSecretNamespace,
+					Labels: map[string]string{
+						"fooobar": "bazz",
+						"bazzing": "booze",
+					},
+					Annotations: map[string]string{
+						"hihihih":   "hehehe",
+						"harharhra": "yallayalla",
+					},
 				},
 				Spec: esv1alpha1.ExternalSecretSpec{
 					SecretStoreRef: esv1alpha1.SecretStoreRef{
@@ -155,8 +164,267 @@ var _ = Describe("ExternalSecret controller", func() {
 				v := syncedSecret.Data[targetProp]
 				return string(v) == secretVal
 			}, timeout, interval).Should(BeTrue())
+			Expect(syncedSecret.ObjectMeta.Labels).To(BeEquivalentTo(es.ObjectMeta.Labels))
+			Expect(syncedSecret.ObjectMeta.Annotations).To(BeEquivalentTo(es.ObjectMeta.Annotations))
+		})
+
+		It("should fetch secrets using dataFrom", func() {
+			By("creating an ExternalSecret")
+			ctx := context.Background()
+			const secretVal = "someValue"
+			es := &esv1alpha1.ExternalSecret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      ExternalSecretName,
+					Namespace: ExternalSecretNamespace,
+				},
+				Spec: esv1alpha1.ExternalSecretSpec{
+					SecretStoreRef: esv1alpha1.SecretStoreRef{
+						Name: ExternalSecretStore,
+					},
+					Target: esv1alpha1.ExternalSecretTarget{
+						Name: ExternalSecretTargetSecretName,
+					},
+					DataFrom: []esv1alpha1.ExternalSecretDataRemoteRef{
+						{
+							Key: "barz",
+						},
+					},
+				},
+			}
+
+			fakeProvider.WithGetSecretMap(map[string][]byte{
+				"foo": []byte("bar"),
+				"baz": []byte("bang"),
+			}, nil)
+			fakeProvider.WithGetSecret([]byte(secretVal), nil)
+			Expect(k8sClient.Create(ctx, es)).Should(Succeed())
+			secretLookupKey := types.NamespacedName{
+				Name:      ExternalSecretTargetSecretName,
+				Namespace: ExternalSecretNamespace}
+			syncedSecret := &v1.Secret{}
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, secretLookupKey, syncedSecret)
+				if err != nil {
+					return false
+				}
+				x := syncedSecret.Data["foo"]
+				y := syncedSecret.Data["baz"]
+				return string(x) == "bar" && string(y) == "bang"
+			}, timeout, interval).Should(BeTrue())
+		})
+
+		It("should set an error condition when provider errors", func() {
+			By("creating an ExternalSecret")
+			ctx := context.Background()
+			const targetProp = "targetProperty"
+			es := &esv1alpha1.ExternalSecret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      ExternalSecretName,
+					Namespace: ExternalSecretNamespace,
+				},
+				Spec: esv1alpha1.ExternalSecretSpec{
+					SecretStoreRef: esv1alpha1.SecretStoreRef{
+						Name: ExternalSecretStore,
+					},
+					Target: esv1alpha1.ExternalSecretTarget{
+						Name: ExternalSecretTargetSecretName,
+					},
+					Data: []esv1alpha1.ExternalSecretData{
+						{
+							SecretKey: targetProp,
+							RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+								Key:      "barz",
+								Property: "bang",
+							},
+						},
+					},
+				},
+			}
 
+			fakeProvider.WithGetSecret(nil, fmt.Errorf("artificial testing error"))
+			Expect(k8sClient.Create(ctx, es)).Should(Succeed())
+			esLookupKey := types.NamespacedName{
+				Name:      ExternalSecretName,
+				Namespace: ExternalSecretNamespace}
+			createdES := &esv1alpha1.ExternalSecret{}
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, esLookupKey, createdES)
+				if err != nil {
+					return false
+				}
+				// condition must be false
+				cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
+				if cond == nil || cond.Status != v1.ConditionFalse || cond.Reason != esv1alpha1.ConditionReasonSecretSyncedError {
+					return false
+				}
+				return true
+			}, timeout, interval).Should(BeTrue())
 		})
+
+		It("should set an error condition when store does not exist", func() {
+			By("creating an ExternalSecret")
+			ctx := context.Background()
+			const targetProp = "targetProperty"
+			es := &esv1alpha1.ExternalSecret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      ExternalSecretName,
+					Namespace: ExternalSecretNamespace,
+				},
+				Spec: esv1alpha1.ExternalSecretSpec{
+					SecretStoreRef: esv1alpha1.SecretStoreRef{
+						Name: "storeshouldnotexist",
+					},
+					Target: esv1alpha1.ExternalSecretTarget{
+						Name: ExternalSecretTargetSecretName,
+					},
+					Data: []esv1alpha1.ExternalSecretData{
+						{
+							SecretKey: targetProp,
+							RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+								Key:      "barz",
+								Property: "bang",
+							},
+						},
+					},
+				},
+			}
+
+			Expect(k8sClient.Create(ctx, es)).Should(Succeed())
+			esLookupKey := types.NamespacedName{
+				Name:      ExternalSecretName,
+				Namespace: ExternalSecretNamespace}
+			createdES := &esv1alpha1.ExternalSecret{}
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, esLookupKey, createdES)
+				if err != nil {
+					return false
+				}
+				// condition must be false
+				cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
+				if cond == nil || cond.Status != v1.ConditionFalse || cond.Reason != esv1alpha1.ConditionReasonSecretSyncedError {
+					return false
+				}
+				return true
+			}, timeout, interval).Should(BeTrue())
+		})
+
+		It("should set an error condition when store provider constructor fails", func() {
+			By("creating an ExternalSecret")
+			ctx := context.Background()
+			const targetProp = "targetProperty"
+			es := &esv1alpha1.ExternalSecret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      ExternalSecretName,
+					Namespace: ExternalSecretNamespace,
+				},
+				Spec: esv1alpha1.ExternalSecretSpec{
+					SecretStoreRef: esv1alpha1.SecretStoreRef{
+						Name: ExternalSecretStore,
+					},
+					Target: esv1alpha1.ExternalSecretTarget{
+						Name: ExternalSecretTargetSecretName,
+					},
+					Data: []esv1alpha1.ExternalSecretData{
+						{
+							SecretKey: targetProp,
+							RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+								Key:      "barz",
+								Property: "bang",
+							},
+						},
+					},
+				},
+			}
+
+			fakeProvider.WithNew(func(context.Context, esv1alpha1.GenericStore, client.Client,
+				string) (provider.Provider, error) {
+				return nil, fmt.Errorf("artificial constructor error")
+			})
+			Expect(k8sClient.Create(ctx, es)).Should(Succeed())
+			esLookupKey := types.NamespacedName{
+				Name:      ExternalSecretName,
+				Namespace: ExternalSecretNamespace}
+			createdES := &esv1alpha1.ExternalSecret{}
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, esLookupKey, createdES)
+				if err != nil {
+					return false
+				}
+				// condition must be false
+				cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
+				if cond == nil || cond.Status != v1.ConditionFalse || cond.Reason != esv1alpha1.ConditionReasonSecretSyncedError {
+					return false
+				}
+				return true
+			}, timeout, interval).Should(BeTrue())
+		})
+
+		It("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)
+				return cond == nil
+			}, timeout, interval).Should(BeTrue())
+		})
+
 	})
 })
 

+ 3 - 2
pkg/controllers/secretstore/secretstore_controller.go

@@ -28,8 +28,9 @@ import (
 // Reconciler reconciles a SecretStore object.
 type Reconciler struct {
 	client.Client
-	Log    logr.Logger
-	Scheme *runtime.Scheme
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	ControllerClass string
 }
 
 // +kubebuilder:rbac:groups=external-secrets.io,resources=secretstores,verbs=get;list;watch;create;update;patch;delete

+ 57 - 0
pkg/provider/aws/provider.go

@@ -0,0 +1,57 @@
+package aws
+
+import (
+	"fmt"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
+	"github.com/aws/aws-sdk-go/aws/request"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/sts"
+	ctrl "sigs.k8s.io/controller-runtime"
+)
+
+// Config contains configuration to create a new AWS provider.
+type Config struct {
+	AssumeRole string
+	Region     string
+	APIRetries int
+}
+
+var log = ctrl.Log.WithName("provider").WithName("aws")
+
+// NewSession creates a new aws session based on the supported input methods.
+// https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
+func NewSession(sak, aks, region, role string, stsprovider STSProvider) (*session.Session, error) {
+	config := aws.NewConfig()
+	sessionOpts := session.Options{
+		Config: *config,
+	}
+	if sak != "" && aks != "" {
+		sessionOpts.Config.Credentials = credentials.NewStaticCredentials(aks, sak, "")
+		sessionOpts.SharedConfigState = session.SharedConfigDisable
+	}
+	sess, err := session.NewSessionWithOptions(sessionOpts)
+	if err != nil {
+		return nil, fmt.Errorf("unable to create aws session: %w", err)
+	}
+	if region != "" {
+		log.V(1).Info("using region", "region", region)
+		sess.Config.WithRegion(region)
+	}
+
+	if role != "" {
+		log.V(1).Info("assuming role", "role", role)
+		stsclient := stsprovider(sess)
+		sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, role))
+	}
+	sess.Handlers.Build.PushBack(request.WithAppendUserAgent("external-secrets"))
+	return sess, nil
+}
+
+type STSProvider func(*session.Session) stscreds.AssumeRoler
+
+func DefaultSTSProvider(sess *session.Session) stscreds.AssumeRoler {
+	return sts.New(sess)
+}

+ 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)
+}

+ 130 - 7
pkg/provider/aws/secretsmanager/secretsmanager.go

@@ -15,32 +15,155 @@ package secretsmanager
 
 import (
 	"context"
+	"encoding/json"
+	"fmt"
 
+	"github.com/aws/aws-sdk-go/aws/session"
+	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
+	v1 "k8s.io/api/core/v1"
+	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	"github.com/external-secrets/external-secrets/pkg/provider"
+	"github.com/external-secrets/external-secrets/pkg/provider/aws"
 	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
 
 // SecretsManager is a provider for AWS SecretsManager.
-type SecretsManager struct{}
+type SecretsManager struct {
+	session     *session.Session
+	stsProvider aws.STSProvider
+	client      SMInterface
+}
+
+// SMInterface is a subset of the smiface api.
+// see: https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/secretsmanageriface/
+type SMInterface interface {
+	GetSecretValue(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
+}
 
-// New constructs a SecretsManager Provider.
+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) {
-	return sm, nil // stub
+	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 smProvider.Auth != nil {
+		log.V(1).Info("fetching secrets for authentication")
+		ke := client.ObjectKey{
+			Name:      smProvider.Auth.SecretRef.AccessKeyID.Name,
+			Namespace: namespace, // default to ExternalSecret namespace
+		}
+		// only ClusterStore is allowed to set namespace (and then it's required)
+		if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
+			if smProvider.Auth.SecretRef.AccessKeyID.Namespace == nil {
+				return nil, fmt.Errorf("invalid ClusterSecretStore: missing AWSSM AccessKeyID Namespace")
+			}
+			ke.Namespace = *smProvider.Auth.SecretRef.AccessKeyID.Namespace
+		}
+		akSecret := v1.Secret{}
+		err := kube.Get(ctx, ke, &akSecret)
+		if err != nil {
+			return nil, fmt.Errorf("could not fetch accessKeyID secret: %w", err)
+		}
+		ke = client.ObjectKey{
+			Name:      smProvider.Auth.SecretRef.SecretAccessKey.Name,
+			Namespace: namespace, // default to ExternalSecret namespace
+		}
+		// only ClusterStore is allowed to set namespace (and then it's required)
+		if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
+			if smProvider.Auth.SecretRef.SecretAccessKey.Namespace == nil {
+				return nil, fmt.Errorf("invalid ClusterSecretStore: missing AWSSM 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[smProvider.Auth.SecretRef.SecretAccessKey.Key])
+		aks = string(akSecret.Data[smProvider.Auth.SecretRef.AccessKeyID.Key])
+		if sak == "" {
+			return nil, fmt.Errorf("missing SecretAccessKey")
+		}
+		if aks == "" {
+			return nil, fmt.Errorf("missing AccessKeyID")
+		}
+	}
+	if sm.stsProvider == nil {
+		sm.stsProvider = aws.DefaultSTSProvider
+	}
+	sess, err := aws.NewSession(sak, aks, smProvider.Region, smProvider.Role, sm.stsProvider)
+	if err != nil {
+		return nil, err
+	}
+	sm.session = sess
+	sm.client = awssm.New(sess)
+	return sm, nil
 }
 
 // GetSecret returns a single secret from the provider.
 func (sm *SecretsManager) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	return []byte("NOOP"), nil
+	ver := "AWSCURRENT"
+	if ref.Version != "" {
+		ver = ref.Version
+	}
+	log.Info("fetching secret value", "key", ref.Key, "version", ver)
+	secretOut, err := sm.client.GetSecretValue(&awssm.GetSecretValueInput{
+		SecretId:     &ref.Key,
+		VersionStage: &ver,
+	})
+	if err != nil {
+		return nil, err
+	}
+	if ref.Property == "" {
+		return []byte(*secretOut.SecretString), nil
+	}
+	kv := make(map[string]string)
+	err = json.Unmarshal([]byte(*secretOut.SecretString), &kv)
+	if err != nil {
+		return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
+	}
+	val, ok := kv[ref.Property]
+	if !ok {
+		return nil, fmt.Errorf("secret %s has no property %s", ref.Key, ref.Property)
+	}
+	return []byte(val), nil
 }
 
 // GetSecretMap returns multiple k/v pairs from the provider.
 func (sm *SecretsManager) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	return map[string][]byte{
-		"noop": []byte("NOOP"),
-	}, nil
+	log.Info("fetching secret map", "key", ref.Key)
+	data, err := sm.GetSecret(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+	kv := make(map[string]string)
+	err = json.Unmarshal(data, &kv)
+	if err != nil {
+		return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
+	}
+	secretData := make(map[string][]byte)
+	for k, v := range kv {
+		secretData[k] = []byte(v)
+	}
+	return secretData, nil
 }
 
 func init() {

+ 679 - 0
pkg/provider/aws/secretsmanager/secretsmanager_test.go

@@ -1 +1,680 @@
+/*
+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 (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
+	"github.com/aws/aws-sdk-go/aws/session"
+	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/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"
+
+	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 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{},
+				},
+			},
+		},
+		{
+			name: "configure aws using environment variables",
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{},
+					},
+				},
+			},
+			env: map[string]string{
+				"AWS_ACCESS_KEY_ID":     "1111",
+				"AWS_SECRET_ACCESS_KEY": "2222",
+			},
+			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
+					},
+				}
+			},
+			store: &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWSSM: &esv1alpha1.AWSSMProvider{
+							Role: "foo-bar-baz",
+						},
+					},
+				},
+			},
+			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",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			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",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "onesecret",
+						Namespace: "foo",
+					},
+					Data: map[string][]byte{
+						"one": []byte("1111"),
+						"two": []byte("2222"),
+					},
+				},
+			},
+			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",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "brokensecret",
+						Namespace: "foo",
+					},
+					Data: map[string][]byte{},
+				},
+			},
+			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",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "onesecret",
+						Namespace: "evil",
+					},
+					Data: map[string][]byte{
+						"one": []byte("1111"),
+						"two": []byte("2222"),
+					},
+				},
+			},
+			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,
+				},
+				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",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			secrets: []v1.Secret{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "onesecret",
+						Namespace: "platform-team-ns",
+					},
+					Data: map[string][]byte{
+						"one": []byte("1111"),
+						"two": []byte("2222"),
+					},
+				},
+			},
+			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,
+				},
+				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",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			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)
+		})
+	}
+}
+
+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 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)
+	}
+	for k, v := range row.env {
+		os.Setenv(k, v)
+	}
+	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)
+}
+
+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 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")
+}
+
+// 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))
+		}
+	}
+}
+
+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 ErrorContains(out error, want string) bool {
+	if out == nil {
+		return want == ""
+	}
+	if want == "" {
+		return false
+	}
+	return strings.Contains(out.Error(), want)
+}