forked from TrueCloudLab/frostfs-node
[#1501] util: Move eACL-to-APE converter to pkg/util
* `ConvertEACLToAPE` is useful method which couldn't be imported out of frostfs-node so far as it has been in `internal` * Since `ConvertEACLToAPE` and related structures and unit-tests are placed in `pkg/util` Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
9f4ce600ac
commit
e2cb0640f1
3 changed files with 1 additions and 1 deletions
280
pkg/util/ape/converter.go
Normal file
280
pkg/util/ape/converter.go
Normal file
|
@ -0,0 +1,280 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
v2acl "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
)
|
||||
|
||||
type ConvertEACLError struct {
|
||||
nested error
|
||||
}
|
||||
|
||||
func (e *ConvertEACLError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return "failed to convert eACL table to policy engine chain: " + e.nested.Error()
|
||||
}
|
||||
|
||||
func (e *ConvertEACLError) Unwrap() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.nested
|
||||
}
|
||||
|
||||
// ConvertEACLToAPE converts eacl.Table to apechain.Chain.
|
||||
func ConvertEACLToAPE(eaclTable *eacl.Table) (*apechain.Chain, error) {
|
||||
if eaclTable == nil {
|
||||
return nil, nil
|
||||
}
|
||||
res := &apechain.Chain{
|
||||
MatchType: apechain.MatchTypeFirstMatch,
|
||||
}
|
||||
|
||||
resource := getResource(eaclTable)
|
||||
|
||||
for _, eaclRecord := range eaclTable.Records() {
|
||||
if len(eaclRecord.Targets()) == 0 {
|
||||
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
|
||||
// and https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L36
|
||||
// such record doesn't have any effect
|
||||
continue
|
||||
}
|
||||
|
||||
st, err := actionToStatus(eaclRecord.Action())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
act, err := operationToAction(eaclRecord.Operation())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(eaclRecord.Filters()) == 0 {
|
||||
res.Rules = appendTargetsOnly(res.Rules, st, act, resource, eaclRecord.Targets())
|
||||
} else {
|
||||
res.Rules, err = appendTargetsAndFilters(res.Rules, st, act, resource, eaclRecord.Targets(), eaclRecord.Filters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func apeRoleConds(role eacl.Role) (res []apechain.Condition) {
|
||||
switch role {
|
||||
case eacl.RoleSystem:
|
||||
res = append(res,
|
||||
apechain.Condition{
|
||||
Op: apechain.CondStringEquals,
|
||||
Kind: apechain.KindRequest,
|
||||
Key: nativeschema.PropertyKeyActorRole,
|
||||
Value: nativeschema.PropertyValueContainerRoleContainer,
|
||||
},
|
||||
)
|
||||
res = append(res,
|
||||
apechain.Condition{
|
||||
Op: apechain.CondStringEquals,
|
||||
Kind: apechain.KindRequest,
|
||||
Key: nativeschema.PropertyKeyActorRole,
|
||||
Value: nativeschema.PropertyValueContainerRoleIR,
|
||||
},
|
||||
)
|
||||
case eacl.RoleOthers:
|
||||
res = append(res,
|
||||
apechain.Condition{
|
||||
Op: apechain.CondStringEquals,
|
||||
Kind: apechain.KindRequest,
|
||||
Key: nativeschema.PropertyKeyActorRole,
|
||||
Value: nativeschema.PropertyValueContainerRoleOthers,
|
||||
},
|
||||
)
|
||||
case eacl.RoleUser:
|
||||
res = append(res,
|
||||
apechain.Condition{
|
||||
Op: apechain.CondStringEquals,
|
||||
Kind: apechain.KindRequest,
|
||||
Key: nativeschema.PropertyKeyActorRole,
|
||||
Value: nativeschema.PropertyValueContainerRoleOwner,
|
||||
},
|
||||
)
|
||||
case eacl.RoleUnknown:
|
||||
// such condition has no effect
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func appendTargetsOnly(source []apechain.Rule, st apechain.Status, act apechain.Actions, res apechain.Resources, targets []eacl.Target) []apechain.Rule {
|
||||
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
|
||||
// role OR public key must be equal
|
||||
rule := apechain.Rule{
|
||||
Status: st,
|
||||
Actions: act,
|
||||
Resources: res,
|
||||
Any: true,
|
||||
}
|
||||
for _, target := range targets {
|
||||
rule.Condition = append(rule.Condition, apeRoleConds(target.Role())...)
|
||||
for _, binKey := range target.BinaryKeys() {
|
||||
var pubKeyCondition apechain.Condition
|
||||
pubKeyCondition.Kind = apechain.KindRequest
|
||||
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
|
||||
pubKeyCondition.Value = hex.EncodeToString(binKey)
|
||||
pubKeyCondition.Op = apechain.CondStringEquals
|
||||
rule.Condition = append(rule.Condition, pubKeyCondition)
|
||||
}
|
||||
}
|
||||
return append(source, rule)
|
||||
}
|
||||
|
||||
func appendTargetsAndFilters(source []apechain.Rule, st apechain.Status, act apechain.Actions, res apechain.Resources,
|
||||
targets []eacl.Target, filters []eacl.Filter,
|
||||
) ([]apechain.Rule, error) {
|
||||
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
|
||||
// role OR public key must be equal
|
||||
// so filters are repeated for each role and public key
|
||||
var err error
|
||||
for _, target := range targets {
|
||||
rule := apechain.Rule{
|
||||
Status: st,
|
||||
Actions: act,
|
||||
Resources: res,
|
||||
}
|
||||
rule.Condition = append(rule.Condition, apeRoleConds(target.Role())...)
|
||||
rule.Condition, err = appendFilters(rule.Condition, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source = append(source, rule)
|
||||
|
||||
for _, binKey := range target.BinaryKeys() {
|
||||
rule := apechain.Rule{
|
||||
Status: st,
|
||||
Actions: act,
|
||||
Resources: res,
|
||||
}
|
||||
var pubKeyCondition apechain.Condition
|
||||
pubKeyCondition.Kind = apechain.KindRequest
|
||||
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
|
||||
pubKeyCondition.Value = hex.EncodeToString(binKey)
|
||||
pubKeyCondition.Op = apechain.CondStringEquals
|
||||
|
||||
rule.Condition = append(rule.Condition, pubKeyCondition)
|
||||
rule.Condition, err = appendFilters(rule.Condition, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source = append(source, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func appendFilters(source []apechain.Condition, filters []eacl.Filter) ([]apechain.Condition, error) {
|
||||
for _, filter := range filters {
|
||||
var cond apechain.Condition
|
||||
var isObject bool
|
||||
if filter.From() == eacl.HeaderFromObject {
|
||||
cond.Kind = apechain.KindResource
|
||||
isObject = true
|
||||
} else if filter.From() == eacl.HeaderFromRequest {
|
||||
cond.Kind = apechain.KindRequest
|
||||
} else {
|
||||
return nil, &ConvertEACLError{nested: fmt.Errorf("unknown filter from: %d", filter.From())}
|
||||
}
|
||||
|
||||
if filter.Matcher() == eacl.MatchStringEqual {
|
||||
cond.Op = apechain.CondStringEquals
|
||||
} else if filter.Matcher() == eacl.MatchStringNotEqual {
|
||||
cond.Op = apechain.CondStringNotEquals
|
||||
} else {
|
||||
return nil, &ConvertEACLError{nested: fmt.Errorf("unknown filter matcher: %d", filter.Matcher())}
|
||||
}
|
||||
|
||||
cond.Key = eaclKeyToAPEKey(filter.Key(), isObject)
|
||||
cond.Value = filter.Value()
|
||||
|
||||
source = append(source, cond)
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func eaclKeyToAPEKey(key string, isObject bool) string {
|
||||
if !isObject {
|
||||
return key
|
||||
}
|
||||
switch key {
|
||||
default:
|
||||
return key
|
||||
case v2acl.FilterObjectVersion:
|
||||
return nativeschema.PropertyKeyObjectVersion
|
||||
case v2acl.FilterObjectID:
|
||||
return nativeschema.PropertyKeyObjectID
|
||||
case v2acl.FilterObjectContainerID:
|
||||
return nativeschema.PropertyKeyObjectContainerID
|
||||
case v2acl.FilterObjectOwnerID:
|
||||
return nativeschema.PropertyKeyObjectOwnerID
|
||||
case v2acl.FilterObjectCreationEpoch:
|
||||
return nativeschema.PropertyKeyObjectCreationEpoch
|
||||
case v2acl.FilterObjectPayloadLength:
|
||||
return nativeschema.PropertyKeyObjectPayloadLength
|
||||
case v2acl.FilterObjectPayloadHash:
|
||||
return nativeschema.PropertyKeyObjectPayloadHash
|
||||
case v2acl.FilterObjectType:
|
||||
return nativeschema.PropertyKeyObjectType
|
||||
case v2acl.FilterObjectHomomorphicHash:
|
||||
return nativeschema.PropertyKeyObjectHomomorphicHash
|
||||
}
|
||||
}
|
||||
|
||||
func getResource(eaclTable *eacl.Table) apechain.Resources {
|
||||
cnrID, isSet := eaclTable.CID()
|
||||
if isSet {
|
||||
return apechain.Resources{
|
||||
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
|
||||
}
|
||||
}
|
||||
return apechain.Resources{
|
||||
Names: []string{nativeschema.ResourceFormatRootObjects},
|
||||
}
|
||||
}
|
||||
|
||||
func actionToStatus(a eacl.Action) (apechain.Status, error) {
|
||||
switch a {
|
||||
case eacl.ActionAllow:
|
||||
return apechain.Allow, nil
|
||||
case eacl.ActionDeny:
|
||||
return apechain.AccessDenied, nil
|
||||
default:
|
||||
return apechain.NoRuleFound, &ConvertEACLError{nested: fmt.Errorf("unknown action: %d", a)}
|
||||
}
|
||||
}
|
||||
|
||||
var eaclOperationToEngineAction = map[eacl.Operation]apechain.Actions{
|
||||
eacl.OperationGet: {Names: []string{nativeschema.MethodGetObject}},
|
||||
eacl.OperationHead: {Names: []string{nativeschema.MethodHeadObject}},
|
||||
eacl.OperationPut: {Names: []string{nativeschema.MethodPutObject}},
|
||||
eacl.OperationDelete: {Names: []string{nativeschema.MethodDeleteObject}},
|
||||
eacl.OperationSearch: {Names: []string{nativeschema.MethodSearchObject}},
|
||||
eacl.OperationRange: {Names: []string{nativeschema.MethodRangeObject}},
|
||||
eacl.OperationRangeHash: {Names: []string{nativeschema.MethodHashObject}},
|
||||
}
|
||||
|
||||
func operationToAction(op eacl.Operation) (apechain.Actions, error) {
|
||||
if v, ok := eaclOperationToEngineAction[op]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return apechain.Actions{}, &ConvertEACLError{nested: fmt.Errorf("unknown operation: %d", op)}
|
||||
}
|
471
pkg/util/ape/converter_test.go
Normal file
471
pkg/util/ape/converter_test.go
Normal file
|
@ -0,0 +1,471 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEACLTableWithoutRecords(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tb := eacl.NewTable()
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
req := &testRequest{
|
||||
res: &testResource{name: nativeschema.ResourceFormatRootObjects},
|
||||
}
|
||||
|
||||
compare(t, vu, ch, req)
|
||||
|
||||
cnrID := cidtest.ID()
|
||||
tb.SetCID(cnrID)
|
||||
vu.WithContainerID(&cnrID)
|
||||
req.res.name = fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())
|
||||
|
||||
ch, err = ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
|
||||
func TestNoTargets(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, act := range []eacl.Action{eacl.ActionAllow, eacl.ActionDeny} {
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
vu.WithRole(eacl.RoleOthers)
|
||||
|
||||
// deny delete without role or key specified
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(act)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
record.AddObjectContainerIDFilter(eacl.MatchStringEqual, cnrID)
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorRole: eacl.RoleOthers.String(),
|
||||
},
|
||||
res: &testResource{name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoFilters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("target match by role only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, act := range []eacl.Action{eacl.ActionAllow, eacl.ActionDeny} {
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
vu.WithRole(eacl.RoleOthers)
|
||||
|
||||
// allow/deny for OTHERS
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(act)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
|
||||
target := eacl.NewTarget()
|
||||
target.SetRole(eacl.RoleOthers)
|
||||
record.SetTargets(*target)
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorRole: nativeschema.PropertyValueContainerRoleOthers,
|
||||
},
|
||||
res: &testResource{name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("target match by role and public key", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, act := range []eacl.Action{eacl.ActionAllow, eacl.ActionDeny} {
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
vu.WithRole(eacl.RoleOthers)
|
||||
|
||||
// allow/deny for OTHERS
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(act)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
|
||||
p1, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
p2, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
vu.WithSenderKey(p2.PublicKey().Bytes())
|
||||
|
||||
target := eacl.NewTarget()
|
||||
target.SetRole(eacl.RoleOthers)
|
||||
target.SetBinaryKeys([][]byte{p1.PublicKey().Bytes(), p2.PublicKey().Bytes()})
|
||||
record.SetTargets(*target)
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorRole: nativeschema.PropertyValueContainerRoleOthers,
|
||||
nativeschema.PropertyKeyActorPublicKey: string(p2.PublicKey().Bytes()),
|
||||
},
|
||||
res: &testResource{name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("target match by public key only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, act := range []eacl.Action{eacl.ActionAllow, eacl.ActionDeny} {
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
|
||||
// allow/deny for OTHERS
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(act)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
|
||||
p1, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
p2, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
vu.WithSenderKey(p2.PublicKey().Bytes())
|
||||
|
||||
target := eacl.NewTarget()
|
||||
target.SetRole(eacl.RoleOthers)
|
||||
target.SetBinaryKeys([][]byte{p1.PublicKey().Bytes(), p2.PublicKey().Bytes()})
|
||||
record.SetTargets(*target)
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(p2.PublicKey().Bytes()),
|
||||
},
|
||||
res: &testResource{name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("target doesn't match", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, act := range []eacl.Action{eacl.ActionAllow, eacl.ActionDeny} {
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
vu.WithRole(eacl.RoleSystem)
|
||||
|
||||
// allow/deny for OTHERS
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(act)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
|
||||
target := eacl.NewTarget()
|
||||
target.SetRole(eacl.RoleOthers)
|
||||
record.SetTargets(*target)
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorRole: eacl.RoleSystem.String(),
|
||||
},
|
||||
res: &testResource{name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithFilters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("object attributes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const attrKey = "attribute_1"
|
||||
const attrValue = "attribute_1_value"
|
||||
|
||||
for _, act := range []eacl.Action{eacl.ActionAllow, eacl.ActionDeny} {
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
vu.WithRole(eacl.RoleOthers)
|
||||
vu.WithHeaderSource(&testHeaderSource{
|
||||
headers: map[eacl.FilterHeaderType][]eacl.Header{
|
||||
eacl.HeaderFromObject: {&testHeader{key: attrKey, value: attrValue}},
|
||||
},
|
||||
})
|
||||
|
||||
// allow/deny for OTHERS
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(act)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
|
||||
target := eacl.NewTarget()
|
||||
target.SetRole(eacl.RoleOthers)
|
||||
record.SetTargets(*target)
|
||||
|
||||
record.AddObjectAttributeFilter(eacl.MatchStringEqual, attrKey, attrValue)
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorRole: nativeschema.PropertyValueContainerRoleOthers,
|
||||
},
|
||||
res: &testResource{
|
||||
name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString()),
|
||||
props: map[string]string{
|
||||
attrKey: attrValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("request attributes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const attrKey = "attribute_1"
|
||||
const attrValue = "attribute_1_value"
|
||||
|
||||
for _, act := range []eacl.Action{eacl.ActionAllow, eacl.ActionDeny} {
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
vu.WithRole(eacl.RoleOthers)
|
||||
vu.WithHeaderSource(&testHeaderSource{
|
||||
headers: map[eacl.FilterHeaderType][]eacl.Header{
|
||||
eacl.HeaderFromRequest: {&testHeader{key: attrKey, value: attrValue}},
|
||||
},
|
||||
})
|
||||
|
||||
// allow/deny for OTHERS
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(act)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
|
||||
target := eacl.NewTarget()
|
||||
target.SetRole(eacl.RoleOthers)
|
||||
record.SetTargets(*target)
|
||||
|
||||
record.AddFilter(eacl.HeaderFromRequest, eacl.MatchStringEqual, attrKey, attrValue)
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorRole: nativeschema.PropertyValueContainerRoleOthers,
|
||||
attrKey: attrValue,
|
||||
},
|
||||
res: &testResource{
|
||||
name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString()),
|
||||
},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNoHeader(t *testing.T) {
|
||||
t.Skip("Should pass after https://git.frostfs.info/TrueCloudLab/policy-engine/issues/8#issuecomment-26126")
|
||||
|
||||
t.Parallel()
|
||||
|
||||
const attrKey = "attribute_1"
|
||||
cnrID := cidtest.ID()
|
||||
tb := eacl.NewTable()
|
||||
tb.SetCID(cnrID)
|
||||
|
||||
vu := &eacl.ValidationUnit{}
|
||||
vu.WithEACLTable(tb)
|
||||
vu.WithContainerID(&cnrID)
|
||||
vu.WithRole(eacl.RoleOthers)
|
||||
vu.WithHeaderSource(&testHeaderSource{
|
||||
headers: map[eacl.FilterHeaderType][]eacl.Header{
|
||||
eacl.HeaderFromRequest: {},
|
||||
},
|
||||
})
|
||||
|
||||
// allow/deny for OTHERS
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(eacl.ActionDeny)
|
||||
record.SetOperation(eacl.OperationDelete)
|
||||
|
||||
target := eacl.NewTarget()
|
||||
target.SetRole(eacl.RoleOthers)
|
||||
record.SetTargets(*target)
|
||||
|
||||
record.AddFilter(eacl.HeaderFromRequest, eacl.MatchStringEqual, attrKey, "")
|
||||
|
||||
tb.AddRecord(record)
|
||||
|
||||
ch, err := ConvertEACLToAPE(tb)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &testRequest{
|
||||
props: map[string]string{
|
||||
nativeschema.PropertyKeyActorRole: eacl.RoleOthers.String(),
|
||||
},
|
||||
res: &testResource{
|
||||
name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString()),
|
||||
},
|
||||
}
|
||||
compare(t, vu, ch, req)
|
||||
}
|
||||
|
||||
func compare(t *testing.T, vu *eacl.ValidationUnit, ch *apechain.Chain, req *testRequest) {
|
||||
validator := eacl.NewValidator()
|
||||
for eaclOp, apeOp := range eaclOperationToEngineAction {
|
||||
vu.WithOperation(eaclOp)
|
||||
req.op = apeOp.Names[0]
|
||||
|
||||
eaclAct, recordFound := validator.CalculateAction(vu)
|
||||
apeSt, ruleFound := ch.Match(req)
|
||||
|
||||
require.Equal(t, recordFound, ruleFound)
|
||||
require.NotEqual(t, eacl.ActionUnknown, eaclAct)
|
||||
if eaclAct == eacl.ActionAllow {
|
||||
if recordFound {
|
||||
require.Equal(t, apechain.Allow, apeSt)
|
||||
} else {
|
||||
require.Equal(t, apechain.NoRuleFound, apeSt)
|
||||
}
|
||||
} else {
|
||||
require.Equal(t, apechain.AccessDenied, apeSt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testRequest struct {
|
||||
op string
|
||||
props map[string]string
|
||||
res *testResource
|
||||
}
|
||||
|
||||
func (r *testRequest) Operation() string {
|
||||
return r.op
|
||||
}
|
||||
|
||||
func (r *testRequest) Property(key string) string {
|
||||
if v, ok := r.props[key]; ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *testRequest) Resource() resource.Resource {
|
||||
return r.res
|
||||
}
|
||||
|
||||
type testResource struct {
|
||||
name string
|
||||
props map[string]string
|
||||
}
|
||||
|
||||
func (r *testResource) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *testResource) Property(key string) string {
|
||||
if v, ok := r.props[key]; ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type testHeaderSource struct {
|
||||
headers map[eacl.FilterHeaderType][]eacl.Header
|
||||
}
|
||||
|
||||
func (s *testHeaderSource) HeadersOfType(t eacl.FilterHeaderType) ([]eacl.Header, bool) {
|
||||
v, ok := s.headers[t]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
type testHeader struct {
|
||||
key, value string
|
||||
}
|
||||
|
||||
func (h *testHeader) Key() string { return h.key }
|
||||
func (h *testHeader) Value() string { return h.value }
|
Loading…
Add table
Add a link
Reference in a new issue