package iam import ( "errors" "fmt" "strings" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" ) const PropertyKeyFilePath = "FilePath" // ErrActionsNotApplicable occurs when failed to convert any actions. var ErrActionsNotApplicable = errors.New("actions not applicable") var actionToOpMap = map[string][]string{ supportedS3ActionDeleteObject: {native.MethodDeleteObject}, supportedS3ActionGetObject: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}, supportedS3ActionHeadObject: {native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}, supportedS3ActionPutObject: {native.MethodPutObject}, supportedS3ActionListBucket: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}, } const ( supportedS3ActionDeleteObject = "s3:DeleteObject" supportedS3ActionGetObject = "s3:GetObject" supportedS3ActionHeadObject = "s3:HeadObject" supportedS3ActionPutObject = "s3:PutObject" supportedS3ActionListBucket = "s3:ListBucket" ) type NativeResolver interface { GetUserKey(account, name string) (string, error) GetBucketCID(bucket string) (string, error) } func ConvertToNativeChain(p Policy, resolver NativeResolver) (*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() nativeActions, err := formNativeActionNames(action) if err != nil { return nil, err } ruleAction := chain.Actions{Inverted: actionInverted, Names: nativeActions} if len(ruleAction.Names) == 0 { continue } resource, resourceInverted := statement.resource() groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver) if err != nil { return nil, err } groupedConditions, err := convertToNativeChainCondition(statement.Conditions, resolver) if err != nil { return nil, err } splitConditions := splitGroupedConditions(groupedConditions) principals, principalCondFn, err := getNativePrincipalsAndConditionFunc(statement, resolver) if err != nil { return nil, err } for _, groupedResource := range groupedResources { for _, principal := range principals { for _, conditions := range splitConditions { ruleConditions := append([]chain.Condition{principalCondFn(principal)}, groupedResource.Conditions...) r := chain.Rule{ Status: status, Actions: ruleAction, Resources: chain.Resources{ Inverted: resourceInverted, Names: groupedResource.Names, }, Condition: append(ruleConditions, conditions...), } engineChain.Rules = append(engineChain.Rules, r) } } } } if len(engineChain.Rules) == 0 { return nil, ErrActionsNotApplicable } return &engineChain, nil } func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeResolver) ([]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 := formNativePrincipal(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: native.PropertyKeyActorPublicKey, Value: principal, } }, nil } func convertToNativeChainCondition(c Conditions, resolver NativeResolver) ([]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 = native.PropertyKeyActorPublicKey val, err := formPrincipalKey(gr.Conditions[i].Value, resolver) if err != nil { return GroupedConditions{}, err } gr.Conditions[i].Value = val } } return gr, nil }) } type GroupedResources struct { Names []string Conditions []chain.Condition } func formNativeResourceNamesAndConditions(names []string, resolver NativeResolver) ([]GroupedResources, error) { res := make([]GroupedResources, 0, len(names)) var combined []string for _, resource := range names { if err := validateResource(resource); err != nil { return nil, err } if resource == Wildcard { res = res[:0] return append(res, GroupedResources{Names: []string{native.ResourceFormatAllObjects}}), nil } if !strings.HasPrefix(resource, s3ResourcePrefix) { continue } var bkt, obj string s3Resource := strings.TrimPrefix(resource, s3ResourcePrefix) if s3Resource == Wildcard { res = res[:0] return append(res, GroupedResources{Names: []string{native.ResourceFormatAllObjects}}), nil } if sepIndex := strings.Index(s3Resource, "/"); sepIndex < 0 { bkt = s3Resource } else { bkt = s3Resource[:sepIndex] obj = s3Resource[sepIndex+1:] if len(obj) == 0 { obj = Wildcard } } cnrID, err := resolver.GetBucketCID(bkt) if err != nil { return nil, err } nativeResource := fmt.Sprintf(native.ResourceFormatRootContainerObjects, cnrID) if obj == Wildcard || obj == "" { combined = append(combined, nativeResource) continue } res = append(res, GroupedResources{ Names: []string{nativeResource}, Conditions: []chain.Condition{ { Op: chain.CondStringLike, Object: chain.ObjectResource, Key: PropertyKeyFilePath, Value: obj, }, }, }) } if len(combined) != 0 { res = append(res, GroupedResources{Names: combined}) } return res, nil } func formNativePrincipal(principal []string, resolver NativeResolver) ([]string, error) { res := make([]string, len(principal)) var err error for i := range principal { if res[i], err = formPrincipalKey(principal[i], resolver); err != nil { return nil, err } } return res, nil } func formPrincipalKey(principal string, resolver NativeResolver) (string, error) { account, user, err := parsePrincipalAsIAMUser(principal) if err != nil { return "", err } key, err := resolver.GetUserKey(account, user) if err != nil { return "", fmt.Errorf("get user key: %w", err) } return key, nil } func formNativeActionNames(names []string) ([]string, error) { res := make([]string, 0, len(names)) for _, action := range names { if err := validateAction(action); err != nil { return nil, err } if action == Wildcard { return []string{Wildcard}, nil } if !strings.HasPrefix(action, s3ActionPrefix) { continue } if strings.TrimPrefix(action, s3ActionPrefix) == Wildcard { return []string{Wildcard}, nil } res = append(res, actionToOpMap[action]...) } return res, nil }