Skip to content

Commit 917098f

Browse files
EKS Pod Identity support for pod level credentials (#458)
*Description of changes:* With this change, we will support the configuration of pod level credentials with EKS Pod Identity. In EKS clusters, this is an alternative to IRSA with an easier configuration process. *Key Changes:* - Credential Provider - In `provider_pod.go` the precedence is given to IRSA credentials. If they are not configured, then EKS Pod Identity credentials are forwarded to the Mountpoint process instead. - Added unit tests - Testing - Added E2E tests covering EKS Pod Identity setup scenarios with IAM roles of varying levels of S3 access - Added one pod level IRSA E2E test to make sure driver level EKS Pod Identity is not used instead - Added E2E tests covering setup scenario of IRSA and EKS Pod Identity - Manual testing performed on personal EKS cluster By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Signed-off-by: Renan Magagnin <renanmag@amazon.co.uk>
1 parent e8cc260 commit 917098f

4 files changed

Lines changed: 500 additions & 119 deletions

File tree

charts/aws-mountpoint-s3-csi-driver/templates/csidriver.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ spec:
1212
tokenRequests:
1313
- audience: "sts.amazonaws.com"
1414
expirationSeconds: 3600
15+
{{- if .Values.experimental.podMounter }}
16+
- audience: "pods.eks.amazonaws.com"
17+
expirationSeconds: 3600
18+
{{- end }}
1519
requiresRepublish: true

pkg/driver/node/credentialprovider/provider_pod.go

Lines changed: 142 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ import (
1616
"k8s.io/klog/v2"
1717

1818
"github.com/awslabs/aws-s3-csi-driver/pkg/driver/node/envprovider"
19+
"github.com/awslabs/aws-s3-csi-driver/pkg/util"
1920
)
2021

2122
const (
22-
serviceAccountTokenAudienceSTS = "sts.amazonaws.com"
23-
serviceAccountRoleAnnotation = "eks.amazonaws.com/role-arn"
23+
serviceAccountTokenAudienceSTS = "sts.amazonaws.com"
24+
serviceAccountTokenAudiencePodIdentity = "pods.eks.amazonaws.com"
25+
serviceAccountRoleAnnotation = "eks.amazonaws.com/role-arn"
26+
// TODO: Add a driver configuration flag to handle custom values of podIdentityCredURI. Currently we are assuming the default IPv4 address as determined in the references below:
27+
// Doc: https://docs.aws.amazon.com/eks/latest/userguide/pod-id-agent-setup.html
28+
// Source code: https://github.com/aws/eks-pod-identity-agent/blob/8bd71a236522993f02427083e485c83f6ae4fe31/configuration/config.go
29+
podIdentityCredURI = "http://169.254.170.23/v1/credentials"
2430
)
2531

2632
const podLevelCredentialsDocsPage = "https://github.com/awslabs/mountpoint-s3-csi-driver/blob/main/docs/CONFIGURATION.md#pod-level-credentials"
@@ -35,6 +41,12 @@ type serviceAccountToken struct {
3541
func (c *Provider) provideFromPod(ctx context.Context, provideCtx ProvideContext) (envprovider.Environment, error) {
3642
klog.V(4).Infof("credentialprovider: Using pod identity")
3743

44+
podID := provideCtx.GetCredentialPodID()
45+
if podID == "" {
46+
return nil, status.Error(codes.InvalidArgument, "Missing Pod info. Please make sure to enable `podInfoOnMountCompat`, see "+podLevelCredentialsDocsPage)
47+
}
48+
49+
// 1. Parse ServiceAccountTokens map
3850
tokensJson := provideCtx.ServiceAccountTokens
3951
if tokensJson == "" {
4052
klog.Error("credentialprovider: `authenticationSource` configured to `pod` but no service account tokens are received. Please make sure to enable `podInfoOnMountCompat`, see " + podLevelCredentialsDocsPage)
@@ -48,78 +60,110 @@ func (c *Provider) provideFromPod(ctx context.Context, provideCtx ProvideContext
4860

4961
stsToken := tokens[serviceAccountTokenAudienceSTS]
5062
if stsToken == nil {
51-
klog.Errorf("credentialprovider: `authenticationSource` configured to `pod` but no service account tokens for %s received. Please make sure to enable `podInfoOnMountCompat`, see "+podLevelCredentialsDocsPage, serviceAccountTokenAudienceSTS)
63+
klog.Errorf("credentialprovider: `authenticationSource` configured to `pod` but no service account token for %s received. Please make sure to enable `podInfoOnMountCompat`, see "+podLevelCredentialsDocsPage, serviceAccountTokenAudienceSTS)
5264
return nil, status.Errorf(codes.InvalidArgument, "Missing service account token for %s", serviceAccountTokenAudienceSTS)
5365
}
5466

55-
roleARN, err := c.findPodServiceAccountRole(ctx, provideCtx)
56-
if err != nil {
57-
return nil, err
58-
}
59-
60-
region, err := c.stsRegion(provideCtx)
61-
if err != nil {
62-
return nil, status.Errorf(codes.InvalidArgument, "Failed to detect STS AWS Region, please explicitly set the AWS Region, see "+stsConfigDocsPage)
63-
}
64-
65-
defaultRegion := os.Getenv(envprovider.EnvDefaultRegion)
66-
if defaultRegion == "" {
67-
defaultRegion = region
68-
}
69-
70-
podID := provideCtx.GetCredentialPodID()
71-
volumeID := provideCtx.VolumeID
72-
if podID == "" {
73-
return nil, status.Error(codes.InvalidArgument, "Missing Pod info. Please make sure to enable `podInfoOnMountCompat`, see "+podLevelCredentialsDocsPage)
74-
}
75-
76-
tokenName := podLevelServiceAccountTokenName(podID, volumeID)
77-
78-
err = renameio.WriteFile(filepath.Join(provideCtx.WritePath, tokenName), []byte(stsToken.Token), CredentialFilePerm)
79-
if err != nil {
80-
return nil, status.Errorf(codes.Internal, "Failed to write service account token: %v", err)
67+
eksToken := tokens[serviceAccountTokenAudiencePodIdentity]
68+
if util.UsePodMounter() && eksToken == nil {
69+
klog.Errorf("credentialprovider: `authenticationSource` configured to `pod` but no service account token for %s received. Please make sure to enable `podInfoOnMountCompat`, see "+podLevelCredentialsDocsPage, serviceAccountTokenAudiencePodIdentity)
70+
return nil, status.Errorf(codes.InvalidArgument, "Missing service account token for %s", serviceAccountTokenAudiencePodIdentity)
8171
}
8272

73+
// 2. Create environment to be returned with common variables (used in both cases: IRSA and EKS PI)
8374
podNamespace := provideCtx.PodNamespace
8475
podServiceAccount := provideCtx.ServiceAccountName
8576
cacheKey := podNamespace + "/" + podServiceAccount
8677

87-
return envprovider.Environment{
88-
envprovider.EnvRoleARN: roleARN,
89-
envprovider.EnvWebIdentityTokenFile: filepath.Join(provideCtx.EnvPath, tokenName),
90-
91-
envprovider.EnvRegion: region,
92-
envprovider.EnvDefaultRegion: defaultRegion,
93-
78+
env := envprovider.Environment{
9479
envprovider.EnvEC2MetadataDisabled: "true",
9580

9681
// TODO: These were needed with `systemd` but probably won't be necessary with containerization.
9782
envprovider.EnvMountpointCacheKey: cacheKey,
9883
envprovider.EnvConfigFile: filepath.Join(provideCtx.EnvPath, "disable-config"),
9984
envprovider.EnvSharedCredentialsFile: filepath.Join(provideCtx.EnvPath, "disable-credentials"),
100-
}, nil
85+
}
86+
87+
// 3. Provide credentials with IRSA. If not configured, provide credentials with EKS Pod Identity instead.
88+
irsaCredentialsEnvironment, irsaCredentialsEnvironmentError := c.createIRSACredentialsEnvironment(ctx, provideCtx)
89+
if irsaCredentialsEnvironmentError == nil {
90+
klog.V(4).Infof("Providing credentials from pod with STS Web Identity provider (IRSA)")
91+
92+
// Copy STS Token file to WritePath
93+
tokenName := podLevelSTSWebIdentityServiceAccountTokenName(podID, provideCtx.VolumeID)
94+
err := renameio.WriteFile(filepath.Join(provideCtx.WritePath, tokenName), []byte(stsToken.Token), CredentialFilePerm)
95+
if err != nil {
96+
return nil, status.Errorf(codes.Internal, "Failed to write service account STS token: %v", err)
97+
}
98+
99+
env.Merge(irsaCredentialsEnvironment)
100+
return env, nil
101+
} else if errors.Is(irsaCredentialsEnvironmentError, errMissingServiceAccountAnnotationForIRSA) {
102+
if !util.UsePodMounter() {
103+
return nil, status.Errorf(codes.InvalidArgument, "Missing role annotation on pod's service account %s/%s", podNamespace, podServiceAccount)
104+
}
105+
106+
klog.V(4).Infof("Providing credentials from pod with Container credential provider (EKS Pod Identity)")
107+
eksPodIdentityCredentialsEnvironment := c.createEKSPodIdentityCredentialsEnvironment(provideCtx)
108+
109+
// Copy EKS Token file to WritePath
110+
tokenNameEKS := podLevelEksPodIdentityServiceAccountTokenName(podID, provideCtx.VolumeID)
111+
err := renameio.WriteFile(filepath.Join(provideCtx.WritePath, tokenNameEKS), []byte(eksToken.Token), CredentialFilePerm)
112+
if err != nil {
113+
return nil, status.Errorf(codes.Internal, "Failed to write service account EKS Pod Identity token: %v", err)
114+
}
115+
116+
env.Merge(eksPodIdentityCredentialsEnvironment)
117+
return env, nil
118+
}
119+
120+
klog.V(4).Infof("Error providing credentials from pod with STS Web Identity provider (IRSA)")
121+
return nil, irsaCredentialsEnvironmentError
101122
}
102123

103124
// cleanupFromPod removes any credential files that were created for pod-level authentication authentication via [Provider.provideFromPod].
104125
func (c *Provider) cleanupFromPod(cleanupCtx CleanupContext) error {
105-
tokenName := podLevelServiceAccountTokenName(cleanupCtx.PodID, cleanupCtx.VolumeID)
106-
tokenPath := filepath.Join(cleanupCtx.WritePath, tokenName)
107-
err := os.Remove(tokenPath)
108-
if err != nil && errors.Is(err, fs.ErrNotExist) {
109-
return nil
126+
cleanupToken := func(tokenName string) error {
127+
tokenPath := filepath.Join(cleanupCtx.WritePath, tokenName)
128+
err := os.Remove(tokenPath)
129+
if err != nil && errors.Is(err, fs.ErrNotExist) {
130+
return nil
131+
}
132+
133+
return err
110134
}
111-
return err
135+
136+
tokenNameSTS := podLevelSTSWebIdentityServiceAccountTokenName(cleanupCtx.PodID, cleanupCtx.VolumeID)
137+
err := cleanupToken(tokenNameSTS)
138+
if err != nil {
139+
return status.Errorf(codes.Internal, "Failed to cleanup service account STS token: %v", err)
140+
}
141+
142+
tokenNameEKS := podLevelEksPodIdentityServiceAccountTokenName(cleanupCtx.PodID, cleanupCtx.VolumeID)
143+
err = cleanupToken(tokenNameEKS)
144+
if err != nil {
145+
status.Errorf(codes.Internal, "Failed to cleanup service account EKS Pod Identity token: %v", err)
146+
}
147+
148+
return nil
112149
}
113150

151+
var errMissingServiceAccountAnnotationForIRSA = errors.New("Missing role annotation on pod's service account")
152+
114153
// findPodServiceAccountRole tries to provide associated AWS IAM role for service account specified in the volume context.
115154
func (c *Provider) findPodServiceAccountRole(ctx context.Context, provideCtx ProvideContext) (string, error) {
155+
podNamespace := provideCtx.PodNamespace
156+
podServiceAccount := provideCtx.ServiceAccountName
157+
116158
// In PodMounter we get IAM Role ARN from MountpointS3PodAttachment custom resource
117-
if provideCtx.ServiceAccountEKSRoleARN != "" {
118-
return provideCtx.ServiceAccountEKSRoleARN, nil
159+
if util.UsePodMounter() {
160+
if provideCtx.ServiceAccountEKSRoleARN != "" {
161+
return provideCtx.ServiceAccountEKSRoleARN, nil
162+
} else {
163+
return "", errMissingServiceAccountAnnotationForIRSA
164+
}
119165
}
120166

121-
podNamespace := provideCtx.PodNamespace
122-
podServiceAccount := provideCtx.ServiceAccountName
123167
if podNamespace == "" || podServiceAccount == "" {
124168
klog.Error("credentialprovider: `authenticationSource` configured to `pod` but no pod info found. Please make sure to enable `podInfoOnMountCompat`, see " + podLevelCredentialsDocsPage)
125169
return "", status.Error(codes.InvalidArgument, "Missing Pod info. Please make sure to enable `podInfoOnMountCompat`, see "+podLevelCredentialsDocsPage)
@@ -133,15 +177,63 @@ func (c *Provider) findPodServiceAccountRole(ctx context.Context, provideCtx Pro
133177
roleArn := response.Annotations[serviceAccountRoleAnnotation]
134178
if roleArn == "" {
135179
klog.Error("credentialprovider: `authenticationSource` configured to `pod` but pod's service account is not annotated with a role, see " + podLevelCredentialsDocsPage)
136-
return "", status.Errorf(codes.InvalidArgument, "Missing role annotation on pod's service account %s/%s", podNamespace, podServiceAccount)
180+
return "", errMissingServiceAccountAnnotationForIRSA
137181
}
138182

139183
return roleArn, nil
140184
}
141185

142-
// podLevelServiceAccountTokenName returns service account token name for Pod-level identity.
186+
// podLevelSTSWebIdentityServiceAccountTokenName returns service account token name for Pod-level identity.
143187
// It escapes from slashes to make this token name path-safe.
144-
func podLevelServiceAccountTokenName(podID string, volumeID string) string {
188+
func podLevelSTSWebIdentityServiceAccountTokenName(podID string, volumeID string) string {
145189
id := escapedVolumeIdentifier(podID, volumeID)
146190
return id + ".token"
147191
}
192+
193+
// podLevelEksPodIdentityServiceAccountTokenName returns service account token name for Pod-level identity with EKS Pod Identity.
194+
// It escapes from slashes to make this token name path-safe.
195+
func podLevelEksPodIdentityServiceAccountTokenName(podID string, volumeID string) string {
196+
id := escapedVolumeIdentifier(podID, volumeID)
197+
return id + "-eks-pod-identity.token"
198+
}
199+
200+
// createIRSACredentialsEnvironment creates an environment with the environment variables needed for pod-level authentication with IRSA
201+
func (c *Provider) createIRSACredentialsEnvironment(ctx context.Context, provideCtx ProvideContext) (envprovider.Environment, error) {
202+
roleARN, err := c.findPodServiceAccountRole(ctx, provideCtx)
203+
if err != nil {
204+
return nil, err
205+
}
206+
207+
region, err := c.stsRegion(provideCtx)
208+
if err != nil {
209+
return nil, status.Errorf(codes.InvalidArgument, "Failed to detect STS AWS Region, please explicitly set the AWS Region, see "+stsConfigDocsPage)
210+
}
211+
212+
defaultRegion := os.Getenv(envprovider.EnvDefaultRegion)
213+
if defaultRegion == "" {
214+
defaultRegion = region
215+
}
216+
217+
podID := provideCtx.GetCredentialPodID()
218+
tokenName := podLevelSTSWebIdentityServiceAccountTokenName(podID, provideCtx.VolumeID)
219+
tokenFile := filepath.Join(provideCtx.EnvPath, tokenName)
220+
221+
return envprovider.Environment{
222+
envprovider.EnvRoleARN: roleARN,
223+
envprovider.EnvWebIdentityTokenFile: tokenFile,
224+
envprovider.EnvRegion: region,
225+
envprovider.EnvDefaultRegion: defaultRegion,
226+
}, nil
227+
}
228+
229+
// createEKSPodIdentityCredentialsEnvironment creates an environment with the environment variables needed for pod-level authentication with EKS Pod Identity
230+
func (c *Provider) createEKSPodIdentityCredentialsEnvironment(provideCtx ProvideContext) envprovider.Environment {
231+
podID := provideCtx.GetCredentialPodID()
232+
tokenName := podLevelEksPodIdentityServiceAccountTokenName(podID, provideCtx.VolumeID)
233+
tokenFile := filepath.Join(provideCtx.EnvPath, tokenName)
234+
235+
return envprovider.Environment{
236+
envprovider.EnvContainerCredentialsFullURI: podIdentityCredURI,
237+
envprovider.EnvContainerAuthorizationTokenFile: tokenFile,
238+
}
239+
}

0 commit comments

Comments
 (0)