Airat Arifullin
e2cb0640f1
* `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>
471 lines
12 KiB
Go
471 lines
12 KiB
Go
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 }
|