Browse Source

Merge branch 'main' into mtls-auth

ric 4 years ago
parent
commit
2ab70cc510
52 changed files with 2015 additions and 904 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. 27 0
      docs/guides-all-keys-one-secret.md
  8. 77 0
      docs/guides-common-k8s-secret-types.md
  9. BIN
      docs/pictures/diagrams-provider-aws-auth-pod-identity.png
  10. BIN
      docs/pictures/diagrams-provider-aws-auth-secret-ref.png
  11. BIN
      docs/pictures/diagrams-provider-aws-auth-service-account.png
  12. 1 1
      docs/pictures/diagrams.drawio
  13. BIN
      docs/pictures/screenshot_docker_config_json_example.png
  14. BIN
      docs/pictures/screenshot_json_string_gcp_secret_value.png
  15. BIN
      docs/pictures/screenshot_ssh_privkey_example.png
  16. BIN
      docs/pictures/screenshot_ssl_certificate_p12_example.png
  17. 14 0
      docs/snippets/gcpsm-data-from-external-secret.yaml
  18. 23 0
      docs/snippets/gcpsm-docker-config-externalsecret.yaml
  19. 23 0
      docs/snippets/gcpsm-ssh-auth-externalsecret.yaml
  20. 26 0
      docs/snippets/gcpsm-tls-externalsecret.yaml
  21. 81 5
      docs/snippets/provider-aws-access.md
  22. 47 2
      docs/spec.md
  23. 1 1
      e2e/Makefile
  24. 4 0
      e2e/k8s/eso.values.yaml
  25. 2 0
      e2e/k8s/localstack.values.yaml
  26. 8 0
      e2e/kind.yaml
  27. 4 4
      e2e/suite/aws/provider.go
  28. 87 1
      e2e/suite/aws/secretsmanager.go
  29. 2 2
      go.mod
  30. 4 6
      go.sum
  31. 2 0
      hack/api-docs/mkdocs.yml
  32. 231 0
      pkg/provider/aws/auth/auth.go
  33. 534 0
      pkg/provider/aws/auth/auth_test.go
  34. 25 1
      pkg/provider/aws/session/fake/assumeroler.go
  35. 54 0
      pkg/provider/aws/auth/resolver.go
  36. 58 0
      pkg/provider/aws/auth/resolver_test.go
  37. 50 0
      pkg/provider/aws/auth/token_fetcher.go
  38. 63 0
      pkg/provider/aws/auth/token_fetcher_test.go
  39. 0 10
      pkg/provider/aws/parameterstore/parameterstore_test.go
  40. 8 136
      pkg/provider/aws/provider.go
  41. 2 481
      pkg/provider/aws/provider_test.go
  42. 1 11
      pkg/provider/aws/secretsmanager/secretsmanager_test.go
  43. 0 71
      pkg/provider/aws/session/session.go
  44. 0 91
      pkg/provider/aws/session/session_test.go
  45. 6 9
      pkg/provider/aws/util/errors.go
  46. 42 0
      pkg/provider/aws/util/errors_test.go
  47. 47 0
      pkg/provider/aws/util/provider.go
  48. 8 6
      pkg/provider/gcp/secretmanager/fake/fake.go
  49. 2 1
      pkg/provider/gcp/secretmanager/secretsmanager.go
  50. 8 1
      pkg/provider/gcp/secretmanager/secretsmanager_test.go
  51. 178 31
      pkg/provider/ibm/provider.go
  52. 169 13
      pkg/provider/ibm/provider_test.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

+ 27 - 0
docs/guides-all-keys-one-secret.md

@@ -0,0 +1,27 @@
+# All Keys, One Secret
+
+To get multiple key-values from an external secret, not having to worry about how many, or what these keys are, we have to use the dataFrom field of the ExternalSecret resource, instead of the data field. We will give an example here with the gcp provider (should work with other providers in the same way).
+
+Please follow the authentication and SecretStore steps of the [Google Cloud Secrets Manager guide](provider-google-secrets-manager.md) to setup access to your google cloud account first.
+
+Then create a secret in Google Cloud Secret Manager that contains a JSON string with multiple key values like this:
+
+![secret-value](./pictures/screenshot_json_string_gcp_secret_value.png)
+
+Let's call this secret all-keys-example-secret on Google Cloud.
+
+
+### Creating dataFrom external secret
+
+Now, when creating our ExternalSecret resource, instead of using the data field, we use the dataFrom field:
+
+```yaml
+{% include 'gcpsm-data-from-external-secret.yaml' %}
+```
+
+To check both values we can run:
+
+```
+kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.username}' | base64 -d
+kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.surname}' | base64 -d
+```

+ 77 - 0
docs/guides-common-k8s-secret-types.md

@@ -0,0 +1,77 @@
+# A few common k8s secret types examples
+
+Here we will give some examples of how to work with a few common k8s secret types. We will give this examples here with the gcp provider (should work with other providers in the same way). Please also check the guides on [Advanced Templating](guides-templating.md) to understand the details.
+
+Please follow the authentication and SecretStore steps of the [Google Cloud Secrets Manager guide](provider-google-secrets-manager.md) to setup access to your google cloud account first.
+
+
+## Dockerconfigjson example
+
+First create a secret in Google Cloud Secrets Manager containing your docker config:
+
+![iam](./pictures/screenshot_docker_config_json_example.png)
+
+Let's call this secret docker-config-example on Google Cloud.
+
+Then create a ExternalSecret resource taking advantage of templating to populate the generated secret:
+
+```yaml
+{% include 'gcpsm-docker-config-externalsecret.yaml' %}
+```
+
+This will generate a valid dockerconfigjson secret for you to use!
+
+You can get the final value with:
+
+```bash
+kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data\.dockerconfigjson}" | base64 -d
+```
+
+## TLS Cert example
+
+We are assuming here that you already have valid certificates, maybe generated with letsencrypt or any other CA. So to simplify you can use openssl to generate a single secret pkcs12 cert based on your cert.pem and privkey.pen files.
+
+```bash
+openssl pkcs12 -export -out certificate.p12 -inkey privkey.pem -in cert.pem
+```
+
+With a certificate.p12 you can upload it to Google Cloud Secrets Manager:
+
+![p12](./pictures/screenshot_ssl_certificate_p12_example.png)
+
+And now you can create an ExternalSecret that gets it. You will end up with a k8s secret of type tls with pem values.
+
+```yaml
+{% include 'gcpsm-tls-externalsecret.yaml' %}
+```
+
+You can get their values with:
+
+```bash
+kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data.tls\.crt}" | base64 -d
+kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data.tls\.key}" | base64 -d
+```
+
+
+## SSH Auth example
+
+Add the ssh privkey to a new Google Cloud Secrets Manager secret:
+
+![ssh](./pictures/screenshot_ssh_privkey_example.png)
+
+And now you can create an ExternalSecret that gets it. You will end up with a k8s secret of type ssh-auth with the privatekey value.
+
+```yaml
+{% include 'gcpsm-ssh-auth-externalsecret.yaml' %}
+```
+
+You can get the privkey value with:
+
+```bash
+kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data.ssh-privatekey}" | base64 -d
+```
+
+## More examples
+
+!!! note "We need more examples here" 
+    Feel free to contribute with our docs and add more examples here!

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


BIN
docs/pictures/screenshot_docker_config_json_example.png


BIN
docs/pictures/screenshot_json_string_gcp_secret_value.png


BIN
docs/pictures/screenshot_ssh_privkey_example.png


BIN
docs/pictures/screenshot_ssl_certificate_p12_example.png


+ 14 - 0
docs/snippets/gcpsm-data-from-external-secret.yaml

@@ -0,0 +1,14 @@
+apiVersion: external-secrets.io/v1alpha1
+kind: ExternalSecret
+metadata:
+  name: example
+spec:
+  refreshInterval: 1h           # rate SecretManager pulls GCPSM
+  secretStoreRef:
+    kind: SecretStore
+    name: example               # name of the SecretStore (or kind specified)
+  target:
+    name: secret-to-be-created  # name of the k8s Secret to be created
+    creationPolicy: Owner
+  dataFrom:
+  - key: all-keys-example-secret  # name of the GCPSM secret

+ 23 - 0
docs/snippets/gcpsm-docker-config-externalsecret.yaml

@@ -0,0 +1,23 @@
+{% raw %}
+apiVersion: external-secrets.io/v1alpha1
+kind: ExternalSecret
+metadata:
+  name: dk-cfg-example
+spec:
+  refreshInterval: 1h
+  secretStoreRef:
+    name: example
+    kind: SecretStore
+  target:
+    name: secret-to-be-created
+    template:
+      type: kubernetes.io/dockerconfigjson
+      data:
+        .dockerconfigjson: "{{ .mysecret | toString }}"
+    name: secret-to-be-created
+    creationPolicy: Owner
+  data:
+  - secretKey: mysecret
+    remoteRef:
+      key: docker-config-example
+{% endraw %}

+ 23 - 0
docs/snippets/gcpsm-ssh-auth-externalsecret.yaml

@@ -0,0 +1,23 @@
+{% raw %}
+apiVersion: external-secrets.io/v1alpha1
+kind: ExternalSecret
+metadata:
+  name: ssh-auth-example
+spec:
+  refreshInterval: 1h
+  secretStoreRef:
+    name: example
+    kind: SecretStore
+  target:
+    name: secret-to-be-created
+    template:
+      type: kubernetes.io/ssh-auth
+      data:
+        ssh-privatekey: "{{ .mysecret | toString }}"
+    name: secret-to-be-created
+    creationPolicy: Owner
+  data:
+  - secretKey: mysecret
+    remoteRef:
+      key: ssh-priv-key-example
+{% endraw %}

+ 26 - 0
docs/snippets/gcpsm-tls-externalsecret.yaml

@@ -0,0 +1,26 @@
+{% raw %}
+apiVersion: external-secrets.io/v1alpha1
+kind: ExternalSecret
+metadata:
+  name: template-tls-example
+spec:
+  refreshInterval: 1h
+  secretStoreRef:
+    name: example
+    kind: SecretStore
+  target:
+    name: secret-to-be-created
+    # this is how the Kind=Secret will look like
+    template:
+      type: kubernetes.io/tls
+      data:
+        tls.crt: "{{ .mysecret | pkcs12cert | pemCertificate }}"
+        tls.key: "{{ .mysecret | pkcs12key | pemPrivateKey }}"
+
+  data:
+  # this is a pkcs12 archive that contains
+  # a cert and a private key
+  - secretKey: mysecret
+    remoteRef:
+      key: ssl-certificate-p12-example
+{% endraw %}

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

+ 2 - 2
go.mod

@@ -37,8 +37,8 @@ require (
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
 	github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
 	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
-	github.com/IBM/go-sdk-core/v5 v5.4.5
-	github.com/IBM/secrets-manager-go-sdk v0.1.21
+	github.com/IBM/go-sdk-core/v5 v5.5.0
+	github.com/IBM/secrets-manager-go-sdk v1.0.23
 	github.com/aws/aws-sdk-go v1.38.6
 	github.com/crossplane/crossplane-runtime v0.13.0
 	github.com/fatih/color v1.10.0 // indirect

+ 4 - 6
go.sum

@@ -63,11 +63,10 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
-github.com/IBM/go-sdk-core/v5 v5.4.2/go.mod h1:Sn+z+qTDREQvCr+UFa22TqqfXNxx3o723y8GsfLV8e0=
-github.com/IBM/go-sdk-core/v5 v5.4.5 h1:fP/SLOMxRzhHaqS+I5XN+1CWrAAWn8fdzWuZvbR5KxE=
-github.com/IBM/go-sdk-core/v5 v5.4.5/go.mod h1:Sn+z+qTDREQvCr+UFa22TqqfXNxx3o723y8GsfLV8e0=
-github.com/IBM/secrets-manager-go-sdk v0.1.21 h1:llT2ryUHpSvNwqzNQ+8cxInIXi7WgRtgUz9ycerJKvA=
-github.com/IBM/secrets-manager-go-sdk v0.1.21/go.mod h1:aw+EgLmbwxGDxlcohb/ye46O4nLeBSapAapHmWLZ2fY=
+github.com/IBM/go-sdk-core/v5 v5.5.0 h1:etP4m0kzMCxjZRI4Bu6cRTfK9YDvY3xFuagXugkCyxc=
+github.com/IBM/go-sdk-core/v5 v5.5.0/go.mod h1:Sn+z+qTDREQvCr+UFa22TqqfXNxx3o723y8GsfLV8e0=
+github.com/IBM/secrets-manager-go-sdk v1.0.23 h1:YvRB2jmCfXVwTiTozCNVIRfl6q9Qcl2JiL4x6chOSI4=
+github.com/IBM/secrets-manager-go-sdk v1.0.23/go.mod h1:ruP6eQ0/J/zHBbnMfUyWeMsTe9vgnGL4rDeLiSKhZhU=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -527,7 +526,6 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
-github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
 github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=

+ 2 - 0
hack/api-docs/mkdocs.yml

@@ -31,6 +31,8 @@ nav:
     - Introduction: guides-introduction.md
     - Getting started: guides-getting-started.md
     - Advanced Templating: guides-templating.md
+    - All keys, One secret: guides-all-keys-one-secret.md
+    - Common K8S Secret Types: guides-common-k8s-secret-types.md
     - Multi Tenancy: guides-multi-tenancy.md
     - Metrics: guides-metrics.md
   - Provider:

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

+ 1 - 11
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
@@ -181,7 +171,7 @@ func TestSecretsManagerGetSecret(t *testing.T) {
 		if !ErrorContains(err, v.expectError) {
 			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
 		}
-		if string(out) != v.expectedSecret {
+		if err == nil && string(out) != v.expectedSecret {
 			t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
 		}
 	}

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

+ 8 - 6
pkg/provider/gcp/secretmanager/fake/fake.go

@@ -43,12 +43,14 @@ func (mc *MockSMClient) NilClose() {
 }
 
 func (mc *MockSMClient) WithValue(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, val *secretmanagerpb.AccessSecretVersionResponse, err error) {
-	mc.accessSecretFn = func(paramCtx context.Context, paramReq *secretmanagerpb.AccessSecretVersionRequest, paramOpts ...grpc.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
-		// type secretmanagerpb.AccessSecretVersionRequest contains unexported fields
-		// use cmpopts.IgnoreUnexported to ignore all the unexported fields in the cmp.
-		if !cmp.Equal(paramReq, req, cmpopts.IgnoreUnexported(secretmanagerpb.AccessSecretVersionRequest{})) {
-			return nil, fmt.Errorf("unexpected test argument")
+	if mc != nil {
+		mc.accessSecretFn = func(paramCtx context.Context, paramReq *secretmanagerpb.AccessSecretVersionRequest, paramOpts ...grpc.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
+			// type secretmanagerpb.AccessSecretVersionRequest contains unexported fields
+			// use cmpopts.IgnoreUnexported to ignore all the unexported fields in the cmp.
+			if !cmp.Equal(paramReq, req, cmpopts.IgnoreUnexported(secretmanagerpb.AccessSecretVersionRequest{})) {
+				return nil, fmt.Errorf("unexpected test argument")
+			}
+			return val, err
 		}
-		return val, err
 	}
 }

+ 2 - 1
pkg/provider/gcp/secretmanager/secretsmanager.go

@@ -31,6 +31,7 @@ import (
 	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/schema"
+	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 
 const (
@@ -137,7 +138,7 @@ func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1alpha1.GenericSt
 
 // GetSecret returns a single secret from the provider.
 func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	if sm.SecretManagerClient == nil || sm.projectID == "" {
+	if utils.IsNil(sm.SecretManagerClient) || sm.projectID == "" {
 		return nil, fmt.Errorf(errUninitalizedGCPProvider)
 	}
 

+ 8 - 1
pkg/provider/gcp/secretmanager/secretsmanager_test.go

@@ -93,6 +93,11 @@ var setAPIErr = func(smtc *secretManagerTestCase) {
 	smtc.expectError = "oh no"
 }
 
+var setNilMockClient = func(smtc *secretManagerTestCase) {
+	smtc.mockClient = nil
+	smtc.expectError = errUninitalizedGCPProvider
+}
+
 // test the sm<->gcp interface
 // make sure correct values are passed and errors are handled accordingly.
 func TestSecretManagerGetSecret(t *testing.T) {
@@ -137,6 +142,7 @@ func TestSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setCustomVersion),
 		makeValidSecretManagerTestCaseCustom(setAPIErr),
 		makeValidSecretManagerTestCaseCustom(setCustomRef),
+		makeValidSecretManagerTestCaseCustom(setNilMockClient),
 	}
 
 	sm := ProviderGCP{}
@@ -147,7 +153,7 @@ func TestSecretManagerGetSecret(t *testing.T) {
 		if !ErrorContains(err, v.expectError) {
 			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
 		}
-		if string(out) != v.expectedSecret {
+		if err == nil && string(out) != v.expectedSecret {
 			t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
 		}
 	}
@@ -169,6 +175,7 @@ func TestGetSecretMap(t *testing.T) {
 	successCases := []*secretManagerTestCase{
 		makeValidSecretManagerTestCaseCustom(setDeserialization),
 		makeValidSecretManagerTestCaseCustom(setAPIErr),
+		makeValidSecretManagerTestCaseCustom(setNilMockClient),
 		makeValidSecretManagerTestCaseCustom(setInvalidJSON),
 	}
 

+ 178 - 31
pkg/provider/ibm/provider.go

@@ -17,6 +17,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"strings"
 
 	"github.com/IBM/go-sdk-core/v5/core"
 	sm "github.com/IBM/secrets-manager-go-sdk/secretsmanagerv1"
@@ -95,51 +96,197 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
 	if utils.IsNil(ibm.IBMClient) {
 		return nil, fmt.Errorf(errUninitalizedIBMProvider)
 	}
-	response, _, err := ibm.IBMClient.GetSecret(
-		&sm.GetSecretOptions{
-			SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
-			ID:         &ref.Key,
-		})
 
-	if err != nil {
-		return nil, err
+	secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
+	secretName := ref.Key
+	nameSplitted := strings.Split(secretName, "/")
+
+	if len(nameSplitted) > 1 {
+		secretType = nameSplitted[0]
+		secretName = nameSplitted[1]
 	}
 
-	secret := response.Resources[0].(*sm.SecretResource)
-	secretData := secret.SecretData.(map[string]interface{})
-	arbitrarySecretPayload := secretData["payload"].(string)
-	return []byte(arbitrarySecretPayload), nil
+	switch secretType {
+	case sm.GetSecretOptionsSecretTypeArbitraryConst:
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := secret.SecretData.(map[string]interface{})
+		arbitrarySecretPayload := secretData["payload"].(string)
+		return []byte(arbitrarySecretPayload), nil
+
+	case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
+		if ref.Property == "" {
+			return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
+		}
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := secret.SecretData.(map[string]interface{})
+
+		if val, ok := secretData[ref.Property]; ok {
+			return []byte(val.(string)), nil
+		}
+		return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
+
+	case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := *secret.APIKey
+
+		return []byte(secretData), nil
+
+	case sm.CreateSecretOptionsSecretTypeImportedCertConst:
+		if ref.Property == "" {
+			return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
+		}
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := secret.SecretData.(map[string]interface{})
+
+		if val, ok := secretData[ref.Property]; ok {
+			return []byte(val.(string)), nil
+		}
+		return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
+
+	default:
+		return nil, fmt.Errorf("unknown secret type %s", secretType)
+	}
 }
 
 func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	if utils.IsNil(ibm.IBMClient) {
 		return nil, fmt.Errorf(errUninitalizedIBMProvider)
 	}
-	response, _, err := ibm.IBMClient.GetSecret(
-		&sm.GetSecretOptions{
-			SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
-			ID:         &ref.Key,
-		})
-	if err != nil {
-		return nil, err
-	}
 
-	secret := response.Resources[0].(*sm.SecretResource)
-	secretData := secret.SecretData.(map[string]interface{})
-	arbitrarySecretPayload := secretData["payload"].(string)
+	secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
+	secretName := ref.Key
+	nameSplitted := strings.Split(secretName, "/")
 
-	kv := make(map[string]string)
-	err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
-	if err != nil {
-		return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+	if len(nameSplitted) > 1 {
+		secretType = nameSplitted[0]
+		secretName = nameSplitted[1]
 	}
 
-	secretMap := make(map[string][]byte)
-	for k, v := range kv {
-		secretMap[k] = []byte(v)
-	}
+	switch secretType {
+	case sm.GetSecretOptionsSecretTypeArbitraryConst:
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
+				ID:         &ref.Key,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := secret.SecretData.(map[string]interface{})
+		arbitrarySecretPayload := secretData["payload"].(string)
+
+		kv := make(map[string]string)
+		err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
+		if err != nil {
+			return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+		}
+
+		secretMap := make(map[string][]byte)
+		for k, v := range kv {
+			secretMap[k] = []byte(v)
+		}
+
+		return secretMap, nil
+
+	case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := secret.SecretData.(map[string]interface{})
+
+		secretMap := make(map[string][]byte)
+		for k, v := range secretData {
+			secretMap[k] = []byte(v.(string))
+		}
 
-	return secretMap, nil
+		return secretMap, nil
+
+	case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := *secret.APIKey
+
+		secretMap := make(map[string][]byte)
+		secretMap["apikey"] = []byte(secretData)
+
+		return secretMap, nil
+
+	case sm.CreateSecretOptionsSecretTypeImportedCertConst:
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := secret.SecretData.(map[string]interface{})
+
+		secretMap := make(map[string][]byte)
+		for k, v := range secretData {
+			secretMap[k] = []byte(v.(string))
+		}
+
+		return secretMap, nil
+
+	default:
+		return nil, fmt.Errorf("unknown secret type %s", secretType)
+	}
 }
 
 func (ibm *providerIBM) Close() error {

+ 169 - 13
pkg/provider/ibm/provider_test.go

@@ -78,7 +78,7 @@ func makeValidAPIOutput() *sm.GetSecret {
 	return &sm.GetSecret{
 		Resources: []sm.SecretResourceIntf{
 			&sm.SecretResource{
-				Type:       utilpointer.StringPtr("testytype"),
+				SecretType: utilpointer.StringPtr("testytype"),
 				Name:       utilpointer.StringPtr("testyname"),
 				SecretData: secretData,
 			},
@@ -111,34 +111,120 @@ var setNilMockClient = func(smtc *secretManagerTestCase) {
 // make sure correct values are passed and errors are handled accordingly.
 func TestIBMSecretManagerGetSecret(t *testing.T) {
 	secretData := make(map[string]interface{})
-	secretValue := "changedvalue"
-	secretData["payload"] = secretValue
+	secretString := "changedvalue"
+	secretPassword := "P@ssw0rd"
+	secretAPIKey := "01234567890"
+	secretCertificate := "certificate_value"
+
+	secretData["payload"] = secretString
+	secretData["password"] = secretPassword
+	secretData["certificate"] = secretCertificate
+
 	// good case: default version is set
 	// key is passed in, output is sent back
 	setSecretString := func(smtc *secretManagerTestCase) {
 		resources := []sm.SecretResourceIntf{
 			&sm.SecretResource{
-				Type:       utilpointer.StringPtr("testytype"),
+				SecretType: utilpointer.StringPtr("testytype"),
 				Name:       utilpointer.StringPtr("testyname"),
 				SecretData: secretData,
 			}}
 
 		smtc.apiOutput.Resources = resources
-		smtc.expectedSecret = secretValue
+		smtc.expectedSecret = secretString
 	}
 
 	// good case: custom version set
 	setCustomKey := func(smtc *secretManagerTestCase) {
 		resources := []sm.SecretResourceIntf{
 			&sm.SecretResource{
-				Type:       utilpointer.StringPtr("testytype"),
+				SecretType: utilpointer.StringPtr("testytype"),
 				Name:       utilpointer.StringPtr("testyname"),
 				SecretData: secretData,
 			}}
 		smtc.ref.Key = "testyname"
 		smtc.apiInput.ID = utilpointer.StringPtr("testyname")
 		smtc.apiOutput.Resources = resources
-		smtc.expectedSecret = secretValue
+		smtc.expectedSecret = secretString
+	}
+
+	// bad case: username_password type without property
+	secretUserPass := "username_password/test-secret"
+	badSecretUserPass := func(smtc *secretManagerTestCase) {
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = secretUserPass
+		smtc.expectError = "remoteRef.property required for secret type username_password"
+	}
+
+	// good case: username_password type with property
+	setSecretUserPass := func(smtc *secretManagerTestCase) {
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = secretUserPass
+		smtc.ref.Property = "password"
+		smtc.expectedSecret = secretPassword
+	}
+
+	// good case: iam_credenatials type
+	setSecretIam := func(smtc *secretManagerTestCase) {
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				APIKey:     utilpointer.StringPtr(secretAPIKey),
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = "iam_credentials/test-secret"
+		smtc.expectedSecret = secretAPIKey
+	}
+
+	// good case: imported_cert type with property
+	secretCert := "imported_cert/test-secret"
+	setSecretCert := func(smtc *secretManagerTestCase) {
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = secretCert
+		smtc.ref.Property = "certificate"
+		smtc.expectedSecret = secretCertificate
+	}
+
+	// bad case: imported_cert type without property
+	badSecretCert := func(smtc *secretManagerTestCase) {
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = secretCert
+		smtc.expectError = "remoteRef.property required for secret type imported_cert"
 	}
 
 	successCases := []*secretManagerTestCase{
@@ -147,6 +233,11 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setCustomKey),
 		makeValidSecretManagerTestCaseCustom(setAPIErr),
 		makeValidSecretManagerTestCaseCustom(setNilMockClient),
+		makeValidSecretManagerTestCaseCustom(badSecretUserPass),
+		makeValidSecretManagerTestCaseCustom(setSecretUserPass),
+		makeValidSecretManagerTestCaseCustom(setSecretIam),
+		makeValidSecretManagerTestCaseCustom(setSecretCert),
+		makeValidSecretManagerTestCaseCustom(badSecretCert),
 	}
 
 	sm := providerIBM{}
@@ -163,17 +254,25 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 }
 
 func TestGetSecretMap(t *testing.T) {
+	secretUsername := "user1"
+	secretPassword := "P@ssw0rd"
+	secretAPIKey := "01234567890"
+	secretCertificate := "certificate_value"
+	secretPrivateKey := "private_key_value"
+	secretIntermediate := "intermediate_value"
+
 	// good case: default version & deserialization
 	setDeserialization := func(smtc *secretManagerTestCase) {
 		secretData := make(map[string]interface{})
-		secretValue := `{"foo":"bar"}`
-		secretData["payload"] = secretValue
+		secretData["payload"] = `{"foo":"bar"}`
+
 		resources := []sm.SecretResourceIntf{
 			&sm.SecretResource{
-				Type:       utilpointer.StringPtr("testytype"),
+				SecretType: utilpointer.StringPtr("testytype"),
 				Name:       utilpointer.StringPtr("testyname"),
 				SecretData: secretData,
 			}}
+
 		smtc.apiOutput.Resources = resources
 		smtc.expectedData["foo"] = []byte("bar")
 	}
@@ -181,26 +280,83 @@ func TestGetSecretMap(t *testing.T) {
 	// bad case: invalid json
 	setInvalidJSON := func(smtc *secretManagerTestCase) {
 		secretData := make(map[string]interface{})
-
 		secretData["payload"] = `-----------------`
 
 		resources := []sm.SecretResourceIntf{
 			&sm.SecretResource{
-				Type:       utilpointer.StringPtr("testytype"),
+				SecretType: utilpointer.StringPtr("testytype"),
 				Name:       utilpointer.StringPtr("testyname"),
 				SecretData: secretData,
 			}}
 
 		smtc.apiOutput.Resources = resources
-
 		smtc.expectError = "unable to unmarshal secret: invalid character '-' in numeric literal"
 	}
 
+	// good case: username_password
+	setSecretUserPass := func(smtc *secretManagerTestCase) {
+		secretData := make(map[string]interface{})
+		secretData["username"] = secretUsername
+		secretData["password"] = secretPassword
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = "username_password/test-secret"
+		smtc.expectedData["username"] = []byte(secretUsername)
+		smtc.expectedData["password"] = []byte(secretPassword)
+	}
+
+	// good case: iam_credentials
+	setSecretIam := func(smtc *secretManagerTestCase) {
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				APIKey:     utilpointer.StringPtr(secretAPIKey),
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = "iam_credentials/test-secret"
+		smtc.expectedData["apikey"] = []byte(secretAPIKey)
+	}
+
+	// good case: imported_cert
+	setSecretCert := func(smtc *secretManagerTestCase) {
+		secretData := make(map[string]interface{})
+		secretData["certificate"] = secretCertificate
+		secretData["private_key"] = secretPrivateKey
+		secretData["intermediate"] = secretIntermediate
+
+		resources := []sm.SecretResourceIntf{
+			&sm.SecretResource{
+				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
+				Name:       utilpointer.StringPtr("testyname"),
+				SecretData: secretData,
+			}}
+
+		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
+		smtc.apiOutput.Resources = resources
+		smtc.ref.Key = "imported_cert/test-secret"
+		smtc.expectedData["certificate"] = []byte(secretCertificate)
+		smtc.expectedData["private_key"] = []byte(secretPrivateKey)
+		smtc.expectedData["intermediate"] = []byte(secretIntermediate)
+	}
+
 	successCases := []*secretManagerTestCase{
 		makeValidSecretManagerTestCaseCustom(setDeserialization),
 		makeValidSecretManagerTestCaseCustom(setInvalidJSON),
 		makeValidSecretManagerTestCaseCustom(setNilMockClient),
 		makeValidSecretManagerTestCaseCustom(setAPIErr),
+		makeValidSecretManagerTestCaseCustom(setSecretUserPass),
+		makeValidSecretManagerTestCaseCustom(setSecretIam),
+		makeValidSecretManagerTestCaseCustom(setSecretCert),
 	}
 
 	sm := providerIBM{}