diff --git a/pkg/services/object/acl/acl.go b/pkg/services/object/acl/acl.go index 4a29883d39..5966ac5d13 100644 --- a/pkg/services/object/acl/acl.go +++ b/pkg/services/object/acl/acl.go @@ -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 +} diff --git a/pkg/services/object/acl/classifier.go b/pkg/services/object/acl/classifier.go index 444aef703d..9700f60686 100644 --- a/pkg/services/object/acl/classifier.go +++ b/pkg/services/object/acl/classifier.go @@ -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 { diff --git a/pkg/services/object/acl/eacl/types.go b/pkg/services/object/acl/eacl/types.go index 7095724757..c2ec8ac8d5 100644 --- a/pkg/services/object/acl/eacl/types.go +++ b/pkg/services/object/acl/eacl/types.go @@ -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 +} diff --git a/pkg/services/object/acl/eacl/validator.go b/pkg/services/object/acl/eacl/validator.go index a047d86107..1315836ed5 100644 --- a/pkg/services/object/acl/eacl/validator.go +++ b/pkg/services/object/acl/eacl/validator.go @@ -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)