package iam import ( "fmt" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/schema/s3" ) var specialActionToS3OpMap = map[string][]string{ specialS3ActionsListAllMyBuckets: {"s3:ListBuckets"}, specialS3ActionsListBucket: {"s3:HeadBucket", "s3:GetBucketLocation", "s3:ListObjectsV1", "s3:ListObjectsV2"}, specialS3ActionsListBucketVersions: {"s3:ListBucketObjectVersions"}, specialS3ActionsListBucketMultipartUploads: {"s3:ListMultipartUploads"}, specialS3ActionsGetBucketObjectLockConfiguration: {"s3:GetBucketObjectLockConfig"}, specialS3ActionsGetEncryptionConfiguration: {"s3:GetBucketEncryption"}, specialS3ActionsGetLifecycleConfiguration: {"s3:GetBucketLifecycle"}, specialS3ActionsGetBucketACL: {"s3:GetBucketACL"}, specialS3ActionsGetBucketCORS: {"s3:GetBucketCors"}, specialS3ActionsPutBucketTagging: {"s3:PutBucketTagging", "s3:DeleteBucketTagging"}, specialS3ActionsPutBucketObjectLockConfiguration: {"s3:PutBucketObjectLockConfig"}, specialS3ActionsPutEncryptionConfiguration: {"s3:PutBucketEncryption", "s3:DeleteBucketEncryption"}, specialS3ActionsPutLifecycleConfiguration: {"s3:PutBucketLifecycle", "s3:DeleteBucketLifecycle"}, specialS3ActionsPutBucketACL: {"s3:PutBucketACL"}, specialS3ActionsPutBucketCORS: {"s3:PutBucketCors", "s3:DeleteBucketCors"}, specialS3ActionsDeleteBucketCORS: {"s3:DeleteBucketCors"}, specialS3ActionsListMultipartUploadParts: {"s3:ListParts"}, specialS3ActionsGetObjectACL: {"s3:GetObjectACL"}, specialS3ActionsGetObject: {"s3:GetObject", "s3:HeadObject"}, specialS3ActionsGetObjectVersion: {"s3:GetObject", "s3:HeadObject"}, specialS3ActionsGetObjectVersionACL: {"s3:GetObjectACL"}, specialS3ActionsGetObjectVersionAttributes: {"s3:GetObjectAttributes"}, specialS3ActionsGetObjectVersionTagging: {"s3:GetObjectTagging"}, specialS3ActionsPutObjectACL: {"s3:PutObjectACL"}, specialS3ActionsPutObjectVersionACL: {"s3:PutObjectACL"}, specialS3ActionsPutObjectVersionTagging: {"s3:PutObjectTagging"}, specialS3ActionsPutObject: { "s3:PutObject", "s3:PostObject", "s3:CopyObject", "s3:UploadPart", "s3:UploadPartCopy", "s3:CreateMultipartUpload", "s3:CompleteMultipartUpload", }, specialS3ActionsDeleteObjectVersionTagging: {"s3:DeleteObjectTagging"}, specialS3ActionsDeleteObject: {"s3:DeleteObject", "s3:DeleteMultipleObjects"}, specialS3ActionsDeleteObjectVersion: {"s3:DeleteObject", "s3:DeleteMultipleObjects"}, } const ( specialS3ActionsListAllMyBuckets = "s3:ListAllMyBuckets" specialS3ActionsListBucket = "s3:ListBucket" specialS3ActionsListBucketVersions = "s3:ListBucketVersions" specialS3ActionsListBucketMultipartUploads = "s3:ListBucketMultipartUploads" specialS3ActionsGetBucketObjectLockConfiguration = "s3:GetBucketObjectLockConfiguration" specialS3ActionsGetEncryptionConfiguration = "s3:GetEncryptionConfiguration" specialS3ActionsGetLifecycleConfiguration = "s3:GetLifecycleConfiguration" specialS3ActionsGetBucketACL = "s3:GetBucketAcl" specialS3ActionsGetBucketCORS = "s3:GetBucketCORS" specialS3ActionsPutBucketTagging = "s3:PutBucketTagging" specialS3ActionsPutBucketObjectLockConfiguration = "s3:PutBucketObjectLockConfiguration" specialS3ActionsPutEncryptionConfiguration = "s3:PutEncryptionConfiguration" specialS3ActionsPutLifecycleConfiguration = "s3:PutLifecycleConfiguration" specialS3ActionsPutBucketACL = "s3:PutBucketAcl" specialS3ActionsPutBucketCORS = "s3:PutBucketCORS" specialS3ActionsDeleteBucketCORS = "s3:DeleteBucketCORS" specialS3ActionsListMultipartUploadParts = "s3:ListMultipartUploadParts" specialS3ActionsGetObjectACL = "s3:GetObjectAcl" specialS3ActionsGetObject = "s3:GetObject" specialS3ActionsGetObjectVersion = "s3:GetObjectVersion" specialS3ActionsGetObjectVersionACL = "s3:GetObjectVersionAcl" specialS3ActionsGetObjectVersionAttributes = "s3:GetObjectVersionAttributes" specialS3ActionsGetObjectVersionTagging = "s3:GetObjectVersionTagging" specialS3ActionsPutObjectACL = "s3:PutObjectAcl" specialS3ActionsPutObjectVersionACL = "s3:PutObjectVersionAcl" specialS3ActionsPutObjectVersionTagging = "s3:PutObjectVersionTagging" specialS3ActionsPutObject = "s3:PutObject" specialS3ActionsDeleteObjectVersionTagging = "s3:DeleteObjectVersionTagging" specialS3ActionsDeleteObject = "s3:DeleteObject" specialS3ActionsDeleteObjectVersion = "s3:DeleteObjectVersion" ) 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() s3Actions, err := formS3ActionNames(actions) if err != nil { return nil, err } ruleAction := chain.Actions{Inverted: actionInverted, Names: s3Actions} 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 { var principalCondition []chain.Condition if principal != Wildcard { principalCondition = []chain.Condition{principalCondFn(principal)} } r := chain.Rule{ Status: status, Actions: ruleAction, Resources: ruleResource, Condition: append(principalCondition, 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 formS3ActionNames(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 actions, ok := specialActionToS3OpMap[action]; ok { res = append(res, actions...) } else { res = append(res, action) } } return res, nil }