frostfs-node/services/public/object/acl_test.go

513 lines
13 KiB
Go
Raw Normal View History

package object
import (
"context"
"crypto/ecdsa"
"errors"
"testing"
"github.com/nspcc-dev/neofs-api-go/acl"
"github.com/nspcc-dev/neofs-api-go/bootstrap"
"github.com/nspcc-dev/neofs-api-go/container"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
crypto "github.com/nspcc-dev/neofs-crypto"
libacl "github.com/nspcc-dev/neofs-node/lib/acl"
"github.com/nspcc-dev/neofs-node/lib/implementations"
"github.com/nspcc-dev/neofs-node/lib/ir"
"github.com/nspcc-dev/neofs-node/lib/test"
"github.com/stretchr/testify/require"
)
type (
testACLEntity struct {
// Set of interfaces which testCommonEntity must implement, but some methods from those does not call.
serviceRequest
RequestTargeter
implementations.ACLHelper
implementations.ContainerNodesLister
implementations.ContainerOwnerChecker
acl.ExtendedACLTable
libacl.RequestInfo
// Argument interceptor. Used for ascertain of correct parameter passage between components.
f func(...interface{})
// Mocked result of any interface.
res interface{}
// Mocked error of any interface.
err error
}
)
type testBasicChecker struct {
libacl.BasicChecker
actionErr error
action bool
sticky bool
extended bool
bearer bool
}
func (t *testACLEntity) calculateRequestAction(context.Context, requestActionParams) acl.ExtendedACLAction {
return t.res.(acl.ExtendedACLAction)
}
func (t *testACLEntity) buildRequestInfo(req serviceRequest, target acl.Target) (libacl.RequestInfo, error) {
if t.f != nil {
t.f(req, target)
}
if t.err != nil {
return nil, t.err
}
return t.res.(libacl.RequestInfo), nil
}
func (t *testACLEntity) Action(table acl.ExtendedACLTable, req libacl.RequestInfo) acl.ExtendedACLAction {
if t.f != nil {
t.f(table, req)
}
return t.res.(acl.ExtendedACLAction)
}
func (t *testACLEntity) GetExtendedACLTable(_ context.Context, cid CID) (acl.ExtendedACLTable, error) {
if t.f != nil {
t.f(cid)
}
if t.err != nil {
return nil, t.err
}
return t.res.(acl.ExtendedACLTable), nil
}
func (s *testBasicChecker) Extended(uint32) bool {
return s.extended
}
func (s *testBasicChecker) Sticky(uint32) bool {
return s.sticky
}
func (s *testBasicChecker) Bearer(uint32, object.RequestType) (bool, error) {
return s.bearer, nil
}
func (s *testBasicChecker) Action(uint32, object.RequestType, acl.Target) (bool, error) {
return s.action, s.actionErr
}
func (t *testACLEntity) GetBasicACL(context.Context, CID) (uint32, error) {
if t.err != nil {
return 0, t.err
}
return t.res.(uint32), nil
}
func (t *testACLEntity) Target(context.Context, serviceRequest) acl.Target {
return t.res.(acl.Target)
}
func (t *testACLEntity) CID() CID { return CID{} }
func (t *testACLEntity) Type() object.RequestType { return t.res.(object.RequestType) }
func (t *testACLEntity) GetBearerToken() service.BearerToken { return nil }
func (t *testACLEntity) GetOwner() (*ecdsa.PublicKey, error) {
if t.err != nil {
return nil, t.err
}
return t.res.(*ecdsa.PublicKey), nil
}
func (t testACLEntity) GetIRInfo(ir.GetInfoParams) (*ir.GetInfoResult, error) {
if t.err != nil {
return nil, t.err
}
res := new(ir.GetInfoResult)
res.SetInfo(*t.res.(*ir.Info))
return res, nil
}
func (t *testACLEntity) ContainerNodesInfo(ctx context.Context, cid CID, prev int) ([]bootstrap.NodeInfo, error) {
if t.err != nil {
return nil, t.err
}
return t.res.([][]bootstrap.NodeInfo)[prev], nil
}
func (t *testACLEntity) IsContainerOwner(_ context.Context, cid CID, owner OwnerID) (bool, error) {
if t.f != nil {
t.f(cid, owner)
}
if t.err != nil {
return false, t.err
}
return t.res.(bool), nil
}
func (t testACLEntity) GetSignKeyPairs() []service.SignKeyPair {
if t.res == nil {
return nil
}
return t.res.([]service.SignKeyPair)
}
func TestPreprocessor(t *testing.T) {
ctx := context.TODO()
t.Run("empty request", func(t *testing.T) {
require.PanicsWithValue(t, pmEmptyServiceRequest, func() {
_ = new(aclPreProcessor).preProcess(ctx, nil)
})
})
t.Run("everything is okay", func(t *testing.T) {
rule := uint32(0x00000003)
// set F-bit
rule |= 1 << 28
checker := new(libacl.BasicACLChecker)
preprocessor := aclPreProcessor{
log: test.NewTestLogger(false),
aclInfoReceiver: aclInfoReceiver{
basicACLGetter: &testACLEntity{res: rule},
basicChecker: checker,
targetFinder: &testACLEntity{res: acl.Target_Others},
},
basicChecker: checker,
}
require.NoError(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet}))
preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: acl.Target_System}
require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet}))
preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: acl.Target_User}
require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet}))
})
t.Run("can't fetch container", func(t *testing.T) {
preprocessor := aclPreProcessor{
log: test.NewTestLogger(false),
aclInfoReceiver: aclInfoReceiver{
basicACLGetter: &testACLEntity{err: container.ErrNotFound},
targetFinder: &testACLEntity{res: acl.Target_Others},
},
}
require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet}))
})
t.Run("sticky bit", func(t *testing.T) {
checker := &testBasicChecker{
actionErr: nil,
action: true,
sticky: true,
}
s := &aclPreProcessor{
log: test.NewTestLogger(false),
aclInfoReceiver: aclInfoReceiver{
basicACLGetter: &testACLEntity{
res: uint32(0),
},
basicChecker: checker,
targetFinder: &testACLEntity{
res: acl.Target_User,
},
},
basicChecker: checker,
}
ownerKey := &test.DecodeKey(0).PublicKey
ownerID, err := refs.NewOwnerID(ownerKey)
require.NoError(t, err)
okItems := []func() []serviceRequest{
// Read requests
func() []serviceRequest {
return []serviceRequest{
new(object.GetRequest),
new(object.HeadRequest),
new(object.SearchRequest),
new(GetRangeRequest),
new(object.GetRangeHashRequest),
}
},
// PutRequest / DeleteRequest (w/o token)
func() []serviceRequest {
req := object.MakePutRequestHeader(&Object{
SystemHeader: SystemHeader{
OwnerID: ownerID,
},
})
req.AddSignKey(nil, ownerKey)
putReq := &putRequest{
PutRequest: req,
}
delReq := new(object.DeleteRequest)
delReq.OwnerID = ownerID
delReq.AddSignKey(nil, ownerKey)
return []serviceRequest{putReq, delReq}
},
// PutRequest / DeleteRequest (w/ token)
func() []serviceRequest {
token := new(service.Token)
token.SetOwnerID(ownerID)
token.SetOwnerKey(crypto.MarshalPublicKey(ownerKey))
req := object.MakePutRequestHeader(&Object{
SystemHeader: SystemHeader{
OwnerID: ownerID,
},
})
req.SetToken(token)
putReq := &putRequest{
PutRequest: req,
}
delReq := new(object.DeleteRequest)
delReq.OwnerID = ownerID
delReq.SetToken(token)
return []serviceRequest{putReq, delReq}
},
}
failItems := []func() []serviceRequest{
// PutRequest / DeleteRequest (w/o token and wrong owner)
func() []serviceRequest {
otherOwner := ownerID
otherOwner[0]++
req := object.MakePutRequestHeader(&Object{
SystemHeader: SystemHeader{
OwnerID: otherOwner,
},
})
req.AddSignKey(nil, ownerKey)
putReq := &putRequest{
PutRequest: req,
}
delReq := new(object.DeleteRequest)
delReq.OwnerID = otherOwner
delReq.AddSignKey(nil, ownerKey)
return []serviceRequest{putReq, delReq}
},
// PutRequest / DeleteRequest (w/ token w/ wrong owner)
func() []serviceRequest {
otherOwner := ownerID
otherOwner[0]++
token := new(service.Token)
token.SetOwnerID(ownerID)
token.SetOwnerKey(crypto.MarshalPublicKey(ownerKey))
req := object.MakePutRequestHeader(&Object{
SystemHeader: SystemHeader{
OwnerID: otherOwner,
},
})
req.SetToken(token)
putReq := &putRequest{
PutRequest: req,
}
delReq := new(object.DeleteRequest)
delReq.OwnerID = otherOwner
delReq.SetToken(token)
return []serviceRequest{putReq, delReq}
},
}
for _, ok := range okItems {
for _, req := range ok() {
require.NoError(t, s.preProcess(ctx, req))
}
}
for _, fail := range failItems {
for _, req := range fail() {
require.Error(t, s.preProcess(ctx, req))
}
}
})
t.Run("extended ACL", func(t *testing.T) {
target := acl.Target_Others
req := &testACLEntity{
res: object.RequestGet,
}
actCalc := new(testACLEntity)
checker := &testBasicChecker{
action: true,
extended: true,
}
s := &aclPreProcessor{
log: test.NewTestLogger(false),
aclInfoReceiver: aclInfoReceiver{
basicACLGetter: &testACLEntity{
res: uint32(1),
},
basicChecker: checker,
targetFinder: &testACLEntity{
res: target,
},
},
basicChecker: checker,
reqActionCalc: actCalc,
}
// force to return non-ActionAllow
actCalc.res = acl.ActionAllow + 1
require.EqualError(t, s.preProcess(ctx, req), errAccessDenied.Error())
// force to return ActionAllow
actCalc.res = acl.ActionAllow
require.NoError(t, s.preProcess(ctx, req))
})
}
func TestTargetFinder(t *testing.T) {
ctx := context.TODO()
irKey := test.DecodeKey(2)
containerKey := test.DecodeKey(3)
prevContainerKey := test.DecodeKey(4)
irInfo := new(ir.Info)
irNode := ir.Node{}
irNode.SetKey(crypto.MarshalPublicKey(&irKey.PublicKey))
irInfo.SetNodes([]ir.Node{irNode})
finder := &targetFinder{
log: test.NewTestLogger(false),
irStorage: &testACLEntity{
res: irInfo,
},
cnrLister: &testACLEntity{res: [][]bootstrap.NodeInfo{
{{PubKey: crypto.MarshalPublicKey(&containerKey.PublicKey)}},
{{PubKey: crypto.MarshalPublicKey(&prevContainerKey.PublicKey)}},
}},
}
t.Run("trusted node", func(t *testing.T) {
pk := &test.DecodeKey(0).PublicKey
ownerKey := &test.DecodeKey(1).PublicKey
owner, err := refs.NewOwnerID(ownerKey)
require.NoError(t, err)
token := new(service.Token)
token.SetSessionKey(crypto.MarshalPublicKey(pk))
token.SetOwnerKey(crypto.MarshalPublicKey(ownerKey))
token.SetOwnerID(owner)
req := new(object.SearchRequest)
req.ContainerID = CID{1, 2, 3}
req.SetToken(token)
req.AddSignKey(nil, pk)
finder.cnrOwnerChecker = &testACLEntity{
f: func(items ...interface{}) {
require.Equal(t, req.CID(), items[0])
require.Equal(t, owner, items[1])
},
res: true,
}
require.Equal(t, acl.Target_User, finder.Target(ctx, req))
})
t.Run("container owner", func(t *testing.T) {
finder.cnrOwnerChecker = &testACLEntity{res: true}
req := new(object.SearchRequest)
req.AddSignKey(nil, &test.DecodeKey(0).PublicKey)
require.Equal(t, acl.Target_User, finder.Target(ctx, req))
})
t.Run("system owner", func(t *testing.T) {
finder.cnrOwnerChecker = &testACLEntity{res: false}
req := new(object.SearchRequest)
req.AddSignKey(nil, &irKey.PublicKey)
require.Equal(t, acl.Target_System, finder.Target(ctx, req))
req = new(object.SearchRequest)
req.AddSignKey(nil, &containerKey.PublicKey)
require.Equal(t, acl.Target_System, finder.Target(ctx, req))
req = new(object.SearchRequest)
req.AddSignKey(nil, &prevContainerKey.PublicKey)
require.Equal(t, acl.Target_System, finder.Target(ctx, req))
})
t.Run("other owner", func(t *testing.T) {
finder.cnrOwnerChecker = &testACLEntity{res: false}
req := new(object.SearchRequest)
req.AddSignKey(nil, &test.DecodeKey(0).PublicKey)
require.Equal(t, acl.Target_Others, finder.Target(ctx, req))
})
t.Run("can't fetch request owner", func(t *testing.T) {
req := new(object.SearchRequest)
require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req))
})
t.Run("can't fetch container", func(t *testing.T) {
finder.cnrOwnerChecker = &testACLEntity{err: container.ErrNotFound}
req := new(object.SearchRequest)
req.AddSignKey(nil, &test.DecodeKey(0).PublicKey)
require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req))
})
t.Run("can't fetch ir list", func(t *testing.T) {
finder.cnrOwnerChecker = &testACLEntity{res: false}
finder.irStorage = &testACLEntity{err: errors.New("blockchain is busy")}
req := new(object.SearchRequest)
req.AddSignKey(nil, &test.DecodeKey(0).PublicKey)
require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req))
})
t.Run("can't fetch container list", func(t *testing.T) {
finder.cnrOwnerChecker = &testACLEntity{res: false}
finder.cnrLister = &testACLEntity{err: container.ErrNotFound}
req := new(object.SearchRequest)
req.AddSignKey(nil, &test.DecodeKey(0).PublicKey)
require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req))
})
}