diff options
Diffstat (limited to 'weed/s3api/policy_conversion.go')
| -rw-r--r-- | weed/s3api/policy_conversion.go | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/weed/s3api/policy_conversion.go b/weed/s3api/policy_conversion.go new file mode 100644 index 000000000..27a8d7560 --- /dev/null +++ b/weed/s3api/policy_conversion.go @@ -0,0 +1,239 @@ +package s3api + +import ( + "fmt" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "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 type-safe conversion with explicit field mapping and error handling. +// It handles the differences between the two types: +// - Converts []string fields to StringOrStringSlice +// - Maps Condition types with type validation +// - Converts Principal fields with support for AWS principals only +// - Handles optional fields (Id, NotPrincipal, NotAction, NotResource are ignored in policy_engine) +// +// Returns an error if the policy contains unsupported types or malformed data. +func ConvertPolicyDocumentToPolicyEngine(src *policy.PolicyDocument) (*policy_engine.PolicyDocument, error) { + if src == nil { + return nil, nil + } + + // Warn if the policy document Id is being dropped + if src.Id != "" { + glog.Warningf("policy document Id %q is not supported and will be ignored", src.Id) + } + + dest := &policy_engine.PolicyDocument{ + Version: src.Version, + Statement: make([]policy_engine.PolicyStatement, len(src.Statement)), + } + + for i := range src.Statement { + stmt, err := convertStatement(&src.Statement[i]) + if err != nil { + return nil, fmt.Errorf("failed to convert statement %d: %w", i, err) + } + dest.Statement[i] = stmt + } + + return dest, nil +} + +// convertStatement converts a policy.Statement to policy_engine.PolicyStatement +func convertStatement(src *policy.Statement) (policy_engine.PolicyStatement, error) { + // Check for unsupported fields that would fundamentally change policy semantics + // These fields invert the logic and ignoring them could create security holes + if len(src.NotAction) > 0 { + return policy_engine.PolicyStatement{}, fmt.Errorf("statement %q: NotAction is not supported (would invert action logic, creating potential security risk)", src.Sid) + } + if len(src.NotResource) > 0 { + return policy_engine.PolicyStatement{}, fmt.Errorf("statement %q: NotResource is not supported (would invert resource logic, creating potential security risk)", src.Sid) + } + if src.NotPrincipal != nil { + return policy_engine.PolicyStatement{}, fmt.Errorf("statement %q: NotPrincipal is not supported (would invert principal logic, creating potential security risk)", src.Sid) + } + + 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 { + principal, err := convertPrincipal(src.Principal) + if err != nil { + return policy_engine.PolicyStatement{}, fmt.Errorf("statement %q: failed to convert principal: %w", src.Sid, err) + } + stmt.Principal = principal + } + + // Convert Condition (map[string]map[string]interface{} to PolicyConditions) + if len(src.Condition) > 0 { + condition, err := convertCondition(src.Condition) + if err != nil { + return policy_engine.PolicyStatement{}, fmt.Errorf("statement %q: failed to convert condition: %w", src.Sid, err) + } + stmt.Condition = condition + } + + return stmt, nil +} + +// convertPrincipal converts a Principal field to *StringOrStringSlice +func convertPrincipal(principal interface{}) (*policy_engine.StringOrStringSlice, error) { + if principal == nil { + return nil, nil + } + + switch p := principal.(type) { + case string: + if p == "" { + return nil, fmt.Errorf("principal string cannot be empty") + } + result := policy_engine.NewStringOrStringSlice(p) + return &result, nil + case []string: + if len(p) == 0 { + return nil, nil + } + for _, s := range p { + if s == "" { + return nil, fmt.Errorf("principal string in slice cannot be empty") + } + } + result := policy_engine.NewStringOrStringSlice(p...) + return &result, nil + case []interface{}: + strs := make([]string, 0, len(p)) + for _, v := range p { + if v != nil { + str, err := convertToString(v) + if err != nil { + return nil, fmt.Errorf("failed to convert principal array item: %w", err) + } + if str == "" { + return nil, fmt.Errorf("principal string in slice cannot be empty") + } + strs = append(strs, str) + } + } + if len(strs) == 0 { + return nil, nil + } + result := policy_engine.NewStringOrStringSlice(strs...) + return &result, nil + case map[string]interface{}: + // Handle AWS-style principal with service/user keys + // Example: {"AWS": "arn:aws:iam::123456789012:user/Alice"} + // Only AWS principals are supported for now. Other types like Service or Federated need special handling. + + awsPrincipals, ok := p["AWS"] + if !ok || len(p) != 1 { + glog.Warningf("unsupported principal map, only a single 'AWS' key is supported: %v", p) + return nil, fmt.Errorf("unsupported principal map, only a single 'AWS' key is supported, got keys: %v", getMapKeys(p)) + } + + // Recursively convert the AWS principal value + res, err := convertPrincipal(awsPrincipals) + if err != nil { + return nil, fmt.Errorf("invalid 'AWS' principal value: %w", err) + } + return res, nil + default: + return nil, fmt.Errorf("unsupported principal type: %T", p) + } +} + +// convertCondition converts policy conditions to PolicyConditions +func convertCondition(src map[string]map[string]interface{}) (policy_engine.PolicyConditions, error) { + if len(src) == 0 { + return nil, nil + } + + dest := make(policy_engine.PolicyConditions) + for condType, condBlock := range src { + destBlock := make(map[string]policy_engine.StringOrStringSlice) + for key, value := range condBlock { + condValue, err := convertConditionValue(value) + if err != nil { + return nil, fmt.Errorf("failed to convert condition %s[%s]: %w", condType, key, err) + } + destBlock[key] = condValue + } + dest[condType] = destBlock + } + + return dest, nil +} + +// convertConditionValue converts a condition value to StringOrStringSlice +func convertConditionValue(value interface{}) (policy_engine.StringOrStringSlice, error) { + switch v := value.(type) { + case string: + return policy_engine.NewStringOrStringSlice(v), nil + case []string: + return policy_engine.NewStringOrStringSlice(v...), nil + case []interface{}: + strs := make([]string, 0, len(v)) + for _, item := range v { + if item != nil { + str, err := convertToString(item) + if err != nil { + return policy_engine.StringOrStringSlice{}, fmt.Errorf("failed to convert condition array item: %w", err) + } + strs = append(strs, str) + } + } + return policy_engine.NewStringOrStringSlice(strs...), nil + default: + // For non-string types, convert to string + // This handles numbers, booleans, etc. + str, err := convertToString(v) + if err != nil { + return policy_engine.StringOrStringSlice{}, err + } + return policy_engine.NewStringOrStringSlice(str), nil + } +} + +// convertToString converts any value to string representation +// Returns an error for unsupported types to prevent silent data corruption +func convertToString(value interface{}) (string, error) { + switch v := value.(type) { + case string: + return v, nil + case bool, + int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + float32, float64: + // Use fmt.Sprint for supported primitive types + return fmt.Sprint(v), nil + default: + glog.Warningf("unsupported type in policy conversion: %T", v) + return "", fmt.Errorf("unsupported type in policy conversion: %T", v) + } +} + +// getMapKeys returns the keys of a map as a slice (helper for error messages) +func getMapKeys(m map[string]interface{}) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + |
