package iam import ( "fmt" "strconv" "strings" "time" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" ) const ( RequestOwnerProperty = "Owner" ) const ( // String condition operators. CondStringEquals string = "StringEquals" CondStringNotEquals string = "StringNotEquals" CondStringEqualsIgnoreCase string = "StringEqualsIgnoreCase" CondStringNotEqualsIgnoreCase string = "StringNotEqualsIgnoreCase" CondStringLike string = "StringLike" CondStringNotLike string = "StringNotLike" // Numeric condition operators. CondNumericEquals string = "NumericEquals" CondNumericNotEquals string = "NumericNotEquals" CondNumericLessThan string = "NumericLessThan" CondNumericLessThanEquals string = "NumericLessThanEquals" CondNumericGreaterThan string = "NumericGreaterThan" CondNumericGreaterThanEquals string = "NumericGreaterThanEquals" // Date condition operators. CondDateEquals string = "DateEquals" CondDateNotEquals string = "DateNotEquals" CondDateLessThan string = "DateLessThan" CondDateLessThanEquals string = "DateLessThanEquals" CondDateGreaterThan string = "DateGreaterThan" CondDateGreaterThanEquals string = "DateGreaterThanEquals" // Bolean condition operators. CondBool string = "Bool" // IP address condition operators. CondIPAddress string = "IpAddress" CondNotIPAddress string = "NotIpAddress" // ARN condition operators. CondArnEquals string = "ArnEquals" CondArnLike string = "ArnLike" CondArnNotEquals string = "ArnNotEquals" CondArnNotLike string = "ArnNotLike" ) func (p Policy) ToChain() (*chain.Chain, error) { if err := p.Validate(GeneralPolicyType); err != nil { return nil, err } var ch chain.Chain for _, statement := range p.Statement { status := chain.AccessDenied if statement.Effect == AllowEffect { status = chain.Allow } var principals []string var op chain.ConditionType statementPrincipal, inverted := statement.principal() if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false principals = []string{Wildcard} op = chain.CondStringLike } else { for _, principal := range statementPrincipal { principals = append(principals, principal...) } op = chain.CondStringEquals if inverted { op = chain.CondStringNotEquals } } var conditions []chain.Condition for _, principal := range principals { conditions = append(conditions, chain.Condition{ Op: op, Object: chain.ObjectRequest, Key: RequestOwnerProperty, Value: principal, }) } conds, err := statement.Conditions.ToChainCondition() if err != nil { return nil, err } conditions = append(conditions, conds...) action, actionInverted := statement.action() ruleAction := chain.Actions{Inverted: actionInverted, Names: action} resource, resourceInverted := statement.resource() ruleResource := chain.Resources{Inverted: resourceInverted, Names: resource} r := chain.Rule{ Status: status, Actions: ruleAction, Resources: ruleResource, Any: true, Condition: conditions, } ch.Rules = append(ch.Rules, r) } return &ch, nil } //nolint:funlen func (c Conditions) ToChainCondition() ([]chain.Condition, error) { var conditions []chain.Condition var convertValue convertFunction for op, KVs := range c { var condType chain.ConditionType switch { case strings.HasPrefix(op, "String"): convertValue = noConvertFunction switch op { case CondStringEquals: condType = chain.CondStringEquals case CondStringNotEquals: condType = chain.CondStringNotEquals case CondStringEqualsIgnoreCase: condType = chain.CondStringEqualsIgnoreCase case CondStringNotEqualsIgnoreCase: condType = chain.CondStringNotEqualsIgnoreCase case CondStringLike: condType = chain.CondStringLike case CondStringNotLike: condType = chain.CondStringNotLike default: return nil, fmt.Errorf("unsupported condition operator: '%s'", op) } case strings.HasPrefix(op, "Arn"): convertValue = noConvertFunction switch op { case CondArnEquals: condType = chain.CondStringEquals case CondArnNotEquals: condType = chain.CondStringNotEquals case CondArnLike: condType = chain.CondStringLike case CondArnNotLike: condType = chain.CondStringNotLike default: return nil, fmt.Errorf("unsupported condition operator: '%s'", op) } case strings.HasPrefix(op, "Numeric"): // TODO case strings.HasPrefix(op, "Date"): convertValue = dateConvertFunction switch op { case CondDateEquals: condType = chain.CondStringEquals case CondDateNotEquals: condType = chain.CondStringNotEquals case CondDateLessThan: condType = chain.CondStringLessThan case CondDateLessThanEquals: condType = chain.CondStringLessThanEquals case CondDateGreaterThan: condType = chain.CondStringGreaterThan case CondDateGreaterThanEquals: condType = chain.CondStringGreaterThanEquals default: return nil, fmt.Errorf("unsupported condition operator: '%s'", op) } case op == CondBool: convertValue = noConvertFunction condType = chain.CondStringEqualsIgnoreCase case op == CondIPAddress: // todo consider using converters // "203.0.113.0/24" -> "203.0.113.*", // "2001:DB8:1234:5678::/64" -> "2001:DB8:1234:5678:*" // or having specific condition type for IP convertValue = noConvertFunction condType = chain.CondStringLike case op == CondNotIPAddress: convertValue = noConvertFunction condType = chain.CondStringNotLike default: return nil, fmt.Errorf("unsupported condition operator: '%s'", op) } for key, values := range KVs { for _, val := range values { converted, err := convertValue(val) if err != nil { return nil, err } conditions = append(conditions, chain.Condition{ Op: condType, Object: chain.ObjectRequest, Key: key, Value: converted, }) } } } return conditions, nil } type convertFunction func(string) (string, error) func noConvertFunction(val string) (string, error) { return val, nil } func dateConvertFunction(val string) (string, error) { if _, err := strconv.ParseInt(val, 10, 64); err == nil { return val, nil } parsed, err := time.Parse(time.RFC3339, val) if err != nil { return "", err } return strconv.FormatInt(parsed.UTC().Unix(), 10), nil }