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.
		
		
		
		
		
			
		
			
				
					
					
						
							512 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							512 lines
						
					
					
						
							15 KiB
						
					
					
				
								package policy_engine
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"fmt"
							 | 
						|
									"net/http"
							 | 
						|
									"strings"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// Action represents an S3 action - this should match the type in auth_credentials.go
							 | 
						|
								type Action string
							 | 
						|
								
							 | 
						|
								// Identity represents a user identity - this should match the type in auth_credentials.go
							 | 
						|
								type Identity interface {
							 | 
						|
									canDo(action Action, bucket string, objectKey string) bool
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// PolicyBackedIAM provides policy-based access control with fallback to legacy IAM
							 | 
						|
								type PolicyBackedIAM struct {
							 | 
						|
									policyEngine *PolicyEngine
							 | 
						|
									legacyIAM    LegacyIAM // Interface to delegate to existing IAM system
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// LegacyIAM interface for delegating to existing IAM implementation
							 | 
						|
								type LegacyIAM interface {
							 | 
						|
									authRequest(r *http.Request, action Action) (Identity, s3err.ErrorCode)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewPolicyBackedIAM creates a new policy-backed IAM system
							 | 
						|
								func NewPolicyBackedIAM() *PolicyBackedIAM {
							 | 
						|
									return &PolicyBackedIAM{
							 | 
						|
										policyEngine: NewPolicyEngine(),
							 | 
						|
										legacyIAM:    nil, // Will be set when integrated with existing IAM
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewPolicyBackedIAMWithLegacy creates a new policy-backed IAM system with legacy IAM set
							 | 
						|
								func NewPolicyBackedIAMWithLegacy(legacyIAM LegacyIAM) *PolicyBackedIAM {
							 | 
						|
									return &PolicyBackedIAM{
							 | 
						|
										policyEngine: NewPolicyEngine(),
							 | 
						|
										legacyIAM:    legacyIAM,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// SetLegacyIAM sets the legacy IAM system for fallback
							 | 
						|
								func (p *PolicyBackedIAM) SetLegacyIAM(legacyIAM LegacyIAM) {
							 | 
						|
									p.legacyIAM = legacyIAM
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// SetBucketPolicy sets the policy for a bucket
							 | 
						|
								func (p *PolicyBackedIAM) SetBucketPolicy(bucketName string, policyJSON string) error {
							 | 
						|
									return p.policyEngine.SetBucketPolicy(bucketName, policyJSON)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetBucketPolicy gets the policy for a bucket
							 | 
						|
								func (p *PolicyBackedIAM) GetBucketPolicy(bucketName string) (*PolicyDocument, error) {
							 | 
						|
									return p.policyEngine.GetBucketPolicy(bucketName)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// DeleteBucketPolicy deletes the policy for a bucket
							 | 
						|
								func (p *PolicyBackedIAM) DeleteBucketPolicy(bucketName string) error {
							 | 
						|
									return p.policyEngine.DeleteBucketPolicy(bucketName)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CanDo checks if a principal can perform an action on a resource
							 | 
						|
								func (p *PolicyBackedIAM) CanDo(action, bucketName, objectName, principal string, r *http.Request) bool {
							 | 
						|
									// If there's a bucket policy, evaluate it
							 | 
						|
									if p.policyEngine.HasPolicyForBucket(bucketName) {
							 | 
						|
										result := p.policyEngine.EvaluatePolicyForRequest(bucketName, objectName, action, principal, r)
							 | 
						|
										switch result {
							 | 
						|
										case PolicyResultAllow:
							 | 
						|
											return true
							 | 
						|
										case PolicyResultDeny:
							 | 
						|
											return false
							 | 
						|
										case PolicyResultIndeterminate:
							 | 
						|
											// Fall through to legacy system
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// No bucket policy or indeterminate result, use legacy conversion
							 | 
						|
									return p.evaluateLegacyAction(action, bucketName, objectName, principal)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateLegacyAction evaluates actions using legacy identity-based rules
							 | 
						|
								func (p *PolicyBackedIAM) evaluateLegacyAction(action, bucketName, objectName, principal string) bool {
							 | 
						|
									// If we have a legacy IAM system to delegate to, use it
							 | 
						|
									if p.legacyIAM != nil {
							 | 
						|
										// Create a dummy request for legacy evaluation
							 | 
						|
										// In real implementation, this would use the actual request
							 | 
						|
										r := &http.Request{
							 | 
						|
											Header: make(http.Header),
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Convert the action string to Action type
							 | 
						|
										legacyAction := Action(action)
							 | 
						|
								
							 | 
						|
										// Use legacy IAM to check permission
							 | 
						|
										identity, errCode := p.legacyIAM.authRequest(r, legacyAction)
							 | 
						|
										if errCode != s3err.ErrNone {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// If we have an identity, check if it can perform the action
							 | 
						|
										if identity != nil {
							 | 
						|
											return identity.canDo(legacyAction, bucketName, objectName)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// No legacy IAM available, convert to policy and evaluate
							 | 
						|
									return p.evaluateUsingPolicyConversion(action, bucketName, objectName, principal)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evaluateUsingPolicyConversion converts legacy action to policy and evaluates
							 | 
						|
								func (p *PolicyBackedIAM) evaluateUsingPolicyConversion(action, bucketName, objectName, principal string) bool {
							 | 
						|
									// For now, use a conservative approach for legacy actions
							 | 
						|
									// In a real implementation, this would integrate with the existing identity system
							 | 
						|
									glog.V(2).Infof("Legacy action evaluation for %s on %s/%s by %s", action, bucketName, objectName, principal)
							 | 
						|
								
							 | 
						|
									// Return false to maintain security until proper legacy integration is implemented
							 | 
						|
									// This ensures no unintended access is granted
							 | 
						|
									return false
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ConvertIdentityToPolicy converts a legacy identity action to an AWS policy
							 | 
						|
								func ConvertIdentityToPolicy(identityActions []string, bucketName string) (*PolicyDocument, error) {
							 | 
						|
									statements := make([]PolicyStatement, 0)
							 | 
						|
								
							 | 
						|
									for _, action := range identityActions {
							 | 
						|
										stmt, err := convertSingleAction(action, bucketName)
							 | 
						|
										if err != nil {
							 | 
						|
											glog.Warningf("Failed to convert action %s: %v", action, err)
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
										if stmt != nil {
							 | 
						|
											statements = append(statements, *stmt)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(statements) == 0 {
							 | 
						|
										return nil, fmt.Errorf("no valid statements generated")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &PolicyDocument{
							 | 
						|
										Version:   PolicyVersion2012_10_17,
							 | 
						|
										Statement: statements,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// convertSingleAction converts a single legacy action to a policy statement
							 | 
						|
								func convertSingleAction(action, bucketName string) (*PolicyStatement, error) {
							 | 
						|
									parts := strings.Split(action, ":")
							 | 
						|
									if len(parts) != 2 {
							 | 
						|
										return nil, fmt.Errorf("invalid action format: %s", action)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									actionType := parts[0]
							 | 
						|
									resourcePattern := parts[1]
							 | 
						|
								
							 | 
						|
									var s3Actions []string
							 | 
						|
									var resources []string
							 | 
						|
								
							 | 
						|
									switch actionType {
							 | 
						|
									case "Read":
							 | 
						|
										s3Actions = []string{"s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											// Object-level read access
							 | 
						|
											bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											resources = []string{
							 | 
						|
												fmt.Sprintf("arn:aws:s3:::%s", bucket),
							 | 
						|
												fmt.Sprintf("arn:aws:s3:::%s/*", bucket),
							 | 
						|
											}
							 | 
						|
										} else {
							 | 
						|
											// Bucket-level read access
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "Write":
							 | 
						|
										s3Actions = []string{"s3:PutObject", "s3:DeleteObject", "s3:PutObjectAcl"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											// Object-level write access
							 | 
						|
											bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
							 | 
						|
										} else {
							 | 
						|
											// Bucket-level write access
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "Admin":
							 | 
						|
										s3Actions = []string{"s3:*"}
							 | 
						|
										resources = []string{
							 | 
						|
											fmt.Sprintf("arn:aws:s3:::%s", resourcePattern),
							 | 
						|
											fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern),
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "List":
							 | 
						|
										s3Actions = []string{"s3:ListBucket", "s3:ListBucketVersions"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											// Object-level list access - extract bucket from "bucket/prefix/*" pattern
							 | 
						|
											patternWithoutWildcard := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											parts := strings.SplitN(patternWithoutWildcard, "/", 2)
							 | 
						|
											bucket := parts[0]
							 | 
						|
											resources = []string{
							 | 
						|
												fmt.Sprintf("arn:aws:s3:::%s", bucket),
							 | 
						|
												fmt.Sprintf("arn:aws:s3:::%s/*", bucket),
							 | 
						|
											}
							 | 
						|
										} else {
							 | 
						|
											// Bucket-level list access
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "Tagging":
							 | 
						|
										s3Actions = []string{"s3:GetObjectTagging", "s3:PutObjectTagging", "s3:DeleteObjectTagging"}
							 | 
						|
										resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
							 | 
						|
								
							 | 
						|
									case "BypassGovernanceRetention":
							 | 
						|
										s3Actions = []string{"s3:BypassGovernanceRetention"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											// Object-level bypass governance access
							 | 
						|
											bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
							 | 
						|
										} else {
							 | 
						|
											// Bucket-level bypass governance access
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "GetObjectRetention":
							 | 
						|
										s3Actions = []string{"s3:GetObjectRetention"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
							 | 
						|
										} else {
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "PutObjectRetention":
							 | 
						|
										s3Actions = []string{"s3:PutObjectRetention"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
							 | 
						|
										} else {
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "GetObjectLegalHold":
							 | 
						|
										s3Actions = []string{"s3:GetObjectLegalHold"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
							 | 
						|
										} else {
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "PutObjectLegalHold":
							 | 
						|
										s3Actions = []string{"s3:PutObjectLegalHold"}
							 | 
						|
										if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
											bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
							 | 
						|
										} else {
							 | 
						|
											resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									case "GetBucketObjectLockConfiguration":
							 | 
						|
										s3Actions = []string{"s3:GetBucketObjectLockConfiguration"}
							 | 
						|
										resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
							 | 
						|
								
							 | 
						|
									case "PutBucketObjectLockConfiguration":
							 | 
						|
										s3Actions = []string{"s3:PutBucketObjectLockConfiguration"}
							 | 
						|
										resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
							 | 
						|
								
							 | 
						|
									default:
							 | 
						|
										return nil, fmt.Errorf("unknown action type: %s", actionType)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &PolicyStatement{
							 | 
						|
										Effect:   PolicyEffectAllow,
							 | 
						|
										Action:   NewStringOrStringSlice(s3Actions...),
							 | 
						|
										Resource: NewStringOrStringSlice(resources...),
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetActionMappings returns the mapping of legacy actions to S3 actions
							 | 
						|
								func GetActionMappings() map[string][]string {
							 | 
						|
									return map[string][]string{
							 | 
						|
										"Read": {
							 | 
						|
											"s3:GetObject",
							 | 
						|
											"s3:GetObjectVersion",
							 | 
						|
											"s3:GetObjectAcl",
							 | 
						|
											"s3:GetObjectVersionAcl",
							 | 
						|
											"s3:GetObjectTagging",
							 | 
						|
											"s3:GetObjectVersionTagging",
							 | 
						|
											"s3:ListBucket",
							 | 
						|
											"s3:ListBucketVersions",
							 | 
						|
											"s3:GetBucketLocation",
							 | 
						|
											"s3:GetBucketVersioning",
							 | 
						|
											"s3:GetBucketAcl",
							 | 
						|
											"s3:GetBucketCors",
							 | 
						|
											"s3:GetBucketTagging",
							 | 
						|
											"s3:GetBucketNotification",
							 | 
						|
										},
							 | 
						|
										"Write": {
							 | 
						|
											"s3:PutObject",
							 | 
						|
											"s3:PutObjectAcl",
							 | 
						|
											"s3:PutObjectTagging",
							 | 
						|
											"s3:DeleteObject",
							 | 
						|
											"s3:DeleteObjectVersion",
							 | 
						|
											"s3:DeleteObjectTagging",
							 | 
						|
											"s3:AbortMultipartUpload",
							 | 
						|
											"s3:ListMultipartUploads",
							 | 
						|
											"s3:ListParts",
							 | 
						|
											"s3:PutBucketAcl",
							 | 
						|
											"s3:PutBucketCors",
							 | 
						|
											"s3:PutBucketTagging",
							 | 
						|
											"s3:PutBucketNotification",
							 | 
						|
											"s3:PutBucketVersioning",
							 | 
						|
											"s3:DeleteBucketTagging",
							 | 
						|
											"s3:DeleteBucketCors",
							 | 
						|
										},
							 | 
						|
										"Admin": {
							 | 
						|
											"s3:*",
							 | 
						|
										},
							 | 
						|
										"List": {
							 | 
						|
											"s3:ListBucket",
							 | 
						|
											"s3:ListBucketVersions",
							 | 
						|
											"s3:ListAllMyBuckets",
							 | 
						|
										},
							 | 
						|
										"Tagging": {
							 | 
						|
											"s3:GetObjectTagging",
							 | 
						|
											"s3:PutObjectTagging",
							 | 
						|
											"s3:DeleteObjectTagging",
							 | 
						|
											"s3:GetBucketTagging",
							 | 
						|
											"s3:PutBucketTagging",
							 | 
						|
											"s3:DeleteBucketTagging",
							 | 
						|
										},
							 | 
						|
										"BypassGovernanceRetention": {
							 | 
						|
											"s3:BypassGovernanceRetention",
							 | 
						|
										},
							 | 
						|
										"GetObjectRetention": {
							 | 
						|
											"s3:GetObjectRetention",
							 | 
						|
										},
							 | 
						|
										"PutObjectRetention": {
							 | 
						|
											"s3:PutObjectRetention",
							 | 
						|
										},
							 | 
						|
										"GetObjectLegalHold": {
							 | 
						|
											"s3:GetObjectLegalHold",
							 | 
						|
										},
							 | 
						|
										"PutObjectLegalHold": {
							 | 
						|
											"s3:PutObjectLegalHold",
							 | 
						|
										},
							 | 
						|
										"GetBucketObjectLockConfiguration": {
							 | 
						|
											"s3:GetBucketObjectLockConfiguration",
							 | 
						|
										},
							 | 
						|
										"PutBucketObjectLockConfiguration": {
							 | 
						|
											"s3:PutBucketObjectLockConfiguration",
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidateActionMapping validates that a legacy action can be mapped to S3 actions
							 | 
						|
								func ValidateActionMapping(action string) error {
							 | 
						|
									mappings := GetActionMappings()
							 | 
						|
								
							 | 
						|
									parts := strings.Split(action, ":")
							 | 
						|
									if len(parts) != 2 {
							 | 
						|
										return fmt.Errorf("invalid action format: %s, expected format: 'ActionType:Resource'", action)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									actionType := parts[0]
							 | 
						|
									resource := parts[1]
							 | 
						|
								
							 | 
						|
									if _, exists := mappings[actionType]; !exists {
							 | 
						|
										return fmt.Errorf("unknown action type: %s", actionType)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if resource == "" {
							 | 
						|
										return fmt.Errorf("resource cannot be empty")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ConvertLegacyActions converts an array of legacy actions to S3 actions
							 | 
						|
								func ConvertLegacyActions(legacyActions []string) ([]string, error) {
							 | 
						|
									mappings := GetActionMappings()
							 | 
						|
									s3Actions := make([]string, 0)
							 | 
						|
								
							 | 
						|
									for _, legacyAction := range legacyActions {
							 | 
						|
										if err := ValidateActionMapping(legacyAction); err != nil {
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										parts := strings.Split(legacyAction, ":")
							 | 
						|
										actionType := parts[0]
							 | 
						|
								
							 | 
						|
										if actionType == "Admin" {
							 | 
						|
											// Admin gives all permissions, so we can just return s3:*
							 | 
						|
											return []string{"s3:*"}, nil
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if mapped, exists := mappings[actionType]; exists {
							 | 
						|
											s3Actions = append(s3Actions, mapped...)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Remove duplicates
							 | 
						|
									uniqueActions := make([]string, 0)
							 | 
						|
									seen := make(map[string]bool)
							 | 
						|
									for _, action := range s3Actions {
							 | 
						|
										if !seen[action] {
							 | 
						|
											uniqueActions = append(uniqueActions, action)
							 | 
						|
											seen[action] = true
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return uniqueActions, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetResourcesFromLegacyAction extracts resources from a legacy action
							 | 
						|
								func GetResourcesFromLegacyAction(legacyAction string) ([]string, error) {
							 | 
						|
									parts := strings.Split(legacyAction, ":")
							 | 
						|
									if len(parts) != 2 {
							 | 
						|
										return nil, fmt.Errorf("invalid action format: %s", legacyAction)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									resourcePattern := parts[1]
							 | 
						|
									resources := make([]string, 0)
							 | 
						|
								
							 | 
						|
									if strings.HasSuffix(resourcePattern, "/*") {
							 | 
						|
										// Object-level access
							 | 
						|
										bucket := strings.TrimSuffix(resourcePattern, "/*")
							 | 
						|
										resources = append(resources, fmt.Sprintf("arn:aws:s3:::%s", bucket))
							 | 
						|
										resources = append(resources, fmt.Sprintf("arn:aws:s3:::%s/*", bucket))
							 | 
						|
									} else {
							 | 
						|
										// Bucket-level access
							 | 
						|
										resources = append(resources, fmt.Sprintf("arn:aws:s3:::%s", resourcePattern))
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return resources, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CreatePolicyFromLegacyIdentity creates a policy document from legacy identity actions
							 | 
						|
								func CreatePolicyFromLegacyIdentity(identityName string, actions []string) (*PolicyDocument, error) {
							 | 
						|
									statements := make([]PolicyStatement, 0)
							 | 
						|
								
							 | 
						|
									// Group actions by resource pattern
							 | 
						|
									resourceActions := make(map[string][]string)
							 | 
						|
								
							 | 
						|
									for _, action := range actions {
							 | 
						|
										parts := strings.Split(action, ":")
							 | 
						|
										if len(parts) != 2 {
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										resourcePattern := parts[1]
							 | 
						|
										actionType := parts[0]
							 | 
						|
								
							 | 
						|
										if _, exists := resourceActions[resourcePattern]; !exists {
							 | 
						|
											resourceActions[resourcePattern] = make([]string, 0)
							 | 
						|
										}
							 | 
						|
										resourceActions[resourcePattern] = append(resourceActions[resourcePattern], actionType)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create statements for each resource pattern
							 | 
						|
									for resourcePattern, actionTypes := range resourceActions {
							 | 
						|
										s3Actions := make([]string, 0)
							 | 
						|
								
							 | 
						|
										for _, actionType := range actionTypes {
							 | 
						|
											if actionType == "Admin" {
							 | 
						|
												s3Actions = []string{"s3:*"}
							 | 
						|
												break
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if mapped, exists := GetActionMappings()[actionType]; exists {
							 | 
						|
												s3Actions = append(s3Actions, mapped...)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										resources, err := GetResourcesFromLegacyAction(fmt.Sprintf("dummy:%s", resourcePattern))
							 | 
						|
										if err != nil {
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										statement := PolicyStatement{
							 | 
						|
											Sid:      fmt.Sprintf("%s-%s", identityName, strings.ReplaceAll(resourcePattern, "/", "-")),
							 | 
						|
											Effect:   PolicyEffectAllow,
							 | 
						|
											Action:   NewStringOrStringSlice(s3Actions...),
							 | 
						|
											Resource: NewStringOrStringSlice(resources...),
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										statements = append(statements, statement)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(statements) == 0 {
							 | 
						|
										return nil, fmt.Errorf("no valid statements generated for identity %s", identityName)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &PolicyDocument{
							 | 
						|
										Version:   PolicyVersion2012_10_17,
							 | 
						|
										Statement: statements,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// HasPolicyForBucket checks if a bucket has a policy
							 | 
						|
								func (p *PolicyBackedIAM) HasPolicyForBucket(bucketName string) bool {
							 | 
						|
									return p.policyEngine.HasPolicyForBucket(bucketName)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetPolicyEngine returns the underlying policy engine
							 | 
						|
								func (p *PolicyBackedIAM) GetPolicyEngine() *PolicyEngine {
							 | 
						|
									return p.policyEngine
							 | 
						|
								}
							 |