Browse Source

fix(iam): support both AWS standard and legacy IAM role ARN formats

Fix issue #7946 where SeaweedFS only recognized legacy IAM role ARN format
(arn:aws:iam::role/RoleName) but not the standard AWS format with account ID
(arn:aws:iam::ACCOUNT:role/RoleName). This was breaking EKS pod identity
integration which expects the standard format.

Changes:
- Update ExtractRoleNameFromArn() to handle both formats by searching for
  'role/' marker instead of matching a fixed prefix
- Update ExtractRoleNameFromPrincipal() to clearly document both STS and IAM
  formats it supports
- Simplify role ARN validation in validateRoleAssumptionForWebIdentity() and
  validateRoleAssumptionForCredentials() to use the extraction function

The fix maintains backward compatibility with legacy format while adding
support for standard AWS format with account ID.

Fixes: https://github.com/seaweedfs/seaweedfs/issues/7946
fix-sts-session-token-7941
Chris Lu 1 month ago
parent
commit
e2b58a0a5b
  1. 18
      weed/iam/sts/sts_service.go
  2. 48
      weed/iam/utils/arn_utils.go

18
weed/iam/sts/sts_service.go

@ -699,13 +699,8 @@ func (s *STSService) validateRoleAssumptionForWebIdentity(ctx context.Context, r
return fmt.Errorf("web identity token cannot be empty")
}
// Basic role ARN format validation
expectedPrefix := "arn:aws:iam::role/"
if len(roleArn) < len(expectedPrefix) || roleArn[:len(expectedPrefix)] != expectedPrefix {
return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
}
// Extract role name and validate ARN format
// Validate role ARN and extract role name
// Accepts both arn:aws:iam::role/X and arn:aws:iam::ACCOUNT:role/X
roleName := utils.ExtractRoleNameFromArn(roleArn)
if roleName == "" {
return fmt.Errorf("invalid role ARN format: %s", roleArn)
@ -736,13 +731,8 @@ func (s *STSService) validateRoleAssumptionForCredentials(ctx context.Context, r
return fmt.Errorf("identity cannot be nil")
}
// Basic role ARN format validation
expectedPrefix := "arn:aws:iam::role/"
if len(roleArn) < len(expectedPrefix) || roleArn[:len(expectedPrefix)] != expectedPrefix {
return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
}
// Extract role name and validate ARN format
// Validate role ARN and extract role name
// Accepts both arn:aws:iam::role/X and arn:aws:iam::ACCOUNT:role/X
roleName := utils.ExtractRoleNameFromArn(roleArn)
if roleName == "" {
return fmt.Errorf("invalid role ARN format: %s", roleArn)

48
weed/iam/utils/arn_utils.go

@ -3,37 +3,39 @@ package utils
import "strings"
// ExtractRoleNameFromPrincipal extracts role name from principal ARN
// Handles both STS assumed role and IAM role formats
// Handles both STS assumed role and IAM role formats with or without account ID:
// - arn:aws:sts::assumed-role/Role/Session (legacy)
// - arn:aws:sts::ACCOUNT:assumed-role/Role/Session (standard)
// - arn:aws:iam::role/Role (legacy)
// - arn:aws:iam::ACCOUNT:role/Role (standard)
func ExtractRoleNameFromPrincipal(principal string) string {
// Handle STS assumed role format: arn:aws:sts::assumed-role/RoleName/SessionName
stsPrefix := "arn:aws:sts::assumed-role/"
if strings.HasPrefix(principal, stsPrefix) {
remainder := principal[len(stsPrefix):]
// Split on first '/' to get role name
if slashIndex := strings.Index(remainder, "/"); slashIndex != -1 {
return remainder[:slashIndex]
// Handle STS assumed role format
if strings.HasPrefix(principal, "arn:aws:sts::") {
remainder := principal[len("arn:aws:sts::"):]
if idx := strings.Index(remainder, "assumed-role/"); idx != -1 {
afterMarker := remainder[idx+len("assumed-role/"):]
if slash := strings.Index(afterMarker, "/"); slash != -1 {
return afterMarker[:slash]
}
return afterMarker
}
// If no slash found, return the remainder (edge case)
return remainder
}
// Handle IAM role format: arn:aws:iam::role/RoleName
iamPrefix := "arn:aws:iam::role/"
if strings.HasPrefix(principal, iamPrefix) {
return principal[len(iamPrefix):]
}
// Return empty string to signal invalid ARN format
// This allows callers to handle the error explicitly instead of masking it
return ""
// Handle IAM role format
return ExtractRoleNameFromArn(principal)
}
// ExtractRoleNameFromArn extracts role name from an IAM role ARN
// Specifically handles: arn:aws:iam::role/RoleName
// Handles both formats:
// - arn:aws:iam::role/RoleName (legacy, without account ID)
// - arn:aws:iam::ACCOUNT:role/RoleName (standard AWS format)
func ExtractRoleNameFromArn(roleArn string) string {
prefix := "arn:aws:iam::role/"
if strings.HasPrefix(roleArn, prefix) && len(roleArn) > len(prefix) {
return roleArn[len(prefix):]
if !strings.HasPrefix(roleArn, "arn:aws:iam::") {
return ""
}
remainder := roleArn[len("arn:aws:iam::"):]
if idx := strings.Index(remainder, "role/"); idx != -1 {
return remainder[idx+len("role/"):]
}
return ""
}
Loading…
Cancel
Save