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" policyengine "git.frostfs.info/TrueCloudLab/policy-engine" 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, tb, 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, tb, 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, tb, 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: eacl.RoleOthers.String(), }, res: &testResource{name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())}, } compare(t, tb, 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: eacl.RoleOthers.String(), nativeschema.PropertyKeyActorPublicKey: string(p2.PublicKey().Bytes()), }, res: &testResource{name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())}, } compare(t, tb, 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, tb, 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, tb, 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: eacl.RoleOthers.String(), }, res: &testResource{ name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString()), props: map[string]string{ attrKey: attrValue, }, }, } compare(t, tb, 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: eacl.RoleOthers.String(), attrKey: attrValue, }, res: &testResource{ name: fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString()), }, } compare(t, tb, 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, tb, vu, ch, req) } func compare(t *testing.T, eaclTable *eacl.Table, vu *eacl.ValidationUnit, ch *policyengine.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, policyengine.Allow, apeSt) } else { require.Equal(t, policyengine.NoRuleFound, apeSt) } } else { require.Equal(t, policyengine.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() policyengine.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 }