[#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