package iam import ( "fmt" "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) actions, actionInverted := statement.action() if err := validateS3ActionNames(actions); err != nil { return nil, err } ruleAction := chain.Actions{Inverted: actionInverted, Names: actions} if len(ruleAction.Names) == 0 { continue } resources, resourceInverted := statement.resource() if err := validateS3ResourceNames(resources); err != nil { return nil, err } ruleResource := chain.Resources{Inverted: resourceInverted, Names: resources} 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) } } } if len(engineChain.Rules) == 0 { return nil, ErrActionsNotApplicable } 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 validateS3ResourceNames(names []string) error { for i := range names { if err := validateResource(names[i]); err != nil { return err } } return nil } func validateS3ActionNames(names []string) error { for i := range names { if err := validateAction(names[i]); err != nil { return err } } return nil }