[#106] Process bearer token in ACL service

If bearer token is presented in the request then check
if it is a valid one and then use it to process extended
ACL checks.

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
Alex Vanin 2020-10-21 16:11:18 +03:00 committed by Alex Vanin
parent 094248690b
commit 89cd2ad463
4 changed files with 107 additions and 8 deletions

View file

@ -8,8 +8,12 @@ import (
acl "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" acl "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
"github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/container"
"github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/owner"
"github.com/nspcc-dev/neofs-api-go/util/signature"
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/object"
"github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-api-go/v2/session"
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
crypto "github.com/nspcc-dev/neofs-crypto"
core "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/local_object_storage/localstore" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl" "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
@ -54,6 +58,8 @@ type (
cid *container.ID cid *container.ID
senderKey []byte senderKey []byte
bearer *bearer.BearerToken // bearer token of request
} }
) )
@ -123,6 +129,7 @@ func (b Service) Get(
req := metaWithToken{ req := metaWithToken{
vheader: request.GetVerificationHeader(), vheader: request.GetVerificationHeader(),
token: request.GetMetaHeader().GetSessionToken(), token: request.GetMetaHeader().GetSessionToken(),
bearer: request.GetMetaHeader().GetBearerToken(),
} }
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationGet) reqInfo, err := b.findRequestInfo(req, cid, acl.OperationGet)
@ -167,6 +174,7 @@ func (b Service) Head(
req := metaWithToken{ req := metaWithToken{
vheader: request.GetVerificationHeader(), vheader: request.GetVerificationHeader(),
token: request.GetMetaHeader().GetSessionToken(), token: request.GetMetaHeader().GetSessionToken(),
bearer: request.GetMetaHeader().GetBearerToken(),
} }
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationHead) reqInfo, err := b.findRequestInfo(req, cid, acl.OperationHead)
@ -204,6 +212,7 @@ func (b Service) Search(
req := metaWithToken{ req := metaWithToken{
vheader: request.GetVerificationHeader(), vheader: request.GetVerificationHeader(),
token: request.GetMetaHeader().GetSessionToken(), token: request.GetMetaHeader().GetSessionToken(),
bearer: request.GetMetaHeader().GetBearerToken(),
} }
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationSearch) reqInfo, err := b.findRequestInfo(req, cid, acl.OperationSearch)
@ -233,6 +242,7 @@ func (b Service) Delete(
req := metaWithToken{ req := metaWithToken{
vheader: request.GetVerificationHeader(), vheader: request.GetVerificationHeader(),
token: request.GetMetaHeader().GetSessionToken(), token: request.GetMetaHeader().GetSessionToken(),
bearer: request.GetMetaHeader().GetBearerToken(),
} }
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationDelete) reqInfo, err := b.findRequestInfo(req, cid, acl.OperationDelete)
@ -261,6 +271,7 @@ func (b Service) GetRange(
req := metaWithToken{ req := metaWithToken{
vheader: request.GetVerificationHeader(), vheader: request.GetVerificationHeader(),
token: request.GetMetaHeader().GetSessionToken(), token: request.GetMetaHeader().GetSessionToken(),
bearer: request.GetMetaHeader().GetBearerToken(),
} }
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationRange) reqInfo, err := b.findRequestInfo(req, cid, acl.OperationRange)
@ -290,6 +301,7 @@ func (b Service) GetRangeHash(
req := metaWithToken{ req := metaWithToken{
vheader: request.GetVerificationHeader(), vheader: request.GetVerificationHeader(),
token: request.GetMetaHeader().GetSessionToken(), token: request.GetMetaHeader().GetSessionToken(),
bearer: request.GetMetaHeader().GetBearerToken(),
} }
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationRangeHash) reqInfo, err := b.findRequestInfo(req, cid, acl.OperationRangeHash)
@ -327,6 +339,7 @@ func (p putStreamBasicChecker) Send(request *object.PutRequest) error {
req := metaWithToken{ req := metaWithToken{
vheader: request.GetVerificationHeader(), vheader: request.GetVerificationHeader(),
token: part.GetHeader().GetSessionToken(), token: part.GetHeader().GetSessionToken(),
bearer: request.GetMetaHeader().GetBearerToken(),
} }
reqInfo, err := p.source.findRequestInfo(req, cid, acl.OperationPut) reqInfo, err := p.source.findRequestInfo(req, cid, acl.OperationPut)
@ -411,6 +424,9 @@ func (b Service) findRequestInfo(
// otherwise the request would not pass validation // otherwise the request would not pass validation
info.senderKey = key info.senderKey = key
// add bearer token if it is present in request
info.bearer = req.bearer
return info, nil return info, nil
} }
@ -499,6 +515,11 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
return true return true
} }
// if bearer token is not present, isValidBearer returns true
if !isValidBearer(reqInfo) {
return false
}
hdrSrcOpts := make([]eaclV2.Option, 0, 2) hdrSrcOpts := make([]eaclV2.Option, 0, 2)
hdrSrcOpts = append(hdrSrcOpts, eaclV2.WithLocalObjectStorage(cfg.localStorage)) hdrSrcOpts = append(hdrSrcOpts, eaclV2.WithLocalObjectStorage(cfg.localStorage))
@ -516,7 +537,8 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
WithSenderKey(reqInfo.senderKey). WithSenderKey(reqInfo.senderKey).
WithHeaderSource( WithHeaderSource(
eaclV2.NewMessageHeaderSource(hdrSrcOpts...), eaclV2.NewMessageHeaderSource(hdrSrcOpts...),
), ).
WithBearerToken(reqInfo.bearer),
) )
return action == acl.ActionAllow return action == acl.ActionAllow
@ -575,3 +597,58 @@ func eACLErr(info requestInfo) error {
failedCheckTyp: "extended ACL", failedCheckTyp: "extended ACL",
} }
} }
// isValidBearer returns true if bearer token correctly signed by authorized
// entity. This method might be define on whole ACL service because it will
// require to fetch current epoch to check lifetime.
func isValidBearer(reqInfo requestInfo) bool {
token := reqInfo.bearer
// 0. Check if bearer token is present in reqInfo. It might be non nil
// empty structure.
if token == nil || (token.GetBody() == nil && token.GetSignature() == nil) {
return true
}
// 1. First check if bearer token is signed correctly.
signWrapper := v2signature.StableMarshalerWrapper{SM: token.GetBody()}
if err := signature.VerifyDataWithSource(signWrapper, func() (key, sig []byte) {
tokenSignature := token.GetSignature()
return tokenSignature.GetKey(), tokenSignature.GetSign()
}); err != nil {
return false // invalid signature
}
// 2. Then check if container owner signed this token.
tokenIssuerKey := crypto.UnmarshalPublicKey(token.GetSignature().GetKey())
tokenIssuerWallet, err := owner.NEO3WalletFromPublicKey(tokenIssuerKey)
if err != nil {
return false
}
// here we compare `owner.ID -> wallet` with `wallet <- publicKey`
// consider making equal method on owner.ID structure
// we can compare .String() version of owners but don't think it is good idea
// binary comparison is better but MarshalBinary is more expensive
if !bytes.Equal(reqInfo.owner.ToV2().GetValue(), tokenIssuerWallet.Bytes()) {
// todo: in this case we can issue all owner keys from neofs.id and check once again
return false
}
// 3. Then check if request sender has rights to use this token.
tokenOwnerField := token.GetBody().GetOwnerID()
if tokenOwnerField != nil { // see bearer token owner field description
requestSenderKey := crypto.UnmarshalPublicKey(reqInfo.senderKey)
requestSenderWallet, err := owner.NEO3WalletFromPublicKey(requestSenderKey)
if err != nil {
return false
}
// the same issue as above
if !bytes.Equal(tokenOwnerField.GetValue(), requestSenderWallet.Bytes()) {
return false
}
}
// todo: 4. Then check token lifetime.
return true
}

View file

@ -10,6 +10,7 @@ import (
"github.com/nspcc-dev/neofs-api-go/pkg/netmap" "github.com/nspcc-dev/neofs-api-go/pkg/netmap"
"github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/owner"
"github.com/nspcc-dev/neofs-api-go/util/signature" "github.com/nspcc-dev/neofs-api-go/util/signature"
bearer "github.com/nspcc-dev/neofs-api-go/v2/acl"
"github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-api-go/v2/session"
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature" v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
crypto "github.com/nspcc-dev/neofs-crypto" crypto "github.com/nspcc-dev/neofs-crypto"
@ -25,6 +26,7 @@ type (
metaWithToken struct { metaWithToken struct {
vheader *session.RequestVerificationHeader vheader *session.RequestVerificationHeader
token *session.SessionToken token *session.SessionToken
bearer *bearer.BearerToken
} }
SenderClassifier struct { SenderClassifier struct {

View file

@ -3,6 +3,7 @@ package eacl
import ( import (
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
"github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/container"
bearer "github.com/nspcc-dev/neofs-api-go/v2/acl"
) )
// Storage is the interface that wraps // Storage is the interface that wraps
@ -43,6 +44,8 @@ type ValidationUnit struct {
hdrSrc TypedHeaderSource hdrSrc TypedHeaderSource
key []byte key []byte
bearer *bearer.BearerToken
} }
func (u *ValidationUnit) WithContainerID(v *container.ID) *ValidationUnit { func (u *ValidationUnit) WithContainerID(v *container.ID) *ValidationUnit {
@ -84,3 +87,11 @@ func (u *ValidationUnit) WithSenderKey(v []byte) *ValidationUnit {
return u return u
} }
func (u *ValidationUnit) WithBearerToken(bearer *bearer.BearerToken) *ValidationUnit {
if u != nil {
u.bearer = bearer
}
return u
}

View file

@ -55,14 +55,23 @@ func NewValidator(opts ...Option) *Validator {
// //
// If no matching table entry is found, ActionAllow is returned. // If no matching table entry is found, ActionAllow is returned.
func (v *Validator) CalculateAction(unit *ValidationUnit) eacl.Action { func (v *Validator) CalculateAction(unit *ValidationUnit) eacl.Action {
// get eACL table by container ID var (
table, err := v.storage.GetEACL(unit.cid) err error
if err != nil { table *eacl.Table
v.logger.Error("could not get eACL table", )
zap.String("error", err.Error()),
)
return eacl.ActionUnknown if unit.bearer != nil {
table = eacl.NewTableFromV2(unit.bearer.GetBody().GetEACL())
} else {
// get eACL table by container ID
table, err = v.storage.GetEACL(unit.cid)
if err != nil {
v.logger.Error("could not get eACL table",
zap.String("error", err.Error()),
)
return eacl.ActionUnknown
}
} }
return tableAction(unit, table) return tableAction(unit, table)