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.
		
		
		
		
		
			
		
			
				
					
					
						
							624 lines
						
					
					
						
							19 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							624 lines
						
					
					
						
							19 KiB
						
					
					
				| package integration | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"encoding/base64" | |
| 	"encoding/json" | |
| 	"fmt" | |
| 	"path/filepath" | |
| 	"strings" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/iam/policy" | |
| 	"github.com/seaweedfs/seaweedfs/weed/iam/providers" | |
| 	"github.com/seaweedfs/seaweedfs/weed/iam/sts" | |
| ) | |
| 
 | |
| // IAMManager orchestrates all IAM components | |
| type IAMManager struct { | |
| 	stsService   *sts.STSService | |
| 	policyEngine *policy.PolicyEngine | |
| 	roleStore    RoleStore | |
| 	initialized  bool | |
| } | |
| 
 | |
| // IAMConfig holds configuration for all IAM components | |
| type IAMConfig struct { | |
| 	// STS service configuration | |
| 	STS *sts.STSConfig `json:"sts"` | |
| 
 | |
| 	// Policy engine configuration | |
| 	Policy *policy.PolicyEngineConfig `json:"policy"` | |
| 
 | |
| 	// Role store configuration | |
| 	Roles *RoleStoreConfig `json:"roleStore"` | |
| } | |
| 
 | |
| // RoleStoreConfig holds role store configuration | |
| type RoleStoreConfig struct { | |
| 	// StoreType specifies the role store backend (memory, filer, etc.) | |
| 	StoreType string `json:"storeType"` | |
| 
 | |
| 	// StoreConfig contains store-specific configuration | |
| 	StoreConfig map[string]interface{} `json:"storeConfig,omitempty"` | |
| } | |
| 
 | |
| // RoleDefinition defines a role with its trust policy and attached policies | |
| type RoleDefinition struct { | |
| 	// RoleName is the name of the role | |
| 	RoleName string `json:"roleName"` | |
| 
 | |
| 	// RoleArn is the full ARN of the role | |
| 	RoleArn string `json:"roleArn"` | |
| 
 | |
| 	// TrustPolicy defines who can assume this role | |
| 	TrustPolicy *policy.PolicyDocument `json:"trustPolicy"` | |
| 
 | |
| 	// AttachedPolicies lists the policy names attached to this role | |
| 	AttachedPolicies []string `json:"attachedPolicies"` | |
| 
 | |
| 	// Description is an optional description of the role | |
| 	Description string `json:"description,omitempty"` | |
| } | |
| 
 | |
| // ActionRequest represents a request to perform an action | |
| type ActionRequest struct { | |
| 	// Principal is the entity performing the action | |
| 	Principal string `json:"principal"` | |
| 
 | |
| 	// Action is the action being requested | |
| 	Action string `json:"action"` | |
| 
 | |
| 	// Resource is the resource being accessed | |
| 	Resource string `json:"resource"` | |
| 
 | |
| 	// SessionToken for temporary credential validation | |
| 	SessionToken string `json:"sessionToken"` | |
| 
 | |
| 	// RequestContext contains additional request information | |
| 	RequestContext map[string]interface{} `json:"requestContext,omitempty"` | |
| } | |
| 
 | |
| // NewIAMManager creates a new IAM manager | |
| func NewIAMManager() *IAMManager { | |
| 	return &IAMManager{} | |
| } | |
| 
 | |
| // Initialize initializes the IAM manager with all components | |
| func (m *IAMManager) Initialize(config *IAMConfig) error { | |
| 	if config == nil { | |
| 		return fmt.Errorf("config cannot be nil") | |
| 	} | |
| 
 | |
| 	// Initialize STS service | |
| 	m.stsService = sts.NewSTSService() | |
| 	if err := m.stsService.Initialize(config.STS); err != nil { | |
| 		return fmt.Errorf("failed to initialize STS service: %w", err) | |
| 	} | |
| 
 | |
| 	// Initialize policy engine | |
| 	m.policyEngine = policy.NewPolicyEngine() | |
| 	if err := m.policyEngine.Initialize(config.Policy); err != nil { | |
| 		return fmt.Errorf("failed to initialize policy engine: %w", err) | |
| 	} | |
| 
 | |
| 	// Initialize role store | |
| 	roleStore, err := m.createRoleStore(config.Roles) | |
| 	if err != nil { | |
| 		return fmt.Errorf("failed to initialize role store: %w", err) | |
| 	} | |
| 	m.roleStore = roleStore | |
| 
 | |
| 	m.initialized = true | |
| 	return nil | |
| } | |
| 
 | |
| // createRoleStore creates a role store based on configuration | |
| func (m *IAMManager) createRoleStore(config *RoleStoreConfig) (RoleStore, error) { | |
| 	if config == nil { | |
| 		// Default to memory role store when no config provided | |
| 		return NewMemoryRoleStore(), nil | |
| 	} | |
| 
 | |
| 	switch config.StoreType { | |
| 	case "", "filer": | |
| 		return NewFilerRoleStore(config.StoreConfig) | |
| 	case "memory": | |
| 		return NewMemoryRoleStore(), nil | |
| 	default: | |
| 		return nil, fmt.Errorf("unsupported role store type: %s", config.StoreType) | |
| 	} | |
| } | |
| 
 | |
| // RegisterIdentityProvider registers an identity provider | |
| func (m *IAMManager) RegisterIdentityProvider(provider providers.IdentityProvider) error { | |
| 	if !m.initialized { | |
| 		return fmt.Errorf("IAM manager not initialized") | |
| 	} | |
| 
 | |
| 	return m.stsService.RegisterProvider(provider) | |
| } | |
| 
 | |
| // CreatePolicy creates a new policy | |
| func (m *IAMManager) CreatePolicy(ctx context.Context, filerAddress string, name string, policyDoc *policy.PolicyDocument) error { | |
| 	if !m.initialized { | |
| 		return fmt.Errorf("IAM manager not initialized") | |
| 	} | |
| 
 | |
| 	return m.policyEngine.AddPolicy(filerAddress, name, policyDoc) | |
| } | |
| 
 | |
| // CreateRole creates a new role with trust policy and attached policies | |
| func (m *IAMManager) CreateRole(ctx context.Context, filerAddress string, roleName string, roleDef *RoleDefinition) error { | |
| 	if !m.initialized { | |
| 		return fmt.Errorf("IAM manager not initialized") | |
| 	} | |
| 
 | |
| 	if roleName == "" { | |
| 		return fmt.Errorf("role name cannot be empty") | |
| 	} | |
| 
 | |
| 	if roleDef == nil { | |
| 		return fmt.Errorf("role definition cannot be nil") | |
| 	} | |
| 
 | |
| 	// Set role ARN if not provided | |
| 	if roleDef.RoleArn == "" { | |
| 		roleDef.RoleArn = fmt.Sprintf("arn:seaweed:iam::role/%s", roleName) | |
| 	} | |
| 
 | |
| 	// Validate trust policy | |
| 	if roleDef.TrustPolicy != nil { | |
| 		if err := policy.ValidateTrustPolicyDocument(roleDef.TrustPolicy); err != nil { | |
| 			return fmt.Errorf("invalid trust policy: %w", err) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Store role definition | |
| 	return m.roleStore.StoreRole(ctx, "", roleName, roleDef) | |
| } | |
| 
 | |
| // AssumeRoleWithWebIdentity assumes a role using web identity (OIDC) | |
| func (m *IAMManager) AssumeRoleWithWebIdentity(ctx context.Context, request *sts.AssumeRoleWithWebIdentityRequest) (*sts.AssumeRoleResponse, error) { | |
| 	if !m.initialized { | |
| 		return nil, fmt.Errorf("IAM manager not initialized") | |
| 	} | |
| 
 | |
| 	// Extract role name from ARN | |
| 	roleName := extractRoleNameFromArn(request.RoleArn) | |
| 
 | |
| 	// Get role definition | |
| 	roleDef, err := m.roleStore.GetRole(ctx, "", roleName) | |
| 	if err != nil { | |
| 		return nil, fmt.Errorf("role not found: %s", roleName) | |
| 	} | |
| 
 | |
| 	// Validate trust policy before allowing STS to assume the role | |
| 	if err := m.validateTrustPolicyForWebIdentity(ctx, roleDef, request.WebIdentityToken); err != nil { | |
| 		return nil, fmt.Errorf("trust policy validation failed: %w", err) | |
| 	} | |
| 
 | |
| 	// Use STS service to assume the role | |
| 	return m.stsService.AssumeRoleWithWebIdentity(ctx, request) | |
| } | |
| 
 | |
| // AssumeRoleWithCredentials assumes a role using credentials (LDAP) | |
| func (m *IAMManager) AssumeRoleWithCredentials(ctx context.Context, request *sts.AssumeRoleWithCredentialsRequest) (*sts.AssumeRoleResponse, error) { | |
| 	if !m.initialized { | |
| 		return nil, fmt.Errorf("IAM manager not initialized") | |
| 	} | |
| 
 | |
| 	// Extract role name from ARN | |
| 	roleName := extractRoleNameFromArn(request.RoleArn) | |
| 
 | |
| 	// Get role definition | |
| 	roleDef, err := m.roleStore.GetRole(ctx, "", roleName) | |
| 	if err != nil { | |
| 		return nil, fmt.Errorf("role not found: %s", roleName) | |
| 	} | |
| 
 | |
| 	// Validate trust policy | |
| 	if err := m.validateTrustPolicyForCredentials(ctx, roleDef, request); err != nil { | |
| 		return nil, fmt.Errorf("trust policy validation failed: %w", err) | |
| 	} | |
| 
 | |
| 	// Use STS service to assume the role | |
| 	return m.stsService.AssumeRoleWithCredentials(ctx, request) | |
| } | |
| 
 | |
| // IsActionAllowed checks if a principal is allowed to perform an action on a resource | |
| func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest) (bool, error) { | |
| 	if !m.initialized { | |
| 		return false, fmt.Errorf("IAM manager not initialized") | |
| 	} | |
| 
 | |
| 	// Validate session token first (skip for OIDC tokens which are already validated) | |
| 	if !isOIDCToken(request.SessionToken) { | |
| 		_, err := m.stsService.ValidateSessionToken(ctx, request.SessionToken) | |
| 		if err != nil { | |
| 			return false, fmt.Errorf("invalid session: %w", err) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Extract role name from principal ARN | |
| 	roleName := extractRoleNameFromPrincipal(request.Principal) | |
| 	if roleName == "" { | |
| 		return false, fmt.Errorf("could not extract role from principal: %s", request.Principal) | |
| 	} | |
| 
 | |
| 	// Get role definition | |
| 	roleDef, err := m.roleStore.GetRole(ctx, "", roleName) | |
| 	if err != nil { | |
| 		return false, fmt.Errorf("role not found: %s", roleName) | |
| 	} | |
| 
 | |
| 	// Create evaluation context | |
| 	evalCtx := &policy.EvaluationContext{ | |
| 		Principal:      request.Principal, | |
| 		Action:         request.Action, | |
| 		Resource:       request.Resource, | |
| 		RequestContext: request.RequestContext, | |
| 	} | |
| 
 | |
| 	// Evaluate policies attached to the role | |
| 	result, err := m.policyEngine.Evaluate(ctx, "", evalCtx, roleDef.AttachedPolicies) | |
| 	if err != nil { | |
| 		return false, fmt.Errorf("policy evaluation failed: %w", err) | |
| 	} | |
| 
 | |
| 	return result.Effect == policy.EffectAllow, nil | |
| } | |
| 
 | |
| // ValidateTrustPolicy validates if a principal can assume a role (for testing) | |
| func (m *IAMManager) ValidateTrustPolicy(ctx context.Context, roleArn, provider, userID string) bool { | |
| 	roleName := extractRoleNameFromArn(roleArn) | |
| 	roleDef, err := m.roleStore.GetRole(ctx, "", roleName) | |
| 	if err != nil { | |
| 		return false | |
| 	} | |
| 
 | |
| 	// Simple validation based on provider in trust policy | |
| 	if roleDef.TrustPolicy != nil { | |
| 		for _, statement := range roleDef.TrustPolicy.Statement { | |
| 			if statement.Effect == "Allow" { | |
| 				if principal, ok := statement.Principal.(map[string]interface{}); ok { | |
| 					if federated, ok := principal["Federated"].(string); ok { | |
| 						if federated == "test-"+provider { | |
| 							return true | |
| 						} | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	return false | |
| } | |
| 
 | |
| // validateTrustPolicyForWebIdentity validates trust policy for OIDC assumption | |
| func (m *IAMManager) validateTrustPolicyForWebIdentity(ctx context.Context, roleDef *RoleDefinition, webIdentityToken string) error { | |
| 	if roleDef.TrustPolicy == nil { | |
| 		return fmt.Errorf("role has no trust policy") | |
| 	} | |
| 
 | |
| 	// Create evaluation context for trust policy validation | |
| 	requestContext := make(map[string]interface{}) | |
| 
 | |
| 	// Try to parse as JWT first, fallback to mock token handling | |
| 	tokenClaims, err := parseJWTTokenForTrustPolicy(webIdentityToken) | |
| 	if err != nil { | |
| 		// If JWT parsing fails, this might be a mock token (like "valid-oidc-token") | |
| 		// For mock tokens, we'll use default values that match the trust policy expectations | |
| 		requestContext["seaweed:TokenIssuer"] = "test-oidc" | |
| 		requestContext["seaweed:FederatedProvider"] = "test-oidc" | |
| 		requestContext["seaweed:Subject"] = "mock-user" | |
| 	} else { | |
| 		// Add standard context values from JWT claims that trust policies might check | |
| 		if idp, ok := tokenClaims["idp"].(string); ok { | |
| 			requestContext["seaweed:TokenIssuer"] = idp | |
| 			requestContext["seaweed:FederatedProvider"] = idp | |
| 		} | |
| 		if iss, ok := tokenClaims["iss"].(string); ok { | |
| 			requestContext["seaweed:Issuer"] = iss | |
| 		} | |
| 		if sub, ok := tokenClaims["sub"].(string); ok { | |
| 			requestContext["seaweed:Subject"] = sub | |
| 		} | |
| 		if extUid, ok := tokenClaims["ext_uid"].(string); ok { | |
| 			requestContext["seaweed:ExternalUserId"] = extUid | |
| 		} | |
| 	} | |
| 
 | |
| 	// Create evaluation context for trust policy | |
| 	evalCtx := &policy.EvaluationContext{ | |
| 		Principal:      "web-identity-user", // Placeholder principal for trust policy evaluation | |
| 		Action:         "sts:AssumeRoleWithWebIdentity", | |
| 		Resource:       roleDef.RoleArn, | |
| 		RequestContext: requestContext, | |
| 	} | |
| 
 | |
| 	// Evaluate the trust policy directly | |
| 	if !m.evaluateTrustPolicy(roleDef.TrustPolicy, evalCtx) { | |
| 		return fmt.Errorf("trust policy denies web identity assumption") | |
| 	} | |
| 
 | |
| 	return nil | |
| } | |
| 
 | |
| // validateTrustPolicyForCredentials validates trust policy for credential assumption | |
| func (m *IAMManager) validateTrustPolicyForCredentials(ctx context.Context, roleDef *RoleDefinition, request *sts.AssumeRoleWithCredentialsRequest) error { | |
| 	if roleDef.TrustPolicy == nil { | |
| 		return fmt.Errorf("role has no trust policy") | |
| 	} | |
| 
 | |
| 	// Check if trust policy allows credential assumption for the specific provider | |
| 	for _, statement := range roleDef.TrustPolicy.Statement { | |
| 		if statement.Effect == "Allow" { | |
| 			for _, action := range statement.Action { | |
| 				if action == "sts:AssumeRoleWithCredentials" { | |
| 					if principal, ok := statement.Principal.(map[string]interface{}); ok { | |
| 						if federated, ok := principal["Federated"].(string); ok { | |
| 							if federated == request.ProviderName { | |
| 								return nil // Allow | |
| 							} | |
| 						} | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	return fmt.Errorf("trust policy does not allow credential assumption for provider: %s", request.ProviderName) | |
| } | |
| 
 | |
| // Helper functions | |
|  | |
| // extractRoleNameFromArn extracts role name from role ARN | |
| func extractRoleNameFromArn(roleArn string) string { | |
| 	prefix := "arn:seaweed:iam::role/" | |
| 	if len(roleArn) > len(prefix) && roleArn[:len(prefix)] == prefix { | |
| 		return roleArn[len(prefix):] | |
| 	} | |
| 	return "" | |
| } | |
| 
 | |
| // extractRoleNameFromPrincipal extracts role name from principal ARN | |
| func extractRoleNameFromPrincipal(principal string) string { | |
| 	// Handle STS assumed role format: arn:seaweed:sts::assumed-role/RoleName/SessionName | |
| 	stsPrefix := "arn:seaweed:sts::assumed-role/" | |
| 	if len(principal) > len(stsPrefix) && principal[:len(stsPrefix)] == stsPrefix { | |
| 		remainder := principal[len(stsPrefix):] | |
| 		// Split on first '/' to get role name | |
| 		if slashIndex := indexOf(remainder, "/"); slashIndex != -1 { | |
| 			return remainder[:slashIndex] | |
| 		} | |
| 	} | |
| 
 | |
| 	// Handle IAM role format: arn:seaweed:iam::role/RoleName | |
| 	iamPrefix := "arn:seaweed:iam::role/" | |
| 	if len(principal) > len(iamPrefix) && principal[:len(iamPrefix)] == iamPrefix { | |
| 		return principal[len(iamPrefix):] | |
| 	} | |
| 
 | |
| 	return "" | |
| } | |
| 
 | |
| // indexOf finds the index of the first occurrence of substring in string | |
| func indexOf(s, substr string) int { | |
| 	for i := 0; i <= len(s)-len(substr); i++ { | |
| 		if s[i:i+len(substr)] == substr { | |
| 			return i | |
| 		} | |
| 	} | |
| 	return -1 | |
| } | |
| 
 | |
| // ExpireSessionForTesting manually expires a session for testing purposes | |
| func (m *IAMManager) ExpireSessionForTesting(ctx context.Context, sessionToken string) error { | |
| 	if !m.initialized { | |
| 		return fmt.Errorf("IAM manager not initialized") | |
| 	} | |
| 
 | |
| 	return m.stsService.ExpireSessionForTesting(ctx, sessionToken) | |
| } | |
| 
 | |
| // GetSTSService returns the STS service instance | |
| func (m *IAMManager) GetSTSService() *sts.STSService { | |
| 	return m.stsService | |
| } | |
| 
 | |
| // parseJWTTokenForTrustPolicy parses a JWT token to extract claims for trust policy evaluation | |
| func parseJWTTokenForTrustPolicy(tokenString string) (map[string]interface{}, error) { | |
| 	// Simple JWT parsing without verification (for trust policy context only) | |
| 	// In production, this should use proper JWT parsing with signature verification | |
| 	parts := strings.Split(tokenString, ".") | |
| 	if len(parts) != 3 { | |
| 		return nil, fmt.Errorf("invalid JWT format") | |
| 	} | |
| 
 | |
| 	// Decode the payload (second part) | |
| 	payload := parts[1] | |
| 	// Add padding if needed | |
| 	for len(payload)%4 != 0 { | |
| 		payload += "=" | |
| 	} | |
| 
 | |
| 	decoded, err := base64.URLEncoding.DecodeString(payload) | |
| 	if err != nil { | |
| 		return nil, fmt.Errorf("failed to decode JWT payload: %w", err) | |
| 	} | |
| 
 | |
| 	var claims map[string]interface{} | |
| 	if err := json.Unmarshal(decoded, &claims); err != nil { | |
| 		return nil, fmt.Errorf("failed to unmarshal JWT claims: %w", err) | |
| 	} | |
| 
 | |
| 	return claims, nil | |
| } | |
| 
 | |
| // evaluateTrustPolicy evaluates a trust policy against the evaluation context | |
| func (m *IAMManager) evaluateTrustPolicy(trustPolicy *policy.PolicyDocument, evalCtx *policy.EvaluationContext) bool { | |
| 	if trustPolicy == nil { | |
| 		return false | |
| 	} | |
| 
 | |
| 	// Trust policies work differently from regular policies: | |
| 	// - They check the Principal field to see who can assume the role | |
| 	// - They check Action to see what actions are allowed | |
| 	// - They may have Conditions that must be satisfied | |
|  | |
| 	for _, statement := range trustPolicy.Statement { | |
| 		if statement.Effect == "Allow" { | |
| 			// Check if the action matches | |
| 			actionMatches := false | |
| 			for _, action := range statement.Action { | |
| 				if action == evalCtx.Action || action == "*" { | |
| 					actionMatches = true | |
| 					break | |
| 				} | |
| 			} | |
| 			if !actionMatches { | |
| 				continue | |
| 			} | |
| 
 | |
| 			// Check if the principal matches | |
| 			principalMatches := false | |
| 			if principal, ok := statement.Principal.(map[string]interface{}); ok { | |
| 				// Check for Federated principal (OIDC/SAML) | |
| 				if federated, ok := principal["Federated"].(string); ok { | |
| 					// For web identity, check if the token issuer matches the federated provider | |
| 					if tokenIssuer, exists := evalCtx.RequestContext["seaweed:FederatedProvider"]; exists { | |
| 						if issuerStr, ok := tokenIssuer.(string); ok && issuerStr == federated { | |
| 							principalMatches = true | |
| 						} | |
| 					} | |
| 				} | |
| 				// Could add other principal types here (AWS, Service, etc.) | |
| 			} else if principalStr, ok := statement.Principal.(string); ok { | |
| 				// Handle string principal | |
| 				if principalStr == "*" { | |
| 					principalMatches = true | |
| 				} | |
| 			} | |
| 
 | |
| 			if !principalMatches { | |
| 				continue | |
| 			} | |
| 
 | |
| 			// Check conditions if present | |
| 			if len(statement.Condition) > 0 { | |
| 				conditionsMatch := m.evaluateTrustPolicyConditions(statement.Condition, evalCtx) | |
| 				if !conditionsMatch { | |
| 					continue | |
| 				} | |
| 			} | |
| 
 | |
| 			// All checks passed for this Allow statement | |
| 			return true | |
| 		} | |
| 	} | |
| 
 | |
| 	return false | |
| } | |
| 
 | |
| // evaluateTrustPolicyConditions evaluates conditions in a trust policy statement | |
| func (m *IAMManager) evaluateTrustPolicyConditions(conditions map[string]map[string]interface{}, evalCtx *policy.EvaluationContext) bool { | |
| 	for conditionType, conditionBlock := range conditions { | |
| 		switch conditionType { | |
| 		case "StringEquals": | |
| 			if !m.evaluateStringConditionForTrust(conditionBlock, evalCtx, true, false) { | |
| 				return false | |
| 			} | |
| 		case "StringNotEquals": | |
| 			if !m.evaluateStringConditionForTrust(conditionBlock, evalCtx, false, false) { | |
| 				return false | |
| 			} | |
| 		case "StringLike": | |
| 			if !m.evaluateStringConditionForTrust(conditionBlock, evalCtx, true, true) { | |
| 				return false | |
| 			} | |
| 		// Add other condition types as needed | |
| 		default: | |
| 			// Unknown condition type - fail safe | |
| 			return false | |
| 		} | |
| 	} | |
| 	return true | |
| } | |
| 
 | |
| // evaluateStringConditionForTrust evaluates string conditions for trust policies | |
| func (m *IAMManager) evaluateStringConditionForTrust(block map[string]interface{}, evalCtx *policy.EvaluationContext, shouldMatch bool, useWildcard bool) bool { | |
| 	for conditionKey, conditionValue := range block { | |
| 		contextValues, exists := evalCtx.RequestContext[conditionKey] | |
| 		if !exists { | |
| 			if shouldMatch { | |
| 				return false | |
| 			} | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Convert context value to string slice | |
| 		var contextStrings []string | |
| 		switch v := contextValues.(type) { | |
| 		case string: | |
| 			contextStrings = []string{v} | |
| 		case []string: | |
| 			contextStrings = v | |
| 		default: | |
| 			contextStrings = []string{fmt.Sprintf("%v", v)} | |
| 		} | |
| 
 | |
| 		// Convert condition value to string slice | |
| 		var expectedStrings []string | |
| 		switch v := conditionValue.(type) { | |
| 		case string: | |
| 			expectedStrings = []string{v} | |
| 		case []string: | |
| 			expectedStrings = v | |
| 		default: | |
| 			expectedStrings = []string{fmt.Sprintf("%v", v)} | |
| 		} | |
| 
 | |
| 		// Evaluate the condition | |
| 		conditionMet := false | |
| 		for _, expected := range expectedStrings { | |
| 			for _, contextValue := range contextStrings { | |
| 				if useWildcard { | |
| 					matched, err := filepath.Match(expected, contextValue) | |
| 					if err == nil && matched { | |
| 						conditionMet = true | |
| 						break | |
| 					} | |
| 				} else { | |
| 					if expected == contextValue { | |
| 						conditionMet = true | |
| 						break | |
| 					} | |
| 				} | |
| 			} | |
| 			if conditionMet { | |
| 				break | |
| 			} | |
| 		} | |
| 
 | |
| 		if shouldMatch && !conditionMet { | |
| 			return false | |
| 		} | |
| 		if !shouldMatch && conditionMet { | |
| 			return false | |
| 		} | |
| 	} | |
| 
 | |
| 	return true | |
| } | |
| 
 | |
| // isOIDCToken checks if a token is an OIDC JWT token (vs STS session token) | |
| func isOIDCToken(token string) bool { | |
| 	// JWT tokens have three parts separated by dots and start with base64-encoded JSON | |
| 	parts := strings.Split(token, ".") | |
| 	if len(parts) != 3 { | |
| 		return false | |
| 	} | |
| 
 | |
| 	// JWT tokens typically start with "eyJ" (base64 encoded JSON starting with "{") | |
| 	return strings.HasPrefix(token, "eyJ") | |
| }
 |