package iam import ( "fmt" "strings" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/schema/s3" ) type S3Resolver interface { GetUserAddress(account, user string) (string, error) } func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) { if err := p.Validate(ResourceBasedPolicyType); err != nil { return nil, err } var engineChain chain.Chain for _, statement := range p.Statement { status := formStatus(statement) action, actionInverted := statement.action() ruleAction := chain.Actions{Inverted: actionInverted, Names: formS3ActionNames(action)} resource, resourceInverted := statement.resource() ruleResource := chain.Resources{Inverted: resourceInverted, Names: formS3ResourceNamesAndConditions(resource)} groupedConditions, err := convertToS3ChainCondition(statement.Conditions, resolver) if err != nil { return nil, err } splitConditions := splitGroupedConditions(groupedConditions) principals, principalCondFn, err := getS3PrincipalsAndConditionFunc(statement, resolver) if err != nil { return nil, err } for _, principal := range principals { for _, conditions := range splitConditions { r := chain.Rule{ Status: status, Actions: ruleAction, Resources: ruleResource, Condition: append([]chain.Condition{principalCondFn(principal)}, conditions...), } engineChain.Rules = append(engineChain.Rules, r) } } } return &engineChain, nil } func getS3PrincipalsAndConditionFunc(statement Statement, resolver S3Resolver) ([]string, formPrincipalConditionFunc, error) { 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 principalType, principal := range statementPrincipal { if principalType != AWSPrincipalType { return nil, nil, fmt.Errorf("unsupported principal type '%s'", principalType) } parsedPrincipal, err := formS3Principal(principal, resolver) if err != nil { return nil, nil, fmt.Errorf("parse principal: %w", err) } principals = append(principals, parsedPrincipal...) } op = chain.CondStringEquals if inverted { op = chain.CondStringNotEquals } } return principals, func(principal string) chain.Condition { return chain.Condition{ Op: op, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: principal, } }, nil } func convertToS3ChainCondition(c Conditions, resolver S3Resolver) ([]GroupedConditions, error) { return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) { for i := range gr.Conditions { if gr.Conditions[i].Key == condKeyAWSPrincipalARN { gr.Conditions[i].Key = s3.PropertyKeyOwner val, err := formPrincipalOwner(gr.Conditions[i].Value, resolver) if err != nil { return GroupedConditions{}, err } gr.Conditions[i].Value = val } } return gr, nil }) } func formS3Principal(principal []string, resolver S3Resolver) ([]string, error) { res := make([]string, len(principal)) var err error for i := range principal { if res[i], err = formPrincipalOwner(principal[i], resolver); err != nil { return nil, err } } return res, nil } func formPrincipalOwner(principal string, resolver S3Resolver) (string, error) { account, user, err := parsePrincipalAsIAMUser(principal) if err != nil { return "", err } address, err := resolver.GetUserAddress(account, user) if err != nil { return "", fmt.Errorf("get user address: %w", err) } return address, nil } func formS3ResourceNamesAndConditions(names []string) []string { res := make([]string, len(names)) for i := range names { res[i] = strings.TrimPrefix(names[i], s3ResourcePrefix) } return res } func formS3ActionNames(names []string) []string { res := make([]string, len(names)) for i := range names { res[i] = strings.TrimPrefix(names[i], s3ActionPrefix) } return res }