forked from TrueCloudLab/frostfs-s3-gw
[#680] Add new converter
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
0ba6989197
commit
4d305d0d0f
5 changed files with 542 additions and 114 deletions
2
go.mod
2
go.mod
|
@ -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
4
go.sum
|
@ -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=
|
||||||
|
|
235
pkg/policy-engine/converter_test.go
Normal file
235
pkg/policy-engine/converter_test.go
Normal 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())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +399,27 @@ 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.MethodGetObject, native.MethodPutObject}},
|
||||||
|
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects}},
|
||||||
|
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.MethodDeleteObject, native.MethodPutObject, native.MethodHeadObject, native.MethodGetObject, native.MethodRangeObject}},
|
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}},
|
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
|
||||||
|
@ -367,7 +429,8 @@ func TestConverters(t *testing.T) {
|
||||||
Key: native.PropertyKeyActorPublicKey,
|
Key: native.PropertyKeyActorPublicKey,
|
||||||
Value: mockResolver.users[user],
|
Value: mockResolver.users[user],
|
||||||
}},
|
}},
|
||||||
}}}
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
nativeChain, err := ConvertToNativeChain(p, mockResolver)
|
nativeChain, err := ConvertToNativeChain(p, mockResolver)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -695,7 +758,19 @@ 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{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,
|
Status: chain.Allow,
|
||||||
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
|
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
|
||||||
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
|
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
|
||||||
|
@ -705,7 +780,8 @@ func TestIPConditions(t *testing.T) {
|
||||||
Key: common.PropertyKeyFrostFSSourceIP,
|
Key: common.PropertyKeyFrostFSSourceIP,
|
||||||
Value: "203.0.113.0/24",
|
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{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,
|
Status: chain.Allow,
|
||||||
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
|
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
|
||||||
Resources: chain.Resources{Names: []string{native.ResourceFormatAllObjects, native.ResourceFormatAllContainers}},
|
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{native.MethodGetObject, native.MethodPutObject}},
|
||||||
|
Resources: chain.Resources{Names: []string{fmt.Sprintf(native.ResourceFormatRootContainerObjects, mockResolver.containers["bucket"])}},
|
||||||
|
Condition: []chain.Condition{{
|
||||||
|
Op: chain.CondStringNotEquals,
|
||||||
|
Kind: chain.KindResource,
|
||||||
|
Key: native.ProperyKeyTreeID,
|
||||||
|
Value: "",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
Status: chain.Allow,
|
Status: chain.Allow,
|
||||||
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
|
Actions: chain.Actions{Names: []string{s3common.Wildcard}},
|
||||||
Resources: chain.Resources{Names: []string{
|
Resources: chain.Resources{Names: []string{
|
||||||
fmt.Sprintf(native.ResourceFormatRootContainer, mockResolver.containers["bucket"]),
|
fmt.Sprintf(native.ResourceFormatRootContainer, mockResolver.containers["bucket"]),
|
||||||
fmt.Sprintf(native.ResourceFormatRootContainerObjects, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue