forked from TrueCloudLab/policy-engine
[#46] iam: Handle s3 complex actions
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
1cdb3e5a4a
commit
0edc002441
2 changed files with 102 additions and 17 deletions
|
@ -7,6 +7,76 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
"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 {
|
type S3Resolver interface {
|
||||||
GetUserAddress(account, user string) (string, error)
|
GetUserAddress(account, user string) (string, error)
|
||||||
}
|
}
|
||||||
|
@ -22,10 +92,11 @@ func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) {
|
||||||
status := formStatus(statement)
|
status := formStatus(statement)
|
||||||
|
|
||||||
actions, actionInverted := statement.action()
|
actions, actionInverted := statement.action()
|
||||||
if err := validateS3ActionNames(actions); err != nil {
|
s3Actions, err := formS3ActionNames(actions)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ruleAction := chain.Actions{Inverted: actionInverted, Names: actions}
|
ruleAction := chain.Actions{Inverted: actionInverted, Names: s3Actions}
|
||||||
if len(ruleAction.Names) == 0 {
|
if len(ruleAction.Names) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -161,12 +232,24 @@ func validateS3ResourceNames(names []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateS3ActionNames(names []string) error {
|
func formS3ActionNames(names []string) ([]string, error) {
|
||||||
for i := range names {
|
res := make([]string, 0, len(names))
|
||||||
if err := validateAction(names[i]); err != nil {
|
|
||||||
return err
|
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 nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,8 @@ func TestConverters(t *testing.T) {
|
||||||
bktName := "DOC-EXAMPLE-BUCKET"
|
bktName := "DOC-EXAMPLE-BUCKET"
|
||||||
objName := "object-name"
|
objName := "object-name"
|
||||||
resource := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName)
|
resource := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName)
|
||||||
s3action := "s3:PutObject"
|
s3GetObjectAction := "s3:GetObject"
|
||||||
|
s3HeadObjectAction := "s3:HeadObject"
|
||||||
|
|
||||||
mockResolver := newMockUserResolver([]string{user}, []string{bktName}, namespace)
|
mockResolver := newMockUserResolver([]string{user}, []string{bktName}, namespace)
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ func TestConverters(t *testing.T) {
|
||||||
AWSPrincipalType: {principal},
|
AWSPrincipalType: {principal},
|
||||||
},
|
},
|
||||||
Effect: AllowEffect,
|
Effect: AllowEffect,
|
||||||
Action: []string{"s3:PutObject"},
|
Action: []string{s3GetObjectAction},
|
||||||
Resource: []string{resource},
|
Resource: []string{resource},
|
||||||
Conditions: map[string]Condition{
|
Conditions: map[string]Condition{
|
||||||
CondStringEquals: {
|
CondStringEquals: {
|
||||||
|
@ -96,7 +97,7 @@ func TestConverters(t *testing.T) {
|
||||||
expected := &chain.Chain{Rules: []chain.Rule{
|
expected := &chain.Chain{Rules: []chain.Rule{
|
||||||
{
|
{
|
||||||
Status: chain.Allow,
|
Status: chain.Allow,
|
||||||
Actions: chain.Actions{Names: []string{s3action}},
|
Actions: chain.Actions{Names: []string{s3GetObjectAction, s3HeadObjectAction}},
|
||||||
Resources: chain.Resources{Names: []string{resource}},
|
Resources: chain.Resources{Names: []string{resource}},
|
||||||
Condition: []chain.Condition{
|
Condition: []chain.Condition{
|
||||||
{
|
{
|
||||||
|
@ -162,7 +163,7 @@ func TestConverters(t *testing.T) {
|
||||||
AWSPrincipalType: {principal},
|
AWSPrincipalType: {principal},
|
||||||
},
|
},
|
||||||
Effect: DenyEffect,
|
Effect: DenyEffect,
|
||||||
NotAction: []string{"s3:PutObject"},
|
NotAction: []string{s3GetObjectAction},
|
||||||
NotResource: []string{resource},
|
NotResource: []string{resource},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
@ -170,7 +171,7 @@ func TestConverters(t *testing.T) {
|
||||||
expected := &chain.Chain{Rules: []chain.Rule{
|
expected := &chain.Chain{Rules: []chain.Rule{
|
||||||
{
|
{
|
||||||
Status: chain.AccessDenied,
|
Status: chain.AccessDenied,
|
||||||
Actions: chain.Actions{Inverted: true, Names: []string{s3action}},
|
Actions: chain.Actions{Inverted: true, Names: []string{s3GetObjectAction, s3HeadObjectAction}},
|
||||||
Resources: chain.Resources{Inverted: true, Names: []string{resource}},
|
Resources: chain.Resources{Inverted: true, Names: []string{resource}},
|
||||||
Condition: []chain.Condition{
|
Condition: []chain.Condition{
|
||||||
{
|
{
|
||||||
|
@ -317,14 +318,14 @@ func TestConverters(t *testing.T) {
|
||||||
Statement: []Statement{{
|
Statement: []Statement{{
|
||||||
Principal: map[PrincipalType][]string{AWSPrincipalType: {principal}},
|
Principal: map[PrincipalType][]string{AWSPrincipalType: {principal}},
|
||||||
Effect: AllowEffect,
|
Effect: AllowEffect,
|
||||||
Action: []string{"s3:PutObject", "iam:*"},
|
Action: []string{"s3:DeleteObject", "iam:*"},
|
||||||
Resource: []string{"*"},
|
Resource: []string{"*"},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
s3Expected := &chain.Chain{Rules: []chain.Rule{{
|
s3Expected := &chain.Chain{Rules: []chain.Rule{{
|
||||||
Status: chain.Allow,
|
Status: chain.Allow,
|
||||||
Actions: chain.Actions{Names: []string{"s3:PutObject", "iam:*"}},
|
Actions: chain.Actions{Names: []string{"s3:DeleteObject", "s3:DeleteMultipleObjects", "iam:*"}},
|
||||||
Resources: chain.Resources{Names: []string{"*"}},
|
Resources: chain.Resources{Names: []string{"*"}},
|
||||||
Condition: []chain.Condition{{
|
Condition: []chain.Condition{{
|
||||||
Op: chain.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
|
@ -340,7 +341,7 @@ func TestConverters(t *testing.T) {
|
||||||
|
|
||||||
nativeExpected := &chain.Chain{Rules: []chain.Rule{{
|
nativeExpected := &chain.Chain{Rules: []chain.Rule{{
|
||||||
Status: chain.Allow,
|
Status: chain.Allow,
|
||||||
Actions: chain.Actions{Names: []string{native.MethodPutObject}},
|
Actions: chain.Actions{Names: []string{native.MethodDeleteObject}},
|
||||||
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
|
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
|
||||||
Condition: []chain.Condition{{
|
Condition: []chain.Condition{{
|
||||||
Op: chain.CondStringEquals,
|
Op: chain.CondStringEquals,
|
||||||
|
@ -928,7 +929,8 @@ func TestComplexS3Conditions(t *testing.T) {
|
||||||
resource1 := fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName1, objName1)
|
resource1 := fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName1, objName1)
|
||||||
resource2 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName2)
|
resource2 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName2)
|
||||||
resource3 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName3)
|
resource3 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName3)
|
||||||
action := "s3:PutObject"
|
action := "s3:DeleteObject"
|
||||||
|
action2 := "s3:DeleteMultipleObjects"
|
||||||
|
|
||||||
key1, key2 := "key1", "key2"
|
key1, key2 := "key1", "key2"
|
||||||
val0, val1, val2 := "val0", "val1", "val2"
|
val0, val1, val2 := "val0", "val1", "val2"
|
||||||
|
@ -952,7 +954,7 @@ func TestComplexS3Conditions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedStatus := chain.AccessDenied
|
expectedStatus := chain.AccessDenied
|
||||||
expectedActions := chain.Actions{Names: []string{action}}
|
expectedActions := chain.Actions{Names: []string{action, action2}}
|
||||||
expectedResources := chain.Resources{Names: []string{resource1, resource2, resource3}}
|
expectedResources := chain.Resources{Names: []string{resource1, resource2, resource3}}
|
||||||
|
|
||||||
user1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user1]}
|
user1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user1]}
|
||||||
|
|
Loading…
Reference in a new issue