diff --git a/weed/iam/sts/sts_service.go b/weed/iam/sts/sts_service.go index 4ef216edc..7b523b8d2 100644 --- a/weed/iam/sts/sts_service.go +++ b/weed/iam/sts/sts_service.go @@ -581,6 +581,12 @@ func (s *STSService) validateWebIdentityToken(ctx context.Context, token string) return identity, provider, nil } +// ValidateWebIdentityToken is a public method that exposes secure token validation for external use +// This method uses issuer-based lookup to select the correct provider, ensuring security and efficiency +func (s *STSService) ValidateWebIdentityToken(ctx context.Context, token string) (*providers.ExternalIdentity, providers.IdentityProvider, error) { + return s.validateWebIdentityToken(ctx, token) +} + // validateWithAllProviders is a fallback for non-JWT tokens (e.g., LDAP credentials, test tokens) // This should only be used when the token is not a valid JWT func (s *STSService) validateWithAllProviders(ctx context.Context, token string) (*providers.ExternalIdentity, providers.IdentityProvider, error) { diff --git a/weed/s3api/s3_iam_middleware.go b/weed/s3api/s3_iam_middleware.go index b23e96476..4f931c96c 100644 --- a/weed/s3api/s3_iam_middleware.go +++ b/weed/s3api/s3_iam_middleware.go @@ -97,7 +97,7 @@ func (s3iam *S3IAMIntegration) AuthenticateJWT(ctx context.Context, r *http.Requ ctx, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() - identity, err := s3iam.validateOIDCToken(ctx, sessionToken) + identity, err := s3iam.validateExternalOIDCToken(ctx, sessionToken) elapsed := time.Since(start) if err != nil { @@ -729,77 +729,71 @@ type OIDCIdentity struct { Provider string } -// validateOIDCToken validates an OIDC token using registered identity providers -func (s3iam *S3IAMIntegration) validateOIDCToken(ctx context.Context, token string) (*OIDCIdentity, error) { - glog.V(0).Infof("🔍 validateOIDCToken: Starting OIDC token validation") +// validateExternalOIDCToken validates an external OIDC token using the STS service's secure issuer-based lookup +// This method delegates to the STS service's validateWebIdentityToken for better security and efficiency +func (s3iam *S3IAMIntegration) validateExternalOIDCToken(ctx context.Context, token string) (*OIDCIdentity, error) { + glog.V(0).Infof("🔍 validateExternalOIDCToken: Starting secure OIDC token validation via STS service") if s3iam.iamManager == nil { - glog.V(0).Infof("🔍 validateOIDCToken: IAM manager not available") + glog.V(0).Infof("🔍 validateExternalOIDCToken: IAM manager not available") return nil, fmt.Errorf("IAM manager not available") } - // Get STS service to access identity providers + // Get STS service for secure token validation stsService := s3iam.iamManager.GetSTSService() if stsService == nil { - glog.V(0).Infof("🔍 validateOIDCToken: STS service not available") + glog.V(0).Infof("🔍 validateExternalOIDCToken: STS service not available") return nil, fmt.Errorf("STS service not available") } - // Try to validate token with each registered OIDC provider - providers := stsService.GetProviders() - glog.V(0).Infof("🔍 validateOIDCToken: Found %d providers to try", len(providers)) - - for providerName, provider := range providers { - glog.V(0).Infof("🔍 validateOIDCToken: Trying provider '%s'...", providerName) - start := time.Now() - - // Try to authenticate with this provider - externalIdentity, err := provider.Authenticate(ctx, token) - elapsed := time.Since(start) - - if err != nil { - glog.V(0).Infof("🔍 validateOIDCToken: Provider '%s' FAILED after %v: %v", providerName, elapsed, err) - continue - } + // Use the STS service's secure validateWebIdentityToken method + // This method uses issuer-based lookup to select the correct provider, which is more secure and efficient + externalIdentity, provider, err := stsService.ValidateWebIdentityToken(ctx, token) + if err != nil { + glog.V(0).Infof("🔍 validateExternalOIDCToken: STS validation failed: %v", err) + return nil, fmt.Errorf("token validation failed: %w", err) + } - glog.V(0).Infof("🔍 validateOIDCToken: Provider '%s' SUCCEEDED after %v", providerName, elapsed) + if externalIdentity == nil { + glog.V(0).Infof("🔍 validateExternalOIDCToken: STS validation succeeded but no identity returned") + return nil, fmt.Errorf("authentication succeeded but no identity returned") + } - // Extract role from external identity attributes - rolesAttr, exists := externalIdentity.Attributes["roles"] - if !exists || rolesAttr == "" { - glog.V(3).Infof("No roles found in external identity from provider %s", providerName) - continue - } + glog.V(0).Infof("🔍 validateExternalOIDCToken: STS validation succeeded with provider type: %T", provider) - // Parse roles (stored as comma-separated string) - rolesStr := strings.TrimSpace(rolesAttr) - roles := strings.Split(rolesStr, ",") + // Extract role from external identity attributes + rolesAttr, exists := externalIdentity.Attributes["roles"] + if !exists || rolesAttr == "" { + glog.V(3).Infof("No roles found in external identity") + return nil, fmt.Errorf("no roles found in external identity") + } - // Clean up role names - var cleanRoles []string - for _, role := range roles { - cleanRole := strings.TrimSpace(role) - if cleanRole != "" { - cleanRoles = append(cleanRoles, cleanRole) - } - } + // Parse roles (stored as comma-separated string) + rolesStr := strings.TrimSpace(rolesAttr) + roles := strings.Split(rolesStr, ",") - if len(cleanRoles) == 0 { - glog.V(3).Infof("Empty roles list from provider %s", providerName) - continue + // Clean up role names + var cleanRoles []string + for _, role := range roles { + cleanRole := strings.TrimSpace(role) + if cleanRole != "" { + cleanRoles = append(cleanRoles, cleanRole) } + } - // Determine the primary role using intelligent selection - roleArn := s3iam.selectPrimaryRole(cleanRoles, externalIdentity) - - return &OIDCIdentity{ - UserID: externalIdentity.UserID, - RoleArn: roleArn, - Provider: providerName, - }, nil + if len(cleanRoles) == 0 { + glog.V(3).Infof("Empty roles list after parsing") + return nil, fmt.Errorf("no valid roles found in token") } - return nil, fmt.Errorf("token not valid for any registered OIDC provider") + // Determine the primary role using intelligent selection + roleArn := s3iam.selectPrimaryRole(cleanRoles, externalIdentity) + + return &OIDCIdentity{ + UserID: externalIdentity.UserID, + RoleArn: roleArn, + Provider: fmt.Sprintf("%T", provider), // Use provider type as identifier + }, nil } // selectPrimaryRole simply picks the first role from the list