Browse Source

Merge pull request #251 from external-secrets/feature/aws-jwt

feat(aws): add jwt authentication
paul-the-alien[bot] 4 years ago
parent
commit
8827e3ab92
34 changed files with 1451 additions and 843 deletions
  1. 2 2
      .github/workflows/e2e.yml
  2. 14 5
      apis/externalsecrets/v1alpha1/secretstore_aws_types.go
  3. 30 5
      apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go
  4. 6 0
      deploy/charts/external-secrets/templates/rbac.yaml
  5. 22 4
      deploy/crds/external-secrets.io_clustersecretstores.yaml
  6. 22 4
      deploy/crds/external-secrets.io_secretstores.yaml
  7. BIN
      docs/pictures/diagrams-provider-aws-auth-pod-identity.png
  8. BIN
      docs/pictures/diagrams-provider-aws-auth-secret-ref.png
  9. BIN
      docs/pictures/diagrams-provider-aws-auth-service-account.png
  10. 1 1
      docs/pictures/diagrams.drawio
  11. 81 5
      docs/snippets/provider-aws-access.md
  12. 47 2
      docs/spec.md
  13. 1 1
      e2e/Makefile
  14. 4 0
      e2e/k8s/eso.values.yaml
  15. 2 0
      e2e/k8s/localstack.values.yaml
  16. 8 0
      e2e/kind.yaml
  17. 4 4
      e2e/suite/aws/provider.go
  18. 87 1
      e2e/suite/aws/secretsmanager.go
  19. 231 0
      pkg/provider/aws/auth/auth.go
  20. 534 0
      pkg/provider/aws/auth/auth_test.go
  21. 25 1
      pkg/provider/aws/session/fake/assumeroler.go
  22. 54 0
      pkg/provider/aws/auth/resolver.go
  23. 58 0
      pkg/provider/aws/auth/resolver_test.go
  24. 50 0
      pkg/provider/aws/auth/token_fetcher.go
  25. 63 0
      pkg/provider/aws/auth/token_fetcher_test.go
  26. 0 10
      pkg/provider/aws/parameterstore/parameterstore_test.go
  27. 8 136
      pkg/provider/aws/provider.go
  28. 2 481
      pkg/provider/aws/provider_test.go
  29. 0 10
      pkg/provider/aws/secretsmanager/secretsmanager_test.go
  30. 0 71
      pkg/provider/aws/session/session.go
  31. 0 91
      pkg/provider/aws/session/session_test.go
  32. 6 9
      pkg/provider/aws/util/errors.go
  33. 42 0
      pkg/provider/aws/util/errors_test.go
  34. 47 0
      pkg/provider/aws/util/provider.go

+ 2 - 2
.github/workflows/e2e.yml

@@ -68,7 +68,7 @@ jobs:
       with:
         version: "v0.11.1"
         wait: 10m
-        node_image: kindest/node:v1.20.2
+        node_image: kindest/node:v1.20.7
         name: external-secrets
 
     - name: Run e2e Tests
@@ -125,7 +125,7 @@ jobs:
       with:
         version: "v0.11.1"
         wait: 10m
-        node_image: kindest/node:v1.20.2
+        node_image: kindest/node:v1.20.7
         name: external-secrets
 
     - name: Run e2e Tests

+ 14 - 5
apis/externalsecrets/v1alpha1/secretstore_aws_types.go

@@ -18,12 +18,17 @@ import (
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 )
 
-// AWSAuth contains a secretRef for credentials.
+// AWSAuth tells the controller how to do authentication with aws.
+// Only one of secretRef or jwt can be specified.
+// if none is specified the controller will load credentials using the aws sdk defaults.
 type AWSAuth struct {
-	SecretRef AWSAuthSecretRef `json:"secretRef"`
+	// +optional
+	SecretRef *AWSAuthSecretRef `json:"secretRef,omitempty"`
+	// +optional
+	JWTAuth *AWSJWTAuth `json:"jwt,omitempty"`
 }
 
-// AWSAuthSecretRef holds secret references for aws credentials
+// AWSAuthSecretRef holds secret references for AWS credentials
 // both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
 type AWSAuthSecretRef struct {
 	// The AccessKeyID is used for authentication
@@ -33,6 +38,11 @@ type AWSAuthSecretRef struct {
 	SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
 }
 
+// Authenticate against AWS using service account tokens.
+type AWSJWTAuth struct {
+	ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
+}
+
 // AWSServiceType is a enum that defines the service/API that is used to fetch the secrets.
 // +kubebuilder:validation:Enum=SecretsManager;ParameterStore
 type AWSServiceType string
@@ -54,9 +64,8 @@ type AWSProvider struct {
 	// 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
 	// +optional
-	Auth *AWSAuth `json:"auth"`
+	Auth AWSAuth `json:"auth"`
 
 	// Role is a Role ARN which the SecretManager provider will assume
 	// +optional

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

@@ -27,7 +27,16 @@ import (
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AWSAuth) DeepCopyInto(out *AWSAuth) {
 	*out = *in
-	in.SecretRef.DeepCopyInto(&out.SecretRef)
+	if in.SecretRef != nil {
+		in, out := &in.SecretRef, &out.SecretRef
+		*out = new(AWSAuthSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.JWTAuth != nil {
+		in, out := &in.JWTAuth, &out.JWTAuth
+		*out = new(AWSJWTAuth)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSAuth.
@@ -58,15 +67,31 @@ func (in *AWSAuthSecretRef) DeepCopy() *AWSAuthSecretRef {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AWSProvider) DeepCopyInto(out *AWSProvider) {
+func (in *AWSJWTAuth) DeepCopyInto(out *AWSJWTAuth) {
 	*out = *in
-	if in.Auth != nil {
-		in, out := &in.Auth, &out.Auth
-		*out = new(AWSAuth)
+	if in.ServiceAccountRef != nil {
+		in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
+		*out = new(metav1.ServiceAccountSelector)
 		(*in).DeepCopyInto(*out)
 	}
 }
 
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSJWTAuth.
+func (in *AWSJWTAuth) DeepCopy() *AWSJWTAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(AWSJWTAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AWSProvider) DeepCopyInto(out *AWSProvider) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSProvider.
 func (in *AWSProvider) DeepCopy() *AWSProvider {
 	if in == nil {

+ 6 - 0
deploy/charts/external-secrets/templates/rbac.yaml

@@ -54,6 +54,12 @@ rules:
   - apiGroups:
     - ""
     resources:
+    - "serviceaccounts/token"
+    verbs:
+    - "create"
+  - apiGroups:
+    - ""
+    resources:
     - "events"
     verbs:
     - "create"

+ 22 - 4
deploy/crds/external-secrets.io_clustersecretstores.yaml

@@ -62,11 +62,31 @@ spec:
                         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:
+                          jwt:
+                            description: Authenticate against AWS using service account
+                              tokens.
+                            properties:
+                              serviceAccountRef:
+                                description: A reference to a ServiceAccount resource.
+                                properties:
+                                  name:
+                                    description: The name of the ServiceAccount resource
+                                      being referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                            type: object
                           secretRef:
                             description: AWSAuthSecretRef holds secret references
-                              for aws credentials both AccessKeyID and SecretAccessKey
+                              for AWS credentials both AccessKeyID and SecretAccessKey
                               must be defined in order to properly authenticate.
                             properties:
                               accessKeyIDSecretRef:
@@ -114,8 +134,6 @@ spec:
                                 - name
                                 type: object
                             type: object
-                        required:
-                        - secretRef
                         type: object
                       region:
                         description: AWS Region to be used for the provider

+ 22 - 4
deploy/crds/external-secrets.io_secretstores.yaml

@@ -62,11 +62,31 @@ spec:
                         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:
+                          jwt:
+                            description: Authenticate against AWS using service account
+                              tokens.
+                            properties:
+                              serviceAccountRef:
+                                description: A reference to a ServiceAccount resource.
+                                properties:
+                                  name:
+                                    description: The name of the ServiceAccount resource
+                                      being referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                            type: object
                           secretRef:
                             description: AWSAuthSecretRef holds secret references
-                              for aws credentials both AccessKeyID and SecretAccessKey
+                              for AWS credentials both AccessKeyID and SecretAccessKey
                               must be defined in order to properly authenticate.
                             properties:
                               accessKeyIDSecretRef:
@@ -114,8 +134,6 @@ spec:
                                 - name
                                 type: object
                             type: object
-                        required:
-                        - secretRef
                         type: object
                       region:
                         description: AWS Region to be used for the provider

BIN
docs/pictures/diagrams-provider-aws-auth-pod-identity.png


BIN
docs/pictures/diagrams-provider-aws-auth-secret-ref.png


BIN
docs/pictures/diagrams-provider-aws-auth-service-account.png


File diff suppressed because it is too large
+ 1 - 1
docs/pictures/diagrams.drawio


+ 81 - 5
docs/snippets/provider-aws-access.md

@@ -1,9 +1,85 @@
 ## AWS Authentication
 
-Access to AWS providers can be granted in various ways:
+### Controller's Pod Identity
 
-* [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html): IAM roles for service accounts.
-* Per pod IAM authentication: [kiam](https://github.com/uswitch/kiam) or [kube2iam](https://github.com/jtblin/kube2iam).
-* Directly provide AWS credentials to the External Secrets Operator pod by using environment variables.
+![Pod Identity Authentication](./pictures/diagrams-provider-aws-auth-pod-identity.png)
 
-Additionally, before fetching a secret from a store, ESO is able to assume role (as a proxy so to speak). It is advisable to use multiple roles in a multi-tenant environment.
+This is basicially a zero-configuration authentication method that inherits the credentials from the runtime environment using the [aws sdk default credential chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default).
+
+You can attach a role to the pod using [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html), [kiam](https://github.com/uswitch/kiam) or [kube2iam](https://github.com/jtblin/kube2iam). When no other authentication method is configured in the `Kind=Secretstore` this role is used to make all API calls against AWS Secrets Manager or SSM Parameter Store.
+
+Based on the Pod's identity you can do a `sts:assumeRole` before fetching the secrets to limit access to certain keys in your provider. This is optional.
+
+```yaml
+apiVersion: external-secrets.io/v1alpha1
+kind: SecretStore
+metadata:
+  name: team-b-store
+spec:
+  provider:
+    aws:
+      service: SecretsManager
+      # optional: do a sts:assumeRole before fetching secrets
+      role: team-b
+```
+
+### Access Key ID & Secret Access Key
+![SecretRef](./pictures/diagrams-provider-aws-auth-secret-ref.png)
+
+You can store Access Key ID & Secret Access Key in a `Kind=Secret` and reference it from a SecretStore.
+
+```yaml
+apiVersion: external-secrets.io/v1alpha1
+kind: SecretStore
+metadata:
+  name: team-b-store
+spec:
+  provider:
+    aws:
+      service: SecretsManager
+      # optional: assume role before fetching secrets
+      role: team-b
+      auth:
+        secretRef:
+          accessKeyIDSecretRef:
+            name: awssm-secret
+            key: access-key
+          secretAccessKeySecretRef:
+            name: awssm-secret
+            key: secret-access-key
+```
+
+### EKS Service Account credentials
+
+![Service Account](./pictures/diagrams-provider-aws-auth-service-account.png)
+
+This feature lets you use short-lived service account tokens to authenticate with AWS.
+You must have [Service Account Volume Projection](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection) enabled - it is by default on EKS. See [EKS guide](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html) on how to set up IAM roles for service accounts.
+
+The big advantage of this approach is that ESO runs without any credentials.
+
+```yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  annotations:
+    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/team-a
+  name: my-serviceaccount
+  namespace: default
+```
+
+Reference the service account from above in the Secret Store:
+```yaml
+apiVersion: external-secrets.io/v1alpha1
+kind: SecretStore
+metadata:
+  name: secretstore-sample
+spec:
+  provider:
+    aws:
+      service: SecretsManager
+      auth:
+        jwt:
+          serviceAccountRef:
+            name: my-serviceaccount
+```

+ 47 - 2
docs/spec.md

@@ -17,7 +17,9 @@ Resource Types:
 <a href="#external-secrets.io/v1alpha1.AWSProvider">AWSProvider</a>)
 </p>
 <p>
-<p>AWSAuth contains a secretRef for credentials.</p>
+<p>AWSAuth tells the controller how to do authentication with aws.
+Only one of secretRef or jwt can be specified.
+if none is specified the controller will load credentials using the aws sdk defaults</p>
 </p>
 <table>
 <thead>
@@ -37,6 +39,20 @@ AWSAuthSecretRef
 </em>
 </td>
 <td>
+<em>(Optional)</em>
+</td>
+</tr>
+<tr>
+<td>
+<code>jwt</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.AWSJWTAuth">
+AWSJWTAuth
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
 </td>
 </tr>
 </tbody>
@@ -48,7 +64,7 @@ AWSAuthSecretRef
 <a href="#external-secrets.io/v1alpha1.AWSAuth">AWSAuth</a>)
 </p>
 <p>
-<p>AWSAuthSecretRef holds secret references for aws credentials
+<p>AWSAuthSecretRef holds secret references for AWS credentials
 both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.</p>
 </p>
 <table>
@@ -83,6 +99,35 @@ github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1alpha1.AWSJWTAuth">AWSJWTAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.AWSAuth">AWSAuth</a>)
+</p>
+<p>
+<p>Authenticate against AWS using service account tokens</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>serviceAccountRef</code></br>
+<em>
+github.com/external-secrets/external-secrets/apis/meta/v1.ServiceAccountSelector
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1alpha1.AWSProvider">AWSProvider
 </h3>
 <p>

+ 1 - 1
e2e/Makefile

@@ -4,7 +4,7 @@ SHELL       := /bin/bash
 
 IMG_TAG     = test
 IMG         = local/external-secrets-e2e:$(IMG_TAG)
-K8S_VERSION = "1.19.1"
+K8S_VERSION = "1.20.7"
 export FOCUS := $(FOCUS)
 
 start-kind: ## Start kind cluster

+ 4 - 0
e2e/k8s/eso.values.yaml

@@ -5,3 +5,7 @@ image:
 extraEnv:
   - name: AWS_SECRETSMANAGER_ENDPOINT
     value: "http://localstack.default"
+  - name: AWS_STS_ENDPOINT
+    value: "http://localstack.default"
+  - name: AWS_SSM_ENDPOINT
+    value: "http://localstack.default"

+ 2 - 0
e2e/k8s/localstack.values.yaml

@@ -1,3 +1,5 @@
+image:
+  tag: "0.12.14"
 service:
   type: ClusterIP
   edgeService:

+ 8 - 0
e2e/kind.yaml

@@ -2,6 +2,14 @@ kind: Cluster
 apiVersion: kind.x-k8s.io/v1alpha4
 kubeadmConfigPatches:
 - |
+  kind: ClusterConfiguration
+  apiServer:
+    extraArgs:
+      api-audiences: "sts.amazonaws.com"
+      service-account-key-file: "/etc/kubernetes/pki/sa.pub"
+      service-account-signing-key-file: "/etc/kubernetes/pki/sa.key"
+      service-account-issuer: "https://s3-XXXXXXXXXX.amazonaws.com/XXXXXXXXXXXXXXXXXXXXX"
+- |
   apiVersion: kubelet.config.k8s.io/v1beta1
   kind: KubeletConfiguration
   metadata:

+ 4 - 4
e2e/suite/aws/provider.go

@@ -32,7 +32,7 @@ import (
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	prov "github.com/external-secrets/external-secrets/pkg/provider/aws"
+	"github.com/external-secrets/external-secrets/pkg/provider/aws/auth"
 )
 
 type SMProvider struct {
@@ -45,7 +45,7 @@ func newSMProvider(f *framework.Framework, url string) *SMProvider {
 	sess, err := session.NewSessionWithOptions(session.Options{
 		Config: aws.Config{
 			Credentials: credentials.NewStaticCredentials("foobar", "foobar", "secret-manager"),
-			EndpointResolver: prov.ResolveEndpointWithServiceMap(map[string]string{
+			EndpointResolver: auth.ResolveEndpointWithServiceMap(map[string]string{
 				"secretsmanager": url,
 			}),
 			Region: aws.String("eu-east-1"),
@@ -103,8 +103,8 @@ func (s *SMProvider) BeforeEach() {
 				AWS: &esv1alpha1.AWSProvider{
 					Service: esv1alpha1.AWSServiceSecretsManager,
 					Region:  "us-east-1",
-					Auth: &esv1alpha1.AWSAuth{
-						SecretRef: esv1alpha1.AWSAuthSecretRef{
+					Auth: esv1alpha1.AWSAuth{
+						SecretRef: &esv1alpha1.AWSAuthSecretRef{
 							AccessKeyID: esmeta.SecretKeySelector{
 								Name: "provider-secret",
 								Key:  "kid",

+ 87 - 1
e2e/suite/aws/secretsmanager.go

@@ -14,26 +14,112 @@ limitations under the License.
 package aws
 
 import (
+	"context"
+	"fmt"
 
 	// nolint
 	. "github.com/onsi/ginkgo"
+
 	// nolint
 	. "github.com/onsi/ginkgo/extensions/table"
 
+	// nolint
+	. "github.com/onsi/gomega"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
 	"github.com/external-secrets/external-secrets/e2e/suite/common"
 )
 
 var _ = Describe("[aws] ", func() {
 	f := framework.New("eso-aws")
+	prov := newSMProvider(f, "http://localstack.default")
+
+	jwt := func(tc *framework.TestCase) {
+		saName := "my-sa"
+		err := f.CRClient.Create(context.Background(), &v1.ServiceAccount{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      saName,
+				Namespace: f.Namespace.Name,
+				Annotations: map[string]string{
+					"eks.amazonaws.com/role-arn": "arn:aws:iam::account:role/my-example-role",
+				},
+			},
+		})
+		Expect(err).ToNot(HaveOccurred())
+
+		// create secret store
+		secretStore := &esv1alpha1.SecretStore{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       esv1alpha1.SecretStoreKind,
+				APIVersion: esv1alpha1.SchemeGroupVersion.String(),
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      f.Namespace.Name,
+				Namespace: f.Namespace.Name,
+			},
+			Spec: esv1alpha1.SecretStoreSpec{
+				Provider: &esv1alpha1.SecretStoreProvider{
+					AWS: &esv1alpha1.AWSProvider{
+						Service: esv1alpha1.AWSServiceSecretsManager,
+						Region:  "us-east-1",
+						Auth: esv1alpha1.AWSAuth{
+							JWTAuth: &esv1alpha1.AWSJWTAuth{
+								ServiceAccountRef: &esmeta.ServiceAccountSelector{
+									Name:      saName,
+									Namespace: &f.Namespace.Name,
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		err = f.CRClient.Patch(context.Background(), secretStore, client.Apply, client.FieldOwner("e2e-case"), client.ForceOwnership)
+		Expect(err).ToNot(HaveOccurred())
+
+		secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
+		secretKey2 := fmt.Sprintf("%s-%s", f.Namespace.Name, "other")
+		secretValue := "bar"
+		tc.Secrets = map[string]string{
+			secretKey1: secretValue,
+			secretKey2: secretValue,
+		}
+		tc.ExpectedSecret = &v1.Secret{
+			Type: v1.SecretTypeOpaque,
+			Data: map[string][]byte{
+				secretKey1: []byte(secretValue),
+				secretKey2: []byte(secretValue),
+			},
+		}
+		tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
+			{
+				SecretKey: secretKey1,
+				RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+					Key: secretKey1,
+				},
+			},
+			{
+				SecretKey: secretKey2,
+				RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+					Key: secretKey2,
+				},
+			},
+		}
+	}
 
 	DescribeTable("sync secrets",
 		framework.TableFunc(f,
-			newSMProvider(f, "http://localstack.default")),
+			prov),
 		Entry(common.SimpleDataSync(f)),
 		Entry(common.NestedJSONWithGJSON(f)),
 		Entry(common.JSONDataFromSync(f)),
 		Entry(common.JSONDataWithProperty(f)),
 		Entry(common.JSONDataWithTemplate(f)),
+		Entry("should sync secrets with jwt auth", jwt),
 	)
 })

+ 231 - 0
pkg/provider/aws/auth/auth.go

@@ -0,0 +1,231 @@
+/*
+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 auth
+
+import (
+	"context"
+	"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/defaults"
+	"github.com/aws/aws-sdk-go/aws/request"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/sts"
+	"github.com/aws/aws-sdk-go/service/sts/stsiface"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/kubernetes"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
+)
+
+// 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")
+
+const (
+	roleARNAnnotation = "eks.amazonaws.com/role-arn"
+
+	errInvalidClusterStoreMissingAKIDNamespace = "invalid ClusterSecretStore: missing AWS AccessKeyID Namespace"
+	errInvalidClusterStoreMissingSAKNamespace  = "invalid ClusterSecretStore: missing AWS SecretAccessKey Namespace"
+	errFetchAKIDSecret                         = "could not fetch accessKeyID secret: %w"
+	errFetchSAKSecret                          = "could not fetch SecretAccessKey secret: %w"
+	errMissingSAK                              = "missing SecretAccessKey"
+	errMissingAKID                             = "missing AccessKeyID"
+)
+
+// New creates a new aws session based on the provided store
+// it uses the following authentication mechanisms in order:
+// * service-account token authentication via AssumeRoleWithWebIdentity
+// * static credentials from a Kind=Secret, optionally with doing a AssumeRole.
+// * sdk default provider chain, see: https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default
+func New(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string, assumeRoler STSProvider, jwtProvider jwtProviderFactory) (*session.Session, error) {
+	prov, err := util.GetAWSProvider(store)
+	if err != nil {
+		return nil, err
+	}
+	var creds *credentials.Credentials
+
+	// use credentials via service account token
+	jwtAuth := prov.Auth.JWTAuth
+	if jwtAuth != nil {
+		creds, err = sessionFromServiceAccount(ctx, prov, store, kube, namespace, jwtProvider)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// use credentials from sercretRef
+	secretRef := prov.Auth.SecretRef
+	if secretRef != nil {
+		log.V(1).Info("using credentials from secretRef")
+		creds, err = sessionFromSecretRef(ctx, prov, store, kube, namespace)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	config := aws.NewConfig().WithEndpointResolver(ResolveEndpoint())
+	if creds != nil {
+		config.WithCredentials(creds)
+	}
+	if prov.Region != "" {
+		config.WithRegion(prov.Region)
+	}
+	handlers := defaults.Handlers()
+	handlers.Build.PushBack(request.WithAppendUserAgent("external-secrets"))
+	sess, err := session.NewSessionWithOptions(session.Options{
+		Config:            *config,
+		Handlers:          handlers,
+		SharedConfigState: session.SharedConfigDisable,
+	})
+	if err != nil {
+		return nil, err
+	}
+	if prov.Role != "" {
+		stsclient := assumeRoler(sess)
+		sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, prov.Role))
+	}
+	log.Info("using aws session", "region", *sess.Config.Region, "credentials", creds)
+	return sess, nil
+}
+
+func sessionFromSecretRef(ctx context.Context, prov *esv1alpha1.AWSProvider, store esv1alpha1.GenericStore, kube client.Client, namespace string) (*credentials.Credentials, error) {
+	ke := client.ObjectKey{
+		Name:      prov.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 prov.Auth.SecretRef.AccessKeyID.Namespace == nil {
+			return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
+		}
+		ke.Namespace = *prov.Auth.SecretRef.AccessKeyID.Namespace
+	}
+	akSecret := v1.Secret{}
+	err := kube.Get(ctx, ke, &akSecret)
+	if err != nil {
+		return nil, fmt.Errorf(errFetchAKIDSecret, err)
+	}
+	ke = client.ObjectKey{
+		Name:      prov.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 prov.Auth.SecretRef.SecretAccessKey.Namespace == nil {
+			return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
+		}
+		ke.Namespace = *prov.Auth.SecretRef.SecretAccessKey.Namespace
+	}
+	sakSecret := v1.Secret{}
+	err = kube.Get(ctx, ke, &sakSecret)
+	if err != nil {
+		return nil, fmt.Errorf(errFetchSAKSecret, err)
+	}
+	sak := string(sakSecret.Data[prov.Auth.SecretRef.SecretAccessKey.Key])
+	aks := string(akSecret.Data[prov.Auth.SecretRef.AccessKeyID.Key])
+	if sak == "" {
+		return nil, fmt.Errorf(errMissingSAK)
+	}
+	if aks == "" {
+		return nil, fmt.Errorf(errMissingAKID)
+	}
+
+	return credentials.NewStaticCredentials(aks, sak, ""), err
+}
+
+func sessionFromServiceAccount(ctx context.Context, prov *esv1alpha1.AWSProvider, store esv1alpha1.GenericStore, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) {
+	if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
+		namespace = *prov.Auth.JWTAuth.ServiceAccountRef.Namespace
+	}
+	name := prov.Auth.JWTAuth.ServiceAccountRef.Name
+	sa := v1.ServiceAccount{}
+	err := kube.Get(ctx, types.NamespacedName{
+		Name:      name,
+		Namespace: namespace,
+	}, &sa)
+	if err != nil {
+		return nil, err
+	}
+	// the service account is expected to have a well-known annotation
+	// this is used as input to assumeRoleWithWebIdentity
+	roleArn := sa.Annotations[roleARNAnnotation]
+	if roleArn == "" {
+		return nil, fmt.Errorf("an IAM role must be associated with service account %s (namespace: %s)", name, namespace)
+	}
+	jwtProv, err := jwtProvider(name, namespace, roleArn, prov.Region)
+	if err != nil {
+		return nil, err
+	}
+
+	log.V(1).Info("using credentials via service account", "role", roleArn, "region", prov.Region)
+	return credentials.NewCredentials(jwtProv), nil
+}
+
+type jwtProviderFactory func(name, namespace, roleArn, region string) (credentials.Provider, error)
+
+// DefaultJWTProvider returns a credentials.Provider that calls the AssumeRoleWithWebidentity
+// controller-runtime/client does not support TokenRequest or other subresource APIs
+// so we need to construct our own client and use it to fetch tokens.
+func DefaultJWTProvider(name, namespace, roleArn, region string) (credentials.Provider, error) {
+	cfg, err := ctrlcfg.GetConfig()
+	if err != nil {
+		return nil, err
+	}
+	clientset, err := kubernetes.NewForConfig(cfg)
+	if err != nil {
+		return nil, err
+	}
+	handlers := defaults.Handlers()
+	handlers.Build.PushBack(request.WithAppendUserAgent("external-secrets"))
+	awscfg := aws.NewConfig().WithEndpointResolver(ResolveEndpoint())
+	if region != "" {
+		awscfg.WithRegion(region)
+	}
+	sess, err := session.NewSessionWithOptions(session.Options{
+		Config:            *awscfg,
+		SharedConfigState: session.SharedConfigDisable,
+		Handlers:          handlers,
+	})
+	if err != nil {
+		return nil, err
+	}
+	tokenFetcher := &authTokenFetcher{
+		Namespace:      namespace,
+		ServiceAccount: name,
+		k8sClient:      clientset.CoreV1(),
+	}
+
+	return stscreds.NewWebIdentityRoleProviderWithToken(
+		sts.New(sess), roleArn, "external-secrets-provider-aws", tokenFetcher), nil
+}
+
+type STSProvider func(*session.Session) stsiface.STSAPI
+
+func DefaultSTSProvider(sess *session.Session) stsiface.STSAPI {
+	return sts.New(sess)
+}

+ 534 - 0
pkg/provider/aws/auth/auth_test.go

@@ -0,0 +1,534 @@
+/*
+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 auth
+
+import (
+	"context"
+	"os"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	awssess "github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/sts"
+	"github.com/aws/aws-sdk-go/service/sts/stsiface"
+	"github.com/stretchr/testify/assert"
+	authv1 "k8s.io/api/authentication/v1"
+	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"
+	fakesess "github.com/external-secrets/external-secrets/pkg/provider/aws/auth/fake"
+)
+
+func TestNewSession(t *testing.T) {
+	rows := []TestSessionRow{
+		{
+			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 AWS 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{
+						AWS: &esv1alpha1.AWSProvider{},
+					},
+				},
+			},
+			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(*awssess.Session) stsiface.STSAPI {
+				return &fakesess.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{
+						AWS: &esv1alpha1.AWSProvider{
+							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{
+						AWS: &esv1alpha1.AWSProvider{
+							Auth: esv1alpha1.AWSAuth{
+								SecretRef: &esv1alpha1.AWSAuthSecretRef{
+									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{
+						AWS: &esv1alpha1.AWSProvider{
+							Auth: esv1alpha1.AWSAuth{
+								SecretRef: &esv1alpha1.AWSAuthSecretRef{
+									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{
+						AWS: &esv1alpha1.AWSProvider{
+							Auth: esv1alpha1.AWSAuth{
+								SecretRef: &esv1alpha1.AWSAuthSecretRef{
+									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{
+						AWS: &esv1alpha1.AWSProvider{
+							Auth: esv1alpha1.AWSAuth{
+								SecretRef: &esv1alpha1.AWSAuthSecretRef{
+									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{
+						AWS: &esv1alpha1.AWSProvider{
+							Auth: esv1alpha1.AWSAuth{
+								SecretRef: &esv1alpha1.AWSAuthSecretRef{
+									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{
+						AWS: &esv1alpha1.AWSProvider{
+							Auth: esv1alpha1.AWSAuth{
+								SecretRef: &esv1alpha1.AWSAuthSecretRef{
+									AccessKeyID: esmeta.SecretKeySelector{
+										Name: "onesecret",
+										Key:  "one",
+									},
+									SecretAccessKey: esmeta.SecretKeySelector{
+										Name: "onesecret",
+										Key:  "two",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectErr: "invalid ClusterSecretStore: missing AWS AccessKeyID Namespace",
+		},
+		{
+			name:      "jwt auth via cluster secret store",
+			namespace: "es-namespace",
+			sa: &v1.ServiceAccount{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "my-service-account",
+					Namespace: "other-ns",
+					Annotations: map[string]string{
+						roleARNAnnotation: "my-sa-role",
+					},
+				},
+			},
+			jwtProvider: func(name, namespace, roleArn, region string) (credentials.Provider, error) {
+				assert.Equal(t, "my-service-account", name)
+				assert.Equal(t, "other-ns", namespace)
+				assert.Equal(t, "my-sa-role", roleArn)
+				return fakesess.CredentialsProvider{
+					RetrieveFunc: func() (credentials.Value, error) {
+						return credentials.Value{
+							AccessKeyID:     "3333",
+							SecretAccessKey: "4444",
+							SessionToken:    "1234",
+							ProviderName:    "fake",
+						}, nil
+					},
+					IsExpiredFunc: func() bool { return false },
+				}, nil
+			},
+			store: &esv1alpha1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
+					Kind:       esv1alpha1.ClusterSecretStoreKind,
+				},
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						AWS: &esv1alpha1.AWSProvider{
+							Auth: esv1alpha1.AWSAuth{
+								JWTAuth: &esv1alpha1.AWSJWTAuth{
+									ServiceAccountRef: &esmeta.ServiceAccountSelector{
+										Name:      "my-service-account",
+										Namespace: aws.String("other-ns"),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectProvider:    true,
+			expectedKeyID:     "3333",
+			expectedSecretKey: "4444",
+		},
+	}
+	for i := range rows {
+		row := rows[i]
+		t.Run(row.name, func(t *testing.T) {
+			testRow(t, row)
+		})
+	}
+}
+
+type TestSessionRow struct {
+	name              string
+	store             esv1alpha1.GenericStore
+	secrets           []v1.Secret
+	sa                *v1.ServiceAccount
+	jwtProvider       jwtProviderFactory
+	namespace         string
+	stsProvider       STSProvider
+	expectProvider    bool
+	expectErr         string
+	expectedKeyID     string
+	expectedSecretKey string
+	env               map[string]string
+}
+
+func testRow(t *testing.T, row TestSessionRow) {
+	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)
+	}
+	if row.sa != nil {
+		err := kc.Create(context.Background(), row.sa)
+		assert.Nil(t, err)
+	}
+	err := kc.Create(context.Background(), &authv1.TokenRequest{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-service-account",
+			Namespace: "other-ns",
+		},
+	})
+	assert.Nil(t, err)
+	defer func() {
+		for k := range row.env {
+			os.Unsetenv(k)
+		}
+	}()
+	s, err := New(context.Background(), row.store, kc, row.namespace, row.stsProvider, row.jwtProvider)
+	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 && s == nil {
+		t.Errorf("expected provider object, found nil")
+		return
+	}
+	creds, _ := s.Config.Credentials.Get()
+	assert.Equal(t, row.expectedKeyID, creds.AccessKeyID)
+	assert.Equal(t, row.expectedSecretKey, creds.SecretAccessKey)
+}
+
+func TestSMEnvCredentials(t *testing.T) {
+	k8sClient := clientfake.NewClientBuilder().Build()
+	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")
+	s, err := New(context.Background(), &esv1alpha1.SecretStore{
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				// defaults
+				AWS: &esv1alpha1.AWSProvider{},
+			},
+		},
+	}, k8sClient, "example-ns", DefaultSTSProvider, nil)
+	assert.Nil(t, err)
+	assert.NotNil(t, s)
+	creds, err := s.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 := &fakesess.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
+		},
+	}
+	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")
+	s, err := New(context.Background(), &esv1alpha1.SecretStore{
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				// do assume role!
+				AWS: &esv1alpha1.AWSProvider{
+					Role: "my-awesome-role",
+				},
+			},
+		},
+	}, k8sClient, "example-ns", func(se *awssess.Session) stsiface.STSAPI {
+		// 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
+	}, nil)
+	assert.Nil(t, err)
+	assert.NotNil(t, s)
+
+	creds, err := s.Config.Credentials.Get()
+	assert.Nil(t, err)
+	assert.Equal(t, creds.AccessKeyID, "3333")
+	assert.Equal(t, creds.SecretAccessKey, "4444")
+}
+
+func ErrorContains(out error, want string) bool {
+	if out == nil {
+		return want == ""
+	}
+	if want == "" {
+		return false
+	}
+	return strings.Contains(out.Error(), want)
+}

+ 25 - 1
pkg/provider/aws/session/fake/assumeroler.go

@@ -13,12 +13,36 @@ limitations under the License.
 */
 package fake
 
-import "github.com/aws/aws-sdk-go/service/sts"
+import (
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/request"
+	"github.com/aws/aws-sdk-go/service/sts"
+	"github.com/aws/aws-sdk-go/service/sts/stsiface"
+)
 
 type AssumeRoler struct {
+	stsiface.STSAPI
 	AssumeRoleFunc func(*sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
 }
 
 func (f *AssumeRoler) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
 	return f.AssumeRoleFunc(input)
 }
+
+func (f *AssumeRoler) AssumeRoleWithContext(ctx aws.Context, input *sts.AssumeRoleInput, opts ...request.Option) (*sts.AssumeRoleOutput, error) {
+	return f.AssumeRoleFunc(input)
+}
+
+type CredentialsProvider struct {
+	RetrieveFunc  func() (credentials.Value, error)
+	IsExpiredFunc func() bool
+}
+
+func (t CredentialsProvider) Retrieve() (credentials.Value, error) {
+	return t.RetrieveFunc()
+}
+
+func (t CredentialsProvider) IsExpired() bool {
+	return t.IsExpiredFunc()
+}

+ 54 - 0
pkg/provider/aws/auth/resolver.go

@@ -0,0 +1,54 @@
+/*
+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 auth
+
+import (
+	"os"
+
+	"github.com/aws/aws-sdk-go/aws/endpoints"
+)
+
+const (
+	SecretsManagerEndpointEnv = "AWS_SECRETSMANAGER_ENDPOINT"
+	STSEndpointEnv            = "AWS_STS_ENDPOINT"
+	SSMEndpointEnv            = "AWS_SSM_ENDPOINT"
+)
+
+// ResolveEndpoint returns a ResolverFunc with
+// customizable endpoints.
+func ResolveEndpoint() endpoints.ResolverFunc {
+	customEndpoints := make(map[string]string)
+	if v := os.Getenv(SecretsManagerEndpointEnv); v != "" {
+		customEndpoints["secretsmanager"] = v
+	}
+	if v := os.Getenv(SSMEndpointEnv); v != "" {
+		customEndpoints["ssm"] = v
+	}
+	if v := os.Getenv(STSEndpointEnv); v != "" {
+		customEndpoints["sts"] = v
+	}
+	return ResolveEndpointWithServiceMap(customEndpoints)
+}
+
+func ResolveEndpointWithServiceMap(customEndpoints map[string]string) endpoints.ResolverFunc {
+	defaultResolver := endpoints.DefaultResolver()
+	return func(service, region string, opts ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
+		if ep, ok := customEndpoints[service]; ok {
+			return endpoints.ResolvedEndpoint{
+				URL: ep,
+			}, nil
+		}
+		return defaultResolver.EndpointFor(service, region, opts...)
+	}
+}

+ 58 - 0
pkg/provider/aws/auth/resolver_test.go

@@ -0,0 +1,58 @@
+/*
+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 auth
+
+import (
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestResolver(t *testing.T) {
+	tbl := []struct {
+		env     string
+		service string
+		url     string
+	}{
+		{
+			env:     SecretsManagerEndpointEnv,
+			service: "secretsmanager",
+			url:     "http://sm.foo",
+		},
+		{
+			env:     SSMEndpointEnv,
+			service: "ssm",
+			url:     "http://ssm.foo",
+		},
+		{
+			env:     STSEndpointEnv,
+			service: "sts",
+			url:     "http://sts.foo",
+		},
+	}
+
+	for _, item := range tbl {
+		os.Setenv(item.env, item.url)
+		defer os.Unsetenv(item.env)
+	}
+
+	f := ResolveEndpoint()
+
+	for _, item := range tbl {
+		ep, err := f.EndpointFor(item.service, "")
+		assert.Nil(t, err)
+		assert.Equal(t, item.url, ep.URL)
+	}
+}

+ 50 - 0
pkg/provider/aws/auth/token_fetcher.go

@@ -0,0 +1,50 @@
+/*
+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 auth
+
+import (
+	"fmt"
+
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	authv1 "k8s.io/api/authentication/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
+)
+
+// mostly taken from:
+// https://github.com/aws/secrets-store-csi-driver-provider-aws/blob/main/auth/auth.go#L140-L145
+const (
+	tokenAudience = "sts.amazonaws.com"
+)
+
+type authTokenFetcher struct {
+	Namespace      string
+	ServiceAccount string
+	k8sClient      corev1.CoreV1Interface
+}
+
+// FetchToken satisfies the stscreds.TokenFetcher interface
+// it is used to generate service account tokens which are consumed by the aws sdk.
+func (p authTokenFetcher) FetchToken(ctx credentials.Context) ([]byte, error) {
+	log.V(1).Info("fetching token", "ns", p.Namespace, "sa", p.ServiceAccount)
+	tokRsp, err := p.k8sClient.ServiceAccounts(p.Namespace).CreateToken(ctx, p.ServiceAccount, &authv1.TokenRequest{
+		Spec: authv1.TokenRequestSpec{
+			Audiences: []string{tokenAudience},
+		},
+	}, metav1.CreateOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("error creating service account token: %w", err)
+	}
+	return []byte(tokRsp.Status.Token), nil
+}

+ 63 - 0
pkg/provider/aws/auth/token_fetcher_test.go

@@ -0,0 +1,63 @@
+/*
+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 auth
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	authv1 "k8s.io/api/authentication/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sv1 "k8s.io/client-go/kubernetes/typed/core/v1"
+)
+
+func TestTokenFetcher(t *testing.T) {
+	tf := &authTokenFetcher{
+		ServiceAccount: "foobar",
+		Namespace:      "example",
+		k8sClient:      &mockK8sV1{},
+	}
+	token, err := tf.FetchToken(context.Background())
+	assert.Nil(t, err)
+	assert.Equal(t, []byte("FAKETOKEN"), token)
+}
+
+// Mock K8s client for creating tokens.
+type mockK8sV1 struct {
+	k8sv1.CoreV1Interface
+}
+
+func (m *mockK8sV1) ServiceAccounts(namespace string) k8sv1.ServiceAccountInterface {
+	return &mockK8sV1SA{v1mock: m}
+}
+
+// Mock the K8s service account client.
+type mockK8sV1SA struct {
+	k8sv1.ServiceAccountInterface
+	v1mock *mockK8sV1
+}
+
+func (ma *mockK8sV1SA) CreateToken(
+	ctx context.Context,
+	serviceAccountName string,
+	tokenRequest *authv1.TokenRequest,
+	opts metav1.CreateOptions,
+) (*authv1.TokenRequest, error) {
+	return &authv1.TokenRequest{
+		Status: authv1.TokenRequestStatus{
+			Token: "FAKETOKEN",
+		},
+	}, nil
+}

+ 0 - 10
pkg/provider/aws/parameterstore/parameterstore_test.go

@@ -22,21 +22,11 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/google/go-cmp/cmp"
-	"github.com/stretchr/testify/assert"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	fake "github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore/fake"
-	sess "github.com/external-secrets/external-secrets/pkg/provider/aws/session"
 )
 
-func TestConstructor(t *testing.T) {
-	s, err := sess.New("1111", "2222", "foo", "", nil)
-	assert.Nil(t, err)
-	c, err := New(s)
-	assert.Nil(t, err)
-	assert.NotNil(t, c.client)
-}
-
 type parameterstoreTestCase struct {
 	fakeClient     *fake.Client
 	apiInput       *ssm.GetParameterInput

+ 8 - 136
pkg/provider/aws/provider.go

@@ -17,57 +17,37 @@ package aws
 import (
 	"context"
 	"fmt"
-	"os"
 
-	"github.com/aws/aws-sdk-go/aws/endpoints"
-	"github.com/aws/aws-sdk-go/aws/session"
-	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"
+	awsauth "github.com/external-secrets/external-secrets/pkg/provider/aws/auth"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager"
-	awssess "github.com/external-secrets/external-secrets/pkg/provider/aws/session"
+	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
 	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
 
 // Provider satisfies the provider interface.
 type Provider struct{}
 
-var log = ctrl.Log.WithName("provider").WithName("aws")
-
 const (
-	SecretsManagerEndpointEnv = "AWS_SECRETSMANAGER_ENDPOINT"
-	STSEndpointEnv            = "AWS_STS_ENDPOINT"
-	SSMEndpointEnv            = "AWS_SSM_ENDPOINT"
-
-	errUnableCreateSession                     = "unable to create session: %w"
-	errUnknownProviderService                  = "unknown AWS Provider Service: %s"
-	errInvalidClusterStoreMissingAKIDNamespace = "invalid ClusterSecretStore: missing AWS AccessKeyID Namespace"
-	errInvalidClusterStoreMissingSAKNamespace  = "invalid ClusterSecretStore: missing AWS SecretAccessKey Namespace"
-	errFetchAKIDSecret                         = "could not fetch accessKeyID secret: %w"
-	errFetchSAKSecret                          = "could not fetch SecretAccessKey secret: %w"
-	errMissingSAK                              = "missing SecretAccessKey"
-	errMissingAKID                             = "missing AccessKeyID"
-	errNilStore                                = "found nil store"
-	errMissingStoreSpec                        = "store is missing spec"
-	errMissingProvider                         = "storeSpec is missing provider"
-	errInvalidProvider                         = "invalid provider spec. Missing AWS field in store %s"
+	errUnableCreateSession    = "unable to create session: %w"
+	errUnknownProviderService = "unknown AWS Provider Service: %s"
 )
 
 // NewClient constructs a new secrets client based on the provided store.
 func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
-	return newClient(ctx, store, kube, namespace, awssess.DefaultSTSProvider)
+	return newClient(ctx, store, kube, namespace, awsauth.DefaultSTSProvider)
 }
 
-func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string, assumeRoler awssess.STSProvider) (provider.SecretsClient, error) {
-	prov, err := getAWSProvider(store)
+func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string, assumeRoler awsauth.STSProvider) (provider.SecretsClient, error) {
+	prov, err := util.GetAWSProvider(store)
 	if err != nil {
 		return nil, err
 	}
-	sess, err := newSession(ctx, store, kube, namespace, assumeRoler)
+	sess, err := awsauth.New(ctx, store, kube, namespace, assumeRoler, awsauth.DefaultJWTProvider)
 	if err != nil {
 		return nil, fmt.Errorf(errUnableCreateSession, err)
 	}
@@ -80,114 +60,6 @@ func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.C
 	return nil, fmt.Errorf(errUnknownProviderService, prov.Service)
 }
 
-// newSession creates a new aws session based on a store
-// it looks up credentials at the provided secrets.
-func newSession(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string, assumeRoler awssess.STSProvider) (*session.Session, error) {
-	prov, err := getAWSProvider(store)
-	if err != nil {
-		return nil, err
-	}
-	var sak, aks string
-	// use provided credentials via secret reference
-	if prov.Auth != nil {
-		log.V(1).Info("fetching secrets for authentication")
-		ke := client.ObjectKey{
-			Name:      prov.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 prov.Auth.SecretRef.AccessKeyID.Namespace == nil {
-				return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
-			}
-			ke.Namespace = *prov.Auth.SecretRef.AccessKeyID.Namespace
-		}
-		akSecret := v1.Secret{}
-		err := kube.Get(ctx, ke, &akSecret)
-		if err != nil {
-			return nil, fmt.Errorf(errFetchAKIDSecret, err)
-		}
-		ke = client.ObjectKey{
-			Name:      prov.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 prov.Auth.SecretRef.SecretAccessKey.Namespace == nil {
-				return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
-			}
-			ke.Namespace = *prov.Auth.SecretRef.SecretAccessKey.Namespace
-		}
-		sakSecret := v1.Secret{}
-		err = kube.Get(ctx, ke, &sakSecret)
-		if err != nil {
-			return nil, fmt.Errorf(errFetchSAKSecret, err)
-		}
-		sak = string(sakSecret.Data[prov.Auth.SecretRef.SecretAccessKey.Key])
-		aks = string(akSecret.Data[prov.Auth.SecretRef.AccessKeyID.Key])
-		if sak == "" {
-			return nil, fmt.Errorf(errMissingSAK)
-		}
-		if aks == "" {
-			return nil, fmt.Errorf(errMissingAKID)
-		}
-	}
-	session, err := awssess.New(sak, aks, prov.Region, prov.Role, assumeRoler)
-	if err != nil {
-		return nil, err
-	}
-	session.Config.EndpointResolver = ResolveEndpoint()
-	return session, nil
-}
-
-// getAWSProvider does the necessary nil checks on the generic store
-// it returns the aws provider or an error.
-func getAWSProvider(store esv1alpha1.GenericStore) (*esv1alpha1.AWSProvider, error) {
-	if store == nil {
-		return nil, fmt.Errorf(errNilStore)
-	}
-	spc := store.GetSpec()
-	if spc == nil {
-		return nil, fmt.Errorf(errMissingStoreSpec)
-	}
-	if spc.Provider == nil {
-		return nil, fmt.Errorf(errMissingProvider)
-	}
-	prov := spc.Provider.AWS
-	if prov == nil {
-		return nil, fmt.Errorf(errInvalidProvider, store.GetObjectMeta().String())
-	}
-	return prov, nil
-}
-
-// ResolveEndpoint returns a ResolverFunc with
-// customizable endpoints.
-func ResolveEndpoint() endpoints.ResolverFunc {
-	customEndpoints := make(map[string]string)
-	if v := os.Getenv(SecretsManagerEndpointEnv); v != "" {
-		customEndpoints["secretsmanager"] = v
-	}
-	if v := os.Getenv(SSMEndpointEnv); v != "" {
-		customEndpoints["ssm"] = v
-	}
-	if v := os.Getenv(STSEndpointEnv); v != "" {
-		customEndpoints["sts"] = v
-	}
-	return ResolveEndpointWithServiceMap(customEndpoints)
-}
-
-func ResolveEndpointWithServiceMap(customEndpoints map[string]string) endpoints.ResolverFunc {
-	defaultResolver := endpoints.DefaultResolver()
-	return func(service, region string, opts ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
-		if ep, ok := customEndpoints[service]; ok {
-			return endpoints.ResolvedEndpoint{
-				URL: ep,
-			}, nil
-		}
-		return defaultResolver.EndpointFor(service, region, opts...)
-	}
-}
-
 func init() {
 	schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
 		AWS: &esv1alpha1.AWSProvider{},

+ 2 - 481
pkg/provider/aws/provider_test.go

@@ -16,26 +16,16 @@ package aws
 
 import (
 	"context"
-	"os"
-	"strings"
 	"testing"
-	"time"
 
 	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
-	awssess "github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/sts"
 	"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"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager"
-	session "github.com/external-secrets/external-secrets/pkg/provider/aws/session"
-	fakesess "github.com/external-secrets/external-secrets/pkg/provider/aws/session/fake"
 )
 
 func TestProvider(t *testing.T) {
@@ -118,8 +108,8 @@ func TestProvider(t *testing.T) {
 					Provider: &esv1alpha1.SecretStoreProvider{
 						AWS: &esv1alpha1.AWSProvider{
 							Service: esv1alpha1.AWSServiceParameterStore,
-							Auth: &esv1alpha1.AWSAuth{
-								SecretRef: esv1alpha1.AWSAuthSecretRef{
+							Auth: esv1alpha1.AWSAuth{
+								SecretRef: &esv1alpha1.AWSAuthSecretRef{
 									AccessKeyID: esmeta.SecretKeySelector{
 										Name:      "foo",
 										Namespace: aws.String("NOOP"),
@@ -147,472 +137,3 @@ func TestProvider(t *testing.T) {
 		})
 	}
 }
-
-func TestNewSession(t *testing.T) {
-	rows := []TestSessionRow{
-		{
-			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 AWS 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{
-						AWS: &esv1alpha1.AWSProvider{},
-					},
-				},
-			},
-			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(*awssess.Session) stscreds.AssumeRoler {
-				return &fakesess.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{
-						AWS: &esv1alpha1.AWSProvider{
-							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{
-						AWS: &esv1alpha1.AWSProvider{
-							Auth: &esv1alpha1.AWSAuth{
-								SecretRef: esv1alpha1.AWSAuthSecretRef{
-									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{
-						AWS: &esv1alpha1.AWSProvider{
-							Auth: &esv1alpha1.AWSAuth{
-								SecretRef: esv1alpha1.AWSAuthSecretRef{
-									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{
-						AWS: &esv1alpha1.AWSProvider{
-							Auth: &esv1alpha1.AWSAuth{
-								SecretRef: esv1alpha1.AWSAuthSecretRef{
-									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{
-						AWS: &esv1alpha1.AWSProvider{
-							Auth: &esv1alpha1.AWSAuth{
-								SecretRef: esv1alpha1.AWSAuthSecretRef{
-									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{
-						AWS: &esv1alpha1.AWSProvider{
-							Auth: &esv1alpha1.AWSAuth{
-								SecretRef: esv1alpha1.AWSAuthSecretRef{
-									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{
-						AWS: &esv1alpha1.AWSProvider{
-							Auth: &esv1alpha1.AWSAuth{
-								SecretRef: esv1alpha1.AWSAuthSecretRef{
-									AccessKeyID: esmeta.SecretKeySelector{
-										Name: "onesecret",
-										Key:  "one",
-									},
-									SecretAccessKey: esmeta.SecretKeySelector{
-										Name: "onesecret",
-										Key:  "two",
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-			expectErr: "invalid ClusterSecretStore: missing AWS AccessKeyID Namespace",
-		},
-	}
-	for i := range rows {
-		row := rows[i]
-		t.Run(row.name, func(t *testing.T) {
-			testRow(t, row)
-		})
-	}
-}
-
-type TestSessionRow struct {
-	name              string
-	store             esv1alpha1.GenericStore
-	secrets           []v1.Secret
-	namespace         string
-	stsProvider       session.STSProvider
-	expectProvider    bool
-	expectErr         string
-	expectedKeyID     string
-	expectedSecretKey string
-	env               map[string]string
-}
-
-func testRow(t *testing.T, row TestSessionRow) {
-	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)
-		}
-	}()
-	s, err := newSession(context.Background(), row.store, kc, row.namespace, row.stsProvider)
-	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 && s == nil {
-		t.Errorf("expected provider object, found nil")
-		return
-	}
-	creds, _ := s.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()
-	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")
-	s, err := newSession(context.Background(), &esv1alpha1.SecretStore{
-		Spec: esv1alpha1.SecretStoreSpec{
-			Provider: &esv1alpha1.SecretStoreProvider{
-				// defaults
-				AWS: &esv1alpha1.AWSProvider{},
-			},
-		},
-	}, k8sClient, "example-ns", session.DefaultSTSProvider)
-	assert.Nil(t, err)
-	assert.NotNil(t, s)
-	creds, err := s.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 := &fakesess.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
-		},
-	}
-	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")
-	s, err := newSession(context.Background(), &esv1alpha1.SecretStore{
-		Spec: esv1alpha1.SecretStoreSpec{
-			Provider: &esv1alpha1.SecretStoreProvider{
-				// do assume role!
-				AWS: &esv1alpha1.AWSProvider{
-					Role: "my-awesome-role",
-				},
-			},
-		},
-	}, k8sClient, "example-ns", func(se *awssess.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
-	})
-	assert.Nil(t, err)
-	assert.NotNil(t, s)
-
-	creds, err := s.Config.Credentials.Get()
-	assert.Nil(t, err)
-	assert.Equal(t, creds.AccessKeyID, "3333")
-	assert.Equal(t, creds.SecretAccessKey, "4444")
-}
-
-func TestResolver(t *testing.T) {
-	tbl := []struct {
-		env     string
-		service string
-		url     string
-	}{
-		{
-			env:     SecretsManagerEndpointEnv,
-			service: "secretsmanager",
-			url:     "http://sm.foo",
-		},
-		{
-			env:     SSMEndpointEnv,
-			service: "ssm",
-			url:     "http://ssm.foo",
-		},
-		{
-			env:     STSEndpointEnv,
-			service: "sts",
-			url:     "http://sts.foo",
-		},
-	}
-
-	for _, item := range tbl {
-		os.Setenv(item.env, item.url)
-		defer os.Unsetenv(item.env)
-	}
-
-	f := ResolveEndpoint()
-
-	for _, item := range tbl {
-		ep, err := f.EndpointFor(item.service, "")
-		assert.Nil(t, err)
-		assert.Equal(t, item.url, ep.URL)
-	}
-}
-
-func ErrorContains(out error, want string) bool {
-	if out == nil {
-		return want == ""
-	}
-	if want == "" {
-		return false
-	}
-	return strings.Contains(out.Error(), want)
-}

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

@@ -22,21 +22,11 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
 	"github.com/google/go-cmp/cmp"
-	"github.com/stretchr/testify/assert"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	fakesm "github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager/fake"
-	sess "github.com/external-secrets/external-secrets/pkg/provider/aws/session"
 )
 
-func TestConstructor(t *testing.T) {
-	s, err := sess.New("1111", "2222", "foo", "", nil)
-	assert.Nil(t, err)
-	c, err := New(s)
-	assert.Nil(t, err)
-	assert.NotNil(t, c.client)
-}
-
 type secretsManagerTestCase struct {
 	fakeClient     *fakesm.Client
 	apiInput       *awssm.GetSecretValueInput

+ 0 - 71
pkg/provider/aws/session/session.go

@@ -1,71 +0,0 @@
-/*
-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 session
-
-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"
-	awssess "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")
-
-// New 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 New(sak, aks, region, role string, stsprovider STSProvider) (*awssess.Session, error) {
-	config := aws.NewConfig()
-	sessionOpts := awssess.Options{
-		Config: *config,
-	}
-	if sak != "" && aks != "" {
-		sessionOpts.Config.Credentials = credentials.NewStaticCredentials(aks, sak, "")
-		sessionOpts.SharedConfigState = awssess.SharedConfigDisable
-	}
-	sess, err := awssess.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(*awssess.Session) stscreds.AssumeRoler
-
-func DefaultSTSProvider(sess *awssess.Session) stscreds.AssumeRoler {
-	return sts.New(sess)
-}

+ 0 - 91
pkg/provider/aws/session/session_test.go

@@ -1,91 +0,0 @@
-/*
-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 session
-
-import (
-	"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"
-	"github.com/aws/aws-sdk-go/service/sts"
-	"github.com/stretchr/testify/assert"
-
-	fakesess "github.com/external-secrets/external-secrets/pkg/provider/aws/session/fake"
-)
-
-func TestSession(t *testing.T) {
-	tbl := []struct {
-		test              string
-		aks               string
-		sak               string
-		region            string
-		role              string
-		sts               STSProvider
-		expectedKeyID     string
-		expectedSecretKey string
-	}{
-		{
-			test:              "test default role provider",
-			aks:               "2222",
-			sak:               "1111",
-			region:            "xxxxx",
-			role:              "",
-			sts:               DefaultSTSProvider,
-			expectedSecretKey: "1111",
-			expectedKeyID:     "2222",
-		},
-		{
-			test:   "test custom sts provider",
-			aks:    "1111",
-			sak:    "2222",
-			region: "xxxxx",
-			role:   "zzzzz",
-			sts: func(*session.Session) stscreds.AssumeRoler {
-				return &fakesess.AssumeRoler{
-					AssumeRoleFunc: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
-						assert.Equal(t, *input.RoleArn, "zzzzz")
-						return &sts.AssumeRoleOutput{
-							AssumedRoleUser: &sts.AssumedRoleUser{
-								Arn:           aws.String("1123132"),
-								AssumedRoleId: aws.String("xxxxx"),
-							},
-							Credentials: &sts.Credentials{
-								SecretAccessKey: aws.String("3333"),
-								AccessKeyId:     aws.String("4444"),
-								Expiration:      aws.Time(time.Now().Add(time.Hour)),
-								SessionToken:    aws.String("6666"),
-							},
-						}, nil
-					},
-				}
-			},
-			expectedSecretKey: "3333",
-			expectedKeyID:     "4444",
-		},
-	}
-	for i := range tbl {
-		row := tbl[i]
-		t.Run(row.test, func(t *testing.T) {
-			sess, err := New(row.sak, row.aks, row.region, row.role, row.sts)
-			assert.Nil(t, err)
-			creds, err := sess.Config.Credentials.Get()
-			assert.Nil(t, err)
-			assert.Equal(t, row.expectedKeyID, creds.AccessKeyID)
-			assert.Equal(t, row.expectedSecretKey, creds.SecretAccessKey)
-		})
-	}
-}

+ 6 - 9
pkg/provider/aws/util/errors.go

@@ -16,17 +16,14 @@ package util
 
 import (
 	"errors"
-	"fmt"
-
-	"github.com/aws/aws-sdk-go/aws/awserr"
+	"regexp"
 )
 
-// SanitizeErr removes sanitizes the error string
+var regexReqID = regexp.MustCompile(`request id: (\S+)`)
+
+// SanitizeErr sanitizes the error string
 // because the requestID must not be included in the error.
+// otherwise the secrets keeps syncing.
 func SanitizeErr(err error) error {
-	var bErr awserr.BatchedErrors
-	if errors.As(bErr, &bErr) {
-		return fmt.Errorf("%s: %s", bErr.Code(), bErr.Message())
-	}
-	return err
+	return errors.New(string(regexReqID.ReplaceAll([]byte(err.Error()), nil)))
 }

+ 42 - 0
pkg/provider/aws/util/errors_test.go

@@ -0,0 +1,42 @@
+/*
+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 util
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSanitize(t *testing.T) {
+	tbl := []struct {
+		err      error
+		expected string
+	}{
+		{
+			err:      errors.New("some AccessDeniedException: User: arn:aws:sts::123123123123:assumed-role/foobar is not authorized to perform: secretsmanager:GetSecretValue on resource: example\n\tstatus code: 400, request id: df34-75f-0c5f-4b4c-a71a-f93d581d177c"),
+			expected: "some AccessDeniedException: User: arn:aws:sts::123123123123:assumed-role/foobar is not authorized to perform: secretsmanager:GetSecretValue on resource: example\n\tstatus code: 400, ",
+		},
+		{
+			err:      errors.New("some generic error"),
+			expected: "some generic error",
+		},
+	}
+
+	for _, c := range tbl {
+		out := SanitizeErr(c.err)
+		assert.Equal(t, c.expected, out.Error())
+	}
+}

+ 47 - 0
pkg/provider/aws/util/provider.go

@@ -0,0 +1,47 @@
+/*
+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 util
+
+import (
+	"fmt"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+)
+
+const (
+	errNilStore         = "found nil store"
+	errMissingStoreSpec = "store is missing spec"
+	errMissingProvider  = "storeSpec is missing provider"
+	errInvalidProvider  = "invalid provider spec. Missing AWS field in store %s"
+)
+
+// GetAWSProvider does the necessary nil checks on the generic store
+// it returns the aws provider or an error.
+func GetAWSProvider(store esv1alpha1.GenericStore) (*esv1alpha1.AWSProvider, error) {
+	if store == nil {
+		return nil, fmt.Errorf(errNilStore)
+	}
+	spc := store.GetSpec()
+	if spc == nil {
+		return nil, fmt.Errorf(errMissingStoreSpec)
+	}
+	if spc.Provider == nil {
+		return nil, fmt.Errorf(errMissingProvider)
+	}
+	prov := spc.Provider.AWS
+	if prov == nil {
+		return nil, fmt.Errorf(errInvalidProvider, store.GetObjectMeta().String())
+	}
+	return prov, nil
+}