[#1111] object/acl: Refactor service
Make all operations that related to `neofs-api-go` library be placed in `v2` packages. They parse all v2-versioned structs info `neofs-sdk-go` abstractions and pass them to the corresponding `acl`/`eacl` packages. `v2` packages are the only packages that do import `neofs-api-go` library. `eacl` and `acl` provide public functions that only accepts `sdk` structures. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
parent
7ccd1625af
commit
99b31e3235
17 changed files with 1266 additions and 1000 deletions
|
@ -19,6 +19,7 @@ import (
|
||||||
objectTransportGRPC "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc"
|
objectTransportGRPC "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc"
|
||||||
objectService "github.com/nspcc-dev/neofs-node/pkg/services/object"
|
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"
|
||||||
|
v2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/v2"
|
||||||
deletesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/delete"
|
deletesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/delete"
|
||||||
deletesvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/delete/v2"
|
deletesvcV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/delete/v2"
|
||||||
getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get"
|
getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get"
|
||||||
|
@ -194,7 +195,7 @@ func initObjectService(c *cfg) {
|
||||||
|
|
||||||
coreConstructor := (*coreClientConstructor)(clientConstructor)
|
coreConstructor := (*coreClientConstructor)(clientConstructor)
|
||||||
|
|
||||||
var irFetcher acl.InnerRingFetcher
|
var irFetcher v2.InnerRingFetcher
|
||||||
|
|
||||||
if c.cfgMorph.client.ProbeNotary() {
|
if c.cfgMorph.client.ProbeNotary() {
|
||||||
irFetcher = &innerRingFetcherWithNotary{
|
irFetcher = &innerRingFetcherWithNotary{
|
||||||
|
@ -345,21 +346,22 @@ func initObjectService(c *cfg) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
aclSvc := acl.New(
|
aclSvc := v2.New(
|
||||||
acl.WithSenderClassifier(
|
v2.WithLogger(c.log),
|
||||||
acl.NewSenderClassifier(
|
v2.WithIRFetcher(irFetcher),
|
||||||
c.log,
|
v2.WithNetmapClient(c.cfgNetmap.wrapper),
|
||||||
irFetcher,
|
v2.WithContainerSource(
|
||||||
c.cfgNetmap.wrapper,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
acl.WithContainerSource(
|
|
||||||
c.cfgObject.cnrSource,
|
c.cfgObject.cnrSource,
|
||||||
),
|
),
|
||||||
acl.WithNextService(splitSvc),
|
v2.WithNextService(splitSvc),
|
||||||
acl.WithLocalStorage(ls),
|
v2.WithEACLChecker(
|
||||||
acl.WithEACLSource(c.cfgObject.eaclSource),
|
acl.NewChecker(new(acl.CheckerPrm).
|
||||||
acl.WithNetmapState(c.cfgNetmap.state),
|
SetNetmapState(c.cfgNetmap.state).
|
||||||
|
SetEACLSource(c.cfgObject.eaclSource).
|
||||||
|
SetValidator(eaclSDK.NewValidator()).
|
||||||
|
SetLocalStorage(ls),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
respSvc := objectService.NewResponseService(
|
respSvc := objectService.NewResponseService(
|
||||||
|
|
|
@ -1,614 +1,137 @@
|
||||||
package acl
|
package acl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
bearer "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
|
||||||
"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"
|
|
||||||
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/core/container"
|
"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/core/netmap"
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
|
||||||
objectSvc "github.com/nspcc-dev/neofs-node/pkg/services/object"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
|
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
|
||||||
eaclV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl/v2"
|
eaclV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl/v2"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
v2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/v2"
|
||||||
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
objectSDKAddress "github.com/nspcc-dev/neofs-sdk-go/object/address"
|
addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||||
objectSDKID "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/util/signature"
|
bearerSDK "github.com/nspcc-dev/neofs-sdk-go/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
// CheckerPrm groups parameters for Checker
|
||||||
// Service checks basic ACL rules.
|
// constructor.
|
||||||
Service struct {
|
type CheckerPrm struct {
|
||||||
*cfg
|
eaclSrc eacl.Source
|
||||||
}
|
validator *eaclSDK.Validator
|
||||||
|
|
||||||
putStreamBasicChecker struct {
|
|
||||||
source *Service
|
|
||||||
next objectSvc.PutObjectStream
|
|
||||||
|
|
||||||
*eACLCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
getStreamBasicChecker struct {
|
|
||||||
objectSvc.GetObjectStream
|
|
||||||
|
|
||||||
info requestInfo
|
|
||||||
|
|
||||||
*eACLCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
rangeStreamBasicChecker struct {
|
|
||||||
objectSvc.GetObjectRangeStream
|
|
||||||
|
|
||||||
info requestInfo
|
|
||||||
|
|
||||||
*eACLCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
searchStreamBasicChecker struct {
|
|
||||||
objectSvc.SearchStream
|
|
||||||
|
|
||||||
info requestInfo
|
|
||||||
|
|
||||||
*eACLCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
requestInfo struct {
|
|
||||||
basicACL basicACLHelper
|
|
||||||
requestRole eaclSDK.Role
|
|
||||||
isInnerRing bool
|
|
||||||
operation eaclSDK.Operation // put, get, head, etc.
|
|
||||||
cnrOwner *owner.ID // container owner
|
|
||||||
|
|
||||||
cid *cid.ID
|
|
||||||
|
|
||||||
oid *objectSDKID.ID
|
|
||||||
|
|
||||||
senderKey []byte
|
|
||||||
|
|
||||||
bearer *bearer.BearerToken // bearer token of request
|
|
||||||
|
|
||||||
srcRequest interface{}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option represents Service constructor option.
|
|
||||||
type Option func(*cfg)
|
|
||||||
|
|
||||||
type cfg struct {
|
|
||||||
containers core.Source
|
|
||||||
|
|
||||||
sender SenderClassifier
|
|
||||||
|
|
||||||
next objectSvc.ServiceServer
|
|
||||||
|
|
||||||
*eACLCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
type eACLCfg struct {
|
|
||||||
eaclSource eacl.Source
|
|
||||||
|
|
||||||
eACL *eaclSDK.Validator
|
|
||||||
|
|
||||||
localStorage *engine.StorageEngine
|
localStorage *engine.StorageEngine
|
||||||
|
|
||||||
state netmap.State
|
state netmap.State
|
||||||
}
|
}
|
||||||
|
|
||||||
type accessErr struct {
|
func (c *CheckerPrm) SetEACLSource(v eacl.Source) *CheckerPrm {
|
||||||
requestInfo
|
c.eaclSrc = v
|
||||||
|
return c
|
||||||
failedCheckTyp string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func (c *CheckerPrm) SetValidator(v *eaclSDK.Validator) *CheckerPrm {
|
||||||
ErrMalformedRequest = errors.New("malformed request")
|
c.validator = v
|
||||||
ErrUnknownRole = errors.New("can't classify request sender")
|
return c
|
||||||
ErrUnknownContainer = errors.New("can't fetch container info")
|
}
|
||||||
)
|
|
||||||
|
|
||||||
func defaultCfg() *cfg {
|
func (c *CheckerPrm) SetLocalStorage(v *engine.StorageEngine) *CheckerPrm {
|
||||||
return &cfg{
|
c.localStorage = v
|
||||||
eACLCfg: new(eACLCfg),
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CheckerPrm) SetNetmapState(v netmap.State) *CheckerPrm {
|
||||||
|
c.state = v
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checker implements v2.ACLChecker interfaces and provides
|
||||||
|
// ACL/eACL validation functionality.
|
||||||
|
type Checker struct {
|
||||||
|
eaclSrc eacl.Source
|
||||||
|
validator *eaclSDK.Validator
|
||||||
|
localStorage *engine.StorageEngine
|
||||||
|
state netmap.State
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChecker creates Checker.
|
||||||
|
// Panics if at least one of the parameter is nil.
|
||||||
|
func NewChecker(prm *CheckerPrm) *Checker {
|
||||||
|
panicOnNil := func(fieldName string, field interface{}) {
|
||||||
|
if field == nil {
|
||||||
|
panic(fmt.Sprintf("incorrect field %s (%T): %v", fieldName, field, field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panicOnNil("EACLSource", prm.eaclSrc)
|
||||||
|
panicOnNil("EACLValidator", prm.validator)
|
||||||
|
panicOnNil("LocalStorageEngine", prm.localStorage)
|
||||||
|
panicOnNil("NetmapState", prm.state)
|
||||||
|
|
||||||
|
return &Checker{
|
||||||
|
eaclSrc: prm.eaclSrc,
|
||||||
|
validator: prm.validator,
|
||||||
|
localStorage: prm.localStorage,
|
||||||
|
state: prm.state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New is a constructor for object ACL checking service.
|
// CheckBasicACL is a main check function for basic ACL.
|
||||||
func New(opts ...Option) Service {
|
func (c *Checker) CheckBasicACL(info v2.RequestInfo) bool {
|
||||||
cfg := defaultCfg()
|
|
||||||
|
|
||||||
for i := range opts {
|
|
||||||
opts[i](cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.eACL = eaclSDK.NewValidator()
|
|
||||||
|
|
||||||
return Service{
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Get(request *object.GetRequest, stream objectSvc.GetObjectStream) error {
|
|
||||||
idCnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok := originalSessionToken(request.GetMetaHeader())
|
|
||||||
|
|
||||||
req := metaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: originalBearerToken(request.GetMetaHeader()),
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(req, idCnr, eaclSDK.OperationGet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
useObjectIDFromSession(&reqInfo, sTok)
|
|
||||||
|
|
||||||
if !basicACLCheck(reqInfo) {
|
|
||||||
return basicACLErr(reqInfo)
|
|
||||||
} else if !eACLCheck(request, reqInfo, b.eACLCfg) {
|
|
||||||
return eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.next.Get(request, &getStreamBasicChecker{
|
|
||||||
GetObjectStream: stream,
|
|
||||||
info: reqInfo,
|
|
||||||
eACLCfg: b.eACLCfg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Put(ctx context.Context) (objectSvc.PutObjectStream, error) {
|
|
||||||
streamer, err := b.next.Put(ctx)
|
|
||||||
|
|
||||||
return putStreamBasicChecker{
|
|
||||||
source: &b,
|
|
||||||
next: streamer,
|
|
||||||
eACLCfg: b.eACLCfg,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Head(
|
|
||||||
ctx context.Context,
|
|
||||||
request *object.HeadRequest) (*object.HeadResponse, error) {
|
|
||||||
idCnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok := originalSessionToken(request.GetMetaHeader())
|
|
||||||
|
|
||||||
req := metaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: originalBearerToken(request.GetMetaHeader()),
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(req, idCnr, eaclSDK.OperationHead)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
useObjectIDFromSession(&reqInfo, sTok)
|
|
||||||
|
|
||||||
if !basicACLCheck(reqInfo) {
|
|
||||||
return nil, basicACLErr(reqInfo)
|
|
||||||
} else if !eACLCheck(request, reqInfo, b.eACLCfg) {
|
|
||||||
return nil, eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := b.next.Head(ctx, request)
|
|
||||||
if err == nil {
|
|
||||||
if !eACLCheck(resp, reqInfo, b.eACLCfg) {
|
|
||||||
err = eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Search(request *object.SearchRequest, stream objectSvc.SearchStream) error {
|
|
||||||
var id *cid.ID
|
|
||||||
|
|
||||||
id, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := metaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: originalSessionToken(request.GetMetaHeader()),
|
|
||||||
bearer: originalBearerToken(request.GetMetaHeader()),
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(req, id, eaclSDK.OperationSearch)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
|
|
||||||
if !basicACLCheck(reqInfo) {
|
|
||||||
return basicACLErr(reqInfo)
|
|
||||||
} else if !eACLCheck(request, reqInfo, b.eACLCfg) {
|
|
||||||
return eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.next.Search(request, &searchStreamBasicChecker{
|
|
||||||
SearchStream: stream,
|
|
||||||
info: reqInfo,
|
|
||||||
eACLCfg: b.eACLCfg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Delete(
|
|
||||||
ctx context.Context,
|
|
||||||
request *object.DeleteRequest) (*object.DeleteResponse, error) {
|
|
||||||
idCnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok := originalSessionToken(request.GetMetaHeader())
|
|
||||||
|
|
||||||
req := metaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: originalBearerToken(request.GetMetaHeader()),
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(req, idCnr, eaclSDK.OperationDelete)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
useObjectIDFromSession(&reqInfo, sTok)
|
|
||||||
|
|
||||||
if !basicACLCheck(reqInfo) {
|
|
||||||
return nil, basicACLErr(reqInfo)
|
|
||||||
} else if !eACLCheck(request, reqInfo, b.eACLCfg) {
|
|
||||||
return nil, eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.next.Delete(ctx, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) GetRange(request *object.GetRangeRequest, stream objectSvc.GetObjectRangeStream) error {
|
|
||||||
idCnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok := originalSessionToken(request.GetMetaHeader())
|
|
||||||
|
|
||||||
req := metaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: originalBearerToken(request.GetMetaHeader()),
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(req, idCnr, eaclSDK.OperationRange)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
useObjectIDFromSession(&reqInfo, sTok)
|
|
||||||
|
|
||||||
if !basicACLCheck(reqInfo) {
|
|
||||||
return basicACLErr(reqInfo)
|
|
||||||
} else if !eACLCheck(request, reqInfo, b.eACLCfg) {
|
|
||||||
return eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.next.GetRange(request, &rangeStreamBasicChecker{
|
|
||||||
GetObjectRangeStream: stream,
|
|
||||||
info: reqInfo,
|
|
||||||
eACLCfg: b.eACLCfg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) GetRangeHash(
|
|
||||||
ctx context.Context,
|
|
||||||
request *object.GetRangeHashRequest) (*object.GetRangeHashResponse, error) {
|
|
||||||
idCnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok := originalSessionToken(request.GetMetaHeader())
|
|
||||||
|
|
||||||
req := metaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: originalBearerToken(request.GetMetaHeader()),
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(req, idCnr, eaclSDK.OperationRangeHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
useObjectIDFromSession(&reqInfo, sTok)
|
|
||||||
|
|
||||||
if !basicACLCheck(reqInfo) {
|
|
||||||
return nil, basicACLErr(reqInfo)
|
|
||||||
} else if !eACLCheck(request, reqInfo, b.eACLCfg) {
|
|
||||||
return nil, eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.next.GetRangeHash(ctx, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p putStreamBasicChecker) Send(request *object.PutRequest) error {
|
|
||||||
body := request.GetBody()
|
|
||||||
if body == nil {
|
|
||||||
return ErrMalformedRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
part := body.GetObjectPart()
|
|
||||||
if part, ok := part.(*object.PutObjectPartInit); ok {
|
|
||||||
idCnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ownerID, err := getObjectOwnerFromMessage(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok := request.GetMetaHeader().GetSessionToken()
|
|
||||||
|
|
||||||
req := metaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: originalBearerToken(request.GetMetaHeader()),
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := p.source.findRequestInfo(req, idCnr, eaclSDK.OperationPut)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.oid = getObjectIDFromRequestBody(part)
|
|
||||||
useObjectIDFromSession(&reqInfo, sTok)
|
|
||||||
|
|
||||||
if !basicACLCheck(reqInfo) || !stickyBitCheck(reqInfo, ownerID) {
|
|
||||||
return basicACLErr(reqInfo)
|
|
||||||
} else if !eACLCheck(request, reqInfo, p.eACLCfg) {
|
|
||||||
return eACLErr(reqInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.next.Send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p putStreamBasicChecker) CloseAndRecv() (*object.PutResponse, error) {
|
|
||||||
return p.next.CloseAndRecv()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *getStreamBasicChecker) Send(resp *object.GetResponse) error {
|
|
||||||
if _, ok := resp.GetBody().GetObjectPart().(*object.GetObjectPartInit); ok {
|
|
||||||
if !eACLCheck(resp, g.info, g.eACLCfg) {
|
|
||||||
return eACLErr(g.info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return g.GetObjectStream.Send(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *rangeStreamBasicChecker) Send(resp *object.GetRangeResponse) error {
|
|
||||||
if !eACLCheck(resp, g.info, g.eACLCfg) {
|
|
||||||
return eACLErr(g.info)
|
|
||||||
}
|
|
||||||
|
|
||||||
return g.GetObjectRangeStream.Send(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *searchStreamBasicChecker) Send(resp *object.SearchResponse) error {
|
|
||||||
if !eACLCheck(resp, g.info, g.eACLCfg) {
|
|
||||||
return eACLErr(g.info)
|
|
||||||
}
|
|
||||||
|
|
||||||
return g.SearchStream.Send(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) findRequestInfo(
|
|
||||||
req metaWithToken,
|
|
||||||
cid *cid.ID,
|
|
||||||
op eaclSDK.Operation) (info requestInfo, err error) {
|
|
||||||
cnr, err := b.containers.Get(cid) // fetch actual container
|
|
||||||
if err != nil || cnr.OwnerID() == nil {
|
|
||||||
return info, ErrUnknownContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
// find request role and key
|
|
||||||
role, isIR, key, err := b.sender.Classify(req, cid, cnr)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if role == eaclSDK.RoleUnknown {
|
|
||||||
return info, ErrUnknownRole
|
|
||||||
}
|
|
||||||
|
|
||||||
// find verb from token if it is present
|
|
||||||
verb := sourceVerbOfRequest(req, op)
|
|
||||||
|
|
||||||
info.basicACL = basicACLHelper(cnr.BasicACL())
|
|
||||||
info.requestRole = role
|
|
||||||
info.isInnerRing = isIR
|
|
||||||
info.operation = verb
|
|
||||||
info.cnrOwner = cnr.OwnerID()
|
|
||||||
info.cid = cid
|
|
||||||
|
|
||||||
// it is assumed that at the moment the key will be valid,
|
|
||||||
// otherwise the request would not pass validation
|
|
||||||
info.senderKey = key
|
|
||||||
|
|
||||||
// add bearer token if it is present in request
|
|
||||||
info.bearer = req.bearer
|
|
||||||
|
|
||||||
info.srcRequest = req.src
|
|
||||||
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getContainerIDFromRequest(req interface{}) (id *cid.ID, err error) {
|
|
||||||
switch v := req.(type) {
|
|
||||||
case *object.GetRequest:
|
|
||||||
return cid.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
|
||||||
case *object.PutRequest:
|
|
||||||
objPart := v.GetBody().GetObjectPart()
|
|
||||||
if part, ok := objPart.(*object.PutObjectPartInit); ok {
|
|
||||||
return cid.NewFromV2(part.GetHeader().GetContainerID()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("can't get cid in chunk")
|
|
||||||
case *object.HeadRequest:
|
|
||||||
return cid.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
|
||||||
case *object.SearchRequest:
|
|
||||||
return cid.NewFromV2(v.GetBody().GetContainerID()), nil
|
|
||||||
case *object.DeleteRequest:
|
|
||||||
return cid.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
|
||||||
case *object.GetRangeRequest:
|
|
||||||
return cid.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
|
||||||
case *object.GetRangeHashRequest:
|
|
||||||
return cid.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown request type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func useObjectIDFromSession(req *requestInfo, token *session.Token) {
|
|
||||||
if token == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
objCtx, ok := token.GetBody().GetContext().(*session.ObjectSessionContext)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.oid = objectSDKID.NewIDFromV2(
|
|
||||||
objCtx.GetAddress().GetObjectID(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getObjectIDFromRequestBody(body interface{}) *objectSDKID.ID {
|
|
||||||
switch v := body.(type) {
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
case interface {
|
|
||||||
GetObjectID() *refs.ObjectID
|
|
||||||
}:
|
|
||||||
return objectSDKID.NewIDFromV2(v.GetObjectID())
|
|
||||||
case interface {
|
|
||||||
GetAddress() *refs.Address
|
|
||||||
}:
|
|
||||||
return objectSDKID.NewIDFromV2(v.GetAddress().GetObjectID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getObjectOwnerFromMessage(req interface{}) (id *owner.ID, err error) {
|
|
||||||
switch v := req.(type) {
|
|
||||||
case *object.PutRequest:
|
|
||||||
objPart := v.GetBody().GetObjectPart()
|
|
||||||
if part, ok := objPart.(*object.PutObjectPartInit); ok {
|
|
||||||
return owner.NewIDFromV2(part.GetHeader().GetOwnerID()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("can't get cid in chunk")
|
|
||||||
case *object.GetResponse:
|
|
||||||
objPart := v.GetBody().GetObjectPart()
|
|
||||||
if part, ok := objPart.(*object.GetObjectPartInit); ok {
|
|
||||||
return owner.NewIDFromV2(part.GetHeader().GetOwnerID()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("can't get cid in chunk")
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported request type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// main check function for basic ACL
|
|
||||||
func basicACLCheck(info requestInfo) bool {
|
|
||||||
// check basic ACL permissions
|
// check basic ACL permissions
|
||||||
var checkFn func(eaclSDK.Operation) bool
|
var checkFn func(eaclSDK.Operation) bool
|
||||||
|
|
||||||
switch info.requestRole {
|
switch info.RequestRole() {
|
||||||
case eaclSDK.RoleUser:
|
case eaclSDK.RoleUser:
|
||||||
checkFn = info.basicACL.UserAllowed
|
checkFn = basicACLHelper(info.BasicACL()).UserAllowed
|
||||||
case eaclSDK.RoleSystem:
|
case eaclSDK.RoleSystem:
|
||||||
checkFn = info.basicACL.SystemAllowed
|
checkFn = basicACLHelper(info.BasicACL()).SystemAllowed
|
||||||
if info.isInnerRing {
|
if info.IsInnerRing() {
|
||||||
checkFn = info.basicACL.InnerRingAllowed
|
checkFn = basicACLHelper(info.BasicACL()).InnerRingAllowed
|
||||||
}
|
}
|
||||||
case eaclSDK.RoleOthers:
|
case eaclSDK.RoleOthers:
|
||||||
checkFn = info.basicACL.OthersAllowed
|
checkFn = basicACLHelper(info.BasicACL()).OthersAllowed
|
||||||
default:
|
default:
|
||||||
// log there
|
// log there
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkFn(info.operation)
|
return checkFn(info.Operation())
|
||||||
}
|
}
|
||||||
|
|
||||||
func stickyBitCheck(info requestInfo, owner *owner.ID) bool {
|
// StickyBitCheck validates owner field in the request if sticky bit is enabled.
|
||||||
|
func (c *Checker) StickyBitCheck(info v2.RequestInfo, owner *owner.ID) bool {
|
||||||
// According to NeoFS specification sticky bit has no effect on system nodes
|
// According to NeoFS specification sticky bit has no effect on system nodes
|
||||||
// for correct intra-container work with objects (in particular, replication).
|
// for correct intra-container work with objects (in particular, replication).
|
||||||
if info.requestRole == eaclSDK.RoleSystem {
|
if info.RequestRole() == eaclSDK.RoleSystem {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !info.basicACL.Sticky() {
|
if !basicACLHelper(info.BasicACL()).Sticky() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if owner == nil || len(info.senderKey) == 0 {
|
if owner == nil || len(info.SenderKey()) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
requestSenderKey := unmarshalPublicKey(info.senderKey)
|
requestSenderKey := unmarshalPublicKey(info.SenderKey())
|
||||||
|
|
||||||
return isOwnerFromKey(owner, requestSenderKey)
|
return isOwnerFromKey(owner, requestSenderKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
|
// CheckEACL is a main check function for extended ACL.
|
||||||
if reqInfo.basicACL.Final() {
|
func (c *Checker) CheckEACL(msg interface{}, reqInfo v2.RequestInfo) bool {
|
||||||
|
if basicACLHelper(reqInfo.BasicACL()).Final() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// if bearer token is not allowed, then ignore it
|
// if bearer token is not allowed, then ignore it
|
||||||
if !reqInfo.basicACL.BearerAllowed(reqInfo.operation) {
|
if !basicACLHelper(reqInfo.BasicACL()).BearerAllowed(reqInfo.Operation()) {
|
||||||
reqInfo.bearer = nil
|
reqInfo.CleanBearer()
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -616,29 +139,29 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if reqInfo.bearer == nil {
|
if reqInfo.Bearer().Empty() {
|
||||||
table, err = cfg.eaclSource.GetEACL(reqInfo.cid)
|
table, err = c.eaclSrc.GetEACL(reqInfo.ContainerID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Is(err, container.ErrEACLNotFound)
|
return errors.Is(err, container.ErrEACLNotFound)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
table = eaclSDK.NewTableFromV2(reqInfo.bearer.GetBody().GetEACL())
|
table = reqInfo.Bearer().EACLTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// if bearer token is not present, isValidBearer returns true
|
// if bearer token is not present, isValidBearer returns true
|
||||||
if !isValidBearer(reqInfo, cfg.state) {
|
if !isValidBearer(reqInfo, c.state) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
hdrSrcOpts := make([]eaclV2.Option, 0, 3)
|
hdrSrcOpts := make([]eaclV2.Option, 0, 3)
|
||||||
|
|
||||||
addr := objectSDKAddress.NewAddress()
|
addr := addressSDK.NewAddress()
|
||||||
addr.SetContainerID(reqInfo.cid)
|
addr.SetContainerID(reqInfo.ContainerID())
|
||||||
addr.SetObjectID(reqInfo.oid)
|
addr.SetObjectID(reqInfo.ObjectID())
|
||||||
|
|
||||||
hdrSrcOpts = append(hdrSrcOpts,
|
hdrSrcOpts = append(hdrSrcOpts,
|
||||||
eaclV2.WithLocalObjectStorage(cfg.localStorage),
|
eaclV2.WithLocalObjectStorage(c.localStorage),
|
||||||
eaclV2.WithAddress(addr.ToV2()),
|
eaclV2.WithAddress(addr),
|
||||||
)
|
)
|
||||||
|
|
||||||
if req, ok := msg.(eaclV2.Request); ok {
|
if req, ok := msg.(eaclV2.Request); ok {
|
||||||
|
@ -647,16 +170,16 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
|
||||||
hdrSrcOpts = append(hdrSrcOpts,
|
hdrSrcOpts = append(hdrSrcOpts,
|
||||||
eaclV2.WithServiceResponse(
|
eaclV2.WithServiceResponse(
|
||||||
msg.(eaclV2.Response),
|
msg.(eaclV2.Response),
|
||||||
reqInfo.srcRequest.(eaclV2.Request),
|
reqInfo.Request().(eaclV2.Request),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
action := cfg.eACL.CalculateAction(new(eaclSDK.ValidationUnit).
|
action := c.validator.CalculateAction(new(eaclSDK.ValidationUnit).
|
||||||
WithRole(reqInfo.requestRole).
|
WithRole(reqInfo.RequestRole()).
|
||||||
WithOperation(reqInfo.operation).
|
WithOperation(reqInfo.Operation()).
|
||||||
WithContainerID(reqInfo.cid).
|
WithContainerID(reqInfo.ContainerID()).
|
||||||
WithSenderKey(reqInfo.senderKey).
|
WithSenderKey(reqInfo.SenderKey()).
|
||||||
WithHeaderSource(
|
WithHeaderSource(
|
||||||
eaclV2.NewMessageHeaderSource(hdrSrcOpts...),
|
eaclV2.NewMessageHeaderSource(hdrSrcOpts...),
|
||||||
).
|
).
|
||||||
|
@ -666,97 +189,39 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
|
||||||
return action == eaclSDK.ActionAllow
|
return action == eaclSDK.ActionAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
// sourceVerbOfRequest looks for verb in session token and if it is not found,
|
|
||||||
// returns reqVerb.
|
|
||||||
func sourceVerbOfRequest(req metaWithToken, reqVerb eaclSDK.Operation) eaclSDK.Operation {
|
|
||||||
if req.token != nil {
|
|
||||||
switch v := req.token.GetBody().GetContext().(type) {
|
|
||||||
case *session.ObjectSessionContext:
|
|
||||||
return tokenVerbToOperation(v.GetVerb())
|
|
||||||
default:
|
|
||||||
// do nothing, return request verb
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reqVerb
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenVerbToOperation(verb session.ObjectSessionVerb) eaclSDK.Operation {
|
|
||||||
switch verb {
|
|
||||||
case session.ObjectVerbGet:
|
|
||||||
return eaclSDK.OperationGet
|
|
||||||
case session.ObjectVerbPut:
|
|
||||||
return eaclSDK.OperationPut
|
|
||||||
case session.ObjectVerbHead:
|
|
||||||
return eaclSDK.OperationHead
|
|
||||||
case session.ObjectVerbSearch:
|
|
||||||
return eaclSDK.OperationSearch
|
|
||||||
case session.ObjectVerbDelete:
|
|
||||||
return eaclSDK.OperationDelete
|
|
||||||
case session.ObjectVerbRange:
|
|
||||||
return eaclSDK.OperationRange
|
|
||||||
case session.ObjectVerbRangeHash:
|
|
||||||
return eaclSDK.OperationRangeHash
|
|
||||||
default:
|
|
||||||
return eaclSDK.OperationUnknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *accessErr) Error() string {
|
|
||||||
return fmt.Sprintf("access to operation %v is denied by %s check", a.operation, a.failedCheckTyp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func basicACLErr(info requestInfo) error {
|
|
||||||
return &accessErr{
|
|
||||||
requestInfo: info,
|
|
||||||
failedCheckTyp: "basic ACL",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func eACLErr(info requestInfo) error {
|
|
||||||
return &accessErr{
|
|
||||||
requestInfo: info,
|
|
||||||
failedCheckTyp: "extended ACL",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidBearer returns true if bearer token correctly signed by authorized
|
// isValidBearer returns true if bearer token correctly signed by authorized
|
||||||
// entity. This method might be define on whole ACL service because it will
|
// entity. This method might be defined on whole ACL service because it will
|
||||||
// require to fetch current epoch to check lifetime.
|
// require fetching current epoch to check lifetime.
|
||||||
func isValidBearer(reqInfo requestInfo, st netmap.State) bool {
|
func isValidBearer(reqInfo v2.RequestInfo, st netmap.State) bool {
|
||||||
token := reqInfo.bearer
|
token := reqInfo.Bearer()
|
||||||
|
|
||||||
// 0. Check if bearer token is present in reqInfo. It might be non nil
|
// 0. Check if bearer token is present in reqInfo. It might be non nil
|
||||||
// empty structure.
|
// empty structure.
|
||||||
if token == nil || (token.GetBody() == nil && token.GetSignature() == nil) {
|
if token == nil || token.Empty() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. First check token lifetime. Simplest verification.
|
// 1. First check token lifetime. Simplest verification.
|
||||||
if !isValidLifetime(token.GetBody().GetLifetime(), st.CurrentEpoch()) {
|
if !isValidLifetime(token, st.CurrentEpoch()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Then check if bearer token is signed correctly.
|
// 2. Then check if bearer token is signed correctly.
|
||||||
signWrapper := v2signature.StableMarshalerWrapper{SM: token.GetBody()}
|
if err := token.VerifySignature(); err != nil {
|
||||||
if err := signature.VerifyDataWithSource(signWrapper, func() (key, sig []byte) {
|
|
||||||
tokenSignature := token.GetSignature()
|
|
||||||
return tokenSignature.GetKey(), tokenSignature.GetSign()
|
|
||||||
}); err != nil {
|
|
||||||
return false // invalid signature
|
return false // invalid signature
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Then check if container owner signed this token.
|
// 3. Then check if container owner signed this token.
|
||||||
tokenIssuerKey := unmarshalPublicKey(token.GetSignature().GetKey())
|
tokenIssuerKey := unmarshalPublicKey(token.Signature().Key())
|
||||||
if !isOwnerFromKey(reqInfo.cnrOwner, tokenIssuerKey) {
|
if !isOwnerFromKey(reqInfo.ContainerOwner(), tokenIssuerKey) {
|
||||||
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
|
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Then check if request sender has rights to use this token.
|
// 4. Then check if request sender has rights to use this token.
|
||||||
tokenOwnerField := owner.NewIDFromV2(token.GetBody().GetOwnerID())
|
tokenOwnerField := token.OwnerID()
|
||||||
if tokenOwnerField != nil { // see bearer token owner field description
|
if tokenOwnerField != nil { // see bearer token owner field description
|
||||||
requestSenderKey := unmarshalPublicKey(reqInfo.senderKey)
|
requestSenderKey := unmarshalPublicKey(reqInfo.SenderKey())
|
||||||
if !isOwnerFromKey(tokenOwnerField, requestSenderKey) {
|
if !isOwnerFromKey(tokenOwnerField, requestSenderKey) {
|
||||||
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
|
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
|
||||||
return false
|
return false
|
||||||
|
@ -766,13 +231,13 @@ func isValidBearer(reqInfo requestInfo, st netmap.State) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidLifetime(lifetime *bearer.TokenLifetime, epoch uint64) bool {
|
func isValidLifetime(t *bearerSDK.BearerToken, epoch uint64) bool {
|
||||||
// The "exp" (expiration time) claim identifies the expiration time on
|
// The "exp" (expiration time) claim identifies the expiration time on
|
||||||
// or after which the JWT MUST NOT be accepted for processing.
|
// or after which the JWT MUST NOT be accepted for processing.
|
||||||
// The "nbf" (not before) claim identifies the time before which the JWT
|
// The "nbf" (not before) claim identifies the time before which the JWT
|
||||||
// MUST NOT be accepted for processing
|
// MUST NOT be accepted for processing
|
||||||
// RFC 7519 sections 4.1.4, 4.1.5
|
// RFC 7519 sections 4.1.4, 4.1.5
|
||||||
return epoch >= lifetime.GetNbf() && epoch <= lifetime.GetExp()
|
return epoch >= t.NotBeforeTime() && epoch <= t.Expiration()
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOwnerFromKey(id *owner.ID, key *keys.PublicKey) bool {
|
func isOwnerFromKey(id *owner.ID, key *keys.PublicKey) bool {
|
||||||
|
@ -783,22 +248,10 @@ func isOwnerFromKey(id *owner.ID, key *keys.PublicKey) bool {
|
||||||
return id.Equal(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key)))
|
return id.Equal(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// originalBearerToken goes down to original request meta header and fetches
|
func unmarshalPublicKey(bs []byte) *keys.PublicKey {
|
||||||
// bearer token from there.
|
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
||||||
func originalBearerToken(header *session.RequestMetaHeader) *bearer.BearerToken {
|
if err != nil {
|
||||||
for header.GetOrigin() != nil {
|
return nil
|
||||||
header = header.GetOrigin()
|
|
||||||
}
|
}
|
||||||
|
return pub
|
||||||
return header.GetBearerToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
// originalSessionToken goes down to original request meta header and fetches
|
|
||||||
// session token from there.
|
|
||||||
func originalSessionToken(header *session.RequestMetaHeader) *session.Token {
|
|
||||||
for header.GetOrigin() != nil {
|
|
||||||
header = header.GetOrigin()
|
|
||||||
}
|
|
||||||
|
|
||||||
return header.GetSessionToken()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,71 +3,66 @@ package acl
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
|
||||||
acltest "github.com/nspcc-dev/neofs-api-go/v2/acl/test"
|
v2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/v2"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test"
|
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test"
|
ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOriginalTokens(t *testing.T) {
|
type emptyEACLSource struct{}
|
||||||
sToken := sessiontest.GenerateSessionToken(false)
|
|
||||||
bToken := acltest.GenerateBearerToken(false)
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
func (e emptyEACLSource) GetEACL(_ *cidSDK.ID) (*eaclSDK.Table, error) {
|
||||||
metaHeaders := testGenerateMetaHeader(uint32(i), bToken, sToken)
|
return nil, nil
|
||||||
require.Equal(t, sToken, originalSessionToken(metaHeaders), i)
|
|
||||||
require.Equal(t, bToken, originalBearerToken(metaHeaders), i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGenerateMetaHeader(depth uint32, b *acl.BearerToken, s *session.Token) *session.RequestMetaHeader {
|
type emptyNetmapState struct{}
|
||||||
metaHeader := new(session.RequestMetaHeader)
|
|
||||||
metaHeader.SetBearerToken(b)
|
|
||||||
metaHeader.SetSessionToken(s)
|
|
||||||
|
|
||||||
for i := uint32(0); i < depth; i++ {
|
func (e emptyNetmapState) CurrentEpoch() uint64 {
|
||||||
link := metaHeader
|
return 0
|
||||||
metaHeader = new(session.RequestMetaHeader)
|
|
||||||
metaHeader.SetOrigin(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
return metaHeader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStickyCheck(t *testing.T) {
|
func TestStickyCheck(t *testing.T) {
|
||||||
|
checker := NewChecker(new(CheckerPrm).
|
||||||
|
SetLocalStorage(&engine.StorageEngine{}).
|
||||||
|
SetValidator(eaclSDK.NewValidator()).
|
||||||
|
SetEACLSource(emptyEACLSource{}).
|
||||||
|
SetNetmapState(emptyNetmapState{}),
|
||||||
|
)
|
||||||
|
|
||||||
t.Run("system role", func(t *testing.T) {
|
t.Run("system role", func(t *testing.T) {
|
||||||
var info requestInfo
|
var info v2.RequestInfo
|
||||||
|
|
||||||
info.senderKey = make([]byte, 33) // any non-empty key
|
info.SetSenderKey(make([]byte, 33)) // any non-empty key
|
||||||
info.requestRole = eacl.RoleSystem
|
info.SetRequestRole(eaclSDK.RoleSystem)
|
||||||
|
|
||||||
info.basicACL.SetSticky()
|
setSticky(&info, true)
|
||||||
require.True(t, stickyBitCheck(info, ownertest.ID()))
|
|
||||||
|
|
||||||
info.basicACL.ResetSticky()
|
require.True(t, checker.StickyBitCheck(info, ownertest.ID()))
|
||||||
require.True(t, stickyBitCheck(info, ownertest.ID()))
|
|
||||||
|
setSticky(&info, false)
|
||||||
|
|
||||||
|
require.True(t, checker.StickyBitCheck(info, ownertest.ID()))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("owner ID and/or public key emptiness", func(t *testing.T) {
|
t.Run("owner ID and/or public key emptiness", func(t *testing.T) {
|
||||||
var info requestInfo
|
var info v2.RequestInfo
|
||||||
|
|
||||||
info.requestRole = eacl.RoleOthers // should be non-system role
|
info.SetRequestRole(eaclSDK.RoleOthers) // should be non-system role
|
||||||
|
|
||||||
assertFn := func(isSticky, withKey, withOwner, expected bool) {
|
assertFn := func(isSticky, withKey, withOwner, expected bool) {
|
||||||
if isSticky {
|
if isSticky {
|
||||||
info.basicACL.SetSticky()
|
setSticky(&info, true)
|
||||||
} else {
|
} else {
|
||||||
info.basicACL.ResetSticky()
|
setSticky(&info, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if withKey {
|
if withKey {
|
||||||
info.senderKey = make([]byte, 33)
|
info.SetSenderKey(make([]byte, 33))
|
||||||
} else {
|
} else {
|
||||||
info.senderKey = nil
|
info.SetSenderKey(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ownerID *owner.ID
|
var ownerID *owner.ID
|
||||||
|
@ -76,7 +71,7 @@ func TestStickyCheck(t *testing.T) {
|
||||||
ownerID = ownertest.ID()
|
ownerID = ownertest.ID()
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, expected, stickyBitCheck(info, ownerID))
|
require.Equal(t, expected, checker.StickyBitCheck(info, ownerID))
|
||||||
}
|
}
|
||||||
|
|
||||||
assertFn(true, false, false, false)
|
assertFn(true, false, false, false)
|
||||||
|
@ -88,3 +83,15 @@ func TestStickyCheck(t *testing.T) {
|
||||||
assertFn(false, true, true, true)
|
assertFn(false, true, true, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSticky(req *v2.RequestInfo, enabled bool) {
|
||||||
|
bh := basicACLHelper(req.BasicACL())
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
bh.SetSticky()
|
||||||
|
} else {
|
||||||
|
bh.ResetSticky()
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBasicACL(uint32(bh))
|
||||||
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ const (
|
||||||
|
|
||||||
const leftACLBitPos = opOffset + bitsPerOp*opNumber - 1
|
const leftACLBitPos = opOffset + bitsPerOp*opNumber - 1
|
||||||
|
|
||||||
var (
|
var order = map[eacl.Operation]uint8{
|
||||||
order = map[eacl.Operation]uint8{
|
|
||||||
eacl.OperationRangeHash: 0,
|
eacl.OperationRangeHash: 0,
|
||||||
eacl.OperationRange: 1,
|
eacl.OperationRange: 1,
|
||||||
eacl.OperationSearch: 2,
|
eacl.OperationSearch: 2,
|
||||||
|
@ -37,8 +36,7 @@ var (
|
||||||
eacl.OperationPut: 4,
|
eacl.OperationPut: 4,
|
||||||
eacl.OperationHead: 5,
|
eacl.OperationHead: 5,
|
||||||
eacl.OperationGet: 6,
|
eacl.OperationGet: 6,
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// returns true if n-th left bit is set (starting at 0).
|
// returns true if n-th left bit is set (starting at 0).
|
||||||
func isLeftBitSet(value basicACLHelper, n uint8) bool {
|
func isLeftBitSet(value basicACLHelper, n uint8) bool {
|
||||||
|
|
|
@ -1,219 +0,0 @@
|
||||||
package acl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
||||||
bearer "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
|
||||||
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
|
||||||
core "github.com/nspcc-dev/neofs-node/pkg/core/netmap"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
|
||||||
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/signature"
|
|
||||||
sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
InnerRingFetcher interface {
|
|
||||||
InnerRingKeys() ([][]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
metaWithToken struct {
|
|
||||||
vheader *session.RequestVerificationHeader
|
|
||||||
token *session.Token
|
|
||||||
bearer *bearer.BearerToken
|
|
||||||
src interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
SenderClassifier struct {
|
|
||||||
log *zap.Logger
|
|
||||||
innerRing InnerRingFetcher
|
|
||||||
netmap core.Source
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewSenderClassifier(l *zap.Logger, ir InnerRingFetcher, nm core.Source) SenderClassifier {
|
|
||||||
return SenderClassifier{
|
|
||||||
log: l,
|
|
||||||
innerRing: ir,
|
|
||||||
netmap: nm,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c SenderClassifier) Classify(
|
|
||||||
req metaWithToken,
|
|
||||||
cid *cid.ID,
|
|
||||||
cnr *container.Container) (role eaclSDK.Role, isIR bool, key []byte, err error) {
|
|
||||||
if cid == nil {
|
|
||||||
return 0, false, nil, fmt.Errorf("%w: container id is not set", ErrMalformedRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
ownerID, ownerKey, err := requestOwner(req)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ownerKeyInBytes := ownerKey.Bytes()
|
|
||||||
|
|
||||||
// TODO: #767 get owner from neofs.id if present
|
|
||||||
|
|
||||||
// if request owner is the same as container owner, return RoleUser
|
|
||||||
if ownerID.Equal(cnr.OwnerID()) {
|
|
||||||
return eaclSDK.RoleUser, false, ownerKeyInBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
isInnerRingNode, err := c.isInnerRingKey(ownerKeyInBytes)
|
|
||||||
if err != nil {
|
|
||||||
// do not throw error, try best case matching
|
|
||||||
c.log.Debug("can't check if request from inner ring",
|
|
||||||
zap.String("error", err.Error()))
|
|
||||||
} else if isInnerRingNode {
|
|
||||||
return eaclSDK.RoleSystem, true, ownerKeyInBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
isContainerNode, err := c.isContainerKey(ownerKeyInBytes, cid.ToV2().GetValue(), cnr)
|
|
||||||
if err != nil {
|
|
||||||
// error might happen if request has `RoleOther` key and placement
|
|
||||||
// is not possible for previous epoch, so
|
|
||||||
// do not throw error, try best case matching
|
|
||||||
c.log.Debug("can't check if request from container node",
|
|
||||||
zap.String("error", err.Error()))
|
|
||||||
} else if isContainerNode {
|
|
||||||
return eaclSDK.RoleSystem, false, ownerKeyInBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// if none of above, return RoleOthers
|
|
||||||
return eaclSDK.RoleOthers, false, ownerKeyInBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestOwner(req metaWithToken) (*owner.ID, *keys.PublicKey, error) {
|
|
||||||
if req.vheader == nil {
|
|
||||||
return nil, nil, fmt.Errorf("%w: nil verification header", ErrMalformedRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if session token is presented, use it as truth source
|
|
||||||
if req.token.GetBody() != nil {
|
|
||||||
// verify signature of session token
|
|
||||||
return ownerFromToken(req.token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise get original body signature
|
|
||||||
bodySignature := originalBodySignature(req.vheader)
|
|
||||||
if bodySignature == nil {
|
|
||||||
return nil, nil, fmt.Errorf("%w: nil at body signature", ErrMalformedRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := unmarshalPublicKey(bodySignature.Key())
|
|
||||||
|
|
||||||
return owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key)), key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func originalBodySignature(v *session.RequestVerificationHeader) *signature.Signature {
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for v.GetOrigin() != nil {
|
|
||||||
v = v.GetOrigin()
|
|
||||||
}
|
|
||||||
|
|
||||||
return signature.NewFromV2(v.GetBodySignature())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c SenderClassifier) isInnerRingKey(owner []byte) (bool, error) {
|
|
||||||
innerRingKeys, err := c.innerRing.InnerRingKeys()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if request owner key in the inner ring list, return RoleSystem
|
|
||||||
for i := range innerRingKeys {
|
|
||||||
if bytes.Equal(innerRingKeys[i], owner) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c SenderClassifier) isContainerKey(
|
|
||||||
owner, cid []byte,
|
|
||||||
cnr *container.Container) (bool, error) {
|
|
||||||
nm, err := core.GetLatestNetworkMap(c.netmap) // first check current netmap
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
in, err := lookupKeyInContainer(nm, owner, cid, cnr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
} else if in {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// then check previous netmap, this can happen in-between epoch change
|
|
||||||
// when node migrates data from last epoch container
|
|
||||||
nm, err = core.GetPreviousNetworkMap(c.netmap)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return lookupKeyInContainer(nm, owner, cid, cnr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupKeyInContainer(
|
|
||||||
nm *netmap.Netmap,
|
|
||||||
owner, cid []byte,
|
|
||||||
cnr *container.Container) (bool, error) {
|
|
||||||
cnrNodes, err := nm.GetContainerNodes(cnr.PlacementPolicy(), cid)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
flatCnrNodes := cnrNodes.Flatten() // we need single array to iterate on
|
|
||||||
for i := range flatCnrNodes {
|
|
||||||
if bytes.Equal(flatCnrNodes[i].PublicKey(), owner) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ownerFromToken(token *session.Token) (*owner.ID, *keys.PublicKey, error) {
|
|
||||||
// 1. First check signature of session token.
|
|
||||||
signWrapper := v2signature.StableMarshalerWrapper{SM: token.GetBody()}
|
|
||||||
if err := sigutil.VerifyDataWithSource(signWrapper, func() (key, sig []byte) {
|
|
||||||
tokenSignature := token.GetSignature()
|
|
||||||
return tokenSignature.GetKey(), tokenSignature.GetSign()
|
|
||||||
}); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("%w: invalid session token signature", ErrMalformedRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Then check if session token owner issued the session token
|
|
||||||
tokenIssuerKey := unmarshalPublicKey(token.GetSignature().GetKey())
|
|
||||||
tokenOwner := owner.NewIDFromV2(token.GetBody().GetOwnerID())
|
|
||||||
|
|
||||||
if !isOwnerFromKey(tokenOwner, tokenIssuerKey) {
|
|
||||||
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
|
|
||||||
return nil, nil, fmt.Errorf("%w: invalid session token owner", ErrMalformedRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenOwner, tokenIssuerKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalPublicKey(bs []byte) *keys.PublicKey {
|
|
||||||
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return pub
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||||
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/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-api-go/v2/session"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
|
@ -22,7 +21,7 @@ type cfg struct {
|
||||||
|
|
||||||
msg xHeaderSource
|
msg xHeaderSource
|
||||||
|
|
||||||
addr *refs.Address
|
addr *objectSDKAddress.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObjectStorage interface {
|
type ObjectStorage interface {
|
||||||
|
@ -83,11 +82,6 @@ func requestHeaders(msg xHeaderSource) []eaclSDK.Header {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headerSource) objectHeaders() ([]eaclSDK.Header, bool) {
|
func (h *headerSource) objectHeaders() ([]eaclSDK.Header, bool) {
|
||||||
var addr *objectSDKAddress.Address
|
|
||||||
if h.addr != nil {
|
|
||||||
addr = objectSDKAddress.NewAddressFromV2(h.addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m := h.msg.(type) {
|
switch m := h.msg.(type) {
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
||||||
|
@ -101,20 +95,20 @@ func (h *headerSource) objectHeaders() ([]eaclSDK.Header, bool) {
|
||||||
*objectV2.GetRangeRequest,
|
*objectV2.GetRangeRequest,
|
||||||
*objectV2.GetRangeHashRequest,
|
*objectV2.GetRangeHashRequest,
|
||||||
*objectV2.DeleteRequest:
|
*objectV2.DeleteRequest:
|
||||||
return addressHeaders(objectSDKAddress.NewAddressFromV2(h.addr)), true
|
return addressHeaders(h.addr), true
|
||||||
case *objectV2.PutRequest:
|
case *objectV2.PutRequest:
|
||||||
if v, ok := req.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit); ok {
|
if v, ok := req.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit); ok {
|
||||||
oV2 := new(objectV2.Object)
|
oV2 := new(objectV2.Object)
|
||||||
oV2.SetObjectID(v.GetObjectID())
|
oV2.SetObjectID(v.GetObjectID())
|
||||||
oV2.SetHeader(v.GetHeader())
|
oV2.SetHeader(v.GetHeader())
|
||||||
|
|
||||||
if addr == nil {
|
if h.addr == nil {
|
||||||
addr = objectSDKAddress.NewAddress()
|
addr := objectSDKAddress.NewAddress()
|
||||||
addr.SetContainerID(cid.NewFromV2(v.GetHeader().GetContainerID()))
|
addr.SetContainerID(cid.NewFromV2(v.GetHeader().GetContainerID()))
|
||||||
addr.SetObjectID(objectSDKID.NewIDFromV2(v.GetObjectID()))
|
addr.SetObjectID(objectSDKID.NewIDFromV2(v.GetObjectID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
hs := headersFromObject(object.NewFromV2(oV2), addr)
|
hs := headersFromObject(object.NewFromV2(oV2), h.addr)
|
||||||
|
|
||||||
return hs, true
|
return hs, true
|
||||||
}
|
}
|
||||||
|
@ -135,7 +129,7 @@ func (h *headerSource) objectHeaders() ([]eaclSDK.Header, bool) {
|
||||||
oV2.SetObjectID(v.GetObjectID())
|
oV2.SetObjectID(v.GetObjectID())
|
||||||
oV2.SetHeader(v.GetHeader())
|
oV2.SetHeader(v.GetHeader())
|
||||||
|
|
||||||
return headersFromObject(object.NewFromV2(oV2), addr), true
|
return headersFromObject(object.NewFromV2(oV2), h.addr), true
|
||||||
}
|
}
|
||||||
case *objectV2.HeadResponse:
|
case *objectV2.HeadResponse:
|
||||||
oV2 := new(objectV2.Object)
|
oV2 := new(objectV2.Object)
|
||||||
|
@ -146,7 +140,7 @@ func (h *headerSource) objectHeaders() ([]eaclSDK.Header, bool) {
|
||||||
case *objectV2.ShortHeader:
|
case *objectV2.ShortHeader:
|
||||||
hdr = new(objectV2.Header)
|
hdr = new(objectV2.Header)
|
||||||
|
|
||||||
hdr.SetContainerID(h.addr.GetContainerID())
|
hdr.SetContainerID(h.addr.ContainerID().ToV2())
|
||||||
hdr.SetVersion(v.GetVersion())
|
hdr.SetVersion(v.GetVersion())
|
||||||
hdr.SetCreationEpoch(v.GetCreationEpoch())
|
hdr.SetCreationEpoch(v.GetCreationEpoch())
|
||||||
hdr.SetOwnerID(v.GetOwnerID())
|
hdr.SetOwnerID(v.GetOwnerID())
|
||||||
|
@ -158,16 +152,14 @@ func (h *headerSource) objectHeaders() ([]eaclSDK.Header, bool) {
|
||||||
|
|
||||||
oV2.SetHeader(hdr)
|
oV2.SetHeader(hdr)
|
||||||
|
|
||||||
return headersFromObject(object.NewFromV2(oV2), addr), true
|
return headersFromObject(object.NewFromV2(oV2), h.addr), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headerSource) localObjectHeaders(addrV2 *refs.Address) ([]eaclSDK.Header, bool) {
|
func (h *headerSource) localObjectHeaders(addr *objectSDKAddress.Address) ([]eaclSDK.Header, bool) {
|
||||||
addr := objectSDKAddress.NewAddressFromV2(addrV2)
|
|
||||||
|
|
||||||
obj, err := h.storage.Head(addr)
|
obj, err := h.storage.Head(addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return headersFromObject(obj, addr), true
|
return headersFromObject(obj, addr), true
|
||||||
|
@ -176,10 +168,10 @@ func (h *headerSource) localObjectHeaders(addrV2 *refs.Address) ([]eaclSDK.Heade
|
||||||
return addressHeaders(addr), false
|
return addressHeaders(addr), false
|
||||||
}
|
}
|
||||||
|
|
||||||
func cidHeader(cid *cid.ID) eaclSDK.Header {
|
func cidHeader(idCnr *cid.ID) eaclSDK.Header {
|
||||||
return &sysObjHdr{
|
return &sysObjHdr{
|
||||||
k: acl.FilterObjectContainerID,
|
k: acl.FilterObjectContainerID,
|
||||||
v: cidValue(cid),
|
v: cidValue(idCnr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
objectSDK "github.com/nspcc-dev/neofs-sdk-go/object"
|
||||||
objectSDKAddress "github.com/nspcc-dev/neofs-sdk-go/object/address"
|
objectSDKAddress "github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ type localStorage struct {
|
||||||
ls *engine.StorageEngine
|
ls *engine.StorageEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *localStorage) Head(addr *objectSDKAddress.Address) (*object.Object, error) {
|
func (s *localStorage) Head(addr *objectSDKAddress.Address) (*objectSDK.Object, error) {
|
||||||
if s.ls == nil {
|
if s.ls == nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package v2
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine"
|
||||||
|
addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithObjectStorage(v ObjectStorage) Option {
|
func WithObjectStorage(v ObjectStorage) Option {
|
||||||
|
@ -36,7 +36,7 @@ func WithServiceResponse(resp Response, req Request) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithAddress(v *refs.Address) Option {
|
func WithAddress(v *addressSDK.Address) Option {
|
||||||
return func(c *cfg) {
|
return func(c *cfg) {
|
||||||
c.addr = v
|
c.addr = v
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
package acl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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"
|
|
||||||
objectSvc "github.com/nspcc-dev/neofs-node/pkg/services/object"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WithContainerSource returns option to set container source.
|
|
||||||
func WithContainerSource(v container.Source) Option {
|
|
||||||
return func(c *cfg) {
|
|
||||||
c.containers = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSenderClassifier returns option to set sender classifier.
|
|
||||||
func WithSenderClassifier(v SenderClassifier) Option {
|
|
||||||
return func(c *cfg) {
|
|
||||||
c.sender = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNextService returns option to set next object service.
|
|
||||||
func WithNextService(v objectSvc.ServiceServer) Option {
|
|
||||||
return func(c *cfg) {
|
|
||||||
c.next = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEACLSource returns options to set eACL table source.
|
|
||||||
func WithEACLSource(v eacl.Source) Option {
|
|
||||||
return func(c *cfg) {
|
|
||||||
c.eACLCfg.eaclSource = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLocalStorage returns options to set local object storage.
|
|
||||||
func WithLocalStorage(v *engine.StorageEngine) Option {
|
|
||||||
return func(c *cfg) {
|
|
||||||
c.localStorage = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNetmapState returns options to set global netmap state.
|
|
||||||
func WithNetmapState(v netmap.State) Option {
|
|
||||||
return func(c *cfg) {
|
|
||||||
c.state = v
|
|
||||||
}
|
|
||||||
}
|
|
148
pkg/services/object/acl/v2/classifier.go
Normal file
148
pkg/services/object/acl/v2/classifier.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
core "github.com/nspcc-dev/neofs-node/pkg/core/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||||
|
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
|
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type senderClassifier struct {
|
||||||
|
log *zap.Logger
|
||||||
|
innerRing InnerRingFetcher
|
||||||
|
netmap core.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
var errContainerIDNotSet = errors.New("container id is not set")
|
||||||
|
|
||||||
|
type classifyResult struct {
|
||||||
|
role eaclSDK.Role
|
||||||
|
isIR bool
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c senderClassifier) classify(
|
||||||
|
req MetaWithToken,
|
||||||
|
idCnr *cidSDK.ID,
|
||||||
|
cnr *container.Container) (res *classifyResult, err error) {
|
||||||
|
if idCnr == nil {
|
||||||
|
return nil, errContainerIDNotSet
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerID, ownerKey, err := req.RequestOwner()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerKeyInBytes := ownerKey.Bytes()
|
||||||
|
|
||||||
|
// TODO: #767 get owner from neofs.id if present
|
||||||
|
|
||||||
|
// if request owner is the same as container owner, return RoleUser
|
||||||
|
if ownerID.Equal(cnr.OwnerID()) {
|
||||||
|
return &classifyResult{
|
||||||
|
role: eaclSDK.RoleUser,
|
||||||
|
isIR: false,
|
||||||
|
key: ownerKeyInBytes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isInnerRingNode, err := c.isInnerRingKey(ownerKeyInBytes)
|
||||||
|
if err != nil {
|
||||||
|
// do not throw error, try best case matching
|
||||||
|
c.log.Debug("can't check if request from inner ring",
|
||||||
|
zap.String("error", err.Error()))
|
||||||
|
} else if isInnerRingNode {
|
||||||
|
return &classifyResult{
|
||||||
|
role: eaclSDK.RoleSystem,
|
||||||
|
isIR: true,
|
||||||
|
key: ownerKeyInBytes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isContainerNode, err := c.isContainerKey(ownerKeyInBytes, idCnr.ToV2().GetValue(), cnr)
|
||||||
|
if err != nil {
|
||||||
|
// error might happen if request has `RoleOther` key and placement
|
||||||
|
// is not possible for previous epoch, so
|
||||||
|
// do not throw error, try best case matching
|
||||||
|
c.log.Debug("can't check if request from container node",
|
||||||
|
zap.String("error", err.Error()))
|
||||||
|
} else if isContainerNode {
|
||||||
|
return &classifyResult{
|
||||||
|
role: eaclSDK.RoleSystem,
|
||||||
|
isIR: false,
|
||||||
|
key: ownerKeyInBytes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if none of above, return RoleOthers
|
||||||
|
return &classifyResult{
|
||||||
|
role: eaclSDK.RoleOthers,
|
||||||
|
key: ownerKeyInBytes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c senderClassifier) isInnerRingKey(owner []byte) (bool, error) {
|
||||||
|
innerRingKeys, err := c.innerRing.InnerRingKeys()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if request owner key in the inner ring list, return RoleSystem
|
||||||
|
for i := range innerRingKeys {
|
||||||
|
if bytes.Equal(innerRingKeys[i], owner) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c senderClassifier) isContainerKey(
|
||||||
|
owner, idCnr []byte,
|
||||||
|
cnr *container.Container) (bool, error) {
|
||||||
|
nm, err := core.GetLatestNetworkMap(c.netmap) // first check current netmap
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err := lookupKeyInContainer(nm, owner, idCnr, cnr)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if in {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// then check previous netmap, this can happen in-between epoch change
|
||||||
|
// when node migrates data from last epoch container
|
||||||
|
nm, err = core.GetPreviousNetworkMap(c.netmap)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookupKeyInContainer(nm, owner, idCnr, cnr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupKeyInContainer(
|
||||||
|
nm *netmap.Netmap,
|
||||||
|
owner, idCnr []byte,
|
||||||
|
cnr *container.Container) (bool, error) {
|
||||||
|
cnrNodes, err := nm.GetContainerNodes(cnr.PlacementPolicy(), idCnr)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
flatCnrNodes := cnrNodes.Flatten() // we need single array to iterate on
|
||||||
|
for i := range flatCnrNodes {
|
||||||
|
if bytes.Equal(flatCnrNodes[i].PublicKey(), owner) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
40
pkg/services/object/acl/v2/errors.go
Normal file
40
pkg/services/object/acl/v2/errors.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrMalformedRequest is returned when request contains
|
||||||
|
// invalid data.
|
||||||
|
ErrMalformedRequest = errors.New("malformed request")
|
||||||
|
// ErrUnknownRole is returned when role of the sender is unknown.
|
||||||
|
ErrUnknownRole = errors.New("can't classify request sender")
|
||||||
|
// ErrUnknownContainer is returned when container fetching errors appeared.
|
||||||
|
ErrUnknownContainer = errors.New("can't fetch container info")
|
||||||
|
)
|
||||||
|
|
||||||
|
type accessErr struct {
|
||||||
|
RequestInfo
|
||||||
|
|
||||||
|
failedCheckTyp string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accessErr) Error() string {
|
||||||
|
return fmt.Sprintf("access to operation %v is denied by %s check", a.operation, a.failedCheckTyp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicACLErr(info RequestInfo) error {
|
||||||
|
return &accessErr{
|
||||||
|
RequestInfo: info,
|
||||||
|
failedCheckTyp: "basic ACL",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func eACLErr(info RequestInfo) error {
|
||||||
|
return &accessErr{
|
||||||
|
RequestInfo: info,
|
||||||
|
failedCheckTyp: "extended ACL",
|
||||||
|
}
|
||||||
|
}
|
51
pkg/services/object/acl/v2/opts.go
Normal file
51
pkg/services/object/acl/v2/opts.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/core/container"
|
||||||
|
netmapClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap"
|
||||||
|
objectSvc "github.com/nspcc-dev/neofs-node/pkg/services/object"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithLogger returns option to set logger.
|
||||||
|
func WithLogger(v *zap.Logger) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.log = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNetmapClient return option to set
|
||||||
|
// netmap client.
|
||||||
|
func WithNetmapClient(v *netmapClient.Client) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.nm = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContainerSource returns option to set container source.
|
||||||
|
func WithContainerSource(v container.Source) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.containers = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNextService returns option to set next object service.
|
||||||
|
func WithNextService(v objectSvc.ServiceServer) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.next = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEACLChecker returns option to set eACL checker.
|
||||||
|
func WithEACLChecker(v ACLChecker) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.checker = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithIRFetcher returns option to set inner ring fetcher.
|
||||||
|
func WithIRFetcher(v InnerRingFetcher) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.irFetcher = v
|
||||||
|
}
|
||||||
|
}
|
135
pkg/services/object/acl/v2/request.go
Normal file
135
pkg/services/object/acl/v2/request.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
sessionV2 "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
|
containerIDSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
|
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
|
oidSDK "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
|
sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
|
bearerSDK "github.com/nspcc-dev/neofs-sdk-go/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestInfo groups parsed version-independent (from SDK library)
|
||||||
|
// request information and raw API request.
|
||||||
|
type RequestInfo struct {
|
||||||
|
basicACL uint32
|
||||||
|
requestRole eaclSDK.Role
|
||||||
|
isInnerRing bool
|
||||||
|
operation eaclSDK.Operation // put, get, head, etc.
|
||||||
|
cnrOwner *owner.ID // container owner
|
||||||
|
|
||||||
|
idCnr *containerIDSDK.ID
|
||||||
|
|
||||||
|
oid *oidSDK.ID
|
||||||
|
|
||||||
|
senderKey []byte
|
||||||
|
|
||||||
|
bearer *bearerSDK.BearerToken // bearer token of request
|
||||||
|
|
||||||
|
srcRequest interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RequestInfo) SetBasicACL(basicACL uint32) {
|
||||||
|
r.basicACL = basicACL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RequestInfo) SetRequestRole(requestRole eaclSDK.Role) {
|
||||||
|
r.requestRole = requestRole
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RequestInfo) SetSenderKey(senderKey []byte) {
|
||||||
|
r.senderKey = senderKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request returns raw API request.
|
||||||
|
func (r RequestInfo) Request() interface{} {
|
||||||
|
return r.srcRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerOwner returns owner if the container.
|
||||||
|
func (r RequestInfo) ContainerOwner() *owner.ID {
|
||||||
|
return r.cnrOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectID return object ID.
|
||||||
|
func (r RequestInfo) ObjectID() *oidSDK.ID {
|
||||||
|
return r.oid
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerID return container ID.
|
||||||
|
func (r RequestInfo) ContainerID() *containerIDSDK.ID {
|
||||||
|
return r.idCnr
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanBearer forces cleaning bearer token information.
|
||||||
|
func (r *RequestInfo) CleanBearer() {
|
||||||
|
r.bearer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bearer returns bearer token of the request.
|
||||||
|
func (r RequestInfo) Bearer() *bearerSDK.BearerToken {
|
||||||
|
return r.bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInnerRing specifies if request was made by inner ring.
|
||||||
|
func (r RequestInfo) IsInnerRing() bool {
|
||||||
|
return r.isInnerRing
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicACL returns basic ACL of the container.
|
||||||
|
func (r RequestInfo) BasicACL() uint32 {
|
||||||
|
return r.basicACL
|
||||||
|
}
|
||||||
|
|
||||||
|
// SenderKey returns public key of the request's sender.
|
||||||
|
func (r RequestInfo) SenderKey() []byte {
|
||||||
|
return r.senderKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation returns request's operation.
|
||||||
|
func (r RequestInfo) Operation() eaclSDK.Operation {
|
||||||
|
return r.operation
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestRole returns request sender's role.
|
||||||
|
func (r RequestInfo) RequestRole() eaclSDK.Role {
|
||||||
|
return r.requestRole
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetaWithToken groups session and bearer tokens,
|
||||||
|
// verification header and raw API request.
|
||||||
|
type MetaWithToken struct {
|
||||||
|
vheader *sessionV2.RequestVerificationHeader
|
||||||
|
token *sessionSDK.Token
|
||||||
|
bearer *bearerSDK.BearerToken
|
||||||
|
src interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestOwner returns ownerID and its public key
|
||||||
|
// according to internal meta information.
|
||||||
|
func (r MetaWithToken) RequestOwner() (*owner.ID, *keys.PublicKey, error) {
|
||||||
|
if r.vheader == nil {
|
||||||
|
return nil, nil, fmt.Errorf("%w: nil verification header", ErrMalformedRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if session token is presented, use it as truth source
|
||||||
|
if r.token != nil {
|
||||||
|
// verify signature of session token
|
||||||
|
return ownerFromToken(r.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise get original body signature
|
||||||
|
bodySignature := originalBodySignature(r.vheader)
|
||||||
|
if bodySignature == nil {
|
||||||
|
return nil, nil, fmt.Errorf("%w: nil at body signature", ErrMalformedRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := unmarshalPublicKey(bodySignature.Key())
|
||||||
|
|
||||||
|
return owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key)), key, nil
|
||||||
|
}
|
447
pkg/services/object/acl/v2/service.go
Normal file
447
pkg/services/object/acl/v2/service.go
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/core/container"
|
||||||
|
netmapClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/services/object"
|
||||||
|
cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
|
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
|
sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service checks basic ACL rules.
|
||||||
|
type Service struct {
|
||||||
|
*cfg
|
||||||
|
|
||||||
|
c senderClassifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type putStreamBasicChecker struct {
|
||||||
|
source *Service
|
||||||
|
next object.PutObjectStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type getStreamBasicChecker struct {
|
||||||
|
checker ACLChecker
|
||||||
|
|
||||||
|
object.GetObjectStream
|
||||||
|
|
||||||
|
info RequestInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type rangeStreamBasicChecker struct {
|
||||||
|
checker ACLChecker
|
||||||
|
|
||||||
|
object.GetObjectRangeStream
|
||||||
|
|
||||||
|
info RequestInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type searchStreamBasicChecker struct {
|
||||||
|
checker ACLChecker
|
||||||
|
|
||||||
|
object.SearchStream
|
||||||
|
|
||||||
|
info RequestInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option represents Service constructor option.
|
||||||
|
type Option func(*cfg)
|
||||||
|
|
||||||
|
type cfg struct {
|
||||||
|
log *zap.Logger
|
||||||
|
|
||||||
|
containers container.Source
|
||||||
|
|
||||||
|
checker ACLChecker
|
||||||
|
|
||||||
|
irFetcher InnerRingFetcher
|
||||||
|
|
||||||
|
nm *netmapClient.Client
|
||||||
|
|
||||||
|
next object.ServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultCfg() *cfg {
|
||||||
|
return &cfg{
|
||||||
|
log: zap.L(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New is a constructor for object ACL checking service.
|
||||||
|
func New(opts ...Option) Service {
|
||||||
|
cfg := defaultCfg()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
panicOnNil := func(v interface{}, name string) {
|
||||||
|
if v == nil {
|
||||||
|
panic(fmt.Sprintf("ACL service: %s is nil", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panicOnNil(cfg.next, "next Service")
|
||||||
|
panicOnNil(cfg.nm, "netmap client")
|
||||||
|
panicOnNil(cfg.irFetcher, "inner Ring fetcher")
|
||||||
|
panicOnNil(cfg.checker, "acl checker")
|
||||||
|
panicOnNil(cfg.containers, "container source")
|
||||||
|
|
||||||
|
return Service{
|
||||||
|
cfg: cfg,
|
||||||
|
c: senderClassifier{
|
||||||
|
log: cfg.log,
|
||||||
|
innerRing: cfg.irFetcher,
|
||||||
|
netmap: cfg.nm,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements ServiceServer interface, makes ACL checks and calls
|
||||||
|
// next Get method in the ServiceServer pipeline.
|
||||||
|
func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream) error {
|
||||||
|
cid, err := getContainerIDFromRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sTok := originalSessionToken(request.GetMetaHeader())
|
||||||
|
|
||||||
|
req := MetaWithToken{
|
||||||
|
vheader: request.GetVerificationHeader(),
|
||||||
|
token: sTok,
|
||||||
|
bearer: originalBearerToken(request.GetMetaHeader()),
|
||||||
|
src: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo, err := b.findRequestInfo(req, cid, eaclSDK.OperationGet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
||||||
|
useObjectIDFromSession(&reqInfo, sTok)
|
||||||
|
|
||||||
|
if !b.checker.CheckBasicACL(reqInfo) {
|
||||||
|
return basicACLErr(reqInfo)
|
||||||
|
} else if !b.checker.CheckEACL(request, reqInfo) {
|
||||||
|
return eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.next.Get(request, &getStreamBasicChecker{
|
||||||
|
GetObjectStream: stream,
|
||||||
|
info: reqInfo,
|
||||||
|
checker: b.checker,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Service) Put(ctx context.Context) (object.PutObjectStream, error) {
|
||||||
|
streamer, err := b.next.Put(ctx)
|
||||||
|
|
||||||
|
return putStreamBasicChecker{
|
||||||
|
source: &b,
|
||||||
|
next: streamer,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Service) Head(
|
||||||
|
ctx context.Context,
|
||||||
|
request *objectV2.HeadRequest) (*objectV2.HeadResponse, error) {
|
||||||
|
cid, err := getContainerIDFromRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sTok := originalSessionToken(request.GetMetaHeader())
|
||||||
|
|
||||||
|
req := MetaWithToken{
|
||||||
|
vheader: request.GetVerificationHeader(),
|
||||||
|
token: sTok,
|
||||||
|
bearer: originalBearerToken(request.GetMetaHeader()),
|
||||||
|
src: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo, err := b.findRequestInfo(req, cid, eaclSDK.OperationHead)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
||||||
|
useObjectIDFromSession(&reqInfo, sTok)
|
||||||
|
|
||||||
|
if !b.checker.CheckBasicACL(reqInfo) {
|
||||||
|
return nil, basicACLErr(reqInfo)
|
||||||
|
} else if !b.checker.CheckEACL(request, reqInfo) {
|
||||||
|
return nil, eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := b.next.Head(ctx, request)
|
||||||
|
if err == nil {
|
||||||
|
if !b.checker.CheckEACL(resp, reqInfo) {
|
||||||
|
err = eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStream) error {
|
||||||
|
var id *cidSDK.ID
|
||||||
|
|
||||||
|
id, err := getContainerIDFromRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := MetaWithToken{
|
||||||
|
vheader: request.GetVerificationHeader(),
|
||||||
|
token: originalSessionToken(request.GetMetaHeader()),
|
||||||
|
bearer: originalBearerToken(request.GetMetaHeader()),
|
||||||
|
src: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo, err := b.findRequestInfo(req, id, eaclSDK.OperationSearch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
||||||
|
|
||||||
|
if !b.checker.CheckBasicACL(reqInfo) {
|
||||||
|
return basicACLErr(reqInfo)
|
||||||
|
} else if !b.checker.CheckEACL(request, reqInfo) {
|
||||||
|
return eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.next.Search(request, &searchStreamBasicChecker{
|
||||||
|
checker: b.checker,
|
||||||
|
SearchStream: stream,
|
||||||
|
info: reqInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Service) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
request *objectV2.DeleteRequest) (*objectV2.DeleteResponse, error) {
|
||||||
|
cid, err := getContainerIDFromRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sTok := originalSessionToken(request.GetMetaHeader())
|
||||||
|
|
||||||
|
req := MetaWithToken{
|
||||||
|
vheader: request.GetVerificationHeader(),
|
||||||
|
token: sTok,
|
||||||
|
bearer: originalBearerToken(request.GetMetaHeader()),
|
||||||
|
src: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo, err := b.findRequestInfo(req, cid, eaclSDK.OperationDelete)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
||||||
|
useObjectIDFromSession(&reqInfo, sTok)
|
||||||
|
|
||||||
|
if !b.checker.CheckBasicACL(reqInfo) {
|
||||||
|
return nil, basicACLErr(reqInfo)
|
||||||
|
} else if !b.checker.CheckEACL(request, reqInfo) {
|
||||||
|
return nil, eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.next.Delete(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetObjectRangeStream) error {
|
||||||
|
cid, err := getContainerIDFromRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sTok := originalSessionToken(request.GetMetaHeader())
|
||||||
|
|
||||||
|
req := MetaWithToken{
|
||||||
|
vheader: request.GetVerificationHeader(),
|
||||||
|
token: sTok,
|
||||||
|
bearer: originalBearerToken(request.GetMetaHeader()),
|
||||||
|
src: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo, err := b.findRequestInfo(req, cid, eaclSDK.OperationRange)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
||||||
|
useObjectIDFromSession(&reqInfo, sTok)
|
||||||
|
|
||||||
|
if !b.checker.CheckBasicACL(reqInfo) {
|
||||||
|
return basicACLErr(reqInfo)
|
||||||
|
} else if !b.checker.CheckEACL(request, reqInfo) {
|
||||||
|
return eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.next.GetRange(request, &rangeStreamBasicChecker{
|
||||||
|
checker: b.checker,
|
||||||
|
GetObjectRangeStream: stream,
|
||||||
|
info: reqInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Service) GetRangeHash(
|
||||||
|
ctx context.Context,
|
||||||
|
request *objectV2.GetRangeHashRequest) (*objectV2.GetRangeHashResponse, error) {
|
||||||
|
cid, err := getContainerIDFromRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sTok := originalSessionToken(request.GetMetaHeader())
|
||||||
|
|
||||||
|
req := MetaWithToken{
|
||||||
|
vheader: request.GetVerificationHeader(),
|
||||||
|
token: sTok,
|
||||||
|
bearer: originalBearerToken(request.GetMetaHeader()),
|
||||||
|
src: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo, err := b.findRequestInfo(req, cid, eaclSDK.OperationRangeHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo.oid = getObjectIDFromRequestBody(request.GetBody())
|
||||||
|
useObjectIDFromSession(&reqInfo, sTok)
|
||||||
|
|
||||||
|
if !b.checker.CheckBasicACL(reqInfo) {
|
||||||
|
return nil, basicACLErr(reqInfo)
|
||||||
|
} else if !b.checker.CheckEACL(request, reqInfo) {
|
||||||
|
return nil, eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.next.GetRangeHash(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p putStreamBasicChecker) Send(request *objectV2.PutRequest) error {
|
||||||
|
body := request.GetBody()
|
||||||
|
if body == nil {
|
||||||
|
return ErrMalformedRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
part := body.GetObjectPart()
|
||||||
|
if part, ok := part.(*objectV2.PutObjectPartInit); ok {
|
||||||
|
cid, err := getContainerIDFromRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerID, err := getObjectOwnerFromMessage(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sTok := sessionSDK.NewTokenFromV2(request.GetMetaHeader().GetSessionToken())
|
||||||
|
|
||||||
|
req := MetaWithToken{
|
||||||
|
vheader: request.GetVerificationHeader(),
|
||||||
|
token: sTok,
|
||||||
|
bearer: originalBearerToken(request.GetMetaHeader()),
|
||||||
|
src: request,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo, err := p.source.findRequestInfo(req, cid, eaclSDK.OperationPut)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqInfo.oid = getObjectIDFromRequestBody(part)
|
||||||
|
useObjectIDFromSession(&reqInfo, sTok)
|
||||||
|
|
||||||
|
if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, ownerID) {
|
||||||
|
return basicACLErr(reqInfo)
|
||||||
|
} else if !p.source.checker.CheckEACL(request, reqInfo) {
|
||||||
|
return eACLErr(reqInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.next.Send(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p putStreamBasicChecker) CloseAndRecv() (*objectV2.PutResponse, error) {
|
||||||
|
return p.next.CloseAndRecv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
|
||||||
|
if _, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok {
|
||||||
|
if !g.checker.CheckEACL(resp, g.info) {
|
||||||
|
return eACLErr(g.info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.GetObjectStream.Send(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *rangeStreamBasicChecker) Send(resp *objectV2.GetRangeResponse) error {
|
||||||
|
if !g.checker.CheckEACL(resp, g.info) {
|
||||||
|
return eACLErr(g.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.GetObjectRangeStream.Send(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *searchStreamBasicChecker) Send(resp *objectV2.SearchResponse) error {
|
||||||
|
if !g.checker.CheckEACL(resp, g.info) {
|
||||||
|
return eACLErr(g.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.SearchStream.Send(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Service) findRequestInfo(
|
||||||
|
req MetaWithToken,
|
||||||
|
cid *cidSDK.ID,
|
||||||
|
op eaclSDK.Operation) (info RequestInfo, err error) {
|
||||||
|
cnr, err := b.containers.Get(cid) // fetch actual container
|
||||||
|
if err != nil || cnr.OwnerID() == nil {
|
||||||
|
return info, ErrUnknownContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// find request role and key
|
||||||
|
res, err := b.c.classify(req, cid, cnr)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.role == eaclSDK.RoleUnknown {
|
||||||
|
return info, ErrUnknownRole
|
||||||
|
}
|
||||||
|
|
||||||
|
// find verb from token if it is present
|
||||||
|
verb := sourceVerbOfRequest(req, op)
|
||||||
|
|
||||||
|
info.basicACL = cnr.BasicACL()
|
||||||
|
info.requestRole = res.role
|
||||||
|
info.isInnerRing = res.isIR
|
||||||
|
info.operation = verb
|
||||||
|
info.cnrOwner = cnr.OwnerID()
|
||||||
|
info.idCnr = cid
|
||||||
|
|
||||||
|
// it is assumed that at the moment the key will be valid,
|
||||||
|
// otherwise the request would not pass validation
|
||||||
|
info.senderKey = res.key
|
||||||
|
|
||||||
|
// add bearer token if it is present in request
|
||||||
|
info.bearer = req.bearer
|
||||||
|
|
||||||
|
info.srcRequest = req.src
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
28
pkg/services/object/acl/v2/types.go
Normal file
28
pkg/services/object/acl/v2/types.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ACLChecker is an interface that must provide
|
||||||
|
// ACL related checks.
|
||||||
|
type ACLChecker interface {
|
||||||
|
// CheckBasicACL must return true only if request
|
||||||
|
// passes basic ACL validation.
|
||||||
|
CheckBasicACL(RequestInfo) bool
|
||||||
|
// CheckEACL must return true only if request
|
||||||
|
// passes extended ACL validation.
|
||||||
|
CheckEACL(interface{}, RequestInfo) bool
|
||||||
|
// StickyBitCheck must return true only if sticky bit
|
||||||
|
// is disabled or enabled but request contains correct
|
||||||
|
// owner field.
|
||||||
|
StickyBitCheck(RequestInfo, *owner.ID) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerRingFetcher is an interface that must provide
|
||||||
|
// Inner Ring information.
|
||||||
|
type InnerRingFetcher interface {
|
||||||
|
// InnerRingKeys must return list of public keys of
|
||||||
|
// the actual inner ring.
|
||||||
|
InnerRingKeys() ([][]byte, error)
|
||||||
|
}
|
197
pkg/services/object/acl/v2/util.go
Normal file
197
pkg/services/object/acl/v2/util.go
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
|
refsV2 "github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
|
sessionV2 "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
|
containerIDSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
|
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
|
oidSDK "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
|
sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/signature"
|
||||||
|
bearerSDK "github.com/nspcc-dev/neofs-sdk-go/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getContainerIDFromRequest(req interface{}) (id *containerIDSDK.ID, err error) {
|
||||||
|
switch v := req.(type) {
|
||||||
|
case *objectV2.GetRequest:
|
||||||
|
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
||||||
|
case *objectV2.PutRequest:
|
||||||
|
objPart := v.GetBody().GetObjectPart()
|
||||||
|
if part, ok := objPart.(*objectV2.PutObjectPartInit); ok {
|
||||||
|
return containerIDSDK.NewFromV2(part.GetHeader().GetContainerID()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("can't get container ID in chunk")
|
||||||
|
case *objectV2.HeadRequest:
|
||||||
|
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
||||||
|
case *objectV2.SearchRequest:
|
||||||
|
return containerIDSDK.NewFromV2(v.GetBody().GetContainerID()), nil
|
||||||
|
case *objectV2.DeleteRequest:
|
||||||
|
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
||||||
|
case *objectV2.GetRangeRequest:
|
||||||
|
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
||||||
|
case *objectV2.GetRangeHashRequest:
|
||||||
|
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown request type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// originalBearerToken goes down to original request meta header and fetches
|
||||||
|
// bearer token from there.
|
||||||
|
func originalBearerToken(header *sessionV2.RequestMetaHeader) *bearerSDK.BearerToken {
|
||||||
|
for header.GetOrigin() != nil {
|
||||||
|
header = header.GetOrigin()
|
||||||
|
}
|
||||||
|
|
||||||
|
return bearerSDK.NewBearerTokenFromV2(header.GetBearerToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
// originalSessionToken goes down to original request meta header and fetches
|
||||||
|
// session token from there.
|
||||||
|
func originalSessionToken(header *sessionV2.RequestMetaHeader) *sessionSDK.Token {
|
||||||
|
for header.GetOrigin() != nil {
|
||||||
|
header = header.GetOrigin()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionSDK.NewTokenFromV2(header.GetSessionToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getObjectIDFromRequestBody(body interface{}) *oidSDK.ID {
|
||||||
|
switch v := body.(type) {
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
case interface {
|
||||||
|
GetObjectID() *refsV2.ObjectID
|
||||||
|
}:
|
||||||
|
return oidSDK.NewIDFromV2(v.GetObjectID())
|
||||||
|
case interface {
|
||||||
|
GetAddress() *refsV2.Address
|
||||||
|
}:
|
||||||
|
return oidSDK.NewIDFromV2(v.GetAddress().GetObjectID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getObjectOwnerFromMessage(req interface{}) (id *owner.ID, err error) {
|
||||||
|
switch v := req.(type) {
|
||||||
|
case *objectV2.PutRequest:
|
||||||
|
objPart := v.GetBody().GetObjectPart()
|
||||||
|
if part, ok := objPart.(*objectV2.PutObjectPartInit); ok {
|
||||||
|
return owner.NewIDFromV2(part.GetHeader().GetOwnerID()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("can't get container ID in chunk")
|
||||||
|
case *objectV2.GetResponse:
|
||||||
|
objPart := v.GetBody().GetObjectPart()
|
||||||
|
if part, ok := objPart.(*objectV2.GetObjectPartInit); ok {
|
||||||
|
return owner.NewIDFromV2(part.GetHeader().GetOwnerID()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("can't get container ID in chunk")
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported request type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceVerbOfRequest looks for verb in session token and if it is not found,
|
||||||
|
// returns reqVerb.
|
||||||
|
func sourceVerbOfRequest(req MetaWithToken, reqVerb eaclSDK.Operation) eaclSDK.Operation {
|
||||||
|
if req.token != nil {
|
||||||
|
switch v := req.token.Context().(type) {
|
||||||
|
case *sessionSDK.ObjectContext:
|
||||||
|
return tokenVerbToOperation(v)
|
||||||
|
default:
|
||||||
|
// do nothing, return request verb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reqVerb
|
||||||
|
}
|
||||||
|
|
||||||
|
func useObjectIDFromSession(req *RequestInfo, token *sessionSDK.Token) {
|
||||||
|
if token == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objCtx, ok := token.Context().(*sessionSDK.ObjectContext)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.oid = objCtx.Address().ObjectID()
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenVerbToOperation(ctx *sessionSDK.ObjectContext) eaclSDK.Operation {
|
||||||
|
switch {
|
||||||
|
case ctx.IsForGet():
|
||||||
|
return eaclSDK.OperationGet
|
||||||
|
case ctx.IsForPut():
|
||||||
|
return eaclSDK.OperationPut
|
||||||
|
case ctx.IsForHead():
|
||||||
|
return eaclSDK.OperationHead
|
||||||
|
case ctx.IsForSearch():
|
||||||
|
return eaclSDK.OperationSearch
|
||||||
|
case ctx.IsForDelete():
|
||||||
|
return eaclSDK.OperationDelete
|
||||||
|
case ctx.IsForRange():
|
||||||
|
return eaclSDK.OperationRange
|
||||||
|
case ctx.IsForRangeHash():
|
||||||
|
return eaclSDK.OperationRangeHash
|
||||||
|
default:
|
||||||
|
return eaclSDK.OperationUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ownerFromToken(token *sessionSDK.Token) (*owner.ID, *keys.PublicKey, error) {
|
||||||
|
// 1. First check signature of session token.
|
||||||
|
if !token.VerifySignature() {
|
||||||
|
return nil, nil, fmt.Errorf("%w: invalid session token signature", ErrMalformedRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Then check if session token owner issued the session token
|
||||||
|
tokenIssuerKey := unmarshalPublicKey(token.Signature().Key())
|
||||||
|
tokenOwner := token.OwnerID()
|
||||||
|
|
||||||
|
if !isOwnerFromKey(tokenOwner, tokenIssuerKey) {
|
||||||
|
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
|
||||||
|
return nil, nil, fmt.Errorf("%w: invalid session token owner", ErrMalformedRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenOwner, tokenIssuerKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func originalBodySignature(v *sessionV2.RequestVerificationHeader) *signature.Signature {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for v.GetOrigin() != nil {
|
||||||
|
v = v.GetOrigin()
|
||||||
|
}
|
||||||
|
|
||||||
|
return signature.NewFromV2(v.GetBodySignature())
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalPublicKey(bs []byte) *keys.PublicKey {
|
||||||
|
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return pub
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOwnerFromKey(id *owner.ID, key *keys.PublicKey) bool {
|
||||||
|
if id == nil || key == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return id.Equal(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key)))
|
||||||
|
}
|
38
pkg/services/object/acl/v2/util_test.go
Normal file
38
pkg/services/object/acl/v2/util_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||||
|
acltest "github.com/nspcc-dev/neofs-api-go/v2/acl/test"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
|
sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test"
|
||||||
|
sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
|
bearerSDK "github.com/nspcc-dev/neofs-sdk-go/token"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOriginalTokens(t *testing.T) {
|
||||||
|
sToken := sessiontest.GenerateSessionToken(false)
|
||||||
|
bToken := acltest.GenerateBearerToken(false)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
metaHeaders := testGenerateMetaHeader(uint32(i), bToken, sToken)
|
||||||
|
require.Equal(t, sessionSDK.NewTokenFromV2(sToken), originalSessionToken(metaHeaders), i)
|
||||||
|
require.Equal(t, bearerSDK.NewBearerTokenFromV2(bToken), originalBearerToken(metaHeaders), i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGenerateMetaHeader(depth uint32, b *acl.BearerToken, s *session.SessionToken) *session.RequestMetaHeader {
|
||||||
|
metaHeader := new(session.RequestMetaHeader)
|
||||||
|
metaHeader.SetBearerToken(b)
|
||||||
|
metaHeader.SetSessionToken(s)
|
||||||
|
|
||||||
|
for i := uint32(0); i < depth; i++ {
|
||||||
|
link := metaHeader
|
||||||
|
metaHeader = new(session.RequestMetaHeader)
|
||||||
|
metaHeader.SetOrigin(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
return metaHeader
|
||||||
|
}
|
Loading…
Reference in a new issue