[#67] object/eacl: Implement eACL validator
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
44fcd2f212
commit
69a69cdbee
9 changed files with 833 additions and 0 deletions
38
pkg/services/object/acl/eacl/opts.go
Normal file
38
pkg/services/object/acl/eacl/opts.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/client/container/wrapper"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
|
||||
)
|
||||
|
||||
type morphStorage struct {
|
||||
w *wrapper.Wrapper
|
||||
}
|
||||
|
||||
func (s *morphStorage) GetEACL(cid *container.ID) (*eacl.Table, error) {
|
||||
table, _, err := s.w.GetEACL(cid)
|
||||
|
||||
return table, err
|
||||
}
|
||||
|
||||
func WithLogger(v *logger.Logger) Option {
|
||||
return func(c *cfg) {
|
||||
c.logger = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithEACLStorage(v Storage) Option {
|
||||
return func(c *cfg) {
|
||||
c.storage = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithMorphClient(v *wrapper.Wrapper) Option {
|
||||
return func(c *cfg) {
|
||||
c.storage = &morphStorage{
|
||||
w: v,
|
||||
}
|
||||
}
|
||||
}
|
86
pkg/services/object/acl/eacl/types.go
Normal file
86
pkg/services/object/acl/eacl/types.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||
)
|
||||
|
||||
// Storage is the interface that wraps
|
||||
// basic methods of extended ACL table storage.
|
||||
type Storage interface {
|
||||
// GetEACL reads the table from the storage by identifier.
|
||||
// It returns any error encountered.
|
||||
//
|
||||
// GetEACL must return exactly one non-nil value.
|
||||
GetEACL(*container.ID) (*eacl.Table, error)
|
||||
}
|
||||
|
||||
// Header is an interface of string key-value header.
|
||||
type Header interface {
|
||||
GetKey() string
|
||||
GetValue() 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 *container.ID
|
||||
|
||||
role eacl.Role
|
||||
|
||||
op eacl.Operation
|
||||
|
||||
hdrSrc TypedHeaderSource
|
||||
|
||||
key []byte
|
||||
}
|
||||
|
||||
func (u *ValidationUnit) WithContainerID(v *container.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
|
||||
}
|
175
pkg/services/object/acl/eacl/v2/eacl_test.go
Normal file
175
pkg/services/object/acl/eacl/v2/eacl_test.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||
eacl2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/util/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testLocalStorage struct {
|
||||
t *testing.T
|
||||
|
||||
expAddr *objectSDK.Address
|
||||
|
||||
obj *object.Object
|
||||
}
|
||||
|
||||
type testEACLStorage struct {
|
||||
t *testing.T
|
||||
|
||||
expCID *container.ID
|
||||
|
||||
table *eacl.Table
|
||||
}
|
||||
|
||||
func (s *testEACLStorage) GetEACL(id *container.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.GetContainerID().Equal(addr.GetContainerID()) && addr.GetObjectID().Equal(addr.GetObjectID()))
|
||||
|
||||
return s.obj, nil
|
||||
}
|
||||
|
||||
func testID(t *testing.T) *objectSDK.ID {
|
||||
cs := [sha256.Size]byte{}
|
||||
|
||||
_, err := rand.Read(cs[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
id := objectSDK.NewID()
|
||||
id.SetSHA256(cs)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func testCID(t *testing.T) *container.ID {
|
||||
cs := [sha256.Size]byte{}
|
||||
|
||||
_, err := rand.Read(cs[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
id := container.NewID()
|
||||
id.SetSHA256(cs)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func testAddress(t *testing.T) *objectSDK.Address {
|
||||
addr := objectSDK.NewAddress()
|
||||
addr.SetObjectID(testID(t))
|
||||
addr.SetContainerID(testCID(t))
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
func testXHeaders(strs ...string) []*session.XHeader {
|
||||
res := make([]*session.XHeader, 0, len(strs)/2)
|
||||
|
||||
for i := 0; i < len(strs); i += 2 {
|
||||
x := new(session.XHeader)
|
||||
x.SetKey(strs[i])
|
||||
x.SetValue(strs[i+1])
|
||||
|
||||
res = append(res, x)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func TestHeadRequest(t *testing.T) {
|
||||
req := new(objectV2.HeadRequest)
|
||||
|
||||
meta := new(session.RequestMetaHeader)
|
||||
req.SetMetaHeader(meta)
|
||||
|
||||
body := new(objectV2.HeadRequestBody)
|
||||
req.SetBody(body)
|
||||
|
||||
addr := testAddress(t)
|
||||
body.SetAddress(addr.ToV2())
|
||||
|
||||
xKey := "x-key"
|
||||
xVal := "x-val"
|
||||
xHdrs := testXHeaders(
|
||||
xKey, xVal,
|
||||
)
|
||||
|
||||
meta.SetXHeaders(xHdrs)
|
||||
|
||||
obj := object.NewRaw()
|
||||
|
||||
attrKey := "attr_key"
|
||||
attrVal := "attr_val"
|
||||
attr := objectSDK.NewAttribute()
|
||||
attr.SetKey(attrKey)
|
||||
attr.SetValue(attrVal)
|
||||
obj.SetAttributes(attr)
|
||||
|
||||
table := new(eacl.Table)
|
||||
|
||||
senderKey := test.DecodeKey(-1).PublicKey
|
||||
|
||||
r := new(eacl.Record)
|
||||
r.SetOperation(eacl.OperationHead)
|
||||
r.SetAction(eacl.ActionDeny)
|
||||
r.AddFilter(eacl.HeaderFromObject, eacl.MatchStringEqual, attrKey, attrVal)
|
||||
r.AddFilter(eacl.HeaderFromRequest, eacl.MatchStringEqual, xKey, xVal)
|
||||
r.AddTarget(eacl.RoleUnknown, senderKey)
|
||||
|
||||
table.AddRecord(r)
|
||||
|
||||
lStorage := &testLocalStorage{
|
||||
t: t,
|
||||
expAddr: addr,
|
||||
obj: obj.Object(),
|
||||
}
|
||||
|
||||
cid := addr.GetContainerID()
|
||||
unit := new(eacl2.ValidationUnit).
|
||||
WithContainerID(cid).
|
||||
WithOperation(eacl.OperationHead).
|
||||
WithSenderKey(crypto.MarshalPublicKey(&senderKey)).
|
||||
WithHeaderSource(
|
||||
NewMessageHeaderSource(
|
||||
WithObjectStorage(lStorage),
|
||||
WithServiceRequest(req),
|
||||
),
|
||||
)
|
||||
|
||||
eStorage := &testEACLStorage{
|
||||
t: t,
|
||||
expCID: cid,
|
||||
table: table,
|
||||
}
|
||||
|
||||
validator := eacl2.NewValidator(
|
||||
eacl2.WithEACLStorage(eStorage),
|
||||
)
|
||||
|
||||
require.Equal(t, eacl.ActionDeny, validator.CalculateAction(unit))
|
||||
|
||||
meta.SetXHeaders(nil)
|
||||
|
||||
require.Equal(t, eacl.ActionAllow, validator.CalculateAction(unit))
|
||||
|
||||
meta.SetXHeaders(xHdrs)
|
||||
|
||||
obj.SetAttributes(nil)
|
||||
|
||||
require.Equal(t, eacl.ActionAllow, validator.CalculateAction(unit))
|
||||
}
|
150
pkg/services/object/acl/eacl/v2/headers.go
Normal file
150
pkg/services/object/acl/eacl/v2/headers.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
eaclSDK "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
"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"
|
||||
)
|
||||
|
||||
type Option func(*cfg)
|
||||
|
||||
type cfg struct {
|
||||
storage ObjectStorage
|
||||
|
||||
msg xHeaderSource
|
||||
}
|
||||
|
||||
type ObjectStorage interface {
|
||||
Head(*objectSDK.Address) (*object.Object, error)
|
||||
}
|
||||
|
||||
type Request interface {
|
||||
GetMetaHeader() *session.RequestMetaHeader
|
||||
}
|
||||
|
||||
type Response interface {
|
||||
GetMetaHeader() *session.ResponseMetaHeader
|
||||
}
|
||||
|
||||
type headerSource struct {
|
||||
*cfg
|
||||
}
|
||||
|
||||
func defaultCfg() *cfg {
|
||||
return &cfg{
|
||||
storage: new(localStorage),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMessageHeaderSource(opts ...Option) eacl.TypedHeaderSource {
|
||||
cfg := defaultCfg()
|
||||
|
||||
for i := range opts {
|
||||
opts[i](cfg)
|
||||
}
|
||||
|
||||
return &headerSource{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *headerSource) HeadersOfType(typ eaclSDK.FilterHeaderType) ([]eacl.Header, bool) {
|
||||
switch typ {
|
||||
default:
|
||||
return nil, true
|
||||
case eaclSDK.HeaderFromRequest:
|
||||
return requestHeaders(h.msg), true
|
||||
case eaclSDK.HeaderFromObject:
|
||||
return h.objectHeaders()
|
||||
}
|
||||
}
|
||||
|
||||
func requestHeaders(msg xHeaderSource) []eacl.Header {
|
||||
xHdrs := msg.GetXHeaders()
|
||||
|
||||
res := make([]eacl.Header, 0, len(xHdrs))
|
||||
|
||||
for i := range xHdrs {
|
||||
res = append(res, xHdrs[i])
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (h *headerSource) objectHeaders() ([]eacl.Header, bool) {
|
||||
switch m := h.msg.(type) {
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
||||
case *requestXHeaderSource:
|
||||
switch req := m.req.(type) {
|
||||
case *objectV2.GetRequest:
|
||||
return h.localObjectHeaders(req.GetBody().GetAddress())
|
||||
case *objectV2.DeleteRequest:
|
||||
return h.localObjectHeaders(req.GetBody().GetAddress())
|
||||
case *objectV2.HeadRequest:
|
||||
return h.localObjectHeaders(req.GetBody().GetAddress())
|
||||
case *objectV2.GetRangeRequest:
|
||||
return h.localObjectHeaders(req.GetBody().GetAddress())
|
||||
case *objectV2.GetRangeHashRequest:
|
||||
return h.localObjectHeaders(req.GetBody().GetAddress())
|
||||
case *objectV2.PutRequest:
|
||||
if v, ok := req.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit); ok {
|
||||
oV2 := new(objectV2.Object)
|
||||
oV2.SetObjectID(v.GetObjectID())
|
||||
oV2.SetHeader(v.GetHeader())
|
||||
|
||||
return headersFromObject(object.NewFromV2(oV2)), true
|
||||
}
|
||||
}
|
||||
case *responseXHeaderSource:
|
||||
switch resp := m.resp.(type) {
|
||||
case *objectV2.GetResponse:
|
||||
if v, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok {
|
||||
oV2 := new(objectV2.Object)
|
||||
oV2.SetObjectID(v.GetObjectID())
|
||||
oV2.SetHeader(v.GetHeader())
|
||||
|
||||
return headersFromObject(object.NewFromV2(oV2)), true
|
||||
}
|
||||
case *objectV2.HeadResponse:
|
||||
oV2 := new(objectV2.Object)
|
||||
|
||||
var hdr *objectV2.Header
|
||||
|
||||
switch v := resp.GetBody().GetHeaderPart().(type) {
|
||||
case *objectV2.GetHeaderPartShort:
|
||||
hdr = new(objectV2.Header)
|
||||
h := v.GetShortHeader()
|
||||
|
||||
hdr.SetVersion(h.GetVersion())
|
||||
hdr.SetCreationEpoch(h.GetCreationEpoch())
|
||||
hdr.SetOwnerID(h.GetOwnerID())
|
||||
hdr.SetObjectType(h.GetObjectType())
|
||||
hdr.SetPayloadLength(h.GetPayloadLength())
|
||||
case *objectV2.GetHeaderPartFull:
|
||||
hdr = v.GetHeaderWithSignature().GetHeader()
|
||||
}
|
||||
|
||||
oV2.SetHeader(hdr)
|
||||
|
||||
return headersFromObject(object.NewFromV2(oV2)), true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, true
|
||||
}
|
||||
|
||||
func (h *headerSource) localObjectHeaders(addr *refs.Address) ([]eacl.Header, bool) {
|
||||
obj, err := h.storage.Head(objectSDK.NewAddressFromV2(addr))
|
||||
if err == nil {
|
||||
return headersFromObject(obj), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
26
pkg/services/object/acl/eacl/v2/localstore.go
Normal file
26
pkg/services/object/acl/eacl/v2/localstore.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"
|
||||
)
|
||||
|
||||
type localStorage struct {
|
||||
ls *localstore.Storage
|
||||
}
|
||||
|
||||
func (s *localStorage) Head(addr *objectSDK.Address) (*object.Object, error) {
|
||||
if s.ls == nil {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
meta, err := s.ls.Head(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return meta.Head(), nil
|
||||
}
|
88
pkg/services/object/acl/eacl/v2/object.go
Normal file
88
pkg/services/object/acl/eacl/v2/object.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
|
||||
)
|
||||
|
||||
type sysObjHdr struct {
|
||||
k, v string
|
||||
}
|
||||
|
||||
func (s *sysObjHdr) GetKey() string {
|
||||
return s.k
|
||||
}
|
||||
|
||||
func (s *sysObjHdr) GetValue() string {
|
||||
return s.v
|
||||
}
|
||||
|
||||
// TODO: replace value conversions to neofs-api-go
|
||||
|
||||
func idValue(id *objectSDK.ID) string {
|
||||
return hex.EncodeToString(id.ToV2().GetValue())
|
||||
}
|
||||
|
||||
func cidValue(id *container.ID) string {
|
||||
return hex.EncodeToString(id.ToV2().GetValue())
|
||||
}
|
||||
|
||||
func ownerIDValue(id *owner.ID) string {
|
||||
return hex.EncodeToString(id.ToV2().GetValue())
|
||||
}
|
||||
|
||||
func u64Value(v uint64) string {
|
||||
return strconv.FormatUint(v, 10)
|
||||
}
|
||||
|
||||
func headersFromObject(obj *object.Object) []eacl.Header {
|
||||
// TODO: optimize allocs
|
||||
res := make([]eacl.Header, 0)
|
||||
|
||||
for ; obj != nil; obj = obj.GetParent() {
|
||||
res = append(res,
|
||||
// object ID
|
||||
&sysObjHdr{
|
||||
k: objectSDK.HdrSysNameID,
|
||||
v: idValue(obj.GetID()),
|
||||
},
|
||||
// container ID
|
||||
&sysObjHdr{
|
||||
k: objectSDK.HdrSysNameCID,
|
||||
v: cidValue(obj.GetContainerID()),
|
||||
},
|
||||
// owner ID
|
||||
&sysObjHdr{
|
||||
k: objectSDK.HdrSysNameOwnerID,
|
||||
v: ownerIDValue(obj.GetOwnerID()),
|
||||
},
|
||||
// creation epoch
|
||||
&sysObjHdr{
|
||||
k: objectSDK.HdrSysNameCreatedEpoch,
|
||||
v: u64Value(obj.GetCreationEpoch()),
|
||||
},
|
||||
// payload size
|
||||
&sysObjHdr{
|
||||
k: objectSDK.HdrSysNamePayloadLength,
|
||||
v: u64Value(obj.GetPayloadSize()),
|
||||
},
|
||||
)
|
||||
|
||||
attrs := obj.GetAttributes()
|
||||
hs := make([]eacl.Header, 0, len(attrs))
|
||||
|
||||
for i := range attrs {
|
||||
hs = append(hs, attrs[i])
|
||||
}
|
||||
|
||||
res = append(res, hs...)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
35
pkg/services/object/acl/eacl/v2/opts.go
Normal file
35
pkg/services/object/acl/eacl/v2/opts.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"
|
||||
)
|
||||
|
||||
func WithObjectStorage(v ObjectStorage) Option {
|
||||
return func(c *cfg) {
|
||||
c.storage = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithLocalObjectStorage(v *localstore.Storage) Option {
|
||||
return func(c *cfg) {
|
||||
c.storage = &localStorage{
|
||||
ls: v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithServiceRequest(v Request) Option {
|
||||
return func(c *cfg) {
|
||||
c.msg = &requestXHeaderSource{
|
||||
req: v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithServiceResponse(v Response) Option {
|
||||
return func(c *cfg) {
|
||||
c.msg = &responseXHeaderSource{
|
||||
resp: v,
|
||||
}
|
||||
}
|
||||
}
|
63
pkg/services/object/acl/eacl/v2/xheader.go
Normal file
63
pkg/services/object/acl/eacl/v2/xheader.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
)
|
||||
|
||||
type xHeaderSource interface {
|
||||
GetXHeaders() []*session.XHeader
|
||||
}
|
||||
|
||||
type requestXHeaderSource struct {
|
||||
req Request
|
||||
}
|
||||
|
||||
type responseXHeaderSource struct {
|
||||
resp Response
|
||||
}
|
||||
|
||||
func (s *requestXHeaderSource) GetXHeaders() []*session.XHeader {
|
||||
ln := 0
|
||||
xHdrs := make([][]*session.XHeader, 0)
|
||||
|
||||
for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
x := meta.GetXHeaders()
|
||||
|
||||
ln += len(x)
|
||||
|
||||
xHdrs = append(xHdrs, x)
|
||||
}
|
||||
|
||||
res := make([]*session.XHeader, 0, ln)
|
||||
|
||||
for i := range xHdrs {
|
||||
for j := range xHdrs[i] {
|
||||
res = append(res, xHdrs[i][j])
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *responseXHeaderSource) GetXHeaders() []*session.XHeader {
|
||||
ln := 0
|
||||
xHdrs := make([][]*session.XHeader, 0)
|
||||
|
||||
for meta := s.resp.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
x := meta.GetXHeaders()
|
||||
|
||||
ln += len(x)
|
||||
|
||||
xHdrs = append(xHdrs, x)
|
||||
}
|
||||
|
||||
res := make([]*session.XHeader, 0, ln)
|
||||
|
||||
for i := range xHdrs {
|
||||
for j := range xHdrs[i] {
|
||||
res = append(res, xHdrs[i][j])
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
172
pkg/services/object/acl/eacl/validator.go
Normal file
172
pkg/services/object/acl/eacl/validator.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
|
||||
"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 Storage
|
||||
}
|
||||
|
||||
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 {
|
||||
// get eACL table by container ID
|
||||
table, err := v.storage.GetEACL(unit.cid)
|
||||
if err != nil {
|
||||
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.GetKey() != filter.Name() {
|
||||
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
|
||||
for _, key := range target.Keys() {
|
||||
if bytes.Equal(crypto.MarshalPublicKey(&key), unit.key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 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.GetValue() == filter.Value()
|
||||
},
|
||||
|
||||
eacl.MatchStringNotEqual: func(header Header, filter eacl.Filter) bool {
|
||||
return header.GetValue() != filter.Value()
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue