package iam import ( "errors" "fmt" "strconv" "strings" "time" policyengine "git.frostfs.info/TrueCloudLab/policy-engine" ) const ( FrostFSPrincipal = "FrostFS" 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() (*policyengine.Chain, error) { var chain policyengine.Chain for _, statement := range p.Statement { status := policyengine.AccessDenied if statement.Effect == AllowEffect { status = policyengine.Allow } if len(statement.Principal) != 1 { return nil, errors.New("currently supported exactly one principal type") } var principals []string var op policyengine.ConditionType if _, ok := statement.Principal[Wildcard]; ok { principals = []string{Wildcard} op = policyengine.CondStringLike } else if frostfsPrincipals, ok := statement.Principal[FrostFSPrincipal]; ok { principals = frostfsPrincipals op = policyengine.CondStringEquals } else { return nil, errors.New("currently supported only FrostFS or all (wildcard) principals") } var conditions []policyengine.Condition for _, principal := range principals { conditions = append(conditions, policyengine.Condition{ Op: op, Object: policyengine.ObjectRequest, Key: RequestOwnerProperty, Value: principal, }) } conds, err := statement.Conditions.ToChainCondition() if err != nil { return nil, err } conditions = append(conditions, conds...) r := policyengine.Rule{ Status: status, Action: statement.Action, Resource: statement.Resource, Any: true, Condition: conditions, } chain.Rules = append(chain.Rules, r) } return &chain, nil } //nolint:funlen func (c Conditions) ToChainCondition() ([]policyengine.Condition, error) { var conditions []policyengine.Condition var convertValue convertFunction for op, KVs := range c { var condType policyengine.ConditionType switch { case strings.HasPrefix(op, "String"): convertValue = noConvertFunction switch op { case CondStringEquals: condType = policyengine.CondStringEquals case CondStringNotEquals: condType = policyengine.CondStringNotEquals case CondStringEqualsIgnoreCase: condType = policyengine.CondStringEqualsIgnoreCase case CondStringNotEqualsIgnoreCase: condType = policyengine.CondStringNotEqualsIgnoreCase case CondStringLike: condType = policyengine.CondStringLike case CondStringNotLike: condType = policyengine.CondStringNotLike default: return nil, fmt.Errorf("unsupported condition operator: '%s'", op) } case strings.HasPrefix(op, "Arn"): convertValue = noConvertFunction switch op { case CondArnEquals: condType = policyengine.CondStringEquals case CondArnNotEquals: condType = policyengine.CondStringNotEquals case CondArnLike: condType = policyengine.CondStringLike case CondArnNotLike: condType = policyengine.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 = policyengine.CondStringEquals case CondDateNotEquals: condType = policyengine.CondStringNotEquals case CondDateLessThan: condType = policyengine.CondStringLessThan case CondDateLessThanEquals: condType = policyengine.CondStringLessThanEquals case CondDateGreaterThan: condType = policyengine.CondStringGreaterThan case CondDateGreaterThanEquals: condType = policyengine.CondStringGreaterThanEquals default: return nil, fmt.Errorf("unsupported condition operator: '%s'", op) } case op == CondBool: convertValue = noConvertFunction condType = policyengine.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 = policyengine.CondStringLike case op == CondNotIPAddress: convertValue = noConvertFunction condType = policyengine.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, policyengine.Condition{ Op: condType, Object: policyengine.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 }