diff --git a/weed/s3api/policy_conversion.go b/weed/s3api/policy_conversion.go new file mode 100644 index 000000000..2f9d5be0a --- /dev/null +++ b/weed/s3api/policy_conversion.go @@ -0,0 +1,172 @@ +package s3api + +import ( + "github.com/seaweedfs/seaweedfs/weed/iam/policy" + "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" +) + +// ConvertPolicyDocumentToPolicyEngine converts a policy.PolicyDocument to policy_engine.PolicyDocument +// This function provides efficient type conversion without JSON marshaling overhead. +// It handles the differences between the two types: +// - Converts []string fields to StringOrStringSlice +// - Maps Condition types +// - Handles optional fields (Id, NotPrincipal, NotAction, NotResource are ignored in policy_engine) +func ConvertPolicyDocumentToPolicyEngine(src *policy.PolicyDocument) *policy_engine.PolicyDocument { + if src == nil { + return nil + } + + dest := &policy_engine.PolicyDocument{ + Version: src.Version, + Statement: make([]policy_engine.PolicyStatement, len(src.Statement)), + } + + for i, srcStmt := range src.Statement { + dest.Statement[i] = convertStatement(&srcStmt) + } + + return dest +} + +// convertStatement converts a policy.Statement to policy_engine.PolicyStatement +func convertStatement(src *policy.Statement) policy_engine.PolicyStatement { + stmt := policy_engine.PolicyStatement{ + Sid: src.Sid, + Effect: policy_engine.PolicyEffect(src.Effect), + } + + // Convert Action ([]string to StringOrStringSlice) + if len(src.Action) > 0 { + stmt.Action = policy_engine.NewStringOrStringSlice(src.Action...) + } + + // Convert Resource ([]string to StringOrStringSlice) + if len(src.Resource) > 0 { + stmt.Resource = policy_engine.NewStringOrStringSlice(src.Resource...) + } + + // Convert Principal (interface{} to *StringOrStringSlice) + if src.Principal != nil { + stmt.Principal = convertPrincipal(src.Principal) + } + + // Convert Condition (map[string]map[string]interface{} to PolicyConditions) + if len(src.Condition) > 0 { + stmt.Condition = convertCondition(src.Condition) + } + + return stmt +} + +// convertPrincipal converts a Principal field to *StringOrStringSlice +func convertPrincipal(principal interface{}) *policy_engine.StringOrStringSlice { + if principal == nil { + return nil + } + + switch p := principal.(type) { + case string: + result := policy_engine.NewStringOrStringSlice(p) + return &result + case []string: + result := policy_engine.NewStringOrStringSlice(p...) + return &result + case []interface{}: + // Convert []interface{} to []string + strs := make([]string, 0, len(p)) + for _, v := range p { + if str, ok := v.(string); ok { + strs = append(strs, str) + } + } + if len(strs) > 0 { + result := policy_engine.NewStringOrStringSlice(strs...) + return &result + } + case map[string]interface{}: + // Handle AWS-style principal with service/user keys + // Example: {"AWS": "arn:aws:iam::123456789012:user/Alice"} + strs := make([]string, 0) + for _, v := range p { + switch val := v.(type) { + case string: + strs = append(strs, val) + case []string: + strs = append(strs, val...) + case []interface{}: + for _, item := range val { + if str, ok := item.(string); ok { + strs = append(strs, str) + } + } + } + } + if len(strs) > 0 { + result := policy_engine.NewStringOrStringSlice(strs...) + return &result + } + } + + return nil +} + +// convertCondition converts policy conditions to PolicyConditions +func convertCondition(src map[string]map[string]interface{}) policy_engine.PolicyConditions { + if len(src) == 0 { + return nil + } + + dest := make(policy_engine.PolicyConditions) + for condType, condBlock := range src { + destBlock := make(map[string]policy_engine.StringOrStringSlice) + for key, value := range condBlock { + destBlock[key] = convertConditionValue(value) + } + dest[condType] = destBlock + } + + return dest +} + +// convertConditionValue converts a condition value to StringOrStringSlice +func convertConditionValue(value interface{}) policy_engine.StringOrStringSlice { + switch v := value.(type) { + case string: + return policy_engine.NewStringOrStringSlice(v) + case []string: + return policy_engine.NewStringOrStringSlice(v...) + case []interface{}: + strs := make([]string, 0, len(v)) + for _, item := range v { + if str, ok := item.(string); ok { + strs = append(strs, str) + } + } + return policy_engine.NewStringOrStringSlice(strs...) + default: + // For non-string types, convert to string + // This handles numbers, booleans, etc. + return policy_engine.NewStringOrStringSlice(convertToString(v)) + } +} + +// convertToString converts any value to string representation +func convertToString(value interface{}) string { + switch v := value.(type) { + case string: + return v + case bool: + if v { + return "true" + } + return "false" + case int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + float32, float64: + // Use fmt.Sprintf for numeric types + return "" + default: + return "" + } +} + diff --git a/weed/s3api/s3api_bucket_policy_engine.go b/weed/s3api/s3api_bucket_policy_engine.go index 9e77f407c..15da52a6b 100644 --- a/weed/s3api/s3api_bucket_policy_engine.go +++ b/weed/s3api/s3api_bucket_policy_engine.go @@ -49,11 +49,8 @@ func (bpe *BucketPolicyEngine) LoadBucketPolicy(bucket string, entry *filer_pb.E // LoadBucketPolicyFromCache loads a bucket policy from a cached BucketConfig // -// NOTE: This function uses JSON marshaling/unmarshaling to convert between -// policy.PolicyDocument and policy_engine.PolicyDocument. This is inefficient -// but necessary because the two types are defined in different packages and -// have subtle differences. A future improvement would be to unify these types -// or create a direct conversion function for better performance and type safety. +// This function uses a direct conversion function to efficiently convert between +// policy.PolicyDocument and policy_engine.PolicyDocument without JSON marshaling overhead. func (bpe *BucketPolicyEngine) LoadBucketPolicyFromCache(bucket string, policyDoc *policy.PolicyDocument) error { if policyDoc == nil { // No policy for this bucket - remove it if it exists @@ -61,10 +58,12 @@ func (bpe *BucketPolicyEngine) LoadBucketPolicyFromCache(bucket string, policyDo return nil } - // Convert policy.PolicyDocument to policy_engine.PolicyDocument - // We use JSON marshaling as an intermediate format since both types - // follow the same AWS S3 policy structure - policyJSON, err := json.Marshal(policyDoc) + // Convert policy.PolicyDocument to policy_engine.PolicyDocument using direct conversion + // This is more efficient than JSON marshaling and provides better type safety + enginePolicyDoc := ConvertPolicyDocumentToPolicyEngine(policyDoc) + + // Marshal the converted policy to JSON for storage in the engine + policyJSON, err := json.Marshal(enginePolicyDoc) if err != nil { glog.Errorf("Failed to marshal bucket policy for %s: %v", bucket, err) return err