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.
		
		
		
		
		
			
		
			
				
					
					
						
							650 lines
						
					
					
						
							21 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							650 lines
						
					
					
						
							21 KiB
						
					
					
				
								package sts
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"fmt"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/iam/providers"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// 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
							 | 
						|
									initialized    bool
							 | 
						|
									providers      map[string]providers.IdentityProvider
							 | 
						|
									tokenGenerator *TokenGenerator
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// STSConfig holds STS service configuration
							 | 
						|
								type STSConfig struct {
							 | 
						|
									// TokenDuration is the default duration for issued tokens
							 | 
						|
									TokenDuration time.Duration `json:"tokenDuration"`
							 | 
						|
								
							 | 
						|
									// MaxSessionLength is the maximum duration for any session
							 | 
						|
									MaxSessionLength time.Duration `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"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// SessionStore defines the interface for storing session information
							 | 
						|
								type SessionStore interface {
							 | 
						|
									// StoreSession stores session information (filerAddress ignored for memory stores)
							 | 
						|
									StoreSession(ctx context.Context, filerAddress string, sessionId string, session *SessionInfo) error
							 | 
						|
								
							 | 
						|
									// GetSession retrieves session information (filerAddress ignored for memory stores)
							 | 
						|
									GetSession(ctx context.Context, filerAddress string, sessionId string) (*SessionInfo, error)
							 | 
						|
								
							 | 
						|
									// RevokeSession revokes a session (filerAddress ignored for memory stores)
							 | 
						|
									RevokeSession(ctx context.Context, filerAddress string, sessionId string) error
							 | 
						|
								
							 | 
						|
									// CleanupExpiredSessions removes expired sessions (filerAddress ignored for memory stores)
							 | 
						|
									CleanupExpiredSessions(ctx context.Context, filerAddress string) error
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewSTSService creates a new STS service
							 | 
						|
								func NewSTSService() *STSService {
							 | 
						|
									return &STSService{
							 | 
						|
										providers: 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 <= 0 {
							 | 
						|
										return fmt.Errorf(ErrInvalidTokenDuration)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if config.MaxSessionLength <= 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
							 | 
						|
								
							 | 
						|
									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
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// 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)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// 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
							 | 
						|
									if err := s.validateRoleAssumption(request.RoleArn, externalIdentity); 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
							 | 
						|
									if err := s.validateRoleAssumption(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
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// RevokeSession validates a session token (stateless operation)
							 | 
						|
								// Note: In a stateless JWT system, sessions cannot be revoked without a blacklist.
							 | 
						|
								// This method validates the token format but cannot actually revoke it.
							 | 
						|
								// For production use, consider implementing a token blacklist or use short-lived tokens.
							 | 
						|
								func (s *STSService) RevokeSession(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 revoke JWT tokens without a blacklist
							 | 
						|
									// The token will naturally expire based on its embedded expiration time
							 | 
						|
									glog.V(1).Infof("Session revocation requested for stateless token - token will expire naturally at its embedded expiration time")
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// 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 available providers
							 | 
						|
								func (s *STSService) validateWebIdentityToken(ctx context.Context, token string) (*providers.ExternalIdentity, providers.IdentityProvider, error) {
							 | 
						|
									// Try to validate with each registered provider
							 | 
						|
									for _, provider := range s.providers {
							 | 
						|
										identity, err := provider.Authenticate(ctx, token)
							 | 
						|
										if err == nil && identity != nil {
							 | 
						|
											// Token validated successfully with this provider
							 | 
						|
											return identity, provider, nil
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil, nil, fmt.Errorf("web identity token validation failed with all providers")
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateRoleAssumption checks if the role can be assumed by the external identity
							 | 
						|
								func (s *STSService) validateRoleAssumption(roleArn string, identity *providers.ExternalIdentity) error {
							 | 
						|
									// For now, we'll do basic validation
							 | 
						|
									// In a full implementation, this would check:
							 | 
						|
									// 1. Role exists
							 | 
						|
									// 2. Role trust policy allows assumption by this identity
							 | 
						|
									// 3. Identity has permission to assume the role
							 | 
						|
								
							 | 
						|
									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)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// For testing, reject non-existent roles
							 | 
						|
									roleName := extractRoleNameFromArn(roleArn)
							 | 
						|
									if roleName == "NonExistentRole" {
							 | 
						|
										return fmt.Errorf("role does not exist: %s", roleName)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									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
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// 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")
							 | 
						|
								}
							 |