You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							826 lines
						
					
					
						
							29 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							826 lines
						
					
					
						
							29 KiB
						
					
					
				
								package sts
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"encoding/json"
							 | 
						|
									"fmt"
							 | 
						|
									"strconv"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/golang-jwt/jwt/v5"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/iam/providers"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/iam/utils"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TrustPolicyValidator interface for validating trust policies during role assumption
							 | 
						|
								type TrustPolicyValidator interface {
							 | 
						|
									// ValidateTrustPolicyForWebIdentity validates if a web identity token can assume a role
							 | 
						|
									ValidateTrustPolicyForWebIdentity(ctx context.Context, roleArn string, webIdentityToken string) error
							 | 
						|
								
							 | 
						|
									// ValidateTrustPolicyForCredentials validates if credentials can assume a role
							 | 
						|
									ValidateTrustPolicyForCredentials(ctx context.Context, roleArn string, identity *providers.ExternalIdentity) error
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// FlexibleDuration wraps time.Duration to support both integer nanoseconds and duration strings in JSON
							 | 
						|
								type FlexibleDuration struct {
							 | 
						|
									time.Duration
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// UnmarshalJSON implements JSON unmarshaling for FlexibleDuration
							 | 
						|
								// Supports both: 3600000000000 (nanoseconds) and "1h" (duration string)
							 | 
						|
								func (fd *FlexibleDuration) UnmarshalJSON(data []byte) error {
							 | 
						|
									// Try to unmarshal as a duration string first (e.g., "1h", "30m")
							 | 
						|
									var durationStr string
							 | 
						|
									if err := json.Unmarshal(data, &durationStr); err == nil {
							 | 
						|
										duration, parseErr := time.ParseDuration(durationStr)
							 | 
						|
										if parseErr != nil {
							 | 
						|
											return fmt.Errorf("invalid duration string %q: %w", durationStr, parseErr)
							 | 
						|
										}
							 | 
						|
										fd.Duration = duration
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// If that fails, try to unmarshal as an integer (nanoseconds for backward compatibility)
							 | 
						|
									var nanoseconds int64
							 | 
						|
									if err := json.Unmarshal(data, &nanoseconds); err == nil {
							 | 
						|
										fd.Duration = time.Duration(nanoseconds)
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// If both fail, try unmarshaling as a quoted number string (edge case)
							 | 
						|
									var numberStr string
							 | 
						|
									if err := json.Unmarshal(data, &numberStr); err == nil {
							 | 
						|
										if nanoseconds, parseErr := strconv.ParseInt(numberStr, 10, 64); parseErr == nil {
							 | 
						|
											fd.Duration = time.Duration(nanoseconds)
							 | 
						|
											return nil
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return fmt.Errorf("unable to parse duration from %s (expected duration string like \"1h\" or integer nanoseconds)", data)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// MarshalJSON implements JSON marshaling for FlexibleDuration
							 | 
						|
								// Always marshals as a human-readable duration string
							 | 
						|
								func (fd FlexibleDuration) MarshalJSON() ([]byte, error) {
							 | 
						|
									return json.Marshal(fd.Duration.String())
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// STSService provides Security Token Service functionality
							 | 
						|
								// This service is now completely stateless - all session information is embedded
							 | 
						|
								// in JWT tokens, eliminating the need for session storage and enabling true
							 | 
						|
								// distributed operation without shared state
							 | 
						|
								type STSService struct {
							 | 
						|
									Config               *STSConfig // Public for access by other components
							 | 
						|
									initialized          bool
							 | 
						|
									providers            map[string]providers.IdentityProvider
							 | 
						|
									issuerToProvider     map[string]providers.IdentityProvider // Efficient issuer-based provider lookup
							 | 
						|
									tokenGenerator       *TokenGenerator
							 | 
						|
									trustPolicyValidator TrustPolicyValidator // Interface for trust policy validation
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// STSConfig holds STS service configuration
							 | 
						|
								type STSConfig struct {
							 | 
						|
									// TokenDuration is the default duration for issued tokens
							 | 
						|
									TokenDuration FlexibleDuration `json:"tokenDuration"`
							 | 
						|
								
							 | 
						|
									// MaxSessionLength is the maximum duration for any session
							 | 
						|
									MaxSessionLength FlexibleDuration `json:"maxSessionLength"`
							 | 
						|
								
							 | 
						|
									// Issuer is the STS issuer identifier
							 | 
						|
									Issuer string `json:"issuer"`
							 | 
						|
								
							 | 
						|
									// SigningKey is used to sign session tokens
							 | 
						|
									SigningKey []byte `json:"signingKey"`
							 | 
						|
								
							 | 
						|
									// Providers configuration - enables automatic provider loading
							 | 
						|
									Providers []*ProviderConfig `json:"providers,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ProviderConfig holds identity provider configuration
							 | 
						|
								type ProviderConfig struct {
							 | 
						|
									// Name is the unique identifier for the provider
							 | 
						|
									Name string `json:"name"`
							 | 
						|
								
							 | 
						|
									// Type specifies the provider type (oidc, ldap, etc.)
							 | 
						|
									Type string `json:"type"`
							 | 
						|
								
							 | 
						|
									// Config contains provider-specific configuration
							 | 
						|
									Config map[string]interface{} `json:"config"`
							 | 
						|
								
							 | 
						|
									// Enabled indicates if this provider should be active
							 | 
						|
									Enabled bool `json:"enabled"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AssumeRoleWithWebIdentityRequest represents a request to assume role with web identity
							 | 
						|
								type AssumeRoleWithWebIdentityRequest struct {
							 | 
						|
									// RoleArn is the ARN of the role to assume
							 | 
						|
									RoleArn string `json:"RoleArn"`
							 | 
						|
								
							 | 
						|
									// WebIdentityToken is the OIDC token from the identity provider
							 | 
						|
									WebIdentityToken string `json:"WebIdentityToken"`
							 | 
						|
								
							 | 
						|
									// RoleSessionName is a name for the assumed role session
							 | 
						|
									RoleSessionName string `json:"RoleSessionName"`
							 | 
						|
								
							 | 
						|
									// DurationSeconds is the duration of the role session (optional)
							 | 
						|
									DurationSeconds *int64 `json:"DurationSeconds,omitempty"`
							 | 
						|
								
							 | 
						|
									// Policy is an optional session policy (optional)
							 | 
						|
									Policy *string `json:"Policy,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AssumeRoleWithCredentialsRequest represents a request to assume role with username/password
							 | 
						|
								type AssumeRoleWithCredentialsRequest struct {
							 | 
						|
									// RoleArn is the ARN of the role to assume
							 | 
						|
									RoleArn string `json:"RoleArn"`
							 | 
						|
								
							 | 
						|
									// Username is the username for authentication
							 | 
						|
									Username string `json:"Username"`
							 | 
						|
								
							 | 
						|
									// Password is the password for authentication
							 | 
						|
									Password string `json:"Password"`
							 | 
						|
								
							 | 
						|
									// RoleSessionName is a name for the assumed role session
							 | 
						|
									RoleSessionName string `json:"RoleSessionName"`
							 | 
						|
								
							 | 
						|
									// ProviderName is the name of the identity provider to use
							 | 
						|
									ProviderName string `json:"ProviderName"`
							 | 
						|
								
							 | 
						|
									// DurationSeconds is the duration of the role session (optional)
							 | 
						|
									DurationSeconds *int64 `json:"DurationSeconds,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AssumeRoleResponse represents the response from assume role operations
							 | 
						|
								type AssumeRoleResponse struct {
							 | 
						|
									// Credentials contains the temporary security credentials
							 | 
						|
									Credentials *Credentials `json:"Credentials"`
							 | 
						|
								
							 | 
						|
									// AssumedRoleUser contains information about the assumed role user
							 | 
						|
									AssumedRoleUser *AssumedRoleUser `json:"AssumedRoleUser"`
							 | 
						|
								
							 | 
						|
									// PackedPolicySize is the percentage of max policy size used (AWS compatibility)
							 | 
						|
									PackedPolicySize *int64 `json:"PackedPolicySize,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Credentials represents temporary security credentials
							 | 
						|
								type Credentials struct {
							 | 
						|
									// AccessKeyId is the access key ID
							 | 
						|
									AccessKeyId string `json:"AccessKeyId"`
							 | 
						|
								
							 | 
						|
									// SecretAccessKey is the secret access key
							 | 
						|
									SecretAccessKey string `json:"SecretAccessKey"`
							 | 
						|
								
							 | 
						|
									// SessionToken is the session token
							 | 
						|
									SessionToken string `json:"SessionToken"`
							 | 
						|
								
							 | 
						|
									// Expiration is when the credentials expire
							 | 
						|
									Expiration time.Time `json:"Expiration"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AssumedRoleUser contains information about the assumed role user
							 | 
						|
								type AssumedRoleUser struct {
							 | 
						|
									// AssumedRoleId is the unique identifier of the assumed role
							 | 
						|
									AssumedRoleId string `json:"AssumedRoleId"`
							 | 
						|
								
							 | 
						|
									// Arn is the ARN of the assumed role user
							 | 
						|
									Arn string `json:"Arn"`
							 | 
						|
								
							 | 
						|
									// Subject is the subject identifier from the identity provider
							 | 
						|
									Subject string `json:"Subject,omitempty"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// SessionInfo represents information about an active session
							 | 
						|
								type SessionInfo struct {
							 | 
						|
									// SessionId is the unique identifier for the session
							 | 
						|
									SessionId string `json:"sessionId"`
							 | 
						|
								
							 | 
						|
									// SessionName is the name of the role session
							 | 
						|
									SessionName string `json:"sessionName"`
							 | 
						|
								
							 | 
						|
									// RoleArn is the ARN of the assumed role
							 | 
						|
									RoleArn string `json:"roleArn"`
							 | 
						|
								
							 | 
						|
									// AssumedRoleUser contains information about the assumed role user
							 | 
						|
									AssumedRoleUser string `json:"assumedRoleUser"`
							 | 
						|
								
							 | 
						|
									// Principal is the principal ARN
							 | 
						|
									Principal string `json:"principal"`
							 | 
						|
								
							 | 
						|
									// Subject is the subject identifier from the identity provider
							 | 
						|
									Subject string `json:"subject"`
							 | 
						|
								
							 | 
						|
									// Provider is the identity provider used (legacy field)
							 | 
						|
									Provider string `json:"provider"`
							 | 
						|
								
							 | 
						|
									// IdentityProvider is the identity provider used
							 | 
						|
									IdentityProvider string `json:"identityProvider"`
							 | 
						|
								
							 | 
						|
									// ExternalUserId is the external user identifier from the provider
							 | 
						|
									ExternalUserId string `json:"externalUserId"`
							 | 
						|
								
							 | 
						|
									// ProviderIssuer is the issuer from the identity provider
							 | 
						|
									ProviderIssuer string `json:"providerIssuer"`
							 | 
						|
								
							 | 
						|
									// Policies are the policies associated with this session
							 | 
						|
									Policies []string `json:"policies"`
							 | 
						|
								
							 | 
						|
									// RequestContext contains additional request context for policy evaluation
							 | 
						|
									RequestContext map[string]interface{} `json:"requestContext,omitempty"`
							 | 
						|
								
							 | 
						|
									// CreatedAt is when the session was created
							 | 
						|
									CreatedAt time.Time `json:"createdAt"`
							 | 
						|
								
							 | 
						|
									// ExpiresAt is when the session expires
							 | 
						|
									ExpiresAt time.Time `json:"expiresAt"`
							 | 
						|
								
							 | 
						|
									// Credentials are the temporary credentials for this session
							 | 
						|
									Credentials *Credentials `json:"credentials"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewSTSService creates a new STS service
							 | 
						|
								func NewSTSService() *STSService {
							 | 
						|
									return &STSService{
							 | 
						|
										providers:        make(map[string]providers.IdentityProvider),
							 | 
						|
										issuerToProvider: make(map[string]providers.IdentityProvider),
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Initialize initializes the STS service with configuration
							 | 
						|
								func (s *STSService) Initialize(config *STSConfig) error {
							 | 
						|
									if config == nil {
							 | 
						|
										return fmt.Errorf(ErrConfigCannotBeNil)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := s.validateConfig(config); err != nil {
							 | 
						|
										return fmt.Errorf("invalid STS configuration: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.Config = config
							 | 
						|
								
							 | 
						|
									// Initialize token generator for stateless JWT operations
							 | 
						|
									s.tokenGenerator = NewTokenGenerator(config.SigningKey, config.Issuer)
							 | 
						|
								
							 | 
						|
									// Load identity providers from configuration
							 | 
						|
									if err := s.loadProvidersFromConfig(config); err != nil {
							 | 
						|
										return fmt.Errorf("failed to load identity providers: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.initialized = true
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateConfig validates the STS configuration
							 | 
						|
								func (s *STSService) validateConfig(config *STSConfig) error {
							 | 
						|
									if config.TokenDuration.Duration <= 0 {
							 | 
						|
										return fmt.Errorf(ErrInvalidTokenDuration)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if config.MaxSessionLength.Duration <= 0 {
							 | 
						|
										return fmt.Errorf(ErrInvalidMaxSessionLength)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if config.Issuer == "" {
							 | 
						|
										return fmt.Errorf(ErrIssuerRequired)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(config.SigningKey) < MinSigningKeyLength {
							 | 
						|
										return fmt.Errorf(ErrSigningKeyTooShort, MinSigningKeyLength)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// loadProvidersFromConfig loads identity providers from configuration
							 | 
						|
								func (s *STSService) loadProvidersFromConfig(config *STSConfig) error {
							 | 
						|
									if len(config.Providers) == 0 {
							 | 
						|
										glog.V(2).Infof("No providers configured in STS config")
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									factory := NewProviderFactory()
							 | 
						|
								
							 | 
						|
									// Load all providers from configuration
							 | 
						|
									providersMap, err := factory.LoadProvidersFromConfig(config.Providers)
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("failed to load providers from config: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Replace current providers with new ones
							 | 
						|
									s.providers = providersMap
							 | 
						|
								
							 | 
						|
									// Also populate the issuerToProvider map for efficient and secure JWT validation
							 | 
						|
									s.issuerToProvider = make(map[string]providers.IdentityProvider)
							 | 
						|
									for name, provider := range s.providers {
							 | 
						|
										issuer := s.extractIssuerFromProvider(provider)
							 | 
						|
										if issuer != "" {
							 | 
						|
											if _, exists := s.issuerToProvider[issuer]; exists {
							 | 
						|
												glog.Warningf("Duplicate issuer %s found for provider %s. Overwriting.", issuer, name)
							 | 
						|
											}
							 | 
						|
											s.issuerToProvider[issuer] = provider
							 | 
						|
											glog.V(2).Infof("Registered provider %s with issuer %s for efficient lookup", name, issuer)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									glog.V(1).Infof("Successfully loaded %d identity providers: %v",
							 | 
						|
										len(s.providers), s.getProviderNames())
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// getProviderNames returns list of loaded provider names
							 | 
						|
								func (s *STSService) getProviderNames() []string {
							 | 
						|
									names := make([]string, 0, len(s.providers))
							 | 
						|
									for name := range s.providers {
							 | 
						|
										names = append(names, name)
							 | 
						|
									}
							 | 
						|
									return names
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// IsInitialized returns whether the service is initialized
							 | 
						|
								func (s *STSService) IsInitialized() bool {
							 | 
						|
									return s.initialized
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// RegisterProvider registers an identity provider
							 | 
						|
								func (s *STSService) RegisterProvider(provider providers.IdentityProvider) error {
							 | 
						|
									if provider == nil {
							 | 
						|
										return fmt.Errorf(ErrProviderCannotBeNil)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									name := provider.Name()
							 | 
						|
									if name == "" {
							 | 
						|
										return fmt.Errorf(ErrProviderNameEmpty)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.providers[name] = provider
							 | 
						|
								
							 | 
						|
									// Try to extract issuer information for efficient lookup
							 | 
						|
									// This is a best-effort approach for different provider types
							 | 
						|
									issuer := s.extractIssuerFromProvider(provider)
							 | 
						|
									if issuer != "" {
							 | 
						|
										s.issuerToProvider[issuer] = provider
							 | 
						|
										glog.V(2).Infof("Registered provider %s with issuer %s for efficient lookup", name, issuer)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// extractIssuerFromProvider attempts to extract issuer information from different provider types
							 | 
						|
								func (s *STSService) extractIssuerFromProvider(provider providers.IdentityProvider) string {
							 | 
						|
									// Handle different provider types
							 | 
						|
									switch p := provider.(type) {
							 | 
						|
									case interface{ GetIssuer() string }:
							 | 
						|
										// For providers that implement GetIssuer() method
							 | 
						|
										return p.GetIssuer()
							 | 
						|
									default:
							 | 
						|
										// For other provider types, we'll rely on JWT parsing during validation
							 | 
						|
										// This is still more efficient than the current brute-force approach
							 | 
						|
										return ""
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetProviders returns all registered identity providers
							 | 
						|
								func (s *STSService) GetProviders() map[string]providers.IdentityProvider {
							 | 
						|
									return s.providers
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// SetTrustPolicyValidator sets the trust policy validator for role assumption validation
							 | 
						|
								func (s *STSService) SetTrustPolicyValidator(validator TrustPolicyValidator) {
							 | 
						|
									s.trustPolicyValidator = validator
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AssumeRoleWithWebIdentity assumes a role using a web identity token (OIDC)
							 | 
						|
								// This method is now completely stateless - all session information is embedded in the JWT token
							 | 
						|
								func (s *STSService) AssumeRoleWithWebIdentity(ctx context.Context, request *AssumeRoleWithWebIdentityRequest) (*AssumeRoleResponse, error) {
							 | 
						|
									if !s.initialized {
							 | 
						|
										return nil, fmt.Errorf(ErrSTSServiceNotInitialized)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request == nil {
							 | 
						|
										return nil, fmt.Errorf("request cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate request parameters
							 | 
						|
									if err := s.validateAssumeRoleWithWebIdentityRequest(request); err != nil {
							 | 
						|
										return nil, fmt.Errorf("invalid request: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for unsupported session policy
							 | 
						|
									if request.Policy != nil {
							 | 
						|
										return nil, fmt.Errorf("session policies are not currently supported - Policy parameter must be omitted")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 1. Validate the web identity token with appropriate provider
							 | 
						|
									externalIdentity, provider, err := s.validateWebIdentityToken(ctx, request.WebIdentityToken)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to validate web identity token: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 2. Check if the role exists and can be assumed (includes trust policy validation)
							 | 
						|
									if err := s.validateRoleAssumptionForWebIdentity(ctx, request.RoleArn, request.WebIdentityToken); err != nil {
							 | 
						|
										return nil, fmt.Errorf("role assumption denied: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 3. Calculate session duration
							 | 
						|
									sessionDuration := s.calculateSessionDuration(request.DurationSeconds)
							 | 
						|
									expiresAt := time.Now().Add(sessionDuration)
							 | 
						|
								
							 | 
						|
									// 4. Generate session ID and credentials
							 | 
						|
									sessionId, err := GenerateSessionId()
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate session ID: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									credGenerator := NewCredentialGenerator()
							 | 
						|
									credentials, err := credGenerator.GenerateTemporaryCredentials(sessionId, expiresAt)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate credentials: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 5. Create comprehensive JWT session token with all session information embedded
							 | 
						|
									assumedRoleUser := &AssumedRoleUser{
							 | 
						|
										AssumedRoleId: request.RoleArn,
							 | 
						|
										Arn:           GenerateAssumedRoleArn(request.RoleArn, request.RoleSessionName),
							 | 
						|
										Subject:       externalIdentity.UserID,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create rich JWT claims with all session information
							 | 
						|
									sessionClaims := NewSTSSessionClaims(sessionId, s.Config.Issuer, expiresAt).
							 | 
						|
										WithSessionName(request.RoleSessionName).
							 | 
						|
										WithRoleInfo(request.RoleArn, assumedRoleUser.Arn, assumedRoleUser.Arn).
							 | 
						|
										WithIdentityProvider(provider.Name(), externalIdentity.UserID, "").
							 | 
						|
										WithMaxDuration(sessionDuration)
							 | 
						|
								
							 | 
						|
									// Generate self-contained JWT token with all session information
							 | 
						|
									jwtToken, err := s.tokenGenerator.GenerateJWTWithClaims(sessionClaims)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate JWT session token: %w", err)
							 | 
						|
									}
							 | 
						|
									credentials.SessionToken = jwtToken
							 | 
						|
								
							 | 
						|
									// 6. Build and return response (no session storage needed!)
							 | 
						|
								
							 | 
						|
									return &AssumeRoleResponse{
							 | 
						|
										Credentials:     credentials,
							 | 
						|
										AssumedRoleUser: assumedRoleUser,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// AssumeRoleWithCredentials assumes a role using username/password credentials
							 | 
						|
								// This method is now completely stateless - all session information is embedded in the JWT token
							 | 
						|
								func (s *STSService) AssumeRoleWithCredentials(ctx context.Context, request *AssumeRoleWithCredentialsRequest) (*AssumeRoleResponse, error) {
							 | 
						|
									if !s.initialized {
							 | 
						|
										return nil, fmt.Errorf("STS service not initialized")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request == nil {
							 | 
						|
										return nil, fmt.Errorf("request cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate request parameters
							 | 
						|
									if err := s.validateAssumeRoleWithCredentialsRequest(request); err != nil {
							 | 
						|
										return nil, fmt.Errorf("invalid request: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 1. Get the specified provider
							 | 
						|
									provider, exists := s.providers[request.ProviderName]
							 | 
						|
									if !exists {
							 | 
						|
										return nil, fmt.Errorf("identity provider not found: %s", request.ProviderName)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 2. Validate credentials with the specified provider
							 | 
						|
									credentials := request.Username + ":" + request.Password
							 | 
						|
									externalIdentity, err := provider.Authenticate(ctx, credentials)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to authenticate credentials: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 3. Check if the role exists and can be assumed (includes trust policy validation)
							 | 
						|
									if err := s.validateRoleAssumptionForCredentials(ctx, request.RoleArn, externalIdentity); err != nil {
							 | 
						|
										return nil, fmt.Errorf("role assumption denied: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 4. Calculate session duration
							 | 
						|
									sessionDuration := s.calculateSessionDuration(request.DurationSeconds)
							 | 
						|
									expiresAt := time.Now().Add(sessionDuration)
							 | 
						|
								
							 | 
						|
									// 5. Generate session ID and temporary credentials
							 | 
						|
									sessionId, err := GenerateSessionId()
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate session ID: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									credGenerator := NewCredentialGenerator()
							 | 
						|
									tempCredentials, err := credGenerator.GenerateTemporaryCredentials(sessionId, expiresAt)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate credentials: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 6. Create comprehensive JWT session token with all session information embedded
							 | 
						|
									assumedRoleUser := &AssumedRoleUser{
							 | 
						|
										AssumedRoleId: request.RoleArn,
							 | 
						|
										Arn:           GenerateAssumedRoleArn(request.RoleArn, request.RoleSessionName),
							 | 
						|
										Subject:       externalIdentity.UserID,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create rich JWT claims with all session information
							 | 
						|
									sessionClaims := NewSTSSessionClaims(sessionId, s.Config.Issuer, expiresAt).
							 | 
						|
										WithSessionName(request.RoleSessionName).
							 | 
						|
										WithRoleInfo(request.RoleArn, assumedRoleUser.Arn, assumedRoleUser.Arn).
							 | 
						|
										WithIdentityProvider(provider.Name(), externalIdentity.UserID, "").
							 | 
						|
										WithMaxDuration(sessionDuration)
							 | 
						|
								
							 | 
						|
									// Generate self-contained JWT token with all session information
							 | 
						|
									jwtToken, err := s.tokenGenerator.GenerateJWTWithClaims(sessionClaims)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate JWT session token: %w", err)
							 | 
						|
									}
							 | 
						|
									tempCredentials.SessionToken = jwtToken
							 | 
						|
								
							 | 
						|
									// 7. Build and return response (no session storage needed!)
							 | 
						|
								
							 | 
						|
									return &AssumeRoleResponse{
							 | 
						|
										Credentials:     tempCredentials,
							 | 
						|
										AssumedRoleUser: assumedRoleUser,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidateSessionToken validates a session token and returns session information
							 | 
						|
								// This method is now completely stateless - all session information is extracted from the JWT token
							 | 
						|
								func (s *STSService) ValidateSessionToken(ctx context.Context, sessionToken string) (*SessionInfo, error) {
							 | 
						|
									if !s.initialized {
							 | 
						|
										return nil, fmt.Errorf(ErrSTSServiceNotInitialized)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if sessionToken == "" {
							 | 
						|
										return nil, fmt.Errorf(ErrSessionTokenCannotBeEmpty)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate JWT and extract comprehensive session claims
							 | 
						|
									claims, err := s.tokenGenerator.ValidateJWTWithClaims(sessionToken)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf(ErrSessionValidationFailed, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Convert JWT claims back to SessionInfo
							 | 
						|
									// All session information is embedded in the JWT token itself
							 | 
						|
									return claims.ToSessionInfo(), nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NOTE: Session revocation is not supported in the stateless JWT design.
							 | 
						|
								//
							 | 
						|
								// In a stateless JWT system, tokens cannot be revoked without implementing a token blacklist,
							 | 
						|
								// which would break the stateless architecture. Tokens remain valid until their natural
							 | 
						|
								// expiration time.
							 | 
						|
								//
							 | 
						|
								// For applications requiring token revocation, consider:
							 | 
						|
								// 1. Using shorter token lifespans (e.g., 15-30 minutes)
							 | 
						|
								// 2. Implementing a distributed token blacklist (breaks stateless design)
							 | 
						|
								// 3. Including a "jti" (JWT ID) claim for tracking specific tokens
							 | 
						|
								//
							 | 
						|
								// Use ValidateSessionToken() to verify if a token is valid and not expired.
							 | 
						|
								
							 | 
						|
								// Helper methods for AssumeRoleWithWebIdentity
							 | 
						|
								
							 | 
						|
								// validateAssumeRoleWithWebIdentityRequest validates the request parameters
							 | 
						|
								func (s *STSService) validateAssumeRoleWithWebIdentityRequest(request *AssumeRoleWithWebIdentityRequest) error {
							 | 
						|
									if request.RoleArn == "" {
							 | 
						|
										return fmt.Errorf("RoleArn is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request.WebIdentityToken == "" {
							 | 
						|
										return fmt.Errorf("WebIdentityToken is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request.RoleSessionName == "" {
							 | 
						|
										return fmt.Errorf("RoleSessionName is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate session duration if provided
							 | 
						|
									if request.DurationSeconds != nil {
							 | 
						|
										if *request.DurationSeconds < 900 || *request.DurationSeconds > 43200 { // 15min to 12 hours
							 | 
						|
											return fmt.Errorf("DurationSeconds must be between 900 and 43200 seconds")
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateWebIdentityToken validates the web identity token with strict issuer-to-provider mapping
							 | 
						|
								// SECURITY: JWT tokens with a specific issuer claim MUST only be validated by the provider for that issuer
							 | 
						|
								// SECURITY: This method only accepts JWT tokens. Non-JWT authentication must use AssumeRoleWithCredentials with explicit ProviderName.
							 | 
						|
								func (s *STSService) validateWebIdentityToken(ctx context.Context, token string) (*providers.ExternalIdentity, providers.IdentityProvider, error) {
							 | 
						|
									// Try to extract issuer from JWT token for strict validation
							 | 
						|
									issuer, err := s.extractIssuerFromJWT(token)
							 | 
						|
									if err != nil {
							 | 
						|
										// Token is not a valid JWT or cannot be parsed
							 | 
						|
										// SECURITY: Web identity tokens MUST be JWT tokens. Non-JWT authentication flows
							 | 
						|
										// should use AssumeRoleWithCredentials with explicit ProviderName to prevent
							 | 
						|
										// security vulnerabilities from non-deterministic provider selection.
							 | 
						|
										return nil, nil, fmt.Errorf("web identity token must be a valid JWT token: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Look up the specific provider for this issuer
							 | 
						|
									provider, exists := s.issuerToProvider[issuer]
							 | 
						|
									if !exists {
							 | 
						|
										// SECURITY: If no provider is registered for this issuer, fail immediately
							 | 
						|
										// This prevents JWT tokens from being validated by unintended providers
							 | 
						|
										return nil, nil, fmt.Errorf("no identity provider registered for issuer: %s", issuer)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Authenticate with the correct provider for this issuer
							 | 
						|
									identity, err := provider.Authenticate(ctx, token)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, nil, fmt.Errorf("token validation failed with provider for issuer %s: %w", issuer, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if identity == nil {
							 | 
						|
										return nil, nil, fmt.Errorf("authentication succeeded but no identity returned for issuer %s", issuer)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									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)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// extractIssuerFromJWT extracts the issuer (iss) claim from a JWT token without verification
							 | 
						|
								func (s *STSService) extractIssuerFromJWT(token string) (string, error) {
							 | 
						|
									// Parse token without verification to get claims
							 | 
						|
									parsedToken, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
							 | 
						|
									if err != nil {
							 | 
						|
										return "", fmt.Errorf("failed to parse JWT token: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Extract claims
							 | 
						|
									claims, ok := parsedToken.Claims.(jwt.MapClaims)
							 | 
						|
									if !ok {
							 | 
						|
										return "", fmt.Errorf("invalid token claims")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Get issuer claim
							 | 
						|
									issuer, ok := claims["iss"].(string)
							 | 
						|
									if !ok || issuer == "" {
							 | 
						|
										return "", fmt.Errorf("missing or invalid issuer claim")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return issuer, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateRoleAssumptionForWebIdentity validates role assumption for web identity tokens
							 | 
						|
								// This method performs complete trust policy validation to prevent unauthorized role assumptions
							 | 
						|
								func (s *STSService) validateRoleAssumptionForWebIdentity(ctx context.Context, roleArn string, webIdentityToken string) error {
							 | 
						|
									if roleArn == "" {
							 | 
						|
										return fmt.Errorf("role ARN cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if webIdentityToken == "" {
							 | 
						|
										return fmt.Errorf("web identity token cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Basic role ARN format validation
							 | 
						|
									expectedPrefix := "arn:seaweed: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
							 | 
						|
									roleName := utils.ExtractRoleNameFromArn(roleArn)
							 | 
						|
									if roleName == "" {
							 | 
						|
										return fmt.Errorf("invalid role ARN format: %s", roleArn)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// CRITICAL SECURITY: Perform trust policy validation
							 | 
						|
									if s.trustPolicyValidator != nil {
							 | 
						|
										if err := s.trustPolicyValidator.ValidateTrustPolicyForWebIdentity(ctx, roleArn, webIdentityToken); err != nil {
							 | 
						|
											return fmt.Errorf("trust policy validation failed: %w", err)
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										// If no trust policy validator is configured, fail closed for security
							 | 
						|
										glog.Errorf("SECURITY WARNING: No trust policy validator configured - denying role assumption for security")
							 | 
						|
										return fmt.Errorf("trust policy validation not available - role assumption denied for security")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateRoleAssumptionForCredentials validates role assumption for credential-based authentication
							 | 
						|
								// This method performs complete trust policy validation to prevent unauthorized role assumptions
							 | 
						|
								func (s *STSService) validateRoleAssumptionForCredentials(ctx context.Context, roleArn string, identity *providers.ExternalIdentity) error {
							 | 
						|
									if roleArn == "" {
							 | 
						|
										return fmt.Errorf("role ARN cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if identity == nil {
							 | 
						|
										return fmt.Errorf("identity cannot be nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Basic role ARN format validation
							 | 
						|
									expectedPrefix := "arn:seaweed: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
							 | 
						|
									roleName := utils.ExtractRoleNameFromArn(roleArn)
							 | 
						|
									if roleName == "" {
							 | 
						|
										return fmt.Errorf("invalid role ARN format: %s", roleArn)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// CRITICAL SECURITY: Perform trust policy validation
							 | 
						|
									if s.trustPolicyValidator != nil {
							 | 
						|
										if err := s.trustPolicyValidator.ValidateTrustPolicyForCredentials(ctx, roleArn, identity); err != nil {
							 | 
						|
											return fmt.Errorf("trust policy validation failed: %w", err)
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										// If no trust policy validator is configured, fail closed for security
							 | 
						|
										glog.Errorf("SECURITY WARNING: No trust policy validator configured - denying role assumption for security")
							 | 
						|
										return fmt.Errorf("trust policy validation not available - role assumption denied for security")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// calculateSessionDuration calculates the session duration
							 | 
						|
								func (s *STSService) calculateSessionDuration(durationSeconds *int64) time.Duration {
							 | 
						|
									if durationSeconds != nil {
							 | 
						|
										return time.Duration(*durationSeconds) * time.Second
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Use default from config
							 | 
						|
									return s.Config.TokenDuration.Duration
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// extractSessionIdFromToken extracts session ID from JWT session token
							 | 
						|
								func (s *STSService) extractSessionIdFromToken(sessionToken string) string {
							 | 
						|
									// Parse JWT and extract session ID from claims
							 | 
						|
									claims, err := s.tokenGenerator.ValidateJWTWithClaims(sessionToken)
							 | 
						|
									if err != nil {
							 | 
						|
										// For test compatibility, also handle direct session IDs
							 | 
						|
										if len(sessionToken) == 32 { // Typical session ID length
							 | 
						|
											return sessionToken
							 | 
						|
										}
							 | 
						|
										return ""
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return claims.SessionId
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateAssumeRoleWithCredentialsRequest validates the credentials request parameters
							 | 
						|
								func (s *STSService) validateAssumeRoleWithCredentialsRequest(request *AssumeRoleWithCredentialsRequest) error {
							 | 
						|
									if request.RoleArn == "" {
							 | 
						|
										return fmt.Errorf("RoleArn is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request.Username == "" {
							 | 
						|
										return fmt.Errorf("Username is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request.Password == "" {
							 | 
						|
										return fmt.Errorf("Password is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request.RoleSessionName == "" {
							 | 
						|
										return fmt.Errorf("RoleSessionName is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if request.ProviderName == "" {
							 | 
						|
										return fmt.Errorf("ProviderName is required")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate session duration if provided
							 | 
						|
									if request.DurationSeconds != nil {
							 | 
						|
										if *request.DurationSeconds < 900 || *request.DurationSeconds > 43200 { // 15min to 12 hours
							 | 
						|
											return fmt.Errorf("DurationSeconds must be between 900 and 43200 seconds")
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ExpireSessionForTesting manually expires a session for testing purposes
							 | 
						|
								func (s *STSService) ExpireSessionForTesting(ctx context.Context, sessionToken string) error {
							 | 
						|
									if !s.initialized {
							 | 
						|
										return fmt.Errorf("STS service not initialized")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if sessionToken == "" {
							 | 
						|
										return fmt.Errorf("session token cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate JWT token format
							 | 
						|
									_, err := s.tokenGenerator.ValidateJWTWithClaims(sessionToken)
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("invalid session token format: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// In a stateless system, we cannot manually expire JWT tokens
							 | 
						|
									// The token expiration is embedded in the token itself and handled by JWT validation
							 | 
						|
									glog.V(1).Infof("Manual session expiration requested for stateless token - cannot expire JWT tokens manually")
							 | 
						|
								
							 | 
						|
									return fmt.Errorf("manual session expiration not supported in stateless JWT system")
							 | 
						|
								}
							 |