[#680] Add new converter

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2025-04-07 11:02:08 +03:00 committed by Alexey Vanin
parent 0ba6989197
commit 4d305d0d0f
5 changed files with 542 additions and 114 deletions

2
go.mod
View file

@ -9,7 +9,7 @@ require (
git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250227072915-25102d1e1aa3 git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250227072915-25102d1e1aa3
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250317082814-87bb55f992dc git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250317082814-87bb55f992dc
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20250317084311-594ac20859fc git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20250402100642-acd94d200f88
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02 git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
github.com/aws/aws-sdk-go-v2 v1.34.0 github.com/aws/aws-sdk-go-v2 v1.34.0
github.com/aws/aws-sdk-go-v2/config v1.27.32 github.com/aws/aws-sdk-go-v2/config v1.27.32

4
go.sum
View file

@ -52,8 +52,8 @@ git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8l
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM= git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 h1:/960fWeyn2AFHwQUwDsWB3sbP6lTEnFnMzLMM6tx6N8= git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 h1:/960fWeyn2AFHwQUwDsWB3sbP6lTEnFnMzLMM6tx6N8=
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972/go.mod h1:2hM42MBrlhvN6XToaW6OWNk5ZLcu1FhaukGgxtfpDDI= git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972/go.mod h1:2hM42MBrlhvN6XToaW6OWNk5ZLcu1FhaukGgxtfpDDI=
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20250317084311-594ac20859fc h1:o56+epHNuC7o0NwLJylvT+jf/icpgTZCTQPr7ydanek= git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20250402100642-acd94d200f88 h1:V0a7ia84ZpSM2YxpJq1SKLQfeYmsqFWqcxwweBHJIzc=
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20250317084311-594ac20859fc/go.mod h1:GZTk55RI4dKzsK6BCn5h2xxE28UHNfgoq/NJxW/LQ6A= git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20250402100642-acd94d200f88/go.mod h1:GZTk55RI4dKzsK6BCn5h2xxE28UHNfgoq/NJxW/LQ6A=
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA=
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc=
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA= git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA=

View file

@ -0,0 +1,235 @@
package policyengine
import (
"encoding/json"
"errors"
"fmt"
"testing"
s3common "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/policy-engine/common"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
"github.com/stretchr/testify/require"
)
type apeConverterMock struct {
version ConverterVersion
}
func (a apeConverterMock) ConverterVersion() ConverterVersion {
return a.version
}
type resolverMock struct {
users map[string]string
containers map[string]string
namespace string
}
func newMockUserResolver(accountUsers []string, buckets []string, namespace string) *resolverMock {
userMap := make(map[string]string, len(accountUsers))
for _, user := range accountUsers {
userMap[user] = user + "/resolvedValue"
}
containerMap := make(map[string]string, len(buckets))
for _, bkt := range buckets {
containerMap[bkt] = bkt + "/resolvedValues"
}
return &resolverMock{users: userMap, containers: containerMap, namespace: namespace}
}
func (m *resolverMock) GetUserAddress(account, user string) (string, error) {
key, ok := m.users[account+"/"+user]
if !ok {
return "", errors.New("not found")
}
return key, nil
}
func (m *resolverMock) GetUserKey(account, user string) (string, error) {
key, ok := m.users[account+"/"+user]
if !ok {
return "", errors.New("not found")
}
return key, nil
}
func (m *resolverMock) GetBucketInfo(bkt string) (*s3common.BucketInfo, error) {
cnr, ok := m.containers[bkt]
if !ok {
return nil, errors.New("not found")
}
return &s3common.BucketInfo{Container: cnr, Namespace: m.namespace}, nil
}
func TestChainsIsAllowed(t *testing.T) {
t.Run("ListBucket Allow", func(t *testing.T) {
policy := `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::bkt"
}
]
}
`
var p s3common.Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
resolver := newMockUserResolver(nil, []string{"bkt"}, "")
for _, tc := range []struct {
version ConverterVersion
status chain.Status
}{
{
version: V1,
status: chain.NoRuleFound,
},
{
version: V2,
status: chain.Allow,
},
} {
t.Run(string(tc.version), func(t *testing.T) {
converter := NewConverter(Config{VersionFetcher: apeConverterMock{version: tc.version}})
nativeChain, err := converter.ToNativeChain(p, resolver)
require.NoError(t, err)
s := inmemory.NewInMemory()
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), nativeChain)
require.NoError(t, err)
res := testutil.NewResource(fmt.Sprintf(native.ResourceFormatRootContainerObjects, resolver.containers["bkt"]), map[string]string{native.ProperyKeyTreeID: "system"})
req := testutil.NewRequest(native.MethodGetObject, res, nil)
status, _, err := s.IsAllowed(chain.Ingress, engine.NewRequestTargetWithNamespace(""), req)
require.NoError(t, err)
require.Equal(t, tc.status.String(), status.String())
})
}
})
t.Run("PutObject Deny/Allow", func(t *testing.T) {
user1, user2 := "user1", "user2"
principal1, principal2 := "/"+user1, "/"+user2
policy := fmt.Sprintf(`
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": {"AWS": ["arn:aws:iam:::user/%s"]},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::bkt/*"
},
{
"Effect": "Allow",
"Principal": {"AWS": ["arn:aws:iam:::user/%s"]},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::bkt/*"
}
]
}
`, user1, user2)
var p s3common.Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
resolver := newMockUserResolver([]string{principal1, principal2}, []string{"bkt"}, "")
for _, tc := range []struct {
version ConverterVersion
status chain.Status
}{
{
version: V1,
status: chain.Allow, // because we skip deny rules for native chains
},
{
version: V2,
status: chain.Allow,
},
} {
t.Run(string(tc.version), func(t *testing.T) {
converter := NewConverter(Config{VersionFetcher: apeConverterMock{version: tc.version}})
nativeChain, err := converter.ToNativeChain(p, resolver)
require.NoError(t, err)
s := inmemory.NewInMemory()
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), nativeChain)
require.NoError(t, err)
res := testutil.NewResource(fmt.Sprintf(native.ResourceFormatRootContainerObjects, resolver.containers["bkt"]), map[string]string{native.ProperyKeyTreeID: "system"})
req := testutil.NewRequest(native.MethodPutObject, res, map[string]string{native.PropertyKeyActorPublicKey: resolver.users[principal2]})
status, _, err := s.IsAllowed(chain.Ingress, engine.NewRequestTargetWithNamespace(""), req)
require.NoError(t, err)
require.Equal(t, tc.status.String(), status.String())
})
}
})
t.Run("GetObject Allow", func(t *testing.T) {
policy := `
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bkt/object"
}]
}
`
var p s3common.Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
resolver := newMockUserResolver(nil, []string{"bkt"}, "")
for _, tc := range []struct {
version ConverterVersion
status chain.Status
}{
{
version: V1,
status: chain.NoRuleFound,
},
{
version: V2,
status: chain.Allow,
},
} {
t.Run(string(tc.version), func(t *testing.T) {
converter := NewConverter(Config{VersionFetcher: apeConverterMock{version: tc.version}})
nativeChain, err := converter.ToNativeChain(p, resolver)
require.NoError(t, err)
s := inmemory.NewInMemory()
_, _, err = s.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), nativeChain)
require.NoError(t, err)
res := testutil.NewResource(fmt.Sprintf(native.ResourceFormatRootContainerObjects, resolver.containers["bkt"]), map[string]string{native.ProperyKeyTreeID: "version"})
req := testutil.NewRequest(native.MethodGetObject, res, nil)
status, _, err := s.IsAllowed(chain.Ingress, engine.NewRequestTargetWithNamespace(""), req)
require.NoError(t, err)
require.Equal(t, tc.status.String(), status.String())
})
}
})
}

View file

@ -12,58 +12,63 @@ import (
const PropertyKeyFilePath = "FilePath" const PropertyKeyFilePath = "FilePath"
var actionToNativeOpMap = map[string][]string{ type nativeOperationInfo struct {
s3common.S3ActionAbortMultipartUpload: {native.MethodGetContainer, native.MethodDeleteObject, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, operations []string
s3common.S3ActionCreateBucket: {native.MethodGetContainer, native.MethodPutContainer, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodPutObject}, needTreeWrite bool
s3common.S3ActionDeleteBucket: {native.MethodGetContainer, native.MethodDeleteContainer, native.MethodSearchObject, native.MethodHeadObject, native.MethodGetObject}, }
s3common.S3ActionDeleteBucketPolicy: {native.MethodGetContainer},
s3common.S3ActionDeleteObject: {native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject}, var actionToNativeOpMap = map[string]nativeOperationInfo{
s3common.S3ActionDeleteObjectTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionAbortMultipartUpload: {operations: []string{native.MethodGetContainer, native.MethodDeleteObject, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, needTreeWrite: true},
s3common.S3ActionDeleteObjectVersion: {native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject}, s3common.S3ActionCreateBucket: {operations: []string{native.MethodGetContainer, native.MethodPutContainer, native.MethodSetContainerEACL}, needTreeWrite: true},
s3common.S3ActionDeleteObjectVersionTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionDeleteBucket: {operations: []string{native.MethodGetContainer, native.MethodDeleteContainer, native.MethodSearchObject, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, needTreeWrite: true},
s3common.S3ActionGetBucketACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodGetObject}, s3common.S3ActionDeleteBucketPolicy: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetBucketCORS: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionDeleteObject: {operations: []string{native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject}, needTreeWrite: true},
s3common.S3ActionGetBucketLocation: {native.MethodGetContainer}, s3common.S3ActionDeleteObjectTagging: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionGetBucketNotification: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionDeleteObjectVersion: {operations: []string{native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject}, needTreeWrite: true},
s3common.S3ActionGetBucketObjectLockConfiguration: {native.MethodGetContainer, native.MethodGetObject}, s3common.S3ActionDeleteObjectVersionTagging: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionGetBucketPolicy: {native.MethodGetContainer}, s3common.S3ActionGetBucketACL: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetBucketPolicyStatus: {native.MethodGetContainer}, s3common.S3ActionGetBucketCORS: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}},
s3common.S3ActionGetBucketTagging: {native.MethodGetContainer, native.MethodGetObject}, s3common.S3ActionGetBucketLocation: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetBucketVersioning: {native.MethodGetContainer, native.MethodGetObject}, s3common.S3ActionGetBucketNotification: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}}, // not supported
s3common.S3ActionGetLifecycleConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionGetBucketObjectLockConfiguration: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetObject: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}, s3common.S3ActionGetBucketPolicy: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetObjectACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionGetBucketPolicyStatus: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetObjectAttributes: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionGetBucketTagging: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetObjectLegalHold: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject}, s3common.S3ActionGetBucketVersioning: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetObjectRetention: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject}, s3common.S3ActionGetLifecycleConfiguration: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}},
s3common.S3ActionGetObjectTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject}, s3common.S3ActionGetObject: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}},
s3common.S3ActionGetObjectVersion: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}, s3common.S3ActionGetObjectACL: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetObjectVersionACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionGetObjectAttributes: {operations: []string{native.MethodGetContainer, native.MethodHeadObject}},
s3common.S3ActionGetObjectVersionAttributes: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionGetObjectLegalHold: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionGetObjectVersionTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject}, s3common.S3ActionGetObjectRetention: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionListAllMyBuckets: {native.MethodListContainers, native.MethodGetContainer}, s3common.S3ActionGetObjectTagging: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionListBucket: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}, s3common.S3ActionGetObjectVersion: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}},
s3common.S3ActionListBucketMultipartUploads: {native.MethodGetContainer, native.MethodGetObject}, s3common.S3ActionGetObjectVersionACL: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionListBucketVersions: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}, s3common.S3ActionGetObjectVersionAttributes: {operations: []string{native.MethodGetContainer, native.MethodHeadObject}},
s3common.S3ActionListMultipartUploadParts: {native.MethodGetContainer, native.MethodGetObject}, s3common.S3ActionGetObjectVersionTagging: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionPutBucketACL: {native.MethodGetContainer, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionListAllMyBuckets: {operations: []string{native.MethodListContainers, native.MethodGetContainer}},
s3common.S3ActionPutBucketCORS: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionListBucket: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}},
s3common.S3ActionPutBucketNotification: {native.MethodGetContainer, native.MethodHeadObject, native.MethodDeleteObject, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionListBucketMultipartUploads: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionPutBucketObjectLockConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionListBucketVersions: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject}},
s3common.S3ActionPutBucketPolicy: {native.MethodGetContainer}, s3common.S3ActionListMultipartUploadParts: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionPutBucketTagging: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionPutBucketACL: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionPutBucketVersioning: {native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionPutBucketCORS: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodPutObject}, needTreeWrite: true},
s3common.S3ActionPutLifecycleConfiguration: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPutObject, native.MethodDeleteObject}, s3common.S3ActionPutBucketNotification: {operations: []string{native.MethodGetContainer, native.MethodHeadObject, native.MethodDeleteObject, native.MethodGetObject, native.MethodPutObject}, needTreeWrite: true}, // not supported
s3common.S3ActionPutObject: {native.MethodGetContainer, native.MethodPutObject, native.MethodGetObject, native.MethodHeadObject, native.MethodRangeObject}, s3common.S3ActionPutBucketObjectLockConfiguration: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionPutObjectACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionPutBucketPolicy: {operations: []string{native.MethodGetContainer}},
s3common.S3ActionPutObjectLegalHold: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionPutBucketTagging: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionPutObjectRetention: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionPutBucketVersioning: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionPutObjectTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionPutLifecycleConfiguration: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPutObject, native.MethodDeleteObject}, needTreeWrite: true},
s3common.S3ActionPutObjectVersionACL: {native.MethodGetContainer, native.MethodGetContainerEACL, native.MethodSetContainerEACL, native.MethodGetObject, native.MethodHeadObject}, s3common.S3ActionPutObject: {operations: []string{native.MethodGetContainer, native.MethodPutObject, native.MethodGetObject, native.MethodHeadObject, native.MethodRangeObject}, needTreeWrite: true},
s3common.S3ActionPutObjectVersionTagging: {native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, s3common.S3ActionPutObjectACL: {operations: []string{native.MethodGetContainer}}, // not supported
s3common.S3ActionPatchObject: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPatchObject, native.MethodPutObject, native.MethodRangeObject}, s3common.S3ActionPutObjectLegalHold: {operations: []string{native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, needTreeWrite: true},
s3common.S3ActionPutBucketPublicAccessBlock: {native.MethodGetContainer, native.MethodPutObject, native.MethodDeleteObject, native.MethodGetObject}, s3common.S3ActionPutObjectRetention: {operations: []string{native.MethodGetContainer, native.MethodHeadObject, native.MethodGetObject, native.MethodPutObject}, needTreeWrite: true},
s3common.S3ActionGetBucketPublicAccessBlock: {native.MethodGetContainer, native.MethodGetObject}, s3common.S3ActionPutObjectTagging: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionPutObjectVersionACL: {operations: []string{native.MethodGetContainer}}, // not supported
s3common.S3ActionPutObjectVersionTagging: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionPatchObject: {operations: []string{native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodPatchObject, native.MethodPutObject, native.MethodRangeObject}, needTreeWrite: true},
s3common.S3ActionPutBucketPublicAccessBlock: {operations: []string{native.MethodGetContainer}, needTreeWrite: true},
s3common.S3ActionGetBucketPublicAccessBlock: {operations: []string{native.MethodGetContainer}},
} }
var containerNativeOperations = map[string]struct{}{ var containerNativeOperations = map[string]struct{}{
@ -99,11 +104,12 @@ func ConvertToNativeChain(p s3common.Policy, resolver s3common.NativeResolver) (
if status != chain.Allow { if status != chain.Allow {
// Most s3 methods share the same native operations. Deny rules must not affect shared native operations, // Most s3 methods share the same native operations. Deny rules must not affect shared native operations,
// therefore this code skips all deny rules for native protocol. Deny is applied for s3 protocol only, in this case. // therefore this code skips all deny rules for native protocol. Deny is applied for s3 protocol only, in this case.
// Consider adding new condition for object operations (so that $Tree:ID be empty) if we will stop skipping deny rules.
continue continue
} }
action, actionInverted := statement.GetAction() action, actionInverted := statement.GetAction()
nativeActions, err := formNativeActionNames(action) nativeActions, treeWrite, err := formNativeActionNames(action)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,7 +119,7 @@ func ConvertToNativeChain(p s3common.Policy, resolver s3common.NativeResolver) (
} }
resource, resourceInverted := statement.GetResource() resource, resourceInverted := statement.GetResource()
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver, getActionTypes(nativeActions)) groupedResources, treeRes, err := formNativeResourceNamesAndConditions(resource, resolver, getActionTypes(nativeActions))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -132,6 +138,8 @@ func ConvertToNativeChain(p s3common.Policy, resolver s3common.NativeResolver) (
return nil, err return nil, err
} }
engineChain.Rules = append(engineChain.Rules, getTreeRule(treeRes, principals, principalCondFn, treeWrite)...)
for _, groupedResource := range groupedResources { for _, groupedResource := range groupedResources {
for _, principal := range principals { for _, principal := range principals {
for _, conditions := range splitConditions { for _, conditions := range splitConditions {
@ -164,6 +172,50 @@ func ConvertToNativeChain(p s3common.Policy, resolver s3common.NativeResolver) (
return &engineChain, nil return &engineChain, nil
} }
func getTreeRule(resources []string, principals []string, principalCondFn s3common.FormPrincipalConditionFunc, needWrite bool) []chain.Rule {
ops := []string{native.MethodGetObject}
if needWrite {
ops = append(ops, native.MethodPutObject)
}
treeCondition := chain.Condition{
Op: chain.CondStringNotEquals,
Kind: chain.KindResource,
Key: native.ProperyKeyTreeID,
Value: "",
}
var principalTreeConditions []chain.Condition
for _, principal := range principals {
if principal == s3common.Wildcard {
principalTreeConditions = principalTreeConditions[:0]
break
}
principalTreeConditions = append(principalTreeConditions, principalCondFn(principal))
}
if len(principalTreeConditions) == 0 {
return []chain.Rule{{
Status: chain.Allow,
Actions: chain.Actions{Names: ops},
Resources: chain.Resources{Names: resources},
Condition: []chain.Condition{treeCondition},
}}
}
res := make([]chain.Rule, len(principalTreeConditions))
for i, condition := range principalTreeConditions {
res[i] = chain.Rule{
Status: chain.Allow,
Actions: chain.Actions{Names: ops},
Resources: chain.Resources{Names: resources},
Condition: []chain.Condition{treeCondition, condition},
}
}
return res
}
func getActionTypes(nativeActions []string) ActionTypes { func getActionTypes(nativeActions []string) ActionTypes {
var res ActionTypes var res ActionTypes
for _, action := range nativeActions { for _, action := range nativeActions {
@ -258,23 +310,24 @@ type ActionTypes struct {
Container bool Container bool
} }
func formNativeResourceNamesAndConditions(names []string, resolver s3common.NativeResolver, actionTypes ActionTypes) ([]GroupedResources, error) { func formNativeResourceNamesAndConditions(names []string, resolver s3common.NativeResolver, actionTypes ActionTypes) ([]GroupedResources, []string, error) {
if !actionTypes.Object && !actionTypes.Container { if !actionTypes.Object && !actionTypes.Container {
return nil, s3common.ErrActionsNotApplicable return nil, nil, s3common.ErrActionsNotApplicable
} }
res := make([]GroupedResources, 0, len(names)) res := make([]GroupedResources, 0, len(names))
treeResMap := make(map[string]struct{}, len(names))
combined := make(map[string]struct{}) combined := make(map[string]struct{})
for _, resource := range names { for _, resource := range names {
if err := s3common.ValidateResource(resource); err != nil { if err := s3common.ValidateResource(resource); err != nil {
return nil, err return nil, nil, err
} }
if resource == s3common.Wildcard { if resource == s3common.Wildcard {
res = res[:0] res = res[:0]
return append(res, formWildcardNativeResource(actionTypes)), nil return append(res, formWildcardNativeResource(actionTypes)), []string{native.ResourceFormatAllObjects}, nil
} }
if !strings.HasPrefix(resource, s3common.S3ResourcePrefix) { if !strings.HasPrefix(resource, s3common.S3ResourcePrefix) {
@ -285,7 +338,7 @@ func formNativeResourceNamesAndConditions(names []string, resolver s3common.Nati
s3Resource := strings.TrimPrefix(resource, s3common.S3ResourcePrefix) s3Resource := strings.TrimPrefix(resource, s3common.S3ResourcePrefix)
if s3Resource == s3common.Wildcard { if s3Resource == s3common.Wildcard {
res = res[:0] res = res[:0]
return append(res, formWildcardNativeResource(actionTypes)), nil return append(res, formWildcardNativeResource(actionTypes)), []string{native.ResourceFormatAllObjects}, nil
} }
if sepIndex := strings.Index(s3Resource, "/"); sepIndex < 0 { if sepIndex := strings.Index(s3Resource, "/"); sepIndex < 0 {
@ -300,9 +353,11 @@ func formNativeResourceNamesAndConditions(names []string, resolver s3common.Nati
bktInfo, err := resolver.GetBucketInfo(bkt) bktInfo, err := resolver.GetBucketInfo(bkt)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
treeResMap[fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)] = struct{}{}
if obj == s3common.Wildcard && actionTypes.Object { // this corresponds to arn:aws:s3:::BUCKET/ or arn:aws:s3:::BUCKET/* if obj == s3common.Wildcard && actionTypes.Object { // this corresponds to arn:aws:s3:::BUCKET/ or arn:aws:s3:::BUCKET/*
combined[fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)] = struct{}{} combined[fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)] = struct{}{}
combined[fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container)] = struct{}{} combined[fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container)] = struct{}{}
@ -338,7 +393,12 @@ func formNativeResourceNamesAndConditions(names []string, resolver s3common.Nati
res = append(res, gr) res = append(res, gr)
} }
return res, nil treeRes := make([]string, 0, len(treeResMap))
for k := range treeResMap {
treeRes = append(treeRes, k)
}
return res, treeRes, nil
} }
func formWildcardNativeResource(actionTypes ActionTypes) GroupedResources { func formWildcardNativeResource(actionTypes ActionTypes) GroupedResources {
@ -380,17 +440,18 @@ func formPrincipalKey(principal string, resolver s3common.NativeResolver) (strin
return key, nil return key, nil
} }
func formNativeActionNames(names []string) ([]string, error) { func formNativeActionNames(names []string) ([]string, bool, error) {
uniqueActions := make(map[string]struct{}, len(names)) uniqueActions := make(map[string]struct{}, len(names))
var treeWrite bool
for _, action := range names { for _, action := range names {
if action == s3common.Wildcard { if action == s3common.Wildcard {
return []string{s3common.Wildcard}, nil return []string{s3common.Wildcard}, true, nil
} }
isIAM, err := s3common.ValidateAction(action) isIAM, err := s3common.ValidateAction(action)
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
if isIAM { if isIAM {
@ -398,15 +459,18 @@ func formNativeActionNames(names []string) ([]string, error) {
} }
if action[len(s3common.S3ActionPrefix):] == s3common.Wildcard { if action[len(s3common.S3ActionPrefix):] == s3common.Wildcard {
return []string{s3common.Wildcard}, nil return []string{s3common.Wildcard}, true, nil
} }
nativeActions := actionToNativeOpMap[action] nativeActions := actionToNativeOpMap[action]
if len(nativeActions) == 0 { if nativeActions.needTreeWrite {
return nil, s3common.ErrActionsNotApplicable treeWrite = true
}
if len(nativeActions.operations) == 0 {
return nil, false, s3common.ErrActionsNotApplicable
} }
for _, nativeAction := range nativeActions { for _, nativeAction := range nativeActions.operations {
uniqueActions[nativeAction] = struct{}{} uniqueActions[nativeAction] = struct{}{}
} }
} }
@ -416,5 +480,5 @@ func formNativeActionNames(names []string) ([]string, error) {
res = append(res, key) res = append(res, key)
} }
return res, nil return res, treeWrite, nil
} }

View file

@ -137,6 +137,27 @@ func TestConverters(t *testing.T) {
} }
expected := &chain.Chain{Rules: []chain.Rule{ expected := &chain.Chain{Rules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
Resources: chain.Resources{Names: []string{
fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, namespace, mockResolver.containers[bktName]),
}},
Condition: []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
},
{
Op: chain.CondStringNotEquals,
Kind: chain.KindResource,
Key: native.ProperyKeyTreeID,
Value: "",
},
},
},
{ {
Status: chain.Allow, Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodGetContainer, native.MethodPutObject, Actions: chain.Actions{Names: []string{native.MethodGetContainer, native.MethodPutObject,
@ -212,6 +233,27 @@ func TestConverters(t *testing.T) {
} }
expected := &chain.Chain{Rules: []chain.Rule{ expected := &chain.Chain{Rules: []chain.Rule{
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
Resources: chain.Resources{Names: []string{
fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, namespace, mockResolver.containers[bktName]),
}},
Condition: []chain.Condition{
{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
},
{
Op: chain.CondStringNotEquals,
Kind: chain.KindResource,
Key: native.ProperyKeyTreeID,
Value: "",
},
},
},
{ {
Status: chain.Allow, Status: chain.Allow,
Actions: chain.Actions{Names: []string{ Actions: chain.Actions{Names: []string{
@ -357,17 +399,38 @@ func TestConverters(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assertChainsEqual(t, s3Expected, s3Chain) assertChainsEqual(t, s3Expected, s3Chain)
nativeExpected := &chain.Chain{Rules: []chain.Rule{{ nativeExpected := &chain.Chain{Rules: []chain.Rule{
Status: chain.Allow, {
Actions: chain.Actions{Names: []string{native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject}}, Status: chain.Allow,
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}}, Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
Condition: []chain.Condition{{ Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
Op: chain.CondStringEquals, Condition: []chain.Condition{
Kind: chain.KindRequest, {
Key: native.PropertyKeyActorPublicKey, Op: chain.CondStringEquals,
Value: mockResolver.users[user], Kind: chain.KindRequest,
}}, Key: native.PropertyKeyActorPublicKey,
}}} Value: mockResolver.users[user],
},
{
Op: chain.CondStringNotEquals,
Kind: chain.KindResource,
Key: native.ProperyKeyTreeID,
Value: "",
},
},
},
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{native.MethodGetContainer, native.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
Condition: []chain.Condition{{
Op: chain.CondStringEquals,
Kind: chain.KindRequest,
Key: native.PropertyKeyActorPublicKey,
Value: mockResolver.users[user],
}},
},
}}
nativeChain, err := ConvertToNativeChain(p, mockResolver) nativeChain, err := ConvertToNativeChain(p, mockResolver)
require.NoError(t, err) require.NoError(t, err)
@ -695,17 +758,30 @@ func TestIPConditions(t *testing.T) {
require.Equal(t, s3Expected, s3Chain) require.Equal(t, s3Expected, s3Chain)
nativeExpected := &chain.Chain{ nativeExpected := &chain.Chain{
Rules: []chain.Rule{{ Rules: []chain.Rule{
Status: chain.Allow, {
Actions: chain.Actions{Names: []string{s3common.Wildcard}}, Status: chain.Allow,
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}}, Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
Condition: []chain.Condition{{ Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
Op: chain.CondIPAddress, Condition: []chain.Condition{{
Kind: chain.KindRequest, Op: chain.CondStringNotEquals,
Key: common.PropertyKeyFrostFSSourceIP, Kind: chain.KindResource,
Value: "203.0.113.0/24", Key: native.ProperyKeyTreeID,
}}, Value: "",
}}, }},
},
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
Condition: []chain.Condition{{
Op: chain.CondIPAddress,
Kind: chain.KindRequest,
Key: common.PropertyKeyFrostFSSourceIP,
Value: "203.0.113.0/24",
}},
},
},
} }
nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, "")) nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
@ -853,10 +929,12 @@ func TestComplexNativeConditions(t *testing.T) {
} }
expectedStatus := chain.Allow expectedStatus := chain.Allow
expectedActions := chain.Actions{Names: actionToNativeOpMap["s3:"+action]} expectedActions := chain.Actions{Names: actionToNativeOpMap["s3:"+action].operations}
expectedResource1 := chain.Resources{Names: []string{nativeResource1, nativeResource1cnr}} expectedResource1 := chain.Resources{Names: []string{nativeResource1, nativeResource1cnr}}
expectedResource23 := chain.Resources{Names: []string{nativeResource2, nativeResource2cnr, nativeResource3, nativeResource3cnr}} expectedResource23 := chain.Resources{Names: []string{nativeResource2, nativeResource2cnr, nativeResource3, nativeResource3cnr}}
treeCondition := chain.Condition{Op: chain.CondStringNotEquals, Kind: chain.KindResource, Key: native.ProperyKeyTreeID, Value: ""}
//notTreeCondition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindResource, Key: native.ProperyKeyTreeID, Value: ""} // todo
user1Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user1]} user1Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user1]}
user2Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user2]} user2Condition := chain.Condition{Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: native.PropertyKeyActorPublicKey, Value: mockResolver.users[user2]}
objectName1Condition := chain.Condition{Op: chain.CondStringLike, Kind: chain.KindResource, Key: PropertyKeyFilePath, Value: objName1} objectName1Condition := chain.Condition{Op: chain.CondStringLike, Kind: chain.KindResource, Key: PropertyKeyFilePath, Value: objName1}
@ -865,6 +943,24 @@ func TestComplexNativeConditions(t *testing.T) {
key2val2Condition := chain.Condition{Op: chain.CondStringLike, Kind: chain.KindRequest, Key: key2, Value: val2} key2val2Condition := chain.Condition{Op: chain.CondStringLike, Kind: chain.KindRequest, Key: key2, Value: val2}
expected := &chain.Chain{Rules: []chain.Rule{ expected := &chain.Chain{Rules: []chain.Rule{
{
Status: expectedStatus,
Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
Resources: chain.Resources{Names: []string{nativeResource1, nativeResource2, nativeResource3}},
Condition: []chain.Condition{
user1Condition,
treeCondition,
},
},
{
Status: expectedStatus,
Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
Resources: chain.Resources{Names: []string{nativeResource1, nativeResource2, nativeResource3}},
Condition: []chain.Condition{
user2Condition,
treeCondition,
},
},
{ {
Status: expectedStatus, Status: expectedStatus,
Actions: expectedActions, Actions: expectedActions,
@ -1443,11 +1539,24 @@ func TestWildcardConverters(t *testing.T) {
require.Equal(t, s3Expected, s3Chain) require.Equal(t, s3Expected, s3Chain)
nativeExpected := &chain.Chain{ nativeExpected := &chain.Chain{
Rules: []chain.Rule{{ Rules: []chain.Rule{
Status: chain.Allow, {
Actions: chain.Actions{Names: []string{s3common.Wildcard}}, Status: chain.Allow,
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}}, Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
}}, Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
Condition: []chain.Condition{{
Op: chain.CondStringNotEquals,
Kind: chain.KindResource,
Key: native.ProperyKeyTreeID,
Value: "",
}},
},
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
},
},
} }
nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, "")) nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
@ -1490,14 +1599,27 @@ func TestWildcardObjectsConverters(t *testing.T) {
mockResolver := newMockUserResolver(nil, []string{"bucket"}, "") mockResolver := newMockUserResolver(nil, []string{"bucket"}, "")
nativeExpected := &chain.Chain{ nativeExpected := &chain.Chain{
Rules: []chain.Rule{{ Rules: []chain.Rule{
Status: chain.Allow, {
Actions: chain.Actions{Names: []string{s3common.Wildcard}}, Status: chain.Allow,
Resources: chain.Resources{Names: []string{ Actions: chain.Actions{Names: []string{native.MethodGetObject, native.MethodPutObject}},
fmt.Sprintf(native.ResourceFormatRootContainer, mockResolver.containers["bucket"]), Resources: chain.Resources{Names: []string{fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers["bucket"])}},
fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers["bucket"]), Condition: []chain.Condition{{
}}, Op: chain.CondStringNotEquals,
}}, Kind: chain.KindResource,
Key: native.ProperyKeyTreeID,
Value: "",
}},
},
{
Status: chain.Allow,
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
Resources: chain.Resources{Names: []string{
fmt.Sprintf(native.ResourceFormatRootContainer, mockResolver.containers["bucket"]),
fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers["bucket"]),
}},
},
},
} }
nativeChain, err := ConvertToNativeChain(p, mockResolver) nativeChain, err := ConvertToNativeChain(p, mockResolver)
@ -1655,7 +1777,7 @@ func TestFromActions(t *testing.T) {
}, },
} { } {
t.Run("", func(t *testing.T) { t.Run("", func(t *testing.T) {
actions, err := formNativeActionNames([]string{tc.action}) actions, _, err := formNativeActionNames([]string{tc.action})
if tc.err { if tc.err {
require.Error(t, err) require.Error(t, err)
} else { } else {
@ -1776,13 +1898,19 @@ func TestTagsConditions(t *testing.T) {
}, },
} }
expectedNativeConditions := []chain.Condition{ expectedNativeConditions := [][]chain.Condition{
{ {{
Op: chain.CondStringNotEquals,
Kind: chain.KindResource,
Key: native.ProperyKeyTreeID,
Value: "",
}},
{{
Op: chain.CondStringEquals, Op: chain.CondStringEquals,
Kind: chain.KindRequest, Kind: chain.KindRequest,
Key: fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "tag-department"), Key: fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "tag-department"),
Value: "hr", Value: "hr",
}, }},
} }
for _, tc := range []struct { for _, tc := range []struct {
@ -1847,8 +1975,9 @@ func TestTagsConditions(t *testing.T) {
nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, "")) nativeChain, err := ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, nativeChain.Rules, 1) require.Len(t, nativeChain.Rules, 2)
require.ElementsMatch(t, expectedNativeConditions, nativeChain.Rules[0].Condition) require.ElementsMatch(t, expectedNativeConditions[0], nativeChain.Rules[0].Condition)
require.ElementsMatch(t, expectedNativeConditions[1], nativeChain.Rules[1].Condition)
} }
} }