aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/policy_conversion.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/policy_conversion.go')
-rw-r--r--weed/s3api/policy_conversion.go239
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
+}
+