1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

Enabling Vault IAM auth (#2208)

* Enabling Vault IAM auth

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>

* Adding spec

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>

* Adding test cases and decoupling vault provider from aws for iam auth

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>

* Fixing comments

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>

* Fixing linter issues

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>

* Fixing the check-diff errors

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>

* Adding support for assumeRole operations when using static creds

Signed-off-by: Gaurav Dasson <gdasson@Gauravs-Mac-mini.local>

* Bumping the dependencies to fix the go.mod/go.sum conflicts

Signed-off-by: Gaurav Dasson <gdasson@Gauravs-Mac-mini.local>

* Bumping up e2e go mod files

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>

---------

Signed-off-by: Gaurav Dasson <gaurav.dasson@gmail.com>
This commit is contained in:
Gaurav Dasson 2023-05-11 04:10:07 -05:00 committed by GitHub
parent f6475d63b0
commit 7b8fef2c18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1854 additions and 6 deletions

View file

@ -112,6 +112,11 @@ type VaultAuth struct {
// Cert authentication method
// +optional
Cert *VaultCertAuth `json:"cert,omitempty"`
// Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials
// AWS IAM authentication method
// +optional
Iam *VaultIamAuth `json:"iam,omitempty"`
}
// VaultAppRole authenticates with Vault using the App Role auth mechanism,
@ -178,6 +183,37 @@ type VaultLdapAuth struct {
SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
}
// VaultAwsAuth 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 try to load credentials from its own service account assuming it is IRSA enabled.
type VaultAwsAuth struct {
// +optional
SecretRef *VaultAwsAuthSecretRef `json:"secretRef,omitempty"`
// +optional
JWTAuth *VaultAwsJWTAuth `json:"jwt,omitempty"`
}
// VaultAWSAuthSecretRef holds secret references for AWS credentials
// both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
type VaultAwsAuthSecretRef struct {
// The AccessKeyID is used for authentication
AccessKeyID esmeta.SecretKeySelector `json:"accessKeyIDSecretRef,omitempty"`
// The SecretAccessKey is used for authentication
SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
// The SessionToken used for authentication
// This must be defined if AccessKeyID and SecretAccessKey are temporary credentials
// see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html
// +Optional
SessionToken *esmeta.SecretKeySelector `json:"sessionTokenSecretRef,omitempty"`
}
// Authenticate against AWS using service account tokens.
type VaultAwsJWTAuth struct {
ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
}
// VaultKubernetesServiceAccountTokenAuth authenticates with Vault using a temporary
// Kubernetes service account token retrieved by the `TokenRequest` API.
type VaultKubernetesServiceAccountTokenAuth struct {
@ -237,3 +273,26 @@ type VaultCertAuth struct {
// authenticate with Vault using the Cert authentication method
SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
}
// VaultIamAuth authenticates with Vault using the Vault's AWS IAM authentication method. Refer: https://developer.hashicorp.com/vault/docs/auth/aws
type VaultIamAuth struct {
// Path where the AWS auth method is enabled in Vault, e.g: "aws"
Path string `json:"path,omitempty"`
// AWS region
Region string `json:"region,omitempty"`
// This is the AWS role to be assumed before talking to vault
AWSIAMRole string `json:"role,omitempty"`
// Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine
Role string `json:"vaultRole"`
// AWS External ID set on assumed IAM roles
ExternalID string `json:"externalID,omitempty"`
// X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws
VaultAWSIAMServerID string `json:"vaultAwsIamServerID,omitempty"`
// Specify credentials in a Secret object
// +optional
SecretRef *VaultAwsAuthSecretRef `json:"secretRef,omitempty"`
// Specify a service account with IRSA enabled
// +optional
JWTAuth *VaultAwsJWTAuth `json:"jwt,omitempty"`
}

View file

@ -2051,6 +2051,11 @@ func (in *VaultAuth) DeepCopyInto(out *VaultAuth) {
*out = new(VaultCertAuth)
(*in).DeepCopyInto(*out)
}
if in.Iam != nil {
in, out := &in.Iam, &out.Iam
*out = new(VaultIamAuth)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth.
@ -2063,6 +2068,73 @@ func (in *VaultAuth) DeepCopy() *VaultAuth {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultAwsAuth) DeepCopyInto(out *VaultAwsAuth) {
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(VaultAwsAuthSecretRef)
(*in).DeepCopyInto(*out)
}
if in.JWTAuth != nil {
in, out := &in.JWTAuth, &out.JWTAuth
*out = new(VaultAwsJWTAuth)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAwsAuth.
func (in *VaultAwsAuth) DeepCopy() *VaultAwsAuth {
if in == nil {
return nil
}
out := new(VaultAwsAuth)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultAwsAuthSecretRef) DeepCopyInto(out *VaultAwsAuthSecretRef) {
*out = *in
in.AccessKeyID.DeepCopyInto(&out.AccessKeyID)
in.SecretAccessKey.DeepCopyInto(&out.SecretAccessKey)
if in.SessionToken != nil {
in, out := &in.SessionToken, &out.SessionToken
*out = new(metav1.SecretKeySelector)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAwsAuthSecretRef.
func (in *VaultAwsAuthSecretRef) DeepCopy() *VaultAwsAuthSecretRef {
if in == nil {
return nil
}
out := new(VaultAwsAuthSecretRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultAwsJWTAuth) DeepCopyInto(out *VaultAwsJWTAuth) {
*out = *in
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 VaultAwsJWTAuth.
func (in *VaultAwsJWTAuth) DeepCopy() *VaultAwsJWTAuth {
if in == nil {
return nil
}
out := new(VaultAwsJWTAuth)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultCertAuth) DeepCopyInto(out *VaultCertAuth) {
*out = *in
@ -2080,6 +2152,31 @@ func (in *VaultCertAuth) DeepCopy() *VaultCertAuth {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultIamAuth) DeepCopyInto(out *VaultIamAuth) {
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(VaultAwsAuthSecretRef)
(*in).DeepCopyInto(*out)
}
if in.JWTAuth != nil {
in, out := &in.JWTAuth, &out.JWTAuth
*out = new(VaultAwsJWTAuth)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultIamAuth.
func (in *VaultIamAuth) DeepCopy() *VaultIamAuth {
if in == nil {
return nil
}
out := new(VaultIamAuth)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultJwtAuth) DeepCopyInto(out *VaultJwtAuth) {
*out = *in

View file

@ -3003,6 +3003,135 @@ spec:
type: string
type: object
type: object
iam:
description: Iam authenticates with vault by passing a
special AWS request signed with AWS IAM credentials
AWS IAM authentication method
properties:
externalID:
description: AWS External ID set on assumed IAM roles
type: string
jwt:
description: Specify a service account with IRSA enabled
properties:
serviceAccountRef:
description: A reference to a ServiceAccount resource.
properties:
audiences:
description: Audience specifies the `aud`
claim for the service account token If the
service account uses a well-known annotation
for e.g. IRSA or GCP Workload Identity then
this audiences will be appended to the list
items:
type: string
type: array
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
path:
description: 'Path where the AWS auth method is enabled
in Vault, e.g: "aws"'
type: string
region:
description: AWS region
type: string
role:
description: This is the AWS role to be assumed before
talking to vault
type: string
secretRef:
description: Specify credentials in a Secret object
properties:
accessKeyIDSecretRef:
description: The AccessKeyID is used for authentication
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some
instances of this field may be defaulted,
in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
secretAccessKeySecretRef:
description: The SecretAccessKey is used for authentication
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some
instances of this field may be defaulted,
in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
sessionTokenSecretRef:
description: 'The SessionToken used for authentication
This must be defined if AccessKeyID and SecretAccessKey
are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some
instances of this field may be defaulted,
in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
type: object
vaultAwsIamServerID:
description: 'X-Vault-AWS-IAM-Server-ID is an additional
header used by Vault IAM auth method to mitigate
against different types of replay attacks. More
details here: https://developer.hashicorp.com/vault/docs/auth/aws'
type: string
vaultRole:
description: Vault Role. In vault, a role describes
an identity with a set of permissions, groups, or
policies you want to attach a user of the secrets
engine
type: string
required:
- vaultRole
type: object
jwt:
description: Jwt authenticates with Vault by passing role
and JWT token using the JWT/OIDC authentication method

View file

@ -3003,6 +3003,135 @@ spec:
type: string
type: object
type: object
iam:
description: Iam authenticates with vault by passing a
special AWS request signed with AWS IAM credentials
AWS IAM authentication method
properties:
externalID:
description: AWS External ID set on assumed IAM roles
type: string
jwt:
description: Specify a service account with IRSA enabled
properties:
serviceAccountRef:
description: A reference to a ServiceAccount resource.
properties:
audiences:
description: Audience specifies the `aud`
claim for the service account token If the
service account uses a well-known annotation
for e.g. IRSA or GCP Workload Identity then
this audiences will be appended to the list
items:
type: string
type: array
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
path:
description: 'Path where the AWS auth method is enabled
in Vault, e.g: "aws"'
type: string
region:
description: AWS region
type: string
role:
description: This is the AWS role to be assumed before
talking to vault
type: string
secretRef:
description: Specify credentials in a Secret object
properties:
accessKeyIDSecretRef:
description: The AccessKeyID is used for authentication
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some
instances of this field may be defaulted,
in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
secretAccessKeySecretRef:
description: The SecretAccessKey is used for authentication
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some
instances of this field may be defaulted,
in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
sessionTokenSecretRef:
description: 'The SessionToken used for authentication
This must be defined if AccessKeyID and SecretAccessKey
are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some
instances of this field may be defaulted,
in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
type: object
vaultAwsIamServerID:
description: 'X-Vault-AWS-IAM-Server-ID is an additional
header used by Vault IAM auth method to mitigate
against different types of replay attacks. More
details here: https://developer.hashicorp.com/vault/docs/auth/aws'
type: string
vaultRole:
description: Vault Role. In vault, a role describes
an identity with a set of permissions, groups, or
policies you want to attach a user of the secrets
engine
type: string
required:
- vaultRole
type: object
jwt:
description: Jwt authenticates with Vault by passing role
and JWT token using the JWT/OIDC authentication method

View file

@ -139,6 +139,134 @@ spec:
type: string
type: object
type: object
iam:
description: Iam authenticates with vault by passing a special
AWS request signed with AWS IAM credentials AWS IAM authentication
method
properties:
externalID:
description: AWS External ID set on assumed IAM roles
type: string
jwt:
description: Specify a service account with IRSA enabled
properties:
serviceAccountRef:
description: A reference to a ServiceAccount resource.
properties:
audiences:
description: Audience specifies the `aud` claim
for the service account token If the service
account uses a well-known annotation for e.g.
IRSA or GCP Workload Identity then this audiences
will be appended to the list
items:
type: string
type: array
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
path:
description: 'Path where the AWS auth method is enabled
in Vault, e.g: "aws"'
type: string
region:
description: AWS region
type: string
role:
description: This is the AWS role to be assumed before
talking to vault
type: string
secretRef:
description: Specify credentials in a Secret object
properties:
accessKeyIDSecretRef:
description: The AccessKeyID is used for authentication
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some instances
of this field may be defaulted, in others it
may be required.
type: string
name:
description: The name of the Secret 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
type: object
secretAccessKeySecretRef:
description: The SecretAccessKey is used for authentication
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some instances
of this field may be defaulted, in others it
may be required.
type: string
name:
description: The name of the Secret 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
type: object
sessionTokenSecretRef:
description: 'The SessionToken used for authentication
This must be defined if AccessKeyID and SecretAccessKey
are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
properties:
key:
description: The key of the entry in the Secret
resource's `data` field to be used. Some instances
of this field may be defaulted, in others it
may be required.
type: string
name:
description: The name of the Secret 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
type: object
type: object
vaultAwsIamServerID:
description: 'X-Vault-AWS-IAM-Server-ID is an additional
header used by Vault IAM auth method to mitigate against
different types of replay attacks. More details here:
https://developer.hashicorp.com/vault/docs/auth/aws'
type: string
vaultRole:
description: Vault Role. In vault, a role describes an
identity with a set of permissions, groups, or policies
you want to attach a user of the secrets engine
type: string
required:
- vaultRole
type: object
jwt:
description: Jwt authenticates with Vault by passing role
and JWT token using the JWT/OIDC authentication method

View file

@ -2656,6 +2656,94 @@ spec:
type: string
type: object
type: object
iam:
description: Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials AWS IAM authentication method
properties:
externalID:
description: AWS External ID set on assumed IAM roles
type: string
jwt:
description: Specify a service account with IRSA enabled
properties:
serviceAccountRef:
description: A reference to a ServiceAccount resource.
properties:
audiences:
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
items:
type: string
type: array
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
path:
description: 'Path where the AWS auth method is enabled in Vault, e.g: "aws"'
type: string
region:
description: AWS region
type: string
role:
description: This is the AWS role to be assumed before talking to vault
type: string
secretRef:
description: Specify credentials in a Secret object
properties:
accessKeyIDSecretRef:
description: The AccessKeyID is used for authentication
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
secretAccessKeySecretRef:
description: The SecretAccessKey is used for authentication
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
sessionTokenSecretRef:
description: 'The SessionToken used for authentication This must be defined if AccessKeyID and SecretAccessKey are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
type: object
vaultAwsIamServerID:
description: 'X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws'
type: string
vaultRole:
description: Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine
type: string
required:
- vaultRole
type: object
jwt:
description: Jwt authenticates with Vault by passing role and JWT token using the JWT/OIDC authentication method
properties:
@ -6113,6 +6201,94 @@ spec:
type: string
type: object
type: object
iam:
description: Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials AWS IAM authentication method
properties:
externalID:
description: AWS External ID set on assumed IAM roles
type: string
jwt:
description: Specify a service account with IRSA enabled
properties:
serviceAccountRef:
description: A reference to a ServiceAccount resource.
properties:
audiences:
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
items:
type: string
type: array
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
path:
description: 'Path where the AWS auth method is enabled in Vault, e.g: "aws"'
type: string
region:
description: AWS region
type: string
role:
description: This is the AWS role to be assumed before talking to vault
type: string
secretRef:
description: Specify credentials in a Secret object
properties:
accessKeyIDSecretRef:
description: The AccessKeyID is used for authentication
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
secretAccessKeySecretRef:
description: The SecretAccessKey is used for authentication
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
sessionTokenSecretRef:
description: 'The SessionToken used for authentication This must be defined if AccessKeyID and SecretAccessKey are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
type: object
vaultAwsIamServerID:
description: 'X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws'
type: string
vaultRole:
description: Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine
type: string
required:
- vaultRole
type: object
jwt:
description: Jwt authenticates with Vault by passing role and JWT token using the JWT/OIDC authentication method
properties:
@ -7154,6 +7330,94 @@ spec:
type: string
type: object
type: object
iam:
description: Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials AWS IAM authentication method
properties:
externalID:
description: AWS External ID set on assumed IAM roles
type: string
jwt:
description: Specify a service account with IRSA enabled
properties:
serviceAccountRef:
description: A reference to a ServiceAccount resource.
properties:
audiences:
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
items:
type: string
type: array
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
path:
description: 'Path where the AWS auth method is enabled in Vault, e.g: "aws"'
type: string
region:
description: AWS region
type: string
role:
description: This is the AWS role to be assumed before talking to vault
type: string
secretRef:
description: Specify credentials in a Secret object
properties:
accessKeyIDSecretRef:
description: The AccessKeyID is used for authentication
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
secretAccessKeySecretRef:
description: The SecretAccessKey is used for authentication
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
sessionTokenSecretRef:
description: 'The SessionToken used for authentication This must be defined if AccessKeyID and SecretAccessKey are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
properties:
key:
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
type: string
name:
description: The name of the Secret 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
type: object
type: object
vaultAwsIamServerID:
description: 'X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws'
type: string
vaultRole:
description: Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine
type: string
required:
- vaultRole
type: object
jwt:
description: Jwt authenticates with Vault by passing role and JWT token using the JWT/OIDC authentication method
properties:

View file

@ -5543,6 +5543,158 @@ VaultCertAuth
Cert authentication method</p>
</td>
</tr>
<tr>
<td>
<code>iam</code></br>
<em>
<a href="#external-secrets.io/v1beta1.VaultIamAuth">
VaultIamAuth
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials
AWS IAM authentication method</p>
</td>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1beta1.VaultAwsAuth">VaultAwsAuth
</h3>
<p>
<p>VaultAwsAuth 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 try to load credentials from its own service account assuming it is IRSA enabled.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>secretRef</code></br>
<em>
<a href="#external-secrets.io/v1beta1.VaultAwsAuthSecretRef">
VaultAwsAuthSecretRef
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>jwt</code></br>
<em>
<a href="#external-secrets.io/v1beta1.VaultAwsJWTAuth">
VaultAwsJWTAuth
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1beta1.VaultAwsAuthSecretRef">VaultAwsAuthSecretRef
</h3>
<p>
(<em>Appears on:</em>
<a href="#external-secrets.io/v1beta1.VaultAwsAuth">VaultAwsAuth</a>,
<a href="#external-secrets.io/v1beta1.VaultIamAuth">VaultIamAuth</a>)
</p>
<p>
<p>VaultAWSAuthSecretRef holds secret references for AWS credentials
both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>accessKeyIDSecretRef</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>
<p>The AccessKeyID is used for authentication</p>
</td>
</tr>
<tr>
<td>
<code>secretAccessKeySecretRef</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>
<p>The SecretAccessKey is used for authentication</p>
</td>
</tr>
<tr>
<td>
<code>sessionTokenSecretRef</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>
<p>The SessionToken used for authentication
This must be defined if AccessKeyID and SecretAccessKey are temporary credentials
see: <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html">https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html</a></p>
</td>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1beta1.VaultAwsJWTAuth">VaultAwsJWTAuth
</h3>
<p>
(<em>Appears on:</em>
<a href="#external-secrets.io/v1beta1.VaultAwsAuth">VaultAwsAuth</a>,
<a href="#external-secrets.io/v1beta1.VaultIamAuth">VaultIamAuth</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>
<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#ServiceAccountSelector">
External Secrets meta/v1.ServiceAccountSelector
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1beta1.VaultCertAuth">VaultCertAuth
@ -5594,6 +5746,119 @@ authenticate with Vault using the Cert authentication method</p>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1beta1.VaultIamAuth">VaultIamAuth
</h3>
<p>
(<em>Appears on:</em>
<a href="#external-secrets.io/v1beta1.VaultAuth">VaultAuth</a>)
</p>
<p>
<p>VaultIamAuth authenticates with Vault using the Vault&rsquo;s AWS IAM authentication method. Refer: <a href="https://developer.hashicorp.com/vault/docs/auth/aws">https://developer.hashicorp.com/vault/docs/auth/aws</a></p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>path</code></br>
<em>
string
</em>
</td>
<td>
<p>Path where the AWS auth method is enabled in Vault, e.g: &ldquo;aws&rdquo;</p>
</td>
</tr>
<tr>
<td>
<code>region</code></br>
<em>
string
</em>
</td>
<td>
<p>AWS region</p>
</td>
</tr>
<tr>
<td>
<code>role</code></br>
<em>
string
</em>
</td>
<td>
<p>This is the AWS role to be assumed before talking to vault</p>
</td>
</tr>
<tr>
<td>
<code>vaultRole</code></br>
<em>
string
</em>
</td>
<td>
<p>Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine</p>
</td>
</tr>
<tr>
<td>
<code>externalID</code></br>
<em>
string
</em>
</td>
<td>
<p>AWS External ID set on assumed IAM roles</p>
</td>
</tr>
<tr>
<td>
<code>vaultAwsIamServerID</code></br>
<em>
string
</em>
</td>
<td>
<p>X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: <a href="https://developer.hashicorp.com/vault/docs/auth/aws">https://developer.hashicorp.com/vault/docs/auth/aws</a></p>
</td>
</tr>
<tr>
<td>
<code>secretRef</code></br>
<em>
<a href="#external-secrets.io/v1beta1.VaultAwsAuthSecretRef">
VaultAwsAuthSecretRef
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Specify credentials in a Secret object</p>
</td>
</tr>
<tr>
<td>
<code>jwt</code></br>
<em>
<a href="#external-secrets.io/v1beta1.VaultAwsJWTAuth">
VaultAwsJWTAuth
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Specify a service account with IRSA enabled</p>
</td>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1beta1.VaultJwtAuth">VaultJwtAuth
</h3>
<p>

View file

@ -271,8 +271,9 @@ We support five different modes for authentication:
[token-based](https://www.vaultproject.io/docs/auth/token),
[appRole](https://www.vaultproject.io/docs/auth/approle),
[kubernetes-native](https://www.vaultproject.io/docs/auth/kubernetes),
[ldap](https://www.vaultproject.io/docs/auth/ldap) and
[jwt/oidc](https://www.vaultproject.io/docs/auth/jwt), each one comes with it's own
[ldap](https://www.vaultproject.io/docs/auth/ldap),
[jwt/oidc](https://www.vaultproject.io/docs/auth/jwt) and
[awsAuth](https://developer.hashicorp.com/vault/docs/auth/aws), each one comes with it's own
trade-offs. Depending on the authentication method you need to adapt your environment.
#### Token-based authentication
@ -333,6 +334,54 @@ or `Kind=ClusterSecretStore` resource.
```
**NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` in `secretRef` with the namespace where the secret resides.
#### AWS IAM authentication
[AWS IAM](https://developer.hashicorp.com/vault/docs/auth/aws) uses either a
set of AWS Programmatic access credentials stored in a `Kind=Secret` and referenced by the
`secretRef` or by getting the authentication token from an [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) enabled service account
### Access Key ID & Secret Access Key
You can store Access Key ID & Secret Access Key in a `Kind=Secret` and reference it from a SecretStore.
```yaml
{% include 'vault-iam-store-static-creds.yaml' %}
```
**NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` in `accessKeyIDSecretRef`, `secretAccessKeySecretRef` with the namespaces where the secrets reside.
### EKS Service Account credentials
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
{% include 'vault-iam-store-sa.yaml' %}
```
Reference the service account from above in the Secret Store:
```yaml
{% include 'vault-iam-store.yaml' %}
```
### Controller's Pod Identity
This is basicially a zero-configuration authentication approach that inherits the credentials from the controller's pod identity
This approach assumes that appropriate IRSA setup is done controller's pod (i.e. IRSA enabled IAM role is created appropriately and controller's service account is annotated appropriately with the annotation "eks.amazonaws.com/role-arn" to enable IRSA)
```yaml
{% include 'vault-iam-store-controller-pod-identity.yaml' %}
```
**NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` for `serviceAccountRef` with the namespace where the service account resides.
```yaml
{% include 'vault-jwt-store.yaml' %}
```
**NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` in `secretRef` with the namespace where the secret resides.
### PushSecret
Vault supports PushSecret features which allow you to sync a given kubernetes secret key into a hashicorp vault secret. In order to do so, it is expected that the secret key is a valid JSON object.

View file

@ -0,0 +1,21 @@
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend-aws-iam
spec:
provider:
vault:
server: "http://my.vault.server:8200"
path: secret
version: v2
namespace: <vault_namespace>
auth:
iam:
# Path where the AWS auth method is enabled in Vault, e.g: "aws/". Defaults to aws
path: aws
# AWS Region. Defaults to us-east-1
region: us-east-1
# Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine
vaultRole: vault-role-for-aws-iam-auth
# Optional. Placeholder to supply header X-Vault-AWS-IAM-Server-ID. It is an additional (optional) header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws
vaultAwsIamServerID: example-vaultAwsIamServerID

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-irsa-enabled-role
name: my-serviceaccount
namespace: default

View file

@ -0,0 +1,33 @@
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend-aws-iam
spec:
provider:
vault:
server: "http://my.vault.server:8200"
path: secret
version: v2
namespace: <vault_namespace>
auth:
iam:
# Path where the AWS auth method is enabled in Vault, e.g: "aws/". Defaults to aws
path: aws
# AWS Region. Defaults to us-east-1
region: us-east-1
# optional: assume role before fetching secrets
role: arn:aws:iam::1234567890:role/role-a
# Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine
vaultRole: vault-role-for-aws-iam-auth
# Optional. Placeholder to supply header X-Vault-AWS-IAM-Server-ID. It is an additional (optional) header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws
vaultAwsIamServerID: example-vaultAwsIamServerID
secretRef: #Use this method when you have static AWS creds.
accessKeyIDSecretRef:
name: vault-iam-creds-secret
key: access-key
secretAccessKeySecretRef:
name: vault-iam-creds-secret
key: secret-access-key
sessionTokenSecretRef:
name: vault-iam-creds-secret
key: secret-session-token

View file

@ -0,0 +1,24 @@
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend-aws-iam
spec:
provider:
vault:
server: "http://my.vault.server:8200"
path: secret
version: v2
namespace: <vault_namespace>
auth:
iam:
# Path where the AWS auth method is enabled in Vault, e.g: "aws/". Defaults to aws
path: aws
# AWS Region. Defaults to us-east-1
region: us-east-1
# Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine
vaultRole: vault-role-for-aws-iam-auth
# Optional. Placeholder to supply header X-Vault-AWS-IAM-Server-ID. It is an additional (optional) header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws
vaultAwsIamServerID: example-vaultAwsIamServerID
jwt:
serviceAccountRef:
name: my-serviceaccount #Provide service account with IRSA enabled

View file

@ -179,7 +179,6 @@ require (
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday v1.5.2 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect

View file

@ -853,7 +853,6 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=

5
go.mod
View file

@ -71,7 +71,9 @@ require (
github.com/alibabacloud-go/tea-utils/v2 v2.0.1
github.com/aliyun/credentials-go v1.2.7
github.com/avast/retry-go/v4 v4.3.4
github.com/golang-jwt/jwt/v5 v5.0.0-rc.2
github.com/hashicorp/golang-lru v0.5.4
github.com/hashicorp/vault/api/auth/aws v0.4.0
github.com/keeper-security/secrets-manager-go/core v1.5.0
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.1
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.16
@ -90,6 +92,9 @@ require (
github.com/clbanning/mxj/v2 v2.5.7 // indirect
github.com/go-playground/validator/v10 v10.13.0 // indirect
github.com/google/s2a-go v0.1.3 // indirect
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
)

17
go.sum
View file

@ -154,6 +154,7 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/avast/retry-go/v4 v4.3.4 h1:pHLkL7jvCvP317I8Ge+Km2Yhntv3SdkJm7uekkqbKhM=
github.com/avast/retry-go/v4 v4.3.4/go.mod h1:rv+Nla6Vk3/ilU0H51VHddWHiwimzX66yZ0JT6T+UvE=
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
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.44.254 h1:8baW4yal2xGiM/Wm5/ZU10drS8sd+BVjMjPFjJx2ooc=
github.com/aws/aws-sdk-go v1.44.254/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
@ -255,6 +256,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ=
github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
@ -272,6 +274,8 @@ github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0-rc.2 h1:hXPcSazn8wKOfSb9y2m1bdgUMlDxVDarxh3lJVbC6JE=
github.com/golang-jwt/jwt/v5 v5.0.0-rc.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -381,6 +385,8 @@ github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUD
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
@ -389,6 +395,8 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
@ -401,6 +409,8 @@ github.com/hashicorp/vault/api v1.9.1 h1:LtY/I16+5jVGU8rufyyAkwopgq/HpUnxFBg+QLO
github.com/hashicorp/vault/api v1.9.1/go.mod h1:78kktNcQYbBGSrOjQfHjXN32OhhxXnbYl3zxpd2uPUs=
github.com/hashicorp/vault/api/auth/approle v0.4.0 h1:tjJHoUkPx8zRoFlFy86uvgg/1gpTnDPp0t0BYWTKjjw=
github.com/hashicorp/vault/api/auth/approle v0.4.0/go.mod h1:D2gEpR0aS/F/MEcSjmhUlOsuK1RMVZojsnIQAEf0EV0=
github.com/hashicorp/vault/api/auth/aws v0.4.0 h1:2Myo+XU3X5gQTtr3S+WGXcrLUa6iO4w97VzFFaaBOm8=
github.com/hashicorp/vault/api/auth/aws v0.4.0/go.mod h1:CGm5PAXEREuYpszyA2ERQPFBSIUD+QTqXfKvdI2Gw/Q=
github.com/hashicorp/vault/api/auth/kubernetes v0.4.0 h1:f6OIOF9012JIdqYvOeeewxhtQdJosnog2CHzh33j41s=
github.com/hashicorp/vault/api/auth/kubernetes v0.4.0/go.mod h1:tMewM2hPyFNKP1EXdWbc0dUHHoS5V/0qS04BEaxuy78=
github.com/hashicorp/vault/api/auth/ldap v0.4.0 h1:/P2HCNmcDY6s22JBXxVhr9noaFqPEQS2qwSnWIYezkc=
@ -416,6 +426,7 @@ github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+h
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@ -437,6 +448,7 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -541,7 +553,9 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -812,6 +826,7 @@ golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -0,0 +1,309 @@
/*
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.
*/
// Mostly sourced from ~/external-secrets/pkg/provider/aws/auth
package iamauth
import (
"context"
"fmt"
"os"
"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/endpoints"
"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"
authv1 "k8s.io/api/authentication/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
k8scorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
"github.com/external-secrets/external-secrets/pkg/provider/vault/util"
)
var (
logger = ctrl.Log.WithName("provider").WithName("vault")
)
const (
roleARNAnnotation = "eks.amazonaws.com/role-arn"
audienceAnnotation = "eks.amazonaws.com/audience"
defaultTokenAudience = "sts.amazonaws.com"
STSEndpointEnv = "AWS_STS_ENDPOINT"
AWSWebIdentityTokenFileEnvVar = "AWS_WEB_IDENTITY_TOKEN_FILE"
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"
errFetchSTSecret = "could not fetch SessionToken secret: %w"
errMissingSAK = "missing SecretAccessKey"
errMissingAKID = "missing AccessKeyID"
)
// 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 string, aud []string, 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,
Audiences: aud,
ServiceAccount: name,
k8sClient: clientset.CoreV1(),
}
return stscreds.NewWebIdentityRoleProviderWithOptions(
sts.New(sess), roleArn, "external-secrets-provider-vault", tokenFetcher), nil
}
// ResolveEndpoint returns a ResolverFunc with
// customizable endpoints.
func ResolveEndpoint() endpoints.ResolverFunc {
customEndpoints := make(map[string]string)
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...)
}
}
// mostly taken from:
// https://github.com/aws/secrets-store-csi-driver-provider-aws/blob/main/auth/auth.go#L140-L145
type authTokenFetcher struct {
Namespace string
// Audience is the token aud claim
// which is verified by the aws oidc provider
// see: https://github.com/external-secrets/external-secrets/issues/1251#issuecomment-1161745849
Audiences []string
ServiceAccount string
k8sClient k8scorev1.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) {
logger.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: p.Audiences,
},
}, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("error creating service account token: %w", err)
}
return []byte(tokRsp.Status.Token), nil
}
// CredsFromServiceAccount uses a Kubernetes Service Account to acquire temporary
// credentials using aws.AssumeRoleWithWebIdentity. It will assume the role defined
// in the ServiceAccount annotation.
// If the ClusterSecretStore does not define a namespace it will use the namespace from the ExternalSecret (referentAuth).
// If the ClusterSecretStore defines the namespace it will take precedence.
func CredsFromServiceAccount(ctx context.Context, auth esv1beta1.VaultIamAuth, region string, isClusterKind bool, kube kclient.Client, namespace string, jwtProvider util.JwtProviderFactory) (*credentials.Credentials, error) {
name := auth.JWTAuth.ServiceAccountRef.Name
if isClusterKind && auth.JWTAuth.ServiceAccountRef.Namespace != nil {
namespace = *auth.JWTAuth.ServiceAccountRef.Namespace
}
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)
}
tokenAud := sa.Annotations[audienceAnnotation]
if tokenAud == "" {
tokenAud = defaultTokenAudience
}
audiences := []string{tokenAud}
if len(auth.JWTAuth.ServiceAccountRef.Audiences) > 0 {
audiences = append(audiences, auth.JWTAuth.ServiceAccountRef.Audiences...)
}
jwtProv, err := jwtProvider(name, namespace, roleArn, audiences, region)
if err != nil {
return nil, err
}
logger.V(1).Info("using credentials via service account", "role", roleArn, "region", region)
return credentials.NewCredentials(jwtProv), nil
}
func CredsFromControllerServiceAccount(ctx context.Context, saname, ns, region string, kube kclient.Client, jwtProvider util.JwtProviderFactory) (*credentials.Credentials, error) {
name := saname
nmspc := ns
sa := v1.ServiceAccount{}
err := kube.Get(ctx, types.NamespacedName{
Name: name,
Namespace: nmspc,
}, &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, nmspc)
}
tokenAud := sa.Annotations[audienceAnnotation]
if tokenAud == "" {
tokenAud = defaultTokenAudience
}
audiences := []string{tokenAud}
jwtProv, err := jwtProvider(name, nmspc, roleArn, audiences, region)
if err != nil {
return nil, err
}
logger.V(1).Info("using credentials via service account", "role", roleArn, "region", region)
return credentials.NewCredentials(jwtProv), nil
}
// CredsFromSecretRef pulls access-key / secret-access-key from a secretRef to
// construct a aws.Credentials object
// The namespace of the external secret is used if the ClusterSecretStore does not specify a namespace (referentAuth)
// If the ClusterSecretStore defines a namespace it will take precedence.
func CredsFromSecretRef(ctx context.Context, auth esv1beta1.VaultIamAuth, isClusterKind bool, kube kclient.Client, namespace string) (*credentials.Credentials, error) {
ke := kclient.ObjectKey{
Name: auth.SecretRef.AccessKeyID.Name,
Namespace: namespace,
}
if isClusterKind && auth.SecretRef.AccessKeyID.Namespace != nil {
ke.Namespace = *auth.SecretRef.AccessKeyID.Namespace
}
akSecret := v1.Secret{}
err := kube.Get(ctx, ke, &akSecret)
if err != nil {
return nil, fmt.Errorf(errFetchAKIDSecret, err)
}
ke = kclient.ObjectKey{
Name: auth.SecretRef.SecretAccessKey.Name,
Namespace: namespace,
}
if isClusterKind && auth.SecretRef.SecretAccessKey.Namespace != nil {
ke.Namespace = *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[auth.SecretRef.SecretAccessKey.Key])
aks := string(akSecret.Data[auth.SecretRef.AccessKeyID.Key])
if sak == "" {
return nil, fmt.Errorf(errMissingSAK)
}
if aks == "" {
return nil, fmt.Errorf(errMissingAKID)
}
var sessionToken string
if auth.SecretRef.SessionToken != nil {
ke = kclient.ObjectKey{
Name: auth.SecretRef.SessionToken.Name,
Namespace: namespace,
}
if isClusterKind && auth.SecretRef.SessionToken.Namespace != nil {
ke.Namespace = *auth.SecretRef.SessionToken.Namespace
}
stSecret := v1.Secret{}
err = kube.Get(ctx, ke, &stSecret)
if err != nil {
return nil, fmt.Errorf(errFetchSTSecret, err)
}
sessionToken = string(stSecret.Data[auth.SecretRef.SessionToken.Key])
}
return credentials.NewStaticCredentials(aks, sak, sessionToken), err
}
type STSProvider func(*session.Session) stsiface.STSAPI
func DefaultSTSProvider(sess *session.Session) stsiface.STSAPI {
return sts.New(sess)
}
// getAWSSession returns the aws session or an error.
func GetAWSSession(config *aws.Config) (*session.Session, error) {
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
}
return sess, nil
}

View file

@ -0,0 +1,60 @@
/*
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 iamauth
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/external-secrets/external-secrets/pkg/provider/util/fake"
)
func TestTokenFetcher(t *testing.T) {
tf := &authTokenFetcher{
ServiceAccount: "foobar",
Namespace: "example",
k8sClient: fake.NewCreateTokenMock().WithToken("FAKETOKEN"),
}
token, err := tf.FetchToken(context.Background())
assert.Nil(t, err)
assert.Equal(t, []byte("FAKETOKEN"), token)
}
func TestResolver(t *testing.T) {
tbl := []struct {
env string
service string
url string
}{
{
env: STSEndpointEnv,
service: "sts",
url: "http://sts.foo",
},
}
for _, item := range tbl {
t.Setenv(item.env, item.url)
}
f := ResolveEndpoint()
for _, item := range tbl {
ep, err := f.EndpointFor(item.service, "")
assert.Nil(t, err)
assert.Equal(t, item.url, ep.URL)
}
}

View file

@ -17,9 +17,12 @@ package util
import (
"context"
"github.com/aws/aws-sdk-go/aws/credentials"
vault "github.com/hashicorp/vault/api"
)
type JwtProviderFactory func(name, namespace, roleArn string, aud []string, region string) (credentials.Provider, error)
type Auth interface {
Login(ctx context.Context, authMethod vault.AuthMethod) (*vault.Secret, error)
}

View file

@ -28,9 +28,14 @@ import (
"strconv"
"strings"
"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/go-logr/logr"
"github.com/golang-jwt/jwt/v5"
vault "github.com/hashicorp/vault/api"
approle "github.com/hashicorp/vault/api/auth/approle"
authaws "github.com/hashicorp/vault/api/auth/aws"
authkubernetes "github.com/hashicorp/vault/api/auth/kubernetes"
authldap "github.com/hashicorp/vault/api/auth/ldap"
"github.com/spf13/pflag"
@ -51,6 +56,7 @@ import (
"github.com/external-secrets/external-secrets/pkg/feature"
"github.com/external-secrets/external-secrets/pkg/find"
"github.com/external-secrets/external-secrets/pkg/provider/metrics"
vaultiamauth "github.com/external-secrets/external-secrets/pkg/provider/vault/iamauth"
"github.com/external-secrets/external-secrets/pkg/provider/vault/util"
"github.com/external-secrets/external-secrets/pkg/utils"
)
@ -64,7 +70,9 @@ var (
)
const (
serviceAccTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
serviceAccTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
defaultAWSRegion = "us-east-1"
defaultAWSAuthMountPath = "aws"
errVaultStore = "received invalid Vault SecretStore resource: %w"
errVaultCacheCreate = "cannot create Vault client cache: %s"
@ -87,6 +95,11 @@ const (
errUnsupportedKvVersion = "cannot perform find operations with kv version v1"
errUnsupportedMetadataKvVersion = "cannot perform metadata fetch operations with kv version v1"
errNotFound = "secret not found"
errIrsaTokenEnvVarNotFoundOnPod = "expected env variable: %s not found on controller's pod"
errIrsaTokenFileNotFoundOnPod = "web ddentity token file not found at %s location: %w"
errIrsaTokenFileNotReadable = "could not read the web identity token from the file %s: %w"
errIrsaTokenNotValidJWT = "could not parse web identity token available at %s. not a valid jwt?: %w"
errPodInfoNotFoundOnToken = "could not find pod identity info on token %s: %w"
errGetKubeSA = "cannot get Kubernetes service account %q: %w"
errGetKubeSASecrets = "cannot find secrets bound to service account: %q"
@ -352,6 +365,29 @@ func (c *Connector) ValidateStore(store esv1beta1.GenericStore) error {
return fmt.Errorf(errInvalidTokenRef, err)
}
}
if p.Auth.Iam != nil {
if p.Auth.Iam.JWTAuth != nil {
if p.Auth.Iam.JWTAuth.ServiceAccountRef != nil {
if err := utils.ValidateReferentServiceAccountSelector(store, *p.Auth.Iam.JWTAuth.ServiceAccountRef); err != nil {
return fmt.Errorf(errInvalidTokenRef, err)
}
}
}
if p.Auth.Iam.SecretRef != nil {
if err := utils.ValidateReferentSecretSelector(store, p.Auth.Iam.SecretRef.AccessKeyID); err != nil {
return fmt.Errorf(errInvalidTokenRef, err)
}
if err := utils.ValidateReferentSecretSelector(store, p.Auth.Iam.SecretRef.SecretAccessKey); err != nil {
return fmt.Errorf(errInvalidTokenRef, err)
}
if p.Auth.Iam.SecretRef.SessionToken != nil {
if err := utils.ValidateReferentSecretSelector(store, *p.Auth.Iam.SecretRef.SessionToken); err != nil {
return fmt.Errorf(errInvalidTokenRef, err)
}
}
}
}
return nil
}
@ -740,6 +776,15 @@ func isReferentSpec(prov *esv1beta1.VaultProvider) bool {
if prov.Auth.Cert != nil && prov.Auth.Cert.SecretRef.Namespace == nil {
return true
}
if prov.Auth.Iam != nil && prov.Auth.Iam.JWTAuth != nil && prov.Auth.Iam.JWTAuth.ServiceAccountRef != nil && prov.Auth.Iam.JWTAuth.ServiceAccountRef.Namespace == nil {
return true
}
if prov.Auth.Iam != nil && prov.Auth.Iam.SecretRef != nil &&
(prov.Auth.Iam.SecretRef.AccessKeyID.Namespace == nil ||
prov.Auth.Iam.SecretRef.SecretAccessKey.Namespace == nil ||
(prov.Auth.Iam.SecretRef.SessionToken != nil && prov.Auth.Iam.SecretRef.SessionToken.Namespace == nil)) {
return true
}
return false
}
@ -1034,6 +1079,12 @@ func (v *client) setAuth(ctx context.Context, cfg *vault.Config) error {
return err
}
tokenExists, err = setIamAuthToken(ctx, v, vaultiamauth.DefaultJWTProvider, vaultiamauth.DefaultSTSProvider)
if tokenExists {
v.log.V(1).Info("Retrieved new token using IAM auth")
return err
}
return errors.New(errAuthFormat)
}
@ -1110,6 +1161,19 @@ func setCertAuthToken(ctx context.Context, v *client, cfg *vault.Config) (bool,
return false, nil
}
func setIamAuthToken(ctx context.Context, v *client, jwtProvider util.JwtProviderFactory, assumeRoler vaultiamauth.STSProvider) (bool, error) {
iamAuth := v.store.Auth.Iam
isClusterKind := v.storeKind == esv1beta1.ClusterSecretStoreKind
if iamAuth != nil {
err := v.requestTokenWithIamAuth(ctx, iamAuth, isClusterKind, v.kube, v.namespace, jwtProvider, assumeRoler)
if err != nil {
return true, err
}
return true, nil
}
return false, nil
}
func (v *client) secretKeyRefForServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) (string, error) {
serviceAccount := &corev1.ServiceAccount{}
ref := types.NamespacedName{
@ -1407,6 +1471,133 @@ func (v *client) requestTokenWithCertAuth(ctx context.Context, certAuth *esv1bet
return nil
}
func (v *client) requestTokenWithIamAuth(ctx context.Context, iamAuth *esv1beta1.VaultIamAuth, ick bool, k kclient.Client, n string, jwtProvider util.JwtProviderFactory, assumeRoler vaultiamauth.STSProvider) error {
jwtAuth := iamAuth.JWTAuth
secretRefAuth := iamAuth.SecretRef
regionAWS := defaultAWSRegion
awsAuthMountPath := defaultAWSAuthMountPath
if iamAuth.Region != "" {
regionAWS = iamAuth.Region
}
if iamAuth.Path != "" {
awsAuthMountPath = iamAuth.Path
}
var creds *credentials.Credentials
var err error
if jwtAuth != nil { // use credentials from a sa explicitly defined and referenced. Highest preference is given to this method/configuration.
creds, err = vaultiamauth.CredsFromServiceAccount(ctx, *iamAuth, regionAWS, ick, k, n, jwtProvider)
if err != nil {
return err
}
} else if secretRefAuth != nil { // if jwtAuth is not defined, check if secretRef is defined. Second preference.
logger.V(1).Info("using credentials from secretRef")
creds, err = vaultiamauth.CredsFromSecretRef(ctx, *iamAuth, ick, k, n)
if err != nil {
return err
}
}
// Neither of jwtAuth or secretRefAuth defined. Last preference.
// Default to controller pod's identity
if jwtAuth == nil && secretRefAuth == nil {
// Checking if controller pod's service account is IRSA enabled and Web Identity token is available on pod
tknFile, tknFileEnvVarPresent := os.LookupEnv(vaultiamauth.AWSWebIdentityTokenFileEnvVar)
if !tknFileEnvVarPresent {
return fmt.Errorf(errIrsaTokenEnvVarNotFoundOnPod, vaultiamauth.AWSWebIdentityTokenFileEnvVar) // No Web Identity(IRSA) token found on pod
}
// IRSA enabled service account, let's check that the jwt token filemount and file exists
if _, err := os.Stat(tknFile); err != nil {
return fmt.Errorf(errIrsaTokenFileNotFoundOnPod, tknFile, err)
}
// everything looks good so far, let's fetch the jwt token from AWS_WEB_IDENTITY_TOKEN_FILE
jwtByte, err := os.ReadFile(tknFile)
if err != nil {
return fmt.Errorf(errIrsaTokenFileNotReadable, tknFile, err)
}
// let's parse the jwt token
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
token, _, err := parser.ParseUnverified(string(jwtByte), jwt.MapClaims{})
if err != nil {
return fmt.Errorf(errIrsaTokenNotValidJWT, tknFile, err) // JWT token parser error
}
var ns string
var sa string
// let's fetch the namespace and serviceaccount from parsed jwt token
if claims, ok := token.Claims.(jwt.MapClaims); ok {
ns = claims["kubernetes.io"].(map[string]interface{})["namespace"].(string)
sa = claims["kubernetes.io"].(map[string]interface{})["serviceaccount"].(map[string]interface{})["name"].(string)
} else {
return fmt.Errorf(errPodInfoNotFoundOnToken, tknFile, err)
}
creds, err = vaultiamauth.CredsFromControllerServiceAccount(ctx, sa, ns, regionAWS, k, jwtProvider)
if err != nil {
return err
}
}
config := aws.NewConfig().WithEndpointResolver(vaultiamauth.ResolveEndpoint())
if creds != nil {
config.WithCredentials(creds)
}
if regionAWS != "" {
config.WithRegion(regionAWS)
}
sess, err := vaultiamauth.GetAWSSession(config)
if err != nil {
return err
}
if iamAuth.AWSIAMRole != "" {
stsclient := assumeRoler(sess)
if iamAuth.ExternalID != "" {
var setExternalID = func(p *stscreds.AssumeRoleProvider) {
p.ExternalID = aws.String(iamAuth.ExternalID)
}
sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, iamAuth.AWSIAMRole, setExternalID))
} else {
sess.Config.WithCredentials(stscreds.NewCredentialsWithClient(stsclient, iamAuth.AWSIAMRole))
}
}
getCreds, err := sess.Config.Credentials.Get()
if err != nil {
return err
}
// Set environment variables. These would be fetched by Login
os.Setenv("AWS_ACCESS_KEY_ID", getCreds.AccessKeyID)
os.Setenv("AWS_SECRET_ACCESS_KEY", getCreds.SecretAccessKey)
os.Setenv("AWS_SESSION_TOKEN", getCreds.SessionToken)
var awsAuthClient *authaws.AWSAuth
if iamAuth.VaultAWSIAMServerID != "" {
awsAuthClient, err = authaws.NewAWSAuth(authaws.WithRegion(regionAWS), authaws.WithIAMAuth(), authaws.WithRole(iamAuth.Role), authaws.WithMountPath(awsAuthMountPath), authaws.WithIAMServerIDHeader(iamAuth.VaultAWSIAMServerID))
if err != nil {
return err
}
} else {
awsAuthClient, err = authaws.NewAWSAuth(authaws.WithRegion(regionAWS), authaws.WithIAMAuth(), authaws.WithRole(iamAuth.Role), authaws.WithMountPath(awsAuthMountPath))
if err != nil {
return err
}
}
_, err = v.auth.Login(ctx, awsAuthClient)
metrics.ObserveAPICall(metrics.ProviderHCVault, metrics.CallHCVaultLogin, err)
if err != nil {
return err
}
return nil
}
func init() {
var vaultTokenCacheSize int
fs := pflag.NewFlagSet("vault", pflag.ExitOnError)

View file

@ -161,6 +161,45 @@ func makeInvalidClusterSecretStoreWithK8sCerts() *esv1beta1.ClusterSecretStore {
}
}
func makeValidSecretStoreWithIamAuthSecret() *esv1beta1.SecretStore {
return &esv1beta1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: "vault-store",
Namespace: "default",
},
Spec: esv1beta1.SecretStoreSpec{
Provider: &esv1beta1.SecretStoreProvider{
Vault: &esv1beta1.VaultProvider{
Server: "https://vault.example.com:8200",
Path: &secretStorePath,
Version: esv1beta1.VaultKVStoreV2,
Auth: esv1beta1.VaultAuth{
Iam: &esv1beta1.VaultIamAuth{
Path: "aws",
Region: "us-east-1",
Role: "vault-role",
SecretRef: &esv1beta1.VaultAwsAuthSecretRef{
AccessKeyID: esmeta.SecretKeySelector{
Name: "vault-iam-creds-secret",
Key: "access-key",
},
SecretAccessKey: esmeta.SecretKeySelector{
Name: "vault-iam-creds-secret",
Key: "secret-access-key",
},
SessionToken: &esmeta.SecretKeySelector{
Name: "vault-iam-creds-secret",
Key: "secret-session-token",
},
},
},
},
},
},
},
}
}
type secretStoreTweakFn func(s *esv1beta1.SecretStore)
func makeSecretStore(tweaks ...secretStoreTweakFn) *esv1beta1.SecretStore {
@ -335,6 +374,29 @@ MIIFkTCCA3mgAwIBAgIUBEUg3m/WqAsWHG4Q/II3IePFfuowDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE
err: fmt.Errorf(errVaultCert, errors.New(`cannot find secret data for key: "cert"`)),
},
},
"SuccessfulVaultStoreWithIamAuthSecret": {
reason: "Should return a Vault provider successfully",
args: args{
store: makeValidSecretStoreWithIamAuthSecret(),
ns: "default",
kube: clientfake.NewClientBuilder().WithObjects(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "vault-iam-creds-secret",
Namespace: "default",
},
Data: map[string][]byte{
"access-key": []byte("TESTING"),
"secret-access-key": []byte("ABCDEF"),
"secret-session-token": []byte("c2VjcmV0LXNlc3Npb24tdG9rZW4K"),
},
}).Build(),
corev1: utilfake.NewCreateTokenMock().WithToken("ok"),
newClientFunc: fake.ClientWithLoginMock,
},
want: want{
err: nil,
},
},
"SuccessfulVaultStoreWithK8sCertConfigMap": {
reason: "Should return a Vault prodvider with the cert from k8s",
args: args{