Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ AWS offers two services to manage secrets and parameters conveniently in your co
## Installation

### Requirements
* Amazon Elastic Kubernetes Service (EKS) 1.24+ running an EC2 node group (Fargate node groups are not supported **[^1]**)
* Amazon Elastic Kubernetes Service (EKS) 1.17+ running an EC2 node group (Fargate node groups are not supported **[^1]**). If using EKS Pod Identity feature, EKS 1.24+ is required.
* [Secrets Store CSI driver installed](https://secrets-store-csi-driver.sigs.k8s.io/getting-started/installation.html):
```shell
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
Expand Down Expand Up @@ -183,9 +183,13 @@ The parameters section contains the details of the mount request and contain one
* usePodIdentity: An optional field that determines the authentication approach. When not specified, it defaults to using IAM Roles for Service Accounts (IRSA).
- To use EKS Pod Identity, use any of these values: "true", "True", "TRUE", "t", "T".
- To explicitly use IRSA, set to any of these values: "false", "False", "FALSE", "f", or "F".
* preferredAddressType: An optional field that specifies the preferred IP address type for Pod Identity Agent endpoint communication. The field is only applicable when using EKS Pod Identity feature and will be ignored when using IAM Roles for Service Accounts.Values are case-insensitive. Valid values are:
- "ipv4", "IPv4", or "IPV4" - Force the use of Pod Identity Agent IPv4 endpoint
- "ipv6", "IPv6", or "IPV6" - Force the use of Pod Identity Agent IPv6 endpoint
- not specified or any other value - Use auto endpoint selection, trying IPv4 endpoint first and falling back to IPv6 endpoint if IPv4 fails

The primary objects field of the SecretProviderClass can contain the following sub-fields:
* objectName: This field is required. It specifies the name of the secret or parameter to be fetched. For Secrets Manager this is the [SecretId](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html#API_GetSecretValue_RequestParameters) parameter and can be either the friendly name or full ARN of the secret. For SSM Parameter Store, this must be the [Name](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html#API_GetParameter_RequestParameters) of the parameter and can not be a full ARN.
* objectName: This field is required. It specifies the name of the secret or parameter to be fetched. For Secrets Manager this is the [SecretId](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html#API_GetSecretValue_RequestParameters) parameter and can be either the friendly name or full ARN of the secret. For SSM Parameter Store, this is the [Name](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html#API_GetParameter_RequestParameters) of the parameter and can be either the name or full ARN of the parameter.
* objectType: This field is optional when using a Secrets Manager ARN for objectName, otherwise it is required. This field can be either "secretsmanager" or "ssmparameter".
* objectAlias: This optional field specifies the file name under which the secret will be mounted. When not specified the file name defaults to objectName.
* objectVersion: This field is optional, and generally not recommended since updates to the secret require updating this field. For Secrets Manager this is the [VersionId](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html#API_GetSecretValue_RequestParameters). For SSM Parameter Store, this is the optional [version number](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-versions.html#reference-parameter-version).
Expand Down Expand Up @@ -266,7 +270,7 @@ If 'failoverObject' is defined, then objectAlias is required.
EKS Pod Identity [CreatePodIdentityAssociation](https://docs.aws.amazon.com/eks/latest/APIReference/API_CreatePodIdentityAssociation.html) requires the IAM role to reside in the same AWS account as the EKS cluster.

To mount AWS Secrets Manager secrets from a different AWS account than your EKS cluster, follow [cross-account access](https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_examples_cross.html) to set up resource policy for the secret, key policy for the KMS key, and IAM role used in Pod Identity association.
Fetching cross-account parameters from SSM Parameter Store is not supported in the AWS Provider and Config Provider.
Fetching cross-account parameters from SSM Parameter Store is only supported for parameters in the advanced parameter tier. See [Working with Shared Parameters](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-shared-parameters.html) for details.

### Private Builds
You can pull down this git repository and build and install this plugin into your account's [AWS ECR](https://aws.amazon.com/ecr/) registry using the following steps. First clone the repository:
Expand Down
49 changes: 27 additions & 22 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,44 @@ const (
// K8s service account and usePodIdentity flag (and request context). The caller can then obtain AWS
// sessions by calling GetAWSSession.
type Auth struct {
region, nameSpace, svcAcc, podName string
usePodIdentity bool
k8sClient k8sv1.CoreV1Interface
stsClient stsiface.STSAPI
ctx context.Context
region, nameSpace, svcAcc, podName, preferredAddressType string
usePodIdentity bool
k8sClient k8sv1.CoreV1Interface
stsClient stsiface.STSAPI
ctx context.Context
}

// Factory method to create a new Auth object for an incomming mount request.
func NewAuth(
ctx context.Context,
region, nameSpace, svcAcc, podName string,
region, nameSpace, svcAcc, podName, preferredAddressType string,
usePodIdentity bool,
k8sClient k8sv1.CoreV1Interface,
) (auth *Auth, e error) {
var stsClient stsiface.STSAPI

// Get an initial session to use for STS calls.
sess, err := session.NewSession(aws.NewConfig().
WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint).
WithRegion(region),
)
if err != nil {
return nil, err
if !usePodIdentity {
// Get an initial session to use for STS calls when using IRSA
sess, err := session.NewSession(aws.NewConfig().
WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint).
WithRegion(region),
)
if err != nil {
return nil, err
}
stsClient = sts.New(sess)
}

return &Auth{
region: region,
nameSpace: nameSpace,
svcAcc: svcAcc,
podName: podName,
usePodIdentity: usePodIdentity,
k8sClient: k8sClient,
stsClient: sts.New(sess),
ctx: ctx,
region: region,
nameSpace: nameSpace,
svcAcc: svcAcc,
podName: podName,
preferredAddressType: preferredAddressType,
usePodIdentity: usePodIdentity,
k8sClient: k8sClient,
stsClient: stsClient,
ctx: ctx,
}, nil

}
Expand All @@ -75,7 +80,7 @@ func (p Auth) GetAWSSession() (awsSession *session.Session, e error) {

if p.usePodIdentity {
klog.Infof("Using Pod Identity for authentication in namespace: %s, service account: %s", p.nameSpace, p.svcAcc)
credProvider = credential_provider.NewPodIdentityCredentialProvider(p.region, p.nameSpace, p.svcAcc, p.podName, p.k8sClient)
credProvider = credential_provider.NewPodIdentityCredentialProvider(p.region, p.nameSpace, p.svcAcc, p.podName, p.preferredAddressType, p.k8sClient)
} else {
klog.Infof("Using IAM Roles for Service Accounts for authentication in namespace: %s, service account: %s", p.nameSpace, p.svcAcc)
credProvider = credential_provider.NewIRSACredentialProvider(p.stsClient, p.region, p.nameSpace, p.svcAcc, p.k8sClient, p.ctx)
Expand Down
2 changes: 1 addition & 1 deletion charts/secrets-store-csi-driver-provider-aws/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v2
name: secrets-store-csi-driver-provider-aws
version: 0.3.11
version: 0.3.10
kubeVersion: ">=1.17.0-0"
description: A Helm chart for the AWS Secrets Manager and Config Provider for Secret Store CSI Driver
icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ spec:
env:
- name: AWS_USE_FIPS_ENDPOINT
value: {{ .Values.useFipsEndpoint | quote }}
- name: POD_IP # This is needed to choose between IPv4 and IPv6 Pod Identity Agent endpoint
valueFrom:
fieldRef:
fieldPath: status.podIP
{{- end }}
volumes:
- name: providervol
Expand Down
125 changes: 79 additions & 46 deletions credential_provider/pod_identity_credential_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,58 +11,77 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sv1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/klog/v2"
"net"
"net/http"
"os"
"strings"
"time"
)

const (
podIdentityAudience = "pods.eks.amazonaws.com"
podIdentityAgentEndpointIPv4 = "http://169.254.170.23/v1/credentials"
podIdentityAgentEndpointIPv6 = "http://[fd00:ec2::23]/v1/credentials"
httpTimeout = 5 * time.Second
podIdentityAuthHeader = "Authorization"
podIdentityAudience = "pods.eks.amazonaws.com"
defaultIPv4Endpoint = "http://169.254.170.23/v1/credentials"
defaultIPv6Endpoint = "http://[fd00:ec2::23]/v1/credentials"
httpTimeout = 100 * time.Millisecond
podIdentityAuthHeader = "Authorization"
)

// defaultPodIdentityAgentEndpoint determines the appropriate EKS Pod Identity Agent endpoint based on IP version
var defaultPodIdentityAgentEndpoint = func() string {
isIPv6, err := isIPv6()
if err != nil {
klog.Warningf("Error determining IP version: %v. Defaulting to IPv4 endpoint", err)
return podIdentityAgentEndpointIPv4
}
var (
podIdentityAgentEndpointIPv4 = defaultIPv4Endpoint
podIdentityAgentEndpointIPv6 = defaultIPv6Endpoint
)

if isIPv6 {
klog.Infof("Using Pod Identity Agent IPv6 endpoint")
return podIdentityAgentEndpointIPv6
}
klog.Infof("Using Pod Identity Agent IPv4 endpoint")
return podIdentityAgentEndpointIPv4
}()
// endpointPreference represents the preferred IP address type for Pod Identity Agent endpoint
type endpointPreference int

var podIdentityAgentEndpoint = defaultPodIdentityAgentEndpoint
const (
// preferenceAuto indicates automatic endpoint selection, trying IPv4 first and falling back to IPv6 if IPv4 fails
preferenceAuto endpointPreference = iota

// preferenceIPv4 forces the use of Pod Identity Agent IPv4 endpoint
preferenceIPv4

// preferenceIPv6 forces the use of Pod Identity Agent IPv6 endpoint
preferenceIPv6
)

// PodIdentityCredentialProvider implements CredentialProvider using pod identity
type PodIdentityCredentialProvider struct {
region string
fetcher authTokenFetcher
httpClient *http.Client
region string
preferredEndpoint endpointPreference
fetcher authTokenFetcher
httpClient *http.Client
}

func NewPodIdentityCredentialProvider(
region, nameSpace, svcAcc, podName string,
region, nameSpace, svcAcc, podName, preferredAddressType string,
k8sClient k8sv1.CoreV1Interface,
) CredentialProvider {

preferredEndpoint := parseAddressPreference(preferredAddressType)
return &PodIdentityCredentialProvider{
region: region,
fetcher: newPodIdentityTokenFetcher(nameSpace, svcAcc, podName, k8sClient),
region: region,
preferredEndpoint: preferredEndpoint,
fetcher: newPodIdentityTokenFetcher(nameSpace, svcAcc, podName, k8sClient),
httpClient: &http.Client{
Timeout: httpTimeout,
},
}
}

// parseAddressPreference converts the provided preferred address type string into an endpointPreference.
func parseAddressPreference(preferredAddressType string) endpointPreference {
switch strings.ToLower(preferredAddressType) {
case "ipv4":
return preferenceIPv4
case "ipv6":
return preferenceIPv6
default:
if preferredAddressType != "" {
klog.Warningf("Unknown preferred address type: %s, falling back to auto selection", preferredAddressType)
}
return preferenceAuto
}
}

type podIdentityTokenFetcher struct {
nameSpace, svcAcc, podName string
k8sClient k8sv1.CoreV1Interface
Expand Down Expand Up @@ -93,7 +112,7 @@ func (p *podIdentityTokenFetcher) FetchToken(ctx credentials.Context) ([]byte, e
},
}

// Use the K8s API to fetch the token from the OIDC provider.
// Use the K8s API to fetch the token associated with service account
tokRsp, err := p.k8sClient.ServiceAccounts(p.nameSpace).CreateToken(ctx, p.svcAcc, &authv1.TokenRequest{
Spec: tokenSpec,
}, metav1.CreateOptions{})
Expand All @@ -111,8 +130,39 @@ func (p *PodIdentityCredentialProvider) GetAWSConfig() (*aws.Config, error) {
return nil, fmt.Errorf("failed to fetch token: %+v", err)
}

switch p.preferredEndpoint {
case preferenceIPv4:
klog.Infof("Using preferred Pod Identity Agent IPv4 endpoint")
config, err := p.getAWSConfigFromPodIdentityAgent(token, podIdentityAgentEndpointIPv4)
if err != nil {
return nil, fmt.Errorf("failed to get AWS config from pod identity agent IPv4 endpoint: %+v", err)
}
return config, nil
case preferenceIPv6:
klog.Infof("Using preferred Pod Identity Agent IPv6 endpoint")
config, err := p.getAWSConfigFromPodIdentityAgent(token, podIdentityAgentEndpointIPv6)
if err != nil {
return nil, fmt.Errorf("failed to get AWS config from pod identity agent IPv6 endpoint: %+v", err)
}
return config, nil
default:
klog.Infof("Using auto Pod Identity Agent endpoint selection")
config, err := p.getAWSConfigFromPodIdentityAgent(token, podIdentityAgentEndpointIPv4)
if err != nil {
klog.Warningf("IPv4 endpoint attempt failed: %+v. Trying IPv6 endpoint", err)
config, err = p.getAWSConfigFromPodIdentityAgent(token, podIdentityAgentEndpointIPv6)
if err != nil {
return nil, fmt.Errorf("failed to get AWS config from pod identity agent: %+v", err)
}
}
return config, nil
}
}

func (p *PodIdentityCredentialProvider) getAWSConfigFromPodIdentityAgent(token []byte, podIdentityAgentEndpoint string) (*aws.Config, error) {
req, err := http.NewRequest("GET", podIdentityAgentEndpoint, nil)
if err != nil {

return nil, fmt.Errorf("failed to create HTTP request to pod identity agent: %+v", err)
}
req.Header.Set(podIdentityAuthHeader, string(token))
Expand Down Expand Up @@ -158,20 +208,3 @@ func (p *PodIdentityCredentialProvider) GetAWSConfig() (*aws.Config, error) {
creds.Token,
)), nil
}

func isIPv6() (isIPv6 bool, err error) {
podIP := os.Getenv("POD_IP")
if podIP == "" {
return false, fmt.Errorf("POD_IP environment variable is not set")
}

parsedIP := net.ParseIP(podIP)
if parsedIP == nil {
return false, fmt.Errorf("invalid IP address format in POD_IP: %s", podIP)
}

isIPv6 = parsedIP.To4() == nil
klog.Infof("Pod IP %s is IPv%d", podIP, map[bool]int{false: 4, true: 6}[isIPv6])

return isIPv6, nil
}
Loading