@@ -10,45 +10,47 @@ import (
10
10
11
11
"github.com/aws/aws-sdk-go-v2/aws"
12
12
"github.com/aws/aws-sdk-go-v2/aws/arn"
13
- "github.com/aws/aws-sdk-go-v2/config"
14
- awsCreds "github.com/aws/aws-sdk-go-v2/credentials"
15
- "github.com/aws/aws-sdk-go-v2/service/eks"
16
13
"github.com/aws/aws-sdk-go-v2/service/sts"
17
14
"github.com/aws/aws-sdk-go-v2/service/sts/types"
18
15
"github.com/golang-jwt/jwt/v5"
19
16
"github.com/sirupsen/logrus"
20
17
"go.amzn.com/eks/eks-pod-identity-agent/internal/middleware/logger"
21
18
"go.amzn.com/eks/eks-pod-identity-agent/pkg/credentials"
19
+ "go.amzn.com/eks/eks-pod-identity-agent/pkg/extensions/chainrole/ekspodidentities"
20
+ "go.amzn.com/eks/eks-pod-identity-agent/pkg/extensions/chainrole/serviceaccount"
22
21
)
23
22
24
23
const (
25
24
assumeRoleAnnotationPrefix = "assume-role.ekspia.go.amzn.com/"
26
25
sessionTagRoleAnnotationPrefix = assumeRoleAnnotationPrefix + "session-tag/"
26
+ // service account annotations doesn't support more than one "/"
27
+ sessionTagRoleAnnotationPrefix2 = assumeRoleAnnotationPrefix + "session-tag-"
27
28
)
28
29
29
30
type (
30
31
roleAssumer interface {
31
32
AssumeRole (ctx context.Context , params * sts.AssumeRoleInput , optFns ... func (* sts.Options )) (* sts.AssumeRoleOutput , error )
32
33
}
33
34
34
- sessionConfigFunc func (ctx context.Context , awsCfg aws.Config , clusterName string , associationID string ) (* sts.AssumeRoleInput , error )
35
+ sessionConfigRetriever interface {
36
+ GetSessionConfigMap (ctx context.Context , request * credentials.EksCredentialsRequest ) (map [string ]string , error )
37
+ }
35
38
36
39
CredentialRetriever struct {
37
40
delegate credentials.CredentialRetriever
38
41
jwtParser * jwt.Parser
39
42
roleAssumer roleAssumer
40
- getSessionConfig sessionConfigFunc
43
+ sessionConfigRetriever sessionConfigRetriever
41
44
reNamespaceFilter * regexp.Regexp
42
45
reServiceAccountFilter * regexp.Regexp
43
46
}
44
47
)
45
48
46
49
func NewCredentialsRetriever (awsCfg aws.Config , eksCredentialsRetriever credentials.CredentialRetriever ) * CredentialRetriever {
47
50
cr := & CredentialRetriever {
48
- delegate : eksCredentialsRetriever ,
49
- jwtParser : jwt .NewParser (),
50
- roleAssumer : sts .NewFromConfig (awsCfg ),
51
- getSessionConfig : getSessionConfigurationFromEKSPodIdentityTags ,
51
+ delegate : eksCredentialsRetriever ,
52
+ jwtParser : jwt .NewParser (),
53
+ roleAssumer : sts .NewFromConfig (awsCfg ),
52
54
}
53
55
54
56
log := logger .FromContext (context .TODO ()).WithField ("extension" , "chainrole" )
@@ -69,75 +71,46 @@ func NewCredentialsRetriever(awsCfg aws.Config, eksCredentialsRetriever credenti
69
71
log .Info ("Enabled extension..." )
70
72
}
71
73
72
- return cr
73
- }
74
-
75
- func getSessionConfigurationFromEKSPodIdentityTags (ctx context.Context , awsCfg aws.Config , clusterName , associationID string ) (* sts.AssumeRoleInput , error ) {
76
- // Describe pod identity association to get tags
77
- podIdentityAssociation , err := eks .NewFromConfig (awsCfg ).DescribePodIdentityAssociation (ctx ,
78
- & eks.DescribePodIdentityAssociationInput {
79
- AssociationId : aws .String (associationID ),
80
- ClusterName : aws .String (clusterName ),
81
- })
82
- if err != nil {
83
- return nil , fmt .Errorf ("error describing pod identity association %s/%s: %w" , clusterName , associationID , err )
74
+ switch sessionConfigSourceVal {
75
+ case eksPodIdentityAssociationTags :
76
+ cr .sessionConfigRetriever = ekspodidentities .NewSessionConfigRetriever (eksCredentialsRetriever )
77
+ case serviceAccountAnnotations :
78
+ cr .sessionConfigRetriever = serviceaccount .NewSessionConfigRetriever ()
79
+ default :
84
80
}
85
81
86
- assumeRoleInput := tagsToSTSAssumeRole (podIdentityAssociation .Association .Tags )
87
-
88
- if assumeRoleInput .RoleArn == nil {
89
- return nil , fmt .Errorf ("couldn't get assume role arn from pod identity association tags %v" , podIdentityAssociation .Association .Tags )
90
- }
91
-
92
- return assumeRoleInput , nil
82
+ return cr
93
83
}
94
84
95
85
func (c * CredentialRetriever ) GetIamCredentials (ctx context.Context , request * credentials.EksCredentialsRequest ) (
96
86
* credentials.EksCredentialsResponse , credentials.ResponseMetadata , error ) {
97
87
log := logger .FromContext (ctx ).WithField ("extension" , "chainrole" )
98
88
99
- // Get AWS EKS Pod Identity credentials as usual
100
- iamCredentials , responseMetadata , err := c .delegate .GetIamCredentials (ctx , request )
101
- if err != nil {
102
- return nil , nil , err
103
- }
104
-
105
89
// Get Namespace and ServiceAccount names from JWT token
106
90
ns , sa , err := c .serviceAccountFromJWT (request .ServiceAccountToken )
107
91
if err != nil {
108
92
return nil , nil , fmt .Errorf ("error parsing JWT token: %w" , err )
109
93
}
110
94
111
- log = log .WithFields (logrus.Fields {
112
- "namespace" : ns ,
113
- "serviceaccount" : sa ,
114
- "cluster-name" : request .ClusterName ,
115
- "association-id" : responseMetadata .AssociationId (),
116
- })
117
-
118
95
// Check if Namespace/ServiceAccount filters configured
119
96
// and do not proceed with role chaining if they don't match
120
97
if ! c .isEnabledFor (ns , sa ) {
121
98
log .Debug ("namespace/serviceaccount do not match ChainRole filter. Skipping role chaining" )
122
- return iamCredentials , responseMetadata , nil
99
+ return c . delegate . GetIamCredentials ( ctx , request )
123
100
}
124
101
125
- // Assume eks pod identity credentials
126
- podIdentityCfg , err := config .LoadDefaultConfig (context .TODO (), config .WithCredentialsProvider (
127
- awsCreds .NewStaticCredentialsProvider (iamCredentials .AccessKeyId , iamCredentials .SecretAccessKey , iamCredentials .Token ),
128
- ))
129
- if err != nil {
130
- return nil , nil , fmt .Errorf ("error loading pod identity credentials: %w" , err )
131
- }
102
+ log = log .WithFields (logrus.Fields {
103
+ "namespace" : ns ,
104
+ "serviceaccount" : sa ,
105
+ "cluster-name" : request .ClusterName ,
106
+ })
132
107
133
- // Assume new session based on the configurations provided in tags
134
- // session is assumed based on the IRSA credentials and NOT EKS Identity credentials
135
- // this is because EKS Identity credentials adds bunch of default tags
136
- // leaving no space for our custom tags https://github.com/aws/containers-roadmap/issues/2413
137
- assumeRoleInput , err := c .getSessionConfig (ctx , podIdentityCfg , request .ClusterName , responseMetadata .AssociationId ())
108
+ sessionConfigMap , err := c .sessionConfigRetriever .GetSessionConfigMap (ctx , request )
138
109
if err != nil {
139
- return nil , nil , fmt . Errorf ( "error getting session configuration: %w" , err )
110
+ return nil , nil , err
140
111
}
112
+
113
+ assumeRoleInput := tagsToSTSAssumeRole (sessionConfigMap )
141
114
assumeRoleOutput , err := c .roleAssumer .AssumeRole (ctx , assumeRoleInput )
142
115
if err != nil {
143
116
return nil , nil , fmt .Errorf ("error assuming role %s: %w" , * assumeRoleInput .RoleArn , err )
@@ -154,7 +127,7 @@ func (c *CredentialRetriever) GetIamCredentials(ctx context.Context, request *cr
154
127
return nil , nil , fmt .Errorf ("error formatting IAM credentials: %w" , err )
155
128
}
156
129
157
- return assumedCredentials , responseMetadata , nil
130
+ return assumedCredentials , nil , nil
158
131
}
159
132
160
133
func (c * CredentialRetriever ) isEnabledFor (namespace , serviceAccount string ) bool {
@@ -196,8 +169,9 @@ func tagsToSTSAssumeRole(tags map[string]string) *sts.AssumeRoleInput {
196
169
assumeRoleParams .DurationSeconds = aws .Int32 (int32 (duration .Seconds ()))
197
170
}
198
171
199
- if strings .HasPrefix (key , sessionTagRoleAnnotationPrefix ) {
172
+ if strings .HasPrefix (key , sessionTagRoleAnnotationPrefix ) || strings . HasPrefix ( key , sessionTagRoleAnnotationPrefix2 ) {
200
173
tagKey := strings .TrimPrefix (key , sessionTagRoleAnnotationPrefix )
174
+ tagKey = strings .TrimPrefix (tagKey , sessionTagRoleAnnotationPrefix2 )
201
175
202
176
assumeRoleParams .Tags = append (assumeRoleParams .Tags , types.Tag {
203
177
Key : aws .String (tagKey ),
@@ -228,26 +202,15 @@ func formatIAMCredentials(o *sts.AssumeRoleOutput) (*credentials.EksCredentialsR
228
202
}, nil
229
203
}
230
204
231
- func (c * CredentialRetriever ) serviceAccountFromJWT (token string ) (string , string , error ) {
232
- parsedToken , _ , err := c . jwtParser . ParseUnverified (token , & jwt. RegisteredClaims {} )
205
+ func (c * CredentialRetriever ) serviceAccountFromJWT (token string ) (ns string , sa string , err error ) {
206
+ claims , subject , err := serviceaccount . ServiceAccountFromJWT (token )
233
207
if err != nil {
234
208
return "" , "" , fmt .Errorf ("error parsing JWT token: %w" , err )
235
209
}
236
210
237
- subject , err := parsedToken .Claims .GetSubject ()
238
- if err != nil {
239
- return "" , "" , fmt .Errorf ("error reading JWT token subject: %w" , err )
240
- }
241
-
242
- // subject is in the format: system:serviceaccount:<namespace>:<service_account>
243
- if ! strings .HasPrefix (subject , "system:serviceaccount:" ) {
244
- return "" , "" , errors .New ("JWT token claim subject doesn't start with 'system:serviceaccount:'" )
245
- }
246
-
247
- subjectParts := strings .Split (subject , ":" )
248
- if len (subjectParts ) < 4 {
249
- return "" , "" , errors .New ("invalid JWT token claim subject" )
211
+ if claims != nil && claims .Namespace != "" && claims .ServiceAccount .Name != "" {
212
+ return claims .Namespace , claims .ServiceAccount .Name , nil
250
213
}
251
214
252
- return subjectParts [ 2 ], subjectParts [ 3 ], nil
215
+ return serviceaccount . ServiceAccountFromJWTSubject ( subject )
253
216
}
0 commit comments