forked from TrueCloudLab/frostfs-node
[#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:
parent
094248690b
commit
89cd2ad463
4 changed files with 107 additions and 8 deletions
|
@ -8,8 +8,12 @@ import (
|
|||
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/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/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"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl"
|
||||
|
@ -54,6 +58,8 @@ type (
|
|||
cid *container.ID
|
||||
|
||||
senderKey []byte
|
||||
|
||||
bearer *bearer.BearerToken // bearer token of request
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -123,6 +129,7 @@ func (b Service) Get(
|
|||
req := metaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: request.GetMetaHeader().GetSessionToken(),
|
||||
bearer: request.GetMetaHeader().GetBearerToken(),
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationGet)
|
||||
|
@ -167,6 +174,7 @@ func (b Service) Head(
|
|||
req := metaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: request.GetMetaHeader().GetSessionToken(),
|
||||
bearer: request.GetMetaHeader().GetBearerToken(),
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationHead)
|
||||
|
@ -204,6 +212,7 @@ func (b Service) Search(
|
|||
req := metaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: request.GetMetaHeader().GetSessionToken(),
|
||||
bearer: request.GetMetaHeader().GetBearerToken(),
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationSearch)
|
||||
|
@ -233,6 +242,7 @@ func (b Service) Delete(
|
|||
req := metaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: request.GetMetaHeader().GetSessionToken(),
|
||||
bearer: request.GetMetaHeader().GetBearerToken(),
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationDelete)
|
||||
|
@ -261,6 +271,7 @@ func (b Service) GetRange(
|
|||
req := metaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: request.GetMetaHeader().GetSessionToken(),
|
||||
bearer: request.GetMetaHeader().GetBearerToken(),
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationRange)
|
||||
|
@ -290,6 +301,7 @@ func (b Service) GetRangeHash(
|
|||
req := metaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: request.GetMetaHeader().GetSessionToken(),
|
||||
bearer: request.GetMetaHeader().GetBearerToken(),
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(req, cid, acl.OperationRangeHash)
|
||||
|
@ -327,6 +339,7 @@ func (p putStreamBasicChecker) Send(request *object.PutRequest) error {
|
|||
req := metaWithToken{
|
||||
vheader: request.GetVerificationHeader(),
|
||||
token: part.GetHeader().GetSessionToken(),
|
||||
bearer: request.GetMetaHeader().GetBearerToken(),
|
||||
}
|
||||
|
||||
reqInfo, err := p.source.findRequestInfo(req, cid, acl.OperationPut)
|
||||
|
@ -411,6 +424,9 @@ func (b Service) findRequestInfo(
|
|||
// otherwise the request would not pass validation
|
||||
info.senderKey = key
|
||||
|
||||
// add bearer token if it is present in request
|
||||
info.bearer = req.bearer
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
|
@ -499,6 +515,11 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// if bearer token is not present, isValidBearer returns true
|
||||
if !isValidBearer(reqInfo) {
|
||||
return false
|
||||
}
|
||||
|
||||
hdrSrcOpts := make([]eaclV2.Option, 0, 2)
|
||||
|
||||
hdrSrcOpts = append(hdrSrcOpts, eaclV2.WithLocalObjectStorage(cfg.localStorage))
|
||||
|
@ -516,7 +537,8 @@ func eACLCheck(msg interface{}, reqInfo requestInfo, cfg *eACLCfg) bool {
|
|||
WithSenderKey(reqInfo.senderKey).
|
||||
WithHeaderSource(
|
||||
eaclV2.NewMessageHeaderSource(hdrSrcOpts...),
|
||||
),
|
||||
).
|
||||
WithBearerToken(reqInfo.bearer),
|
||||
)
|
||||
|
||||
return action == acl.ActionAllow
|
||||
|
@ -575,3 +597,58 @@ func eACLErr(info requestInfo) error {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"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/util/signature"
|
||||
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"
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
|
@ -25,6 +26,7 @@ type (
|
|||
metaWithToken struct {
|
||||
vheader *session.RequestVerificationHeader
|
||||
token *session.SessionToken
|
||||
bearer *bearer.BearerToken
|
||||
}
|
||||
|
||||
SenderClassifier struct {
|
||||
|
|
|
@ -3,6 +3,7 @@ package eacl
|
|||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
"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
|
||||
|
@ -43,6 +44,8 @@ type ValidationUnit struct {
|
|||
hdrSrc TypedHeaderSource
|
||||
|
||||
key []byte
|
||||
|
||||
bearer *bearer.BearerToken
|
||||
}
|
||||
|
||||
func (u *ValidationUnit) WithContainerID(v *container.ID) *ValidationUnit {
|
||||
|
@ -84,3 +87,11 @@ func (u *ValidationUnit) WithSenderKey(v []byte) *ValidationUnit {
|
|||
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *ValidationUnit) WithBearerToken(bearer *bearer.BearerToken) *ValidationUnit {
|
||||
if u != nil {
|
||||
u.bearer = bearer
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
|
|
@ -55,14 +55,23 @@ func NewValidator(opts ...Option) *Validator {
|
|||
//
|
||||
// If no matching table entry is found, ActionAllow is returned.
|
||||
func (v *Validator) CalculateAction(unit *ValidationUnit) eacl.Action {
|
||||
// get eACL table by container ID
|
||||
table, err := v.storage.GetEACL(unit.cid)
|
||||
if err != nil {
|
||||
v.logger.Error("could not get eACL table",
|
||||
zap.String("error", err.Error()),
|
||||
)
|
||||
var (
|
||||
err error
|
||||
table *eacl.Table
|
||||
)
|
||||
|
||||
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)
|
||||
|
|
Loading…
Reference in a new issue