[#1096] eacl: Use validator from SDK

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
Pavel Karpy 2022-01-20 12:45:58 +03:00 committed by Alex Vanin
parent 759421ebbf
commit ed156cd738
12 changed files with 57 additions and 409 deletions

View file

@ -20,7 +20,6 @@ import (
objectTransportGRPC "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc"
objectService "github.com/nspcc-dev/neofs-node/pkg/services/object"
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl"
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
deletesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/delete"
deletesvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/delete/v2"
getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get"
@ -365,10 +364,7 @@ func initObjectService(c *cfg) {
),
acl.WithNextService(splitSvc),
acl.WithLocalStorage(ls),
acl.WithEACLValidatorOptions(
eacl.WithEACLSource(c.cfgObject.eaclSource),
eacl.WithLogger(c.log),
),
acl.WithEACLSource(c.cfgObject.eaclSource),
acl.WithNetmapState(c.cfgNetmap.state),
)

4
go.mod
View file

@ -12,8 +12,8 @@ require (
github.com/multiformats/go-multiaddr v0.4.0
github.com/nspcc-dev/hrw v1.0.9
github.com/nspcc-dev/neo-go v0.98.0
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659
github.com/nspcc-dev/neofs-api-go/v2 v2.11.2-0.20220114101721-227a871a04ac
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220119080627-f83ff628fb19
github.com/nspcc-dev/tzhash v1.4.0
github.com/panjf2000/ants/v2 v2.4.0
github.com/paulmach/orb v0.2.2

BIN
go.sum

Binary file not shown.

View file

@ -12,6 +12,7 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/session"
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
"github.com/nspcc-dev/neofs-node/pkg/core/container"
core "github.com/nspcc-dev/neofs-node/pkg/core/container"
"github.com/nspcc-dev/neofs-node/pkg/core/netmap"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
@ -95,9 +96,9 @@ type cfg struct {
}
type eACLCfg struct {
eACLOpts []eacl.Option
eaclSource eacl.Source
eACL *eacl.Validator
eACL *acl.Validator
localStorage *engine.StorageEngine
@ -130,7 +131,7 @@ func New(opts ...Option) Service {
opts[i](cfg)
}
cfg.eACL = eacl.NewValidator(cfg.eACLOpts...)
cfg.eACL = acl.NewValidator()
return Service{
cfg: cfg,
@ -610,6 +611,20 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
reqInfo.bearer = nil
}
var (
table *acl.Table
err error
)
if reqInfo.bearer == nil {
table, err = cfg.eaclSource.GetEACL(reqInfo.cid)
if err != nil {
return errors.Is(err, container.ErrEACLNotFound)
}
} else {
table = acl.NewTableFromV2(reqInfo.bearer.GetBody().GetEACL())
}
// if bearer token is not present, isValidBearer returns true
if !isValidBearer(reqInfo, cfg.state) {
return false
@ -637,7 +652,7 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
)
}
action := cfg.eACL.CalculateAction(new(eacl.ValidationUnit).
action := cfg.eACL.CalculateAction(new(acl.ValidationUnit).
WithRole(reqInfo.requestRole).
WithOperation(reqInfo.operation).
WithContainerID(reqInfo.cid).
@ -645,7 +660,7 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
WithHeaderSource(
eaclV2.NewMessageHeaderSource(hdrSrcOpts...),
).
WithBearerToken(reqInfo.bearer),
WithEACLTable(table),
)
return action == acl.ActionAllow

View file

@ -1,17 +0,0 @@
package eacl
import (
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
)
func WithLogger(v *logger.Logger) Option {
return func(c *cfg) {
c.logger = v
}
}
func WithEACLSource(v Source) Option {
return func(c *cfg) {
c.storage = v
}
}

View file

@ -1,7 +1,6 @@
package eacl
import (
bearer "github.com/nspcc-dev/neofs-api-go/v2/acl"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
)
@ -18,83 +17,3 @@ type Source interface {
// eACL table is not in source.
GetEACL(*cid.ID) (*eacl.Table, error)
}
// Header is an interface of string key-value header.
type Header interface {
Key() string
Value() string
}
// TypedHeaderSource is the interface that wraps
// method for selecting typed headers by type.
type TypedHeaderSource interface {
// HeadersOfType returns the list of key-value headers
// of particular type.
//
// It returns any problem encountered through the boolean
// false value.
HeadersOfType(eacl.FilterHeaderType) ([]Header, bool)
}
// ValidationUnit represents unit of check for Validator.
type ValidationUnit struct {
cid *cid.ID
role eacl.Role
op eacl.Operation
hdrSrc TypedHeaderSource
key []byte
bearer *bearer.BearerToken
}
func (u *ValidationUnit) WithContainerID(v *cid.ID) *ValidationUnit {
if u != nil {
u.cid = v
}
return u
}
func (u *ValidationUnit) WithRole(v eacl.Role) *ValidationUnit {
if u != nil {
u.role = v
}
return u
}
func (u *ValidationUnit) WithOperation(v eacl.Operation) *ValidationUnit {
if u != nil {
u.op = v
}
return u
}
func (u *ValidationUnit) WithHeaderSource(v TypedHeaderSource) *ValidationUnit {
if u != nil {
u.hdrSrc = v
}
return u
}
func (u *ValidationUnit) WithSenderKey(v []byte) *ValidationUnit {
if u != nil {
u.key = v
}
return u
}
func (u *ValidationUnit) WithBearerToken(bearer *bearer.BearerToken) *ValidationUnit {
if u != nil {
u.bearer = bearer
}
return u
}

View file

@ -10,10 +10,8 @@ import (
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-node/pkg/core/object"
eacl2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
objectSDK "github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/stretchr/testify/require"
)
@ -26,20 +24,6 @@ type testLocalStorage struct {
obj *object.Object
}
type testEACLStorage struct {
t *testing.T
expCID *cid.ID
table *eacl.Table
}
func (s *testEACLStorage) GetEACL(id *cid.ID) (*eacl.Table, error) {
require.True(s.t, s.expCID.Equal(id))
return s.table, nil
}
func (s *testLocalStorage) Head(addr *objectSDK.Address) (*object.Object, error) {
require.True(s.t, addr.ContainerID().Equal(addr.ContainerID()) && addr.ObjectID().Equal(addr.ObjectID()))
@ -109,18 +93,18 @@ func TestHeadRequest(t *testing.T) {
attr.SetValue(attrVal)
obj.SetAttributes(attr)
table := new(eacl.Table)
table := new(eaclSDK.Table)
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
senderKey := priv.PublicKey()
r := eacl.NewRecord()
r.SetOperation(eacl.OperationHead)
r.SetAction(eacl.ActionDeny)
r.AddFilter(eacl.HeaderFromObject, eacl.MatchStringEqual, attrKey, attrVal)
r.AddFilter(eacl.HeaderFromRequest, eacl.MatchStringEqual, xKey, xVal)
eacl.AddFormedTarget(r, eacl.RoleUnknown, (ecdsa.PublicKey)(*senderKey))
r := eaclSDK.NewRecord()
r.SetOperation(eaclSDK.OperationHead)
r.SetAction(eaclSDK.ActionDeny)
r.AddFilter(eaclSDK.HeaderFromObject, eaclSDK.MatchStringEqual, attrKey, attrVal)
r.AddFilter(eaclSDK.HeaderFromRequest, eaclSDK.MatchStringEqual, xKey, xVal)
eaclSDK.AddFormedTarget(r, eaclSDK.RoleUnknown, (ecdsa.PublicKey)(*senderKey))
table.AddRecord(r)
@ -131,36 +115,29 @@ func TestHeadRequest(t *testing.T) {
}
cid := addr.ContainerID()
unit := new(eacl2.ValidationUnit).
unit := new(eaclSDK.ValidationUnit).
WithContainerID(cid).
WithOperation(eacl.OperationHead).
WithOperation(eaclSDK.OperationHead).
WithSenderKey(senderKey.Bytes()).
WithHeaderSource(
NewMessageHeaderSource(
WithObjectStorage(lStorage),
WithServiceRequest(req),
),
)
).
WithEACLTable(table)
eStorage := &testEACLStorage{
t: t,
expCID: cid,
table: table,
}
validator := eaclSDK.NewValidator()
validator := eacl2.NewValidator(
eacl2.WithEACLSource(eStorage),
)
require.Equal(t, eacl.ActionDeny, validator.CalculateAction(unit))
require.Equal(t, eaclSDK.ActionDeny, validator.CalculateAction(unit))
meta.SetXHeaders(nil)
require.Equal(t, eacl.ActionAllow, validator.CalculateAction(unit))
require.Equal(t, eaclSDK.ActionAllow, validator.CalculateAction(unit))
meta.SetXHeaders(xHdrs)
obj.SetAttributes(nil)
require.Equal(t, eacl.ActionAllow, validator.CalculateAction(unit))
require.Equal(t, eaclSDK.ActionAllow, validator.CalculateAction(unit))
}

View file

@ -8,7 +8,6 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-node/pkg/core/object"
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
objectSDK "github.com/nspcc-dev/neofs-sdk-go/object"
@ -47,7 +46,7 @@ func defaultCfg() *cfg {
}
}
func NewMessageHeaderSource(opts ...Option) eacl.TypedHeaderSource {
func NewMessageHeaderSource(opts ...Option) eaclSDK.TypedHeaderSource {
cfg := defaultCfg()
for i := range opts {
@ -59,7 +58,7 @@ func NewMessageHeaderSource(opts ...Option) eacl.TypedHeaderSource {
}
}
func (h *headerSource) HeadersOfType(typ eaclSDK.FilterHeaderType) ([]eacl.Header, bool) {
func (h *headerSource) HeadersOfType(typ eaclSDK.FilterHeaderType) ([]eaclSDK.Header, bool) {
switch typ {
default:
return nil, true
@ -70,10 +69,10 @@ func (h *headerSource) HeadersOfType(typ eaclSDK.FilterHeaderType) ([]eacl.Heade
}
}
func requestHeaders(msg xHeaderSource) []eacl.Header {
func requestHeaders(msg xHeaderSource) []eaclSDK.Header {
xHdrs := msg.GetXHeaders()
res := make([]eacl.Header, 0, len(xHdrs))
res := make([]eaclSDK.Header, 0, len(xHdrs))
for i := range xHdrs {
res = append(res, sessionSDK.NewXHeaderFromV2(xHdrs[i]))
@ -82,7 +81,7 @@ func requestHeaders(msg xHeaderSource) []eacl.Header {
return res
}
func (h *headerSource) objectHeaders() ([]eacl.Header, bool) {
func (h *headerSource) objectHeaders() ([]eaclSDK.Header, bool) {
var addr *objectSDK.Address
if h.addr != nil {
addr = objectSDK.NewAddressFromV2(h.addr)
@ -119,7 +118,7 @@ func (h *headerSource) objectHeaders() ([]eacl.Header, bool) {
return hs, true
}
case *objectV2.SearchRequest:
return []eacl.Header{cidHeader(
return []eaclSDK.Header{cidHeader(
cid.NewFromV2(
req.GetBody().GetContainerID()),
)}, true
@ -165,7 +164,7 @@ func (h *headerSource) objectHeaders() ([]eacl.Header, bool) {
return nil, true
}
func (h *headerSource) localObjectHeaders(addrV2 *refs.Address) ([]eacl.Header, bool) {
func (h *headerSource) localObjectHeaders(addrV2 *refs.Address) ([]eaclSDK.Header, bool) {
addr := objectSDK.NewAddressFromV2(addrV2)
obj, err := h.storage.Head(addr)
@ -176,22 +175,22 @@ func (h *headerSource) localObjectHeaders(addrV2 *refs.Address) ([]eacl.Header,
return addressHeaders(addr), false
}
func cidHeader(cid *cid.ID) eacl.Header {
func cidHeader(cid *cid.ID) eaclSDK.Header {
return &sysObjHdr{
k: acl.FilterObjectContainerID,
v: cidValue(cid),
}
}
func oidHeader(oid *objectSDK.ID) eacl.Header {
func oidHeader(oid *objectSDK.ID) eaclSDK.Header {
return &sysObjHdr{
k: acl.FilterObjectID,
v: idValue(oid),
}
}
func addressHeaders(addr *objectSDK.Address) []eacl.Header {
res := make([]eacl.Header, 1, 2)
func addressHeaders(addr *objectSDK.Address) []eaclSDK.Header {
res := make([]eaclSDK.Header, 1, 2)
res[0] = cidHeader(addr.ContainerID())
if oid := addr.ObjectID(); oid != nil {

View file

@ -5,8 +5,8 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/acl"
"github.com/nspcc-dev/neofs-node/pkg/core/object"
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
objectSDK "github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/nspcc-dev/neofs-sdk-go/owner"
)
@ -39,9 +39,9 @@ func u64Value(v uint64) string {
return strconv.FormatUint(v, 10)
}
func headersFromObject(obj *object.Object, addr *objectSDK.Address) []eacl.Header {
func headersFromObject(obj *object.Object, addr *objectSDK.Address) []eaclSDK.Header {
// TODO: optimize allocs
res := make([]eacl.Header, 0)
res := make([]eaclSDK.Header, 0)
for ; obj != nil; obj = obj.GetParent() {
res = append(res,
@ -85,7 +85,7 @@ func headersFromObject(obj *object.Object, addr *objectSDK.Address) []eacl.Heade
)
attrs := obj.Attributes()
hs := make([]eacl.Header, 0, len(attrs))
hs := make([]eaclSDK.Header, 0, len(attrs))
for i := range attrs {
hs = append(hs, attrs[i])

View file

@ -1,189 +0,0 @@
package eacl
import (
"bytes"
"errors"
"github.com/nspcc-dev/neofs-node/pkg/core/container"
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"go.uber.org/zap"
)
// Validator is a tool that calculates
// the action on a request according
// to the extended ACL rule table.
type Validator struct {
*cfg
}
// Option represents Validator option.
type Option func(*cfg)
type cfg struct {
logger *logger.Logger
storage Source
}
func defaultCfg() *cfg {
return &cfg{
logger: zap.L(),
}
}
// NewValidator creates and initializes a new Validator using options.
func NewValidator(opts ...Option) *Validator {
cfg := defaultCfg()
for i := range opts {
opts[i](cfg)
}
return &Validator{
cfg: cfg,
}
}
// CalculateAction calculates action on the request according
// to its information represented in ValidationUnit.
//
// The action is calculated according to the application of
// eACL table of rules to the request.
//
// If the eACL table is not available at the time of the call,
// eacl.ActionUnknown is returned.
//
// If no matching table entry is found, ActionAllow is returned.
func (v *Validator) CalculateAction(unit *ValidationUnit) eacl.Action {
var (
err error
table *eacl.Table
)
if unit.bearer != nil {
table = eacl.NewTableFromV2(unit.bearer.GetBody().GetEACL())
} else {
// get eACL table by container ID
table, err = v.storage.GetEACL(unit.cid)
if err != nil {
if errors.Is(err, container.ErrEACLNotFound) {
return eacl.ActionAllow
}
v.logger.Error("could not get eACL table",
zap.String("error", err.Error()),
)
return eacl.ActionUnknown
}
}
return tableAction(unit, table)
}
// calculates action on the request based on the eACL rules.
func tableAction(unit *ValidationUnit, table *eacl.Table) eacl.Action {
for _, record := range table.Records() {
// check type of operation
if record.Operation() != unit.op {
continue
}
// check target
if !targetMatches(unit, record) {
continue
}
// check headers
switch val := matchFilters(unit.hdrSrc, record.Filters()); {
case val < 0:
// headers of some type could not be composed => allow
return eacl.ActionAllow
case val == 0:
return record.Action()
}
}
return eacl.ActionAllow
}
// returns:
// - positive value if no matching header is found for at least one filter;
// - zero if at least one suitable header is found for all filters;
// - negative value if the headers of at least one filter cannot be obtained.
func matchFilters(hdrSrc TypedHeaderSource, filters []*eacl.Filter) int {
matched := 0
for _, filter := range filters {
headers, ok := hdrSrc.HeadersOfType(filter.From())
if !ok {
return -1
}
// get headers of filtering type
for _, header := range headers {
// prevent NPE
if header == nil {
continue
}
// check header name
if header.Key() != filter.Key() {
continue
}
// get match function
matchFn, ok := mMatchFns[filter.Matcher()]
if !ok {
continue
}
// check match
if !matchFn(header, filter) {
continue
}
// increment match counter
matched++
break
}
}
return len(filters) - matched
}
// returns true if one of ExtendedACLTarget has
// suitable target OR suitable public key.
func targetMatches(unit *ValidationUnit, record *eacl.Record) bool {
for _, target := range record.Targets() {
// check public key match
if pubs := target.BinaryKeys(); len(pubs) != 0 {
for _, key := range pubs {
if bytes.Equal(key, unit.key) {
return true
}
}
continue
}
// check target group match
if unit.role == target.Role() {
return true
}
}
return false
}
// Maps match type to corresponding function.
var mMatchFns = map[eacl.Match]func(Header, *eacl.Filter) bool{
eacl.MatchStringEqual: func(header Header, filter *eacl.Filter) bool {
return header.Value() == filter.Value()
},
eacl.MatchStringNotEqual: func(header Header, filter *eacl.Filter) bool {
return header.Value() != filter.Value()
},
}

View file

@ -1,52 +0,0 @@
package eacl
import (
"math/rand"
"testing"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/stretchr/testify/require"
)
func TestTargetMatches(t *testing.T) {
pubs := make([][]byte, 3)
for i := range pubs {
pubs[i] = make([]byte, 33)
pubs[i][0] = 0x02
_, err := rand.Read(pubs[i][1:])
require.NoError(t, err)
}
tgt1 := eacl.NewTarget()
tgt1.SetBinaryKeys(pubs[0:2])
tgt1.SetRole(eacl.RoleUser)
tgt2 := eacl.NewTarget()
tgt2.SetRole(eacl.RoleOthers)
r := eacl.NewRecord()
r.SetTargets(tgt1, tgt2)
u := newValidationUnit(eacl.RoleUser, pubs[0])
require.True(t, targetMatches(u, r))
u = newValidationUnit(eacl.RoleUser, pubs[2])
require.False(t, targetMatches(u, r))
u = newValidationUnit(eacl.RoleUnknown, pubs[1])
require.True(t, targetMatches(u, r))
u = newValidationUnit(eacl.RoleOthers, pubs[2])
require.True(t, targetMatches(u, r))
u = newValidationUnit(eacl.RoleSystem, pubs[2])
require.False(t, targetMatches(u, r))
}
func newValidationUnit(role eacl.Role, key []byte) *ValidationUnit {
return &ValidationUnit{
role: role,
key: key,
}
}

View file

@ -29,10 +29,10 @@ func WithNextService(v objectSvc.ServiceServer) Option {
}
}
// WithEACLValidatorOptions returns options to set eACL validator options.
func WithEACLValidatorOptions(v ...eacl.Option) Option {
// WithEACLSource returns options to set eACL table source.
func WithEACLSource(v eacl.Source) Option {
return func(c *cfg) {
c.eACLOpts = v
c.eACLCfg.eaclSource = v
}
}