2023-11-10 14:56:41 +00:00
|
|
|
package iam
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
|
|
|
)
|
|
|
|
|
2024-02-01 13:57:07 +00:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
2023-11-10 14:56:41 +00:00
|
|
|
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)
|
|
|
|
|
2023-12-19 07:35:14 +00:00
|
|
|
actions, actionInverted := statement.action()
|
2024-02-01 13:57:07 +00:00
|
|
|
s3Actions, err := formS3ActionNames(actions)
|
|
|
|
if err != nil {
|
2023-11-28 14:56:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2024-02-01 13:57:07 +00:00
|
|
|
ruleAction := chain.Actions{Inverted: actionInverted, Names: s3Actions}
|
2024-01-26 08:16:12 +00:00
|
|
|
if len(ruleAction.Names) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2023-11-10 14:56:41 +00:00
|
|
|
|
2023-12-19 07:35:14 +00:00
|
|
|
resources, resourceInverted := statement.resource()
|
|
|
|
if err := validateS3ResourceNames(resources); err != nil {
|
2023-11-28 14:56:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-19 07:35:14 +00:00
|
|
|
ruleResource := chain.Resources{Inverted: resourceInverted, Names: resources}
|
2023-11-10 14:56:41 +00:00
|
|
|
|
|
|
|
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 {
|
2024-01-26 09:54:39 +00:00
|
|
|
var principalCondition []chain.Condition
|
|
|
|
if principal != Wildcard {
|
|
|
|
principalCondition = []chain.Condition{principalCondFn(principal)}
|
|
|
|
}
|
|
|
|
|
2023-11-10 14:56:41 +00:00
|
|
|
r := chain.Rule{
|
|
|
|
Status: status,
|
|
|
|
Actions: ruleAction,
|
|
|
|
Resources: ruleResource,
|
2024-01-26 09:54:39 +00:00
|
|
|
Condition: append(principalCondition, conditions...),
|
2023-11-10 14:56:41 +00:00
|
|
|
}
|
|
|
|
engineChain.Rules = append(engineChain.Rules, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 08:16:12 +00:00
|
|
|
if len(engineChain.Rules) == 0 {
|
|
|
|
return nil, ErrActionsNotApplicable
|
|
|
|
}
|
|
|
|
|
2023-11-10 14:56:41 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-12-19 07:35:14 +00:00
|
|
|
func validateS3ResourceNames(names []string) error {
|
2023-11-10 14:56:41 +00:00
|
|
|
for i := range names {
|
2023-12-19 07:35:14 +00:00
|
|
|
if err := validateResource(names[i]); err != nil {
|
|
|
|
return err
|
2023-11-28 14:56:36 +00:00
|
|
|
}
|
2023-11-10 14:56:41 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 07:35:14 +00:00
|
|
|
return nil
|
2023-11-10 14:56:41 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 13:57:07 +00:00
|
|
|
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)
|
2023-11-28 14:56:36 +00:00
|
|
|
}
|
2023-11-10 14:56:41 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 13:57:07 +00:00
|
|
|
return res, nil
|
2023-11-10 14:56:41 +00:00
|
|
|
}
|