Skip to content

Commit 125d7a1

Browse files
committed
fix(runs): tolerate base64 padding when decoding forwarded JWT claims
AWS ALB's x-amzn-oidc-data JWT pads the base64url payload, which strict RawURLEncoding rejects — so the cookie/UI path silently dropped the user's claims (email, name) and fell back to subject-only. Decode with padding tolerance. This surfaced once `profile`/`email` scopes enlarged the payload so its length stopped being padding-free. Also removed the temporary AUTHDEBUG diagnostic. Signed-off-by: Kevin Su <pingsutw@apache.org>
1 parent 4f9befc commit 125d7a1

3 files changed

Lines changed: 31 additions & 2 deletions

File tree

runs/service/identity.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func identityFromJWT(token string) *common.EnrichedIdentity {
5959
if len(parts) != 3 {
6060
return nil
6161
}
62-
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
62+
payload, err := decodeJWTSegment(parts[1])
6363
if err != nil {
6464
return nil
6565
}
@@ -78,6 +78,20 @@ func identityFromJWT(token string) *common.EnrichedIdentity {
7878
return id
7979
}
8080

81+
// decodeJWTSegment base64url-decodes a JWT segment, tolerating both the unpadded
82+
// form (per the JWT spec) and the padded form some issuers — notably AWS ALB's
83+
// x-amzn-oidc-data — emit. Without this, a payload whose length isn't a multiple of
84+
// 4 fails strict RawURLEncoding and the claims (email, name) are silently dropped.
85+
func decodeJWTSegment(seg string) ([]byte, error) {
86+
if b, err := base64.RawURLEncoding.DecodeString(seg); err == nil {
87+
return b, nil
88+
}
89+
if pad := len(seg) % 4; pad != 0 {
90+
seg += strings.Repeat("=", 4-pad)
91+
}
92+
return base64.URLEncoding.DecodeString(seg)
93+
}
94+
8195
// subjectOnlyIdentity builds a minimal EnrichedIdentity carrying just the subject.
8296
// Mirrors the cloud transformer fallback; used when only the subject is available.
8397
func subjectOnlyIdentity(subject string) *common.EnrichedIdentity {

runs/service/identity_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ func jwt(payloadJSON string) string {
1414
return enc(`{"alg":"RS256"}`) + "." + enc(payloadJSON) + ".sig"
1515
}
1616

17+
// jwtPadded builds a JWT whose payload uses padded base64url, as AWS ALB's
18+
// x-amzn-oidc-data does. The decoder must tolerate the padding.
19+
func jwtPadded(payloadJSON string) string {
20+
enc := func(s string) string { return base64.RawURLEncoding.EncodeToString([]byte(s)) }
21+
return enc(`{"alg":"ES256"}`) + "." + base64.URLEncoding.EncodeToString([]byte(payloadJSON)) + ".sig"
22+
}
23+
24+
func TestIdentityFromJWT_ToleratesPadding(t *testing.T) {
25+
id := identityFromJWT(jwtPadded(`{"sub":"00u1","given_name":"Kevin","family_name":"Su","email":"kevin@union.ai"}`))
26+
assert.Equal(t, "00u1", id.GetUser().GetId().GetSubject())
27+
assert.Equal(t, "Kevin", id.GetUser().GetSpec().GetFirstName())
28+
assert.Equal(t, "Su", id.GetUser().GetSpec().GetLastName())
29+
assert.Equal(t, "kevin@union.ai", id.GetUser().GetSpec().GetEmail())
30+
}
31+
1732
func TestIdentityFromHeaders(t *testing.T) {
1833
tests := []struct {
1934
name string

runs/service/run_service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ func (s *RunService) CreateRun(
334334
executedBy := identityFromHeaders(req.Header())
335335
executedBy = s.enricher.enrich(ctx, accessTokenFromHeaders(req.Header()), executedBy)
336336

337-
// Persist task spec and create run model
337+
// Persist task spec and create a run model
338338
run, err := s.persistRunModel(ctx, runId, taskID, taskSpec, inputPrefix, runOutputBase, runSpec, request.GetSource(), triggerName, triggerTaskName, triggerRevision, triggerType, executedBy)
339339
if err != nil {
340340
logger.Errorf(ctx, "Failed to create run: %v", err)

0 commit comments

Comments
 (0)