Эх сурвалжийг харах

feat: add iam roles anywhere implementation

TODO:
- [ ] e2e tests
- [ ] revisit CRD structure and naming

Fixes #1585

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 3 жил өмнө
parent
commit
ce2740fe70

+ 1 - 1
.github/workflows/helm.yml

@@ -36,7 +36,7 @@ jobs:
 
       - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
         with:
-          python-version: 3.7
+          python-version: 3.12
 
       - name: Set up chart-testing
         uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1

+ 14 - 0
apis/externalsecrets/v1beta1/secretstore_aws_types.go

@@ -26,6 +26,8 @@ type AWSAuth struct {
 	SecretRef *AWSAuthSecretRef `json:"secretRef,omitempty"`
 	// +optional
 	JWTAuth *AWSJWTAuth `json:"jwt,omitempty"`
+	// +optional
+	IAMAnywhere *AWSIAMAnywhere `json:"iamAnywhere,omitempty"`
 }
 
 // AWSAuthSecretRef holds secret references for AWS credentials
@@ -49,6 +51,18 @@ type AWSJWTAuth struct {
 	ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
 }
 
+type AWSIAMAnywhere struct {
+	PrivateKey     esmeta.SecretKeySelector `json:"privateKeyRef"`
+	Certificate    esmeta.SecretKeySelector `json:"certificateRef"`
+	RoleARN        string                   `json:"roleArn"`
+	ProfileARN     string                   `json:"profileArn"`
+	TrustAnchorArn string                   `json:"trustAnchorArn"`
+	// +optional
+	Region string `json:"region,omitempty"`
+	// +optional
+	Endpoint string `json:"endpoint,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

+ 22 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -37,6 +37,11 @@ func (in *AWSAuth) DeepCopyInto(out *AWSAuth) {
 		*out = new(AWSJWTAuth)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.IAMAnywhere != nil {
+		in, out := &in.IAMAnywhere, &out.IAMAnywhere
+		*out = new(AWSIAMAnywhere)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSAuth.
@@ -71,6 +76,23 @@ func (in *AWSAuthSecretRef) DeepCopy() *AWSAuthSecretRef {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AWSIAMAnywhere) DeepCopyInto(out *AWSIAMAnywhere) {
+	*out = *in
+	in.PrivateKey.DeepCopyInto(&out.PrivateKey)
+	in.Certificate.DeepCopyInto(&out.Certificate)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSIAMAnywhere.
+func (in *AWSIAMAnywhere) DeepCopy() *AWSIAMAnywhere {
+	if in == nil {
+		return nil
+	}
+	out := new(AWSIAMAnywhere)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AWSJWTAuth) DeepCopyInto(out *AWSJWTAuth) {
 	*out = *in

+ 77 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2409,6 +2409,83 @@ spec:
                           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
                         properties:
+                          iamAnywhere:
+                            properties:
+                              certificateRef:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              endpoint:
+                                type: string
+                              privateKeyRef:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              profileArn:
+                                type: string
+                              region:
+                                type: string
+                              roleArn:
+                                type: string
+                              trustAnchorArn:
+                                type: string
+                            required:
+                            - certificateRef
+                            - privateKeyRef
+                            - profileArn
+                            - roleArn
+                            - trustAnchorArn
+                            type: object
                           jwt:
                             description: Authenticate against AWS using service account
                               tokens.

+ 77 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2409,6 +2409,83 @@ spec:
                           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
                         properties:
+                          iamAnywhere:
+                            properties:
+                              certificateRef:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              endpoint:
+                                type: string
+                              privateKeyRef:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              profileArn:
+                                type: string
+                              region:
+                                type: string
+                              roleArn:
+                                type: string
+                              trustAnchorArn:
+                                type: string
+                            required:
+                            - certificateRef
+                            - privateKeyRef
+                            - profileArn
+                            - roleArn
+                            - trustAnchorArn
+                            type: object
                           jwt:
                             description: Authenticate against AWS using service account
                               tokens.

+ 150 - 0
deploy/crds/bundle.yaml

@@ -3017,6 +3017,81 @@ spec:
                             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
                           properties:
+                            iamAnywhere:
+                              properties:
+                                certificateRef:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                endpoint:
+                                  type: string
+                                privateKeyRef:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                profileArn:
+                                  type: string
+                                region:
+                                  type: string
+                                roleArn:
+                                  type: string
+                                trustAnchorArn:
+                                  type: string
+                              required:
+                                - certificateRef
+                                - privateKeyRef
+                                - profileArn
+                                - roleArn
+                                - trustAnchorArn
+                              type: object
                             jwt:
                               description: Authenticate against AWS using service account tokens.
                               properties:
@@ -10134,6 +10209,81 @@ spec:
                             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
                           properties:
+                            iamAnywhere:
+                              properties:
+                                certificateRef:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                endpoint:
+                                  type: string
+                                privateKeyRef:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                profileArn:
+                                  type: string
+                                region:
+                                  type: string
+                                roleArn:
+                                  type: string
+                                trustAnchorArn:
+                                  type: string
+                              required:
+                                - certificateRef
+                                - privateKeyRef
+                                - profileArn
+                                - roleArn
+                                - trustAnchorArn
+                              type: object
                             jwt:
                               description: Authenticate against AWS using service account tokens.
                               properties:

+ 107 - 0
docs/api/spec.md

@@ -55,6 +55,19 @@ AWSJWTAuth
 <em>(Optional)</em>
 </td>
 </tr>
+<tr>
+<td>
+<code>iamAnywhere</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.AWSIAMAnywhere">
+AWSIAMAnywhere
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1beta1.AWSAuthSecretRef">AWSAuthSecretRef
@@ -118,6 +131,100 @@ see: <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_te
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1beta1.AWSIAMAnywhere">AWSIAMAnywhere
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.AWSAuth">AWSAuth</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>privateKeyRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>certificateRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>roleArn</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>profileArn</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>trustAnchorArn</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>region</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+<tr>
+<td>
+<code>endpoint</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.AWSJWTAuth">AWSJWTAuth
 </h3>
 <p>

+ 1 - 0
go.mod

@@ -77,6 +77,7 @@ require (
 	github.com/alibabacloud-go/tea-utils/v2 v2.0.7
 	github.com/aliyun/credentials-go v1.4.3
 	github.com/avast/retry-go/v4 v4.6.0
+	github.com/aws/rolesanywhere-credential-helper v1.4.0
 	github.com/cenkalti/backoff/v4 v4.3.0
 	github.com/cyberark/conjur-api-go v0.12.7
 	github.com/fortanix/sdkms-client-go v0.4.0

+ 2 - 0
go.sum

@@ -201,6 +201,8 @@ github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU
 github.com/aws/aws-sdk-go v1.41.13/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
 github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
 github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
+github.com/aws/rolesanywhere-credential-helper v1.4.0 h1:Ah6+/rVfYOPUvjC0F2cadh9ibhN3CzyKGutOB0gabzg=
+github.com/aws/rolesanywhere-credential-helper v1.4.0/go.mod h1:AJRju2V1Dmj11iUfXmQekBqTM9/mDYtttx8ik3fdnU0=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=

+ 84 - 0
pkg/provider/aws/auth/anywhere/anywhere.go

@@ -0,0 +1,84 @@
+/*
+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 anywhere
+
+import (
+	"crypto/x509"
+	"encoding/base64"
+	"fmt"
+	"runtime"
+
+	"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/aws/session"
+	"github.com/aws/rolesanywhere-credential-helper/rolesanywhere"
+)
+
+// Options
+type Options struct {
+	PrivateKey     string
+	Certificate    string
+	ProfileArn     string
+	TrustAnchorArn string
+	RoleArn        string
+	Region         string
+	Endpoint       string
+}
+
+func Generate(opts Options) (*credentials.Credentials, error) {
+	pk, err := readPrivateKeyData(opts.PrivateKey)
+	if err != nil {
+		return nil, fmt.Errorf("unabl to read private key: %w", err)
+	}
+	certData, err := readCertificateData(opts.Certificate)
+	if err != nil {
+		return nil, err
+	}
+	certificateDerData, err := base64.StdEncoding.DecodeString(certData.CertificateData)
+	if err != nil {
+		return nil, err
+	}
+	certificate, err := x509.ParseCertificate([]byte(certificateDerData))
+	if err != nil {
+		return nil, err
+	}
+	var certificateChain []x509.Certificate
+
+	config := aws.NewConfig().WithRegion(opts.Region)
+	if opts.Endpoint != "" {
+		config.WithEndpoint(opts.Endpoint)
+	}
+	sess, err := session.NewSession()
+	if err != nil {
+		return nil, err
+	}
+	rolesAnywhereClient := rolesanywhere.New(sess, config)
+	rolesAnywhereClient.Handlers.Build.RemoveByName("core.SDKVersionUserAgentHandler")
+	rolesAnywhereClient.Handlers.Build.PushBackNamed(request.NamedHandler{Name: "v4x509.CredHelperUserAgentHandler", Fn: request.MakeAddToUserAgentHandler("eso-aws-auth", "v1", runtime.Version(), runtime.GOOS, runtime.GOARCH)})
+	rolesAnywhereClient.Handlers.Sign.Clear()
+	rolesAnywhereClient.Handlers.Sign.PushBackNamed(request.NamedHandler{Name: "v4x509.SignRequestHandler", Fn: CreateSignFunction(pk, *certificate, certificateChain)})
+
+	output, err := rolesAnywhereClient.CreateSession(&rolesanywhere.CreateSessionInput{
+		Cert:           &opts.Certificate,
+		ProfileArn:     &opts.ProfileArn,
+		TrustAnchorArn: &opts.TrustAnchorArn,
+		RoleArn:        &opts.RoleArn,
+	})
+	if err != nil {
+		return nil, err
+	}
+	return credentials.NewStaticCredentials(*output.CredentialSet[0].Credentials.AccessKeyId, *output.CredentialSet[0].Credentials.SecretAccessKey, *output.CredentialSet[0].Credentials.SessionToken), nil
+}

+ 85 - 0
pkg/provider/aws/auth/anywhere/cert.go

@@ -0,0 +1,85 @@
+/*
+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 anywhere
+
+import (
+	"bytes"
+	"crypto/x509"
+	"encoding/base64"
+	"errors"
+	"fmt"
+)
+
+// Container for certificate data returned to the SDK as JSON.
+type CertificateData struct {
+	// Type for the key contained in the certificate.
+	// Passed back to the `sign-string` command
+	KeyType string `json:"keyType"`
+	// Certificate, as base64-encoded DER; used in the `x-amz-x509`
+	// header in the API request.
+	CertificateData string `json:"certificateData"`
+	// Serial number of the certificate. Used in the credential
+	// field of the Authorization header
+	SerialNumber string `json:"serialNumber"`
+	// Supported signing algorithms based on the KeyType
+	Algorithms []string `json:"supportedAlgorithms"`
+}
+
+// Load the certificate referenced by `certificateId` and extract
+// details required by the SDK to construct the StringToSign.
+func readCertificateData(certificateID string) (CertificateData, error) {
+	block, err := parseDERFromPEM(certificateID, "CERTIFICATE")
+	if err != nil {
+		return CertificateData{}, errors.New("could not parse PEM data")
+	}
+
+	cert, err := x509.ParseCertificate(block.Bytes)
+	if err != nil {
+		return CertificateData{}, errors.New("could not parse certificate")
+	}
+
+	//extract serial number
+	serialNumber := cert.SerialNumber.String()
+
+	//encode certificate
+	encodedDer, _ := encodeDer(block.Bytes)
+
+	//extract key type
+	var keyType string
+	switch cert.PublicKeyAlgorithm {
+	case x509.RSA:
+		keyType = "RSA"
+	case x509.ECDSA:
+		keyType = "EC"
+	default:
+		keyType = ""
+	}
+
+	supportedAlgorithms := []string{
+		fmt.Sprintf("%sSHA256", keyType),
+		fmt.Sprintf("%sSHA384", keyType),
+		fmt.Sprintf("%sSHA512", keyType),
+	}
+
+	return CertificateData{keyType, encodedDer, serialNumber, supportedAlgorithms}, nil
+}
+
+func encodeDer(der []byte) (string, error) {
+	var buf bytes.Buffer
+	encoder := base64.NewEncoder(base64.StdEncoding, &buf)
+	encoder.Write(der)
+	encoder.Close()
+	return buf.String(), nil
+}

+ 109 - 0
pkg/provider/aws/auth/anywhere/helper.go

@@ -0,0 +1,109 @@
+/*
+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 anywhere
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+)
+
+// Load the private key referenced by `privateKeyId`.
+func readPrivateKeyData(privateKeyID string) (crypto.PrivateKey, error) {
+	if key, err := readPKCS8PrivateKey(privateKeyID); err == nil {
+		return key, nil
+	}
+
+	if key, err := readECPrivateKey(privateKeyID); err == nil {
+		return key, nil
+	}
+
+	if key, err := readRSAPrivateKey(privateKeyID); err == nil {
+		return key, nil
+	}
+
+	return nil, errors.New("unable to parse private key")
+}
+
+func readECPrivateKey(privateKeyId string) (ecdsa.PrivateKey, error) {
+	block, err := parseDERFromPEM(privateKeyId, "EC PRIVATE KEY")
+	if err != nil {
+		return ecdsa.PrivateKey{}, errors.New("could not parse PEM data")
+	}
+
+	privateKey, err := x509.ParseECPrivateKey(block.Bytes)
+	if err != nil {
+		return ecdsa.PrivateKey{}, errors.New("could not parse private key")
+	}
+
+	return *privateKey, nil
+}
+
+func readRSAPrivateKey(privateKeyId string) (rsa.PrivateKey, error) {
+	block, err := parseDERFromPEM(privateKeyId, "RSA PRIVATE KEY")
+	if err != nil {
+		return rsa.PrivateKey{}, errors.New("could not parse PEM data")
+	}
+
+	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+	if err != nil {
+		return rsa.PrivateKey{}, errors.New("could not parse private key")
+	}
+
+	return *privateKey, nil
+}
+
+func readPKCS8PrivateKey(privateKeyId string) (crypto.PrivateKey, error) {
+	block, err := parseDERFromPEM(privateKeyId, "PRIVATE KEY")
+	if err != nil {
+		return nil, errors.New("could not parse PEM data")
+	}
+
+	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+	if err != nil {
+		return nil, errors.New("could not parse private key")
+	}
+
+	rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
+	if ok {
+		return *rsaPrivateKey, nil
+	}
+
+	ecPrivateKey, ok := privateKey.(*ecdsa.PrivateKey)
+	if ok {
+		return *ecPrivateKey, nil
+	}
+
+	return nil, errors.New("could not parse PKCS8 private key")
+}
+
+func parseDERFromPEM(pemDataId string, blockType string) (*pem.Block, error) {
+	bytes := []byte(pemDataId)
+
+	var block *pem.Block
+	for len(bytes) > 0 {
+		block, bytes = pem.Decode(bytes)
+		if block == nil {
+			return nil, errors.New("unable to parse PEM data")
+		}
+		if block.Type == blockType {
+			return block, nil
+		}
+	}
+	return nil, errors.New("requested block type could not be found")
+}

+ 378 - 0
pkg/provider/aws/auth/anywhere/sign.go

@@ -0,0 +1,378 @@
+/*
+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 anywhere
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha256"
+	"crypto/sha512"
+	"crypto/x509"
+	"encoding/base64"
+	"encoding/hex"
+	"errors"
+	"io"
+	"net/http"
+	"sort"
+	"strings"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/request"
+)
+
+// Create a function that will sign requests, given the signing certificate, optional certificate chain, and the private key.
+func CreateSignFunction(privateKey crypto.PrivateKey, certificate x509.Certificate, certificateChain []x509.Certificate) func(*request.Request) {
+	v4x509 := RolesAnywhereSigner{privateKey, certificate, certificateChain}
+	return func(r *request.Request) {
+		v4x509.SignWithCurrTime(r)
+	}
+}
+
+type RolesAnywhereSigner struct {
+	PrivateKey       crypto.PrivateKey
+	Certificate      x509.Certificate
+	CertificateChain []x509.Certificate
+}
+
+// Define constants used in signing.
+const (
+	AWS4X509RSASHA256   = "AWS4-X509-RSA-SHA256"
+	AWS4X509ECDSASHA256 = "AWS4-X509-ECDSA-SHA256"
+	timeFormat          = "20060102T150405Z"
+	shortTimeFormat     = "20060102"
+	XAMZDate            = "X-Amz-Date"
+	XAMZX509            = "X-Amz-X509"
+	XAMZX509Chain       = "X-Amz-X509-Chain"
+	XAMZContentSHA256   = "X-Amz-Content-Sha256"
+	authorization       = "Authorization"
+	host                = "Host"
+	emptyStringSHA256   = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
+)
+
+// Sign the request using the current time
+func (v4x509 RolesAnywhereSigner) SignWithCurrTime(req *request.Request) error {
+	// Find the signing algorithm
+	var signingAlgorithm string
+	_, isRsaKey := v4x509.PrivateKey.(rsa.PrivateKey)
+	if isRsaKey {
+		signingAlgorithm = AWS4X509RSASHA256
+	}
+	_, isEcKey := v4x509.PrivateKey.(ecdsa.PrivateKey)
+	if isEcKey {
+		signingAlgorithm = AWS4X509ECDSASHA256
+	}
+	if signingAlgorithm == "" {
+		return errors.New("unsupported algorithm")
+	}
+
+	region := req.ClientInfo.SigningRegion
+	if region == "" {
+		region = aws.StringValue(req.Config.Region)
+	}
+
+	name := req.ClientInfo.SigningName
+	if name == "" {
+		name = req.ClientInfo.ServiceName
+	}
+
+	signerParams := SignerParams{time.Now(), region, name, signingAlgorithm}
+
+	// Set headers that are necessary for signing
+	req.HTTPRequest.Header.Set(host, req.HTTPRequest.URL.Host)
+	req.HTTPRequest.Header.Set(XAMZDate, signerParams.GetFormattedSigningDateTime())
+	req.HTTPRequest.Header.Set(XAMZX509, certificateToString(v4x509.Certificate))
+	if v4x509.CertificateChain != nil {
+		req.HTTPRequest.Header.Set(XAMZX509Chain, certificateChainToString(v4x509.CertificateChain))
+	}
+
+	contentSha256 := calculateContentHash(req.HTTPRequest, req.Body)
+	if req.HTTPRequest.Header.Get(XAMZContentSHA256) == "required" {
+		req.HTTPRequest.Header.Set(XAMZContentSHA256, contentSha256)
+	}
+
+	canonicalRequest, signedHeadersString := createCanonicalRequest(req.HTTPRequest, req.Body, contentSha256)
+
+	stringToSign := CreateStringToSign(canonicalRequest, signerParams)
+
+	signingResult, _ := signPayload([]byte(stringToSign), SigningOpts{v4x509.PrivateKey, crypto.SHA256})
+
+	req.HTTPRequest.Header.Set(authorization, BuildAuthorizationHeader(req.HTTPRequest, req.Body, signedHeadersString, signingResult.Signature, v4x509.Certificate, signerParams))
+	req.SignedHeaderVals = req.HTTPRequest.Header
+	return nil
+}
+
+// Builds the complete authorization header
+func BuildAuthorizationHeader(request *http.Request, body io.ReadSeeker, signedHeadersString string, signature string, certificate x509.Certificate, signerParams SignerParams) string {
+	signingCredentials := certificate.SerialNumber.String() + "/" + signerParams.GetScope()
+	credential := "Credential=" + signingCredentials
+	signerHeaders := "SignedHeaders=" + signedHeadersString
+	signatureHeader := "Signature=" + signature
+
+	var authHeaderStringBuilder strings.Builder
+	authHeaderStringBuilder.WriteString(signerParams.SigningAlgorithm)
+	authHeaderStringBuilder.WriteString(" ")
+	authHeaderStringBuilder.WriteString(credential)
+	authHeaderStringBuilder.WriteString(", ")
+	authHeaderStringBuilder.WriteString(signerHeaders)
+	authHeaderStringBuilder.WriteString(", ")
+	authHeaderStringBuilder.WriteString(signatureHeader)
+	authHeaderString := authHeaderStringBuilder.String()
+	return authHeaderString
+}
+
+type SigningOpts struct {
+	// Private key to use for the signing operation.
+	PrivateKey crypto.PrivateKey
+	// Digest to use in the signing operation. For example, SHA256
+	Digest crypto.Hash
+}
+
+// Container for data returned after performing a signing operation.
+type SigningResult struct {
+	// Signature encoded in hex.
+	Signature string `json:"signature"`
+}
+
+// Sign the provided payload with the specified options.
+func signPayload(payload []byte, opts SigningOpts) (SigningResult, error) {
+	var hash []byte
+	switch opts.Digest {
+	case crypto.SHA256:
+		sum := sha256.Sum256(payload)
+		hash = sum[:]
+	case crypto.SHA384:
+		sum := sha512.Sum384(payload)
+		hash = sum[:]
+	case crypto.SHA512:
+		sum := sha512.Sum512(payload)
+		hash = sum[:]
+	default:
+		return SigningResult{}, errors.New("unsupported digest")
+	}
+
+	ecdsaPrivateKey, ok := opts.PrivateKey.(ecdsa.PrivateKey)
+	if ok {
+		sig, err := ecdsa.SignASN1(rand.Reader, &ecdsaPrivateKey, hash[:])
+		if err == nil {
+			return SigningResult{hex.EncodeToString(sig)}, nil
+		}
+	}
+
+	rsaPrivateKey, ok := opts.PrivateKey.(rsa.PrivateKey)
+	if ok {
+		sig, err := rsa.SignPKCS1v15(rand.Reader, &rsaPrivateKey, opts.Digest, hash[:])
+		if err == nil {
+			return SigningResult{hex.EncodeToString(sig)}, nil
+		}
+	}
+
+	return SigningResult{}, errors.New("unsupported algorithm")
+}
+
+// Create the string to sign.
+func CreateStringToSign(canonicalRequest string, signerParams SignerParams) string {
+	var stringToSignStrBuilder strings.Builder
+	stringToSignStrBuilder.WriteString(signerParams.SigningAlgorithm)
+	stringToSignStrBuilder.WriteString("\n")
+	stringToSignStrBuilder.WriteString(signerParams.GetFormattedSigningDateTime())
+	stringToSignStrBuilder.WriteString("\n")
+	stringToSignStrBuilder.WriteString(signerParams.GetScope())
+	stringToSignStrBuilder.WriteString("\n")
+	stringToSignStrBuilder.WriteString(canonicalRequest)
+	stringToSign := stringToSignStrBuilder.String()
+	return stringToSign
+}
+
+// Create the canonical request.
+func createCanonicalRequest(r *http.Request, body io.ReadSeeker, contentSha256 string) (string, string) {
+	var canonicalRequestStrBuilder strings.Builder
+	canonicalHeaderString, signedHeadersString := createCanonicalHeaderString(r)
+	canonicalRequestStrBuilder.WriteString("POST")
+	canonicalRequestStrBuilder.WriteString("\n")
+	canonicalRequestStrBuilder.WriteString("/sessions")
+	canonicalRequestStrBuilder.WriteString("\n")
+	canonicalRequestStrBuilder.WriteString(createCanonicalQueryString(r, body))
+	canonicalRequestStrBuilder.WriteString("\n")
+	canonicalRequestStrBuilder.WriteString(canonicalHeaderString)
+	canonicalRequestStrBuilder.WriteString("\n\n")
+	canonicalRequestStrBuilder.WriteString(signedHeadersString)
+	canonicalRequestStrBuilder.WriteString("\n")
+	canonicalRequestStrBuilder.WriteString(contentSha256)
+	canonicalRequestString := canonicalRequestStrBuilder.String()
+	canonicalRequestStringHashBytes := sha256.Sum256([]byte(canonicalRequestString))
+	return hex.EncodeToString(canonicalRequestStringHashBytes[:]), signedHeadersString
+}
+
+// Create the canonical query string.
+func createCanonicalQueryString(r *http.Request, body io.ReadSeeker) string {
+	rawQuery := strings.Replace(r.URL.Query().Encode(), "+", "%20", -1)
+	return rawQuery
+}
+
+// Headers that aren't included in calculating the signature
+var ignoredHeaderKeys = map[string]bool{
+	"Authorization":   true,
+	"User-Agent":      true,
+	"X-Amzn-Trace-Id": true,
+}
+
+// Create the canonical header string.
+func createCanonicalHeaderString(r *http.Request) (string, string) {
+	var headers []string
+	signedHeaderVals := make(http.Header)
+	for k, v := range r.Header {
+		canonicalKey := http.CanonicalHeaderKey(k)
+		if ignoredHeaderKeys[canonicalKey] {
+			continue
+		}
+
+		lowerCaseKey := strings.ToLower(k)
+		if _, ok := signedHeaderVals[lowerCaseKey]; ok {
+			// include additional values
+			signedHeaderVals[lowerCaseKey] = append(signedHeaderVals[lowerCaseKey], v...)
+			continue
+		}
+
+		headers = append(headers, lowerCaseKey)
+		signedHeaderVals[lowerCaseKey] = v
+	}
+	sort.Strings(headers)
+
+	headerValues := make([]string, len(headers))
+	for i, k := range headers {
+		headerValues[i] = k + ":" + strings.Join(signedHeaderVals[k], ",")
+	}
+	stripExcessSpaces(headerValues)
+	return strings.Join(headerValues, "\n"), strings.Join(headers, ";")
+}
+
+const doubleSpace = "  "
+
+// stripExcessSpaces will rewrite the passed in slice's string values to not
+// contain muliple side-by-side spaces.
+func stripExcessSpaces(vals []string) {
+	var j, k, l, m, spaces int
+	for i, str := range vals {
+		// Trim trailing spaces
+		for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
+		}
+
+		// Trim leading spaces
+		for k = 0; k < j && str[k] == ' '; k++ {
+		}
+		str = str[k : j+1]
+
+		// Strip multiple spaces.
+		j = strings.Index(str, doubleSpace)
+		if j < 0 {
+			vals[i] = str
+			continue
+		}
+
+		buf := []byte(str)
+		for k, m, l = j, j, len(buf); k < l; k++ {
+			if buf[k] == ' ' {
+				if spaces == 0 {
+					// First space.
+					buf[m] = buf[k]
+					m++
+				}
+				spaces++
+			} else {
+				// End of multiple spaces.
+				spaces = 0
+				buf[m] = buf[k]
+				m++
+			}
+		}
+
+		vals[i] = string(buf[:m])
+	}
+}
+
+// Calculate the hash of the request body
+func calculateContentHash(r *http.Request, body io.ReadSeeker) string {
+	hash := r.Header.Get(XAMZContentSHA256)
+
+	if hash == "" {
+		if body == nil {
+			hash = emptyStringSHA256
+		} else {
+			hash = hex.EncodeToString(makeSha256Reader(body))
+		}
+	}
+
+	return hash
+}
+
+// Find the SHA256 hash of the provided request body as a io.ReadSeeker
+func makeSha256Reader(reader io.ReadSeeker) []byte {
+	hash := sha256.New()
+	start, _ := reader.Seek(0, 1)
+	defer reader.Seek(start, 0)
+
+	io.Copy(hash, reader)
+	return hash.Sum(nil)
+}
+
+// Convert certificate to string, so that it can be present in the HTTP request header
+func certificateToString(certificate x509.Certificate) string {
+	return base64.StdEncoding.EncodeToString(certificate.Raw)
+}
+
+// Convert certificate chain to string, so that it can be pressent in the HTTP request header
+func certificateChainToString(certificateChain []x509.Certificate) string {
+	var x509ChainString strings.Builder
+	for i, certificate := range certificateChain {
+		x509ChainString.WriteString(certificateToString(certificate))
+		if i != len(certificateChain)-1 {
+			x509ChainString.WriteString(",")
+		}
+	}
+	return x509ChainString.String()
+}
+
+type SignerParams struct {
+	OverriddenDate   time.Time
+	RegionName       string
+	ServiceName      string
+	SigningAlgorithm string
+}
+
+// Obtain the date-time, formatted as specified by SigV4
+func (signerParams *SignerParams) GetFormattedSigningDateTime() string {
+	return signerParams.OverriddenDate.UTC().Format(timeFormat)
+}
+
+// Obtain the short date-time, formatted as specified by SigV4
+func (signerParams *SignerParams) GetFormattedShortSigningDateTime() string {
+	return signerParams.OverriddenDate.UTC().Format(shortTimeFormat)
+}
+
+// Obtain the scope as part of the SigV4-X509 signature
+func (signerParams *SignerParams) GetScope() string {
+	var scopeStringBuilder strings.Builder
+	scopeStringBuilder.WriteString(signerParams.GetFormattedShortSigningDateTime())
+	scopeStringBuilder.WriteString("/")
+	scopeStringBuilder.WriteString(signerParams.RegionName)
+	scopeStringBuilder.WriteString("/")
+	scopeStringBuilder.WriteString(signerParams.ServiceName)
+	scopeStringBuilder.WriteString("/")
+	scopeStringBuilder.WriteString("aws4_request")
+	return scopeStringBuilder.String()
+}

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

@@ -26,6 +26,7 @@ import (
 	"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"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/spf13/pflag"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -37,6 +38,7 @@ import (
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/pkg/cache"
 	"github.com/external-secrets/external-secrets/pkg/feature"
+	"github.com/external-secrets/external-secrets/pkg/provider/aws/auth/anywhere"
 	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
 	"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
 )
@@ -105,6 +107,15 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
 		}
 	}
 
+	iamAnywhere := prov.Auth.IAMAnywhere
+	if iamAnywhere != nil {
+		log.V(1).Info("using IAM Anywhere credentials")
+		creds, err = credsFromIAMAnywhere(ctx, prov.Auth, isClusterKind, kube, namespace)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	config := aws.NewConfig().WithEndpointResolver(ResolveEndpoint())
 	if creds != nil {
 		config.WithCredentials(creds)
@@ -229,6 +240,46 @@ func credsFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, storeKind s
 	return credentials.NewStaticCredentials(aks, sak, sessionToken), err
 }
 
+func credsFromIAMAnywhere(ctx context.Context, auth esv1beta1.AWSAuth, isClusterKind bool, kube client.Client, namespace string) (*credentials.Credentials, error) {
+	privateKey, err := resolveSecretKey(ctx, auth.IAMAnywhere.PrivateKey, namespace, kube, isClusterKind)
+	if err != nil {
+		return nil, err
+	}
+	cert, err := resolveSecretKey(ctx, auth.IAMAnywhere.Certificate, namespace, kube, isClusterKind)
+	if err != nil {
+		return nil, err
+	}
+
+	return anywhere.Generate(anywhere.Options{
+		Certificate:    string(cert),
+		PrivateKey:     string(privateKey),
+		ProfileArn:     auth.IAMAnywhere.ProfileARN,
+		TrustAnchorArn: auth.IAMAnywhere.TrustAnchorArn,
+		RoleArn:        auth.IAMAnywhere.RoleARN,
+		Region:         auth.IAMAnywhere.Region,
+		Endpoint:       auth.IAMAnywhere.Endpoint,
+	})
+}
+
+func resolveSecretKey(ctx context.Context, ks esmeta.SecretKeySelector, namespace string, kube client.Client, isClusterKind bool) ([]byte, error) {
+	ke := client.ObjectKey{
+		Name:      ks.Name,
+		Namespace: namespace,
+	}
+	if isClusterKind && ks.Namespace != nil {
+		ke.Namespace = *ks.Namespace
+	}
+	sec := v1.Secret{}
+	err := kube.Get(ctx, ke, &sec)
+	if err != nil {
+		return nil, fmt.Errorf("error fetching secret %v: %w", ks, err)
+	}
+	if val, ok := sec.Data[ks.Key]; ok {
+		return val, nil
+	}
+	return nil, fmt.Errorf("key %s not defined in secret %v", ks.Key, ks)
+}
+
 // credsFromServiceAccount uses a Kubernetes Service Account to acquire temporary
 // credentials using aws.AssumeRoleWithWebIdentity. It will assume the role defined
 // in the ServiceAccount annotation.