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"
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue