frostfs-node/services/public/object/acl.go
alexvanin dadfd90dcd Initial commit
Initial public review release v0.10.0
2020-07-10 17:45:00 +03:00

428 lines
10 KiB
Go

package object
import (
"bytes"
"context"
"crypto/ecdsa"
"github.com/nspcc-dev/neofs-api-go/acl"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
crypto "github.com/nspcc-dev/neofs-crypto"
libacl "github.com/nspcc-dev/neofs-node/lib/acl"
"github.com/nspcc-dev/neofs-node/lib/implementations"
"github.com/nspcc-dev/neofs-node/lib/ir"
"github.com/nspcc-dev/neofs-node/lib/localstore"
"github.com/nspcc-dev/neofs-node/lib/transport"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
// RequestTargeter is an interface of request's ACL target calculator.
RequestTargeter interface {
Target(context.Context, serviceRequest) acl.Target
}
// aclPreProcessor is an implementation of requestPreProcessor interface.
aclPreProcessor struct {
log *zap.Logger
aclInfoReceiver aclInfoReceiver
basicChecker libacl.BasicChecker
reqActionCalc requestActionCalculator
localStore localstore.Localstore
extACLSource libacl.ExtendedACLSource
bearerVerifier bearerTokenVerifier
}
targetFinder struct {
log *zap.Logger
irStorage ir.Storage
cnrLister implementations.ContainerNodesLister
cnrOwnerChecker implementations.ContainerOwnerChecker
}
)
type objectHeadersSource interface {
getHeaders() (*Object, bool)
}
type requestActionCalculator interface {
calculateRequestAction(context.Context, requestActionParams) acl.ExtendedACLAction
}
type aclInfoReceiver struct {
basicACLGetter implementations.BasicACLGetter
basicChecker libacl.BasicChecker
targetFinder RequestTargeter
}
type aclInfo struct {
rule uint32
checkExtended bool
checkBearer bool
target acl.Target
}
type reqActionCalc struct {
extACLChecker libacl.ExtendedACLChecker
log *zap.Logger
}
type serviceRequestInfo struct {
target acl.Target
req serviceRequest
objHdrSrc objectHeadersSource
}
type requestObjHdrSrc struct {
req serviceRequest
ls localstore.Localstore
}
type eaclFromBearer struct {
bearer service.BearerToken
}
var _ requestPreProcessor = (*aclPreProcessor)(nil)
var errMissingSignatures = errors.New("empty signature list")
func (p *aclPreProcessor) preProcess(ctx context.Context, req serviceRequest) error {
if req == nil {
panic(pmEmptyServiceRequest)
}
// fetch ACL info
aclInfo, err := p.aclInfoReceiver.getACLInfo(ctx, req)
if err != nil {
p.log.Warn("can't get acl of the container", zap.Stringer("cid", req.CID()))
return errAccessDenied
}
// check basic ACL permissions
allow, err := p.basicChecker.Action(aclInfo.rule, req.Type(), aclInfo.target)
if err != nil || !allow {
return errAccessDenied
}
if aclInfo.target != acl.Target_System &&
p.basicChecker.Sticky(aclInfo.rule) &&
!checkObjectRequestOwnerMatch(req) {
return errAccessDenied
}
if !aclInfo.checkBearer && !aclInfo.checkExtended {
return nil
}
actionParams := requestActionParams{
eaclSrc: p.extACLSource,
request: req,
objHdrSrc: &requestObjHdrSrc{
req: req,
ls: p.localStore,
},
target: aclInfo.target,
}
if aclInfo.checkBearer {
bearer := req.GetBearerToken()
if err := p.bearerVerifier.verifyBearerToken(ctx, req.CID(), bearer); err != nil {
p.log.Warn("bearer token verification failure",
zap.String("error", err.Error()),
)
return errAccessDenied
}
actionParams.eaclSrc = eaclFromBearer{
bearer: bearer,
}
}
if p.reqActionCalc.calculateRequestAction(ctx, actionParams) != acl.ActionAllow {
return errAccessDenied
}
return nil
}
func (t *targetFinder) Target(ctx context.Context, req serviceRequest) acl.Target {
ownerID, ownerKey, err := requestOwner(req)
if err != nil {
t.log.Warn("could not get request owner",
zap.String("error", err.Error()),
)
return acl.Target_Unknown
} else if ownerKey == nil {
t.log.Warn("signature with nil public key detected")
return acl.Target_Unknown
}
// if request from container owner then return Target_User
isOwner, err := t.cnrOwnerChecker.IsContainerOwner(ctx, req.CID(), ownerID)
if err != nil {
t.log.Warn("can't check container owner", zap.String("err", err.Error()))
return acl.Target_Unknown
} else if isOwner {
return acl.Target_User
}
ownerKeyBytes := crypto.MarshalPublicKey(ownerKey)
// if request from inner ring then return Target_System
isIRKey, err := ir.IsInnerRingKey(t.irStorage, ownerKeyBytes)
if err != nil {
t.log.Warn("could not verify the key belongs to the node", zap.String("err", err.Error()))
return acl.Target_Unknown
} else if isIRKey {
return acl.Target_System
}
// if request from current container node then return Target_System
cnr, err := t.cnrLister.ContainerNodesInfo(ctx, req.CID(), 0)
if err != nil {
t.log.Warn("can't get current container list", zap.String("err", err.Error()))
return acl.Target_Unknown
}
for i := range cnr {
if bytes.Equal(cnr[i].PubKey, ownerKeyBytes) {
return acl.Target_System
}
}
// if request from previous container node then return Target_System
cnr, err = t.cnrLister.ContainerNodesInfo(ctx, req.CID(), 1)
if err != nil {
t.log.Warn("can't get previous container list", zap.String("err", err.Error()))
return acl.Target_Unknown
}
for i := range cnr {
if bytes.Equal(cnr[i].PubKey, ownerKeyBytes) {
return acl.Target_System
}
}
// if none of the above return Target_Others
return acl.Target_Others
}
func checkObjectRequestOwnerMatch(req serviceRequest) bool {
rt := req.Type()
// ignore all request types except Put and Delete
if rt != object.RequestPut && rt != object.RequestDelete {
return true
}
// get request owner
reqOwner, _, err := requestOwner(req)
if err != nil {
return false
}
var payloadOwner OwnerID
// get owner from request payload
if rt == object.RequestPut {
obj := req.(transport.PutInfo).GetHead()
if obj == nil {
return false
}
payloadOwner = obj.GetSystemHeader().OwnerID
} else {
payloadOwner = req.(*object.DeleteRequest).OwnerID
}
return reqOwner.Equal(payloadOwner)
}
// FIXME: this solution only works with healthy key-to-owner conversion.
func requestOwner(req serviceRequest) (OwnerID, *ecdsa.PublicKey, error) {
// if session token exists => return its owner
if token := req.GetSessionToken(); token != nil {
return token.GetOwnerID(), crypto.UnmarshalPublicKey(token.GetOwnerKey()), nil
}
signKeys := req.GetSignKeyPairs()
if len(signKeys) == 0 {
return OwnerID{}, nil, errMissingSignatures
}
firstKey := signKeys[0].GetPublicKey()
if firstKey == nil {
return OwnerID{}, nil, crypto.ErrEmptyPublicKey
}
owner, err := refs.NewOwnerID(firstKey)
return owner, firstKey, err
}
// HeadersOfType returns request or object headers.
func (s serviceRequestInfo) HeadersOfType(typ acl.HeaderType) ([]acl.Header, bool) {
switch typ {
default:
return nil, true
case acl.HdrTypeRequest:
return libacl.TypedHeaderSourceFromExtendedHeaders(s.req).HeadersOfType(typ)
case acl.HdrTypeObjSys, acl.HdrTypeObjUsr:
obj, ok := s.objHdrSrc.getHeaders()
if !ok {
return nil, false
}
return libacl.TypedHeaderSourceFromObject(obj).HeadersOfType(typ)
}
}
// Key returns a binary representation of sender public key.
func (s serviceRequestInfo) Key() []byte {
_, key, err := requestOwner(s.req)
if err != nil {
return nil
}
return crypto.MarshalPublicKey(key)
}
// TypeOf returns true of object request type corresponds to passed OperationType.
func (s serviceRequestInfo) TypeOf(opType acl.OperationType) bool {
switch s.req.Type() {
case object.RequestGet:
return opType == acl.OpTypeGet
case object.RequestPut:
return opType == acl.OpTypePut
case object.RequestHead:
return opType == acl.OpTypeHead
case object.RequestSearch:
return opType == acl.OpTypeSearch
case object.RequestDelete:
return opType == acl.OpTypeDelete
case object.RequestRange:
return opType == acl.OpTypeRange
case object.RequestRangeHash:
return opType == acl.OpTypeRangeHash
default:
return false
}
}
// TargetOf return true if target field is equal to passed ACL target.
func (s serviceRequestInfo) TargetOf(target acl.Target) bool {
return s.target == target
}
func (s requestObjHdrSrc) getHeaders() (*Object, bool) {
switch s.req.Type() {
case object.RequestSearch:
// object header filters is not supported in Search request now
return nil, true
case object.RequestPut:
// for Put we get object headers from request
return s.req.(transport.PutInfo).GetHead(), true
default:
tReq := &transportRequest{
serviceRequest: s.req,
}
// for other requests we get object headers from local storage
m, err := s.ls.Meta(tReq.GetAddress())
if err == nil {
return m.GetObject(), true
}
return nil, false
}
}
type requestActionParams struct {
eaclSrc libacl.ExtendedACLSource
request serviceRequest
objHdrSrc objectHeadersSource
target acl.Target
}
func (s reqActionCalc) calculateRequestAction(ctx context.Context, p requestActionParams) acl.ExtendedACLAction {
// get EACL table
table, err := p.eaclSrc.GetExtendedACLTable(ctx, p.request.CID())
if err != nil {
s.log.Warn("could not get extended acl of the container",
zap.Stringer("cid", p.request.CID()),
zap.String("error", err.Error()),
)
return acl.ActionUndefined
}
// create RequestInfo instance
reqInfo := &serviceRequestInfo{
target: p.target,
req: p.request,
objHdrSrc: p.objHdrSrc,
}
// calculate ACL action
return s.extACLChecker.Action(table, reqInfo)
}
func (s aclInfoReceiver) getACLInfo(ctx context.Context, req serviceRequest) (*aclInfo, error) {
rule, err := s.basicACLGetter.GetBasicACL(ctx, req.CID())
if err != nil {
return nil, err
}
isBearer, err := s.basicChecker.Bearer(rule, req.Type())
if err != nil {
return nil, err
}
// fetch target from the request
target := s.targetFinder.Target(ctx, req)
return &aclInfo{
rule: rule,
checkExtended: target != acl.Target_System && s.basicChecker.Extended(rule),
target: target,
checkBearer: target != acl.Target_System && isBearer && req.GetBearerToken() != nil,
}, nil
}
func (s eaclFromBearer) GetExtendedACLTable(ctx context.Context, cid CID) (acl.ExtendedACLTable, error) {
table := acl.WrapEACLTable(nil)
if err := table.UnmarshalBinary(s.bearer.GetACLRules()); err != nil {
return nil, err
}
return table, nil
}