feature/iam-support_namespaces_in_native_resources #46

Merged
5 changed files with 312 additions and 85 deletions

View file

@ -71,6 +71,9 @@ var (
// ErrInvalidActionFormat occurs when action has unknown/unsupported format.
ErrInvalidActionFormat = errors.New("invalid action format")
// ErrActionsNotApplicable occurs when failed to convert any actions.
ErrActionsNotApplicable = errors.New("actions not applicable")
)
type formPrincipalConditionFunc func(string) chain.Condition

View file

@ -1,7 +1,6 @@
package iam
import (
"errors"
"fmt"
"strings"
@ -11,28 +10,59 @@ import (
const PropertyKeyFilePath = "FilePath"
// ErrActionsNotApplicable occurs when failed to convert any actions.
var ErrActionsNotApplicable = errors.New("actions not applicable")
var supportedActionToNativeOpMap = map[string][]string{
supportedS3NativeActionDeleteObject: {native.MethodDeleteObject},
supportedS3NativeActionGetObject: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
supportedS3NativeActionPutObject: {native.MethodPutObject},
supportedS3NativeActionListBucket: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
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},
supportedS3NativeActionCreateBucket: {native.MethodPutContainer},
supportedS3NativeActionDeleteBucket: {native.MethodDeleteContainer},
supportedS3NativeActionListAllMyBucket: {native.MethodListContainers},
supportedS3NativeActionPutBucketACL: {native.MethodSetContainerEACL},
supportedS3NativeActionGetBucketACL: {native.MethodGetContainerEACL},
}
var containerNativeOperations = map[string]struct{}{
native.MethodPutContainer: {},
native.MethodDeleteContainer: {},
native.MethodGetContainer: {},
native.MethodListContainers: {},
native.MethodSetContainerEACL: {},
native.MethodGetContainerEACL: {},
}
var objectNativeOperations = map[string]struct{}{
native.MethodGetObject: {},
native.MethodPutObject: {},
native.MethodHeadObject: {},
native.MethodDeleteObject: {},
native.MethodSearchObject: {},
native.MethodRangeObject: {},
native.MethodHashObject: {},
}
const (
supportedS3ActionDeleteObject = "s3:DeleteObject"
supportedS3ActionGetObject = "s3:GetObject"
supportedS3ActionHeadObject = "s3:HeadObject"
supportedS3ActionPutObject = "s3:PutObject"
supportedS3ActionListBucket = "s3:ListBucket"
supportedS3NativeActionDeleteObject = "s3:DeleteObject"
supportedS3NativeActionGetObject = "s3:GetObject"
supportedS3NativeActionPutObject = "s3:PutObject"
supportedS3NativeActionListBucket = "s3:ListBucket"
supportedS3NativeActionCreateBucket = "s3:CreateBucket"
supportedS3NativeActionDeleteBucket = "s3:DeleteBucket"
supportedS3NativeActionListAllMyBucket = "s3:ListAllMyBuckets"
supportedS3NativeActionPutBucketACL = "s3:PutBucketAcl"
supportedS3NativeActionGetBucketACL = "s3:GetBucketAcl"
)
type NativeResolver interface {
GetUserKey(account, name string) (string, error)
GetBucketCID(bucket string) (string, error)
GetBucketInfo(bucket string) (*BucketInfo, error)
}
type BucketInfo struct {
Namespace string
Container string
}
func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, error) {
@ -56,7 +86,7 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
}
resource, resourceInverted := statement.resource()
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver)
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver, getActionTypes(nativeActions))
if err != nil {
return nil, err
}
@ -75,7 +105,12 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
for _, groupedResource := range groupedResources {
for _, principal := range principals {
for _, conditions := range splitConditions {
ruleConditions := append([]chain.Condition{principalCondFn(principal)}, groupedResource.Conditions...)
var principalCondition []chain.Condition
if principal != Wildcard {
principalCondition = []chain.Condition{principalCondFn(principal)}
}
ruleConditions := append(principalCondition, groupedResource.Conditions...)
r := chain.Rule{
Status: status,
@ -99,6 +134,23 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
return &engineChain, nil
}
func getActionTypes(nativeActions []string) ActionTypes {
var res ActionTypes
for _, action := range nativeActions {
if res.Object && res.Container {
break
}
_, isObj := objectNativeOperations[action]
_, isCnr := containerNativeOperations[action]
res.Object = isObj || action == Wildcard
res.Container = isCnr || action == Wildcard
}
return res
}
func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeResolver) ([]string, formPrincipalConditionFunc, error) {
var principals []string
var op chain.ConditionType
@ -156,7 +208,16 @@ type GroupedResources struct {
Conditions []chain.Condition
}
func formNativeResourceNamesAndConditions(names []string, resolver NativeResolver) ([]GroupedResources, error) {
type ActionTypes struct {
Object bool
Container bool
}
func formNativeResourceNamesAndConditions(names []string, resolver NativeResolver, actionTypes ActionTypes) ([]GroupedResources, error) {
if !actionTypes.Object && !actionTypes.Container {
return nil, ErrActionsNotApplicable
}
res := make([]GroupedResources, 0, len(names))
var combined []string
@ -168,7 +229,7 @@ func formNativeResourceNamesAndConditions(names []string, resolver NativeResolve
if resource == Wildcard {
res = res[:0]
return append(res, GroupedResources{Names: []string{native.ResourceFormatAllObjects}}), nil
return append(res, formWildcardNativeResource(actionTypes)), nil
}
if !strings.HasPrefix(resource, s3ResourcePrefix) {
@ -179,7 +240,7 @@ func formNativeResourceNamesAndConditions(names []string, resolver NativeResolve
s3Resource := strings.TrimPrefix(resource, s3ResourcePrefix)
if s3Resource == Wildcard {
res = res[:0]
return append(res, GroupedResources{Names: []string{native.ResourceFormatAllObjects}}), nil
return append(res, formWildcardNativeResource(actionTypes)), nil
}
if sepIndex := strings.Index(s3Resource, "/"); sepIndex < 0 {
@ -192,19 +253,22 @@ func formNativeResourceNamesAndConditions(names []string, resolver NativeResolve
}
}
cnrID, err := resolver.GetBucketCID(bkt)
bktInfo, err := resolver.GetBucketInfo(bkt)
if err != nil {
return nil, err
}
nativeResource := fmt.Sprintf(native.ResourceFormatRootContainerObjects, cnrID)
if obj == Wildcard || obj == "" {
combined = append(combined, nativeResource)
if obj == Wildcard && actionTypes.Object { // this corresponds to arn:aws:s3:::BUCKET/ or arn:aws:s3:::BUCKET/*
combined = append(combined, fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container))
continue
}
if obj == "" && actionTypes.Container { // this corresponds to arn:aws:s3:::BUCKET
combined = append(combined, fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container))
continue
}
res = append(res, GroupedResources{
Names: []string{nativeResource},
Names: []string{fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)},
Conditions: []chain.Condition{
{
Op: chain.CondStringLike,
@ -223,6 +287,18 @@ func formNativeResourceNamesAndConditions(names []string, resolver NativeResolve
return res, nil
}
func formWildcardNativeResource(actionTypes ActionTypes) GroupedResources {
groupedNames := make([]string, 0, 2)
if actionTypes.Object {
groupedNames = append(groupedNames, native.ResourceFormatAllObjects)
}
if actionTypes.Container {
groupedNames = append(groupedNames, native.ResourceFormatAllContainers)
}
return GroupedResources{Names: groupedNames}
}
func formNativePrincipal(principal []string, resolver NativeResolver) ([]string, error) {
res := make([]string, len(principal))
@ -270,7 +346,7 @@ func formNativeActionNames(names []string) ([]string, error) {
return []string{Wildcard}, nil
}
res = append(res, actionToOpMap[action]...)
res = append(res, supportedActionToNativeOpMap[action]...)
}
return res, nil

View file

@ -7,6 +7,76 @@ import (
"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)
}
@ -22,10 +92,14 @@ func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) {
status := formStatus(statement)
actions, actionInverted := statement.action()
if err := validateS3ActionNames(actions); err != nil {
s3Actions, err := formS3ActionNames(actions)
if err != nil {
return nil, err
}
ruleAction := chain.Actions{Inverted: actionInverted, Names: actions}
ruleAction := chain.Actions{Inverted: actionInverted, Names: s3Actions}
if len(ruleAction.Names) == 0 {
continue
}
resources, resourceInverted := statement.resource()
if err := validateS3ResourceNames(resources); err != nil {
@ -46,17 +120,26 @@ func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) {
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([]chain.Condition{principalCondFn(principal)}, conditions...),
Condition: append(principalCondition, conditions...),
}
engineChain.Rules = append(engineChain.Rules, r)
}
}
}
if len(engineChain.Rules) == 0 {
return nil, ErrActionsNotApplicable
}
return &engineChain, nil
}
@ -149,12 +232,24 @@ func validateS3ResourceNames(names []string) error {
return nil
}
func validateS3ActionNames(names []string) error {
for i := range names {
if err := validateAction(names[i]); err != nil {
return err
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 nil
return res, nil
}

View file

@ -17,22 +17,23 @@ import (
)
type mockUserResolver struct {
users map[string]string
buckets map[string]string
users map[string]string
containers map[string]string
namespace string
}
func newMockUserResolver(accountUsers []string, buckets []string) *mockUserResolver {
func newMockUserResolver(accountUsers []string, buckets []string, namespace string) *mockUserResolver {
userMap := make(map[string]string, len(accountUsers))
for _, user := range accountUsers {
userMap[user] = user + "/resolvedValue"
}
bucketMap := make(map[string]string, len(buckets))
containerMap := make(map[string]string, len(buckets))
for _, bkt := range buckets {
bucketMap[bkt] = bkt + "/resolvedValues"
containerMap[bkt] = bkt + "/resolvedValues"
}
return &mockUserResolver{users: userMap, buckets: bucketMap}
return &mockUserResolver{users: userMap, containers: containerMap, namespace: namespace}
}
func (m *mockUserResolver) GetUserAddress(account, user string) (string, error) {
@ -53,13 +54,13 @@ func (m *mockUserResolver) GetUserKey(account, user string) (string, error) {
return key, nil
}
func (m *mockUserResolver) GetBucketCID(bkt string) (string, error) {
cnrID, ok := m.buckets[bkt]
func (m *mockUserResolver) GetBucketInfo(bkt string) (*BucketInfo, error) {
cnr, ok := m.containers[bkt]
if !ok {
return "", errors.New("not found")
return nil, errors.New("not found")
}
return cnrID, nil
return &BucketInfo{Container: cnr, Namespace: m.namespace}, nil
}
func TestConverters(t *testing.T) {
@ -70,9 +71,10 @@ func TestConverters(t *testing.T) {
bktName := "DOC-EXAMPLE-BUCKET"
objName := "object-name"
resource := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName)
s3action := "s3:PutObject"
s3GetObjectAction := "s3:GetObject"
s3HeadObjectAction := "s3:HeadObject"
mockResolver := newMockUserResolver([]string{user}, []string{bktName})
mockResolver := newMockUserResolver([]string{user}, []string{bktName}, namespace)
t.Run("valid policy", func(t *testing.T) {
p := Policy{
@ -82,7 +84,7 @@ func TestConverters(t *testing.T) {
AWSPrincipalType: {principal},
},
Effect: AllowEffect,
Action: []string{"s3:PutObject"},
Action: []string{s3GetObjectAction},
Resource: []string{resource},
Conditions: map[string]Condition{
CondStringEquals: {
@ -95,7 +97,7 @@ func TestConverters(t *testing.T) {
expected := &chain.Chain{Rules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{s3action}},
Actions: chain.Actions{Names: []string{s3GetObjectAction, s3HeadObjectAction}},
Resources: chain.Resources{Names: []string{resource}},
Condition: []chain.Condition{
{
@ -136,7 +138,7 @@ func TestConverters(t *testing.T) {
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodPutObject}},
Resources: chain.Resources{Names: []string{fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName])}},
Resources: chain.Resources{Names: []string{fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, namespace, mockResolver.containers[bktName])}},
Condition: []chain.Condition{
{
Op: chain.CondStringEquals,
@ -161,7 +163,7 @@ func TestConverters(t *testing.T) {
AWSPrincipalType: {principal},
},
Effect: DenyEffect,
NotAction: []string{"s3:PutObject"},
NotAction: []string{s3GetObjectAction},
NotResource: []string{resource},
}},
}
@ -169,7 +171,7 @@ func TestConverters(t *testing.T) {
expected := &chain.Chain{Rules: []chain.Rule{
{
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}},
Condition: []chain.Condition{
{
@ -187,25 +189,28 @@ func TestConverters(t *testing.T) {
require.Equal(t, expected, s3Chain)
})
t.Run("valid policy map get action", func(t *testing.T) {
t.Run("valid native policy map action", func(t *testing.T) {
p := Policy{
Version: "2012-10-17",
Statement: []Statement{{
Principal: map[PrincipalType][]string{
AWSPrincipalType: {principal},
},
Effect: DenyEffect,
NotAction: []string{"s3:GetObject"},
NotResource: []string{fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName, objName)},
Effect: DenyEffect,
Action: []string{"s3:DeleteObject", "s3:DeleteBucket"},
Resource: []string{
fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName, objName),
fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName),
},
}},
}
expected := &chain.Chain{Rules: []chain.Rule{
{
Status: chain.AccessDenied,
Actions: chain.Actions{Inverted: true, Names: actionToOpMap["s3:GetObject"]},
Resources: chain.Resources{Inverted: true, Names: []string{
fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName]),
Actions: chain.Actions{Names: []string{native.MethodDeleteObject, native.MethodDeleteContainer}},
Resources: chain.Resources{Names: []string{
fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, namespace, mockResolver.containers[bktName]),
}},
Condition: []chain.Condition{
{
@ -222,6 +227,19 @@ func TestConverters(t *testing.T) {
},
},
},
{
Status: chain.AccessDenied,
Actions: chain.Actions{Names: []string{native.MethodDeleteObject, native.MethodDeleteContainer}},
Resources: chain.Resources{Names: []string{
fmt.Sprintf(native.ResourceFormatNamespaceContainer, namespace, mockResolver.containers[bktName]),
}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
}},
},
}}
nativeChain, err := ConvertToNativeChain(p, mockResolver)
@ -279,19 +297,35 @@ func TestConverters(t *testing.T) {
require.Error(t, err)
})
t.Run("invalid policy (missing s3 actions)", func(t *testing.T) {
p := Policy{
Version: "2012-10-17",
Statement: []Statement{{
Principal: map[PrincipalType][]string{
AWSPrincipalType: {principal},
},
Effect: AllowEffect,
Resource: []string{"arn:aws:s3:::" + resource},
}},
}
_, err := ConvertToS3Chain(p, mockResolver)
require.Error(t, err)
})
t.Run("valid mixed iam/s3 actions", func(t *testing.T) {
p := Policy{
Statement: []Statement{{
Principal: map[PrincipalType][]string{AWSPrincipalType: {principal}},
Effect: AllowEffect,
Action: []string{"s3:PutObject", "iam:*"},
Action: []string{"s3:DeleteObject", "iam:*"},
Resource: []string{"*"},
}},
}
s3Expected := &chain.Chain{Rules: []chain.Rule{{
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{"*"}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
@ -307,7 +341,7 @@ func TestConverters(t *testing.T) {
nativeExpected := &chain.Chain{Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodPutObject}},
Actions: chain.Actions{Names: []string{native.MethodDeleteObject}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
@ -587,10 +621,10 @@ func TestComplexNativeConditions(t *testing.T) {
key1, key2 := "key1", "key2"
val0, val1, val2 := "val0", "val1", "val2"
mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3})
nativeResource1 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName1])
nativeResource2 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName2])
nativeResource3 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets[bktName3])
mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3}, "")
nativeResource1 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers[bktName1])
nativeResource2 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers[bktName2])
nativeResource3 := fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers[bktName3])
p := Policy{
Version: "2012-10-17",
@ -609,7 +643,7 @@ func TestComplexNativeConditions(t *testing.T) {
}
expectedStatus := chain.AccessDenied
expectedActions := chain.Actions{Names: actionToOpMap["s3:"+action]}
expectedActions := chain.Actions{Names: supportedActionToNativeOpMap["s3:"+action]}
expectedResource1 := chain.Resources{Names: []string{nativeResource1}}
expectedResource23 := chain.Resources{Names: []string{nativeResource2, nativeResource3}}
@ -726,7 +760,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket resource1, all conditions matched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName2], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName2], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name",
},
@ -740,7 +774,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket resource3, all conditions matched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName3], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName3], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name",
},
@ -754,7 +788,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket resource, user condition mismatched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName2], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName2], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name",
},
@ -767,7 +801,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket resource, key2 condition mismatched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName3], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName3], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name",
},
@ -781,7 +815,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket resource, key1 condition mismatched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName2], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName2], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name",
},
@ -794,7 +828,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket/object resource, all conditions matched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: objName1,
},
@ -808,7 +842,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket/object resource, user condition mismatched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: objName1,
},
@ -822,7 +856,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket/object resource, key1 condition mismatched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: objName1,
},
@ -835,7 +869,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket/object resource, key2 condition mismatched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: objName1,
},
@ -849,7 +883,7 @@ func TestComplexNativeConditions(t *testing.T) {
{
name: "bucket/object resource, object filepath condition mismatched",
action: action,
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.buckets[bktName1], "some-oid"),
resource: fmt.Sprintf(native.ResourceFormatRootContainerObject, mockResolver.containers[bktName1], "some-oid"),
resourceMap: map[string]string{
PropertyKeyFilePath: "any-object-name",
},
@ -895,12 +929,13 @@ func TestComplexS3Conditions(t *testing.T) {
resource1 := fmt.Sprintf(s3.ResourceFormatS3BucketObject, bktName1, objName1)
resource2 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName2)
resource3 := fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName3)
action := "s3:PutObject"
action := "s3:DeleteObject"
action2 := "s3:DeleteMultipleObjects"
key1, key2 := "key1", "key2"
val0, val1, val2 := "val0", "val1", "val2"
mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3})
mockResolver := newMockUserResolver([]string{user1, user2}, []string{bktName1, bktName2, bktName3}, "")
p := Policy{
Version: "2012-10-17",
@ -919,7 +954,7 @@ func TestComplexS3Conditions(t *testing.T) {
}
expectedStatus := chain.AccessDenied
expectedActions := chain.Actions{Names: []string{action}}
expectedActions := chain.Actions{Names: []string{action, action2}}
expectedResources := chain.Resources{Names: []string{resource1, resource2, resource3}}
user1Condition := chain.Condition{Op: chain.CondStringEquals, Object: chain.ObjectRequest, Key: s3.PropertyKeyOwner, Value: mockResolver.users[user1]}
@ -1120,7 +1155,7 @@ func TestS3BucketResource(t *testing.T) {
bktName1, bktName2 := "bucket1", "bucket2"
chainName := chain.Name("name")
mockResolver := newMockUserResolver([]string{}, []string{})
mockResolver := newMockUserResolver([]string{}, []string{}, "")
p := Policy{
Version: "2012-10-17",
@ -1173,11 +1208,29 @@ func TestWildcardConverters(t *testing.T) {
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
_, err = ConvertToS3Chain(p, newMockUserResolver(nil, nil))
require.NoError(t, err)
s3Expected := &chain.Chain{
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{Wildcard}},
Resources: chain.Resources{Names: []string{Wildcard}},
}},
}
_, err = ConvertToNativeChain(p, newMockUserResolver(nil, nil))
s3Chain, err := ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Equal(t, s3Expected, s3Chain)
nativeExpected := &chain.Chain{
Rules: []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{Wildcard}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
}},
}
nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Equal(t, nativeExpected, nativeChain)
}
func TestActionParsing(t *testing.T) {

View file

@ -474,7 +474,7 @@ func TestProcessDenyFirst(t *testing.T) {
err = json.Unmarshal([]byte(resourceBasedPolicyStr), &resourcePolicy)
require.NoError(t, err)
mockResolver := newMockUserResolver([]string{"root/user-name"}, []string{"test-bucket"})
mockResolver := newMockUserResolver([]string{"root/user-name"}, []string{"test-bucket"}, "")
identityNativePolicy, err := ConvertToNativeChain(identityPolicy, mockResolver)
require.NoError(t, err)
@ -493,7 +493,7 @@ func TestProcessDenyFirst(t *testing.T) {
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, target, resourceNativePolicy)
require.NoError(t, err)
resource := testutil.NewResource(fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.buckets["test-bucket"]), nil)
resource := testutil.NewResource(fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers["test-bucket"]), nil)
request := testutil.NewRequest("PutObject", resource, map[string]string{native.PropertyKeyActorPublicKey: mockResolver.users["root/user-name"]})
status, found, err := s.IsAllowed(chain.Ingress, engine.NewRequestTarget("ns", ""), request)