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.
239 lines
7.7 KiB
239 lines
7.7 KiB
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
|
|
}
|
|
|