Skip to content

Commit b455b9a

Browse files
committed
object: add new session token V2
Signed-off-by: Andrey Butusov <[email protected]>
1 parent bac0a1d commit b455b9a

File tree

12 files changed

+377
-37
lines changed

12 files changed

+377
-37
lines changed

cmd/neofs-node/object.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ func initObjectService(c *cfg) {
292292
fsChain: fsChain,
293293
netmapContract: c.nCli,
294294
}),
295+
v2.WithNodeKey(&c.key.PrivateKey.PublicKey),
295296
v2.WithContainerSource(c.cnrSrc),
296297
)
297298
addNewEpochAsyncNotificationHandler(c, func(event.Event) {

pkg/services/container/server.go

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ type sessionTokenCommonCheckResult struct {
7979
err error
8080
}
8181

82+
type sessionTokenV2CommonCheckResult struct {
83+
token session.TokenV2
84+
err error
85+
}
86+
8287
// Server provides NeoFS API Container service.
8388
type Server struct {
8489
protocontainer.UnimplementedContainerServiceServer
@@ -87,7 +92,8 @@ type Server struct {
8792
contract Contract
8893
historicN3ScriptRunner
8994

90-
sessionTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenCommonCheckResult]
95+
sessionTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenCommonCheckResult]
96+
sessionTokenV2CommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenV2CommonCheckResult]
9197
}
9298

9399
// New provides protocontainer.ContainerServiceServer based on specified
@@ -100,6 +106,10 @@ func New(s *ecdsa.PrivateKey, net netmap.State, fsChain FSChain, c Contract, nc
100106
if err != nil {
101107
panic(fmt.Errorf("unexpected error in lru.New: %w", err))
102108
}
109+
sessionTokenV2CheckCache, err := lru.New[[sha256.Size]byte, sessionTokenV2CommonCheckResult](1000)
110+
if err != nil {
111+
panic(fmt.Errorf("unexpected error in lru.New for v2: %w", err))
112+
}
103113
return &Server{
104114
signer: s,
105115
net: net,
@@ -108,7 +118,8 @@ func New(s *ecdsa.PrivateKey, net netmap.State, fsChain FSChain, c Contract, nc
108118
FSChain: fsChain,
109119
NetmapContract: nc,
110120
},
111-
sessionTokenCommonCheckCache: sessionTokenCheckCache,
121+
sessionTokenCommonCheckCache: sessionTokenCheckCache,
122+
sessionTokenV2CommonCheckCache: sessionTokenV2CheckCache,
112123
}
113124
}
114125

@@ -216,6 +227,101 @@ func (s *Server) checkSessionIssuer(id cid.ID, issuer user.ID) error {
216227
return nil
217228
}
218229

230+
func (s *Server) getVerifiedSessionTokenV2(mh *protosession.RequestMetaHeader, reqVerb session.ContainerVerb, reqCnr cid.ID) (*session.TokenV2, error) {
231+
for omh := mh.GetOrigin(); omh != nil; omh = mh.GetOrigin() {
232+
mh = omh
233+
}
234+
m := mh.GetSessionTokenV2()
235+
if m == nil {
236+
return nil, nil
237+
}
238+
239+
b := make([]byte, m.MarshaledSize())
240+
m.MarshalStable(b)
241+
242+
cacheKey := sha256.Sum256(b)
243+
res, ok := s.sessionTokenV2CommonCheckCache.Get(cacheKey)
244+
if !ok {
245+
res.token, res.err = s.decodeAndVerifySessionTokenV2Common(m)
246+
s.sessionTokenV2CommonCheckCache.Add(cacheKey, res)
247+
}
248+
if res.err != nil {
249+
return nil, res.err
250+
}
251+
252+
if err := s.verifySessionTokenV2AgainstRequest(res.token, reqVerb, reqCnr); err != nil {
253+
return nil, err
254+
}
255+
256+
return &res.token, nil
257+
}
258+
259+
func (s *Server) decodeAndVerifySessionTokenV2Common(m *protosession.SessionTokenV2) (session.TokenV2, error) {
260+
var token session.TokenV2
261+
if err := token.FromProtoMessage(m); err != nil {
262+
return token, fmt.Errorf("decode v2: %w", err)
263+
}
264+
265+
if err := token.Validate(); err != nil {
266+
return token, fmt.Errorf("invalid v2 token: %w", err)
267+
}
268+
269+
if !token.VerifySignature() {
270+
return token, errors.New("v2 session token signature verification failed")
271+
}
272+
273+
serverTarget := session.NewTarget(user.NewFromECDSAPublicKey(s.signer.PublicKey))
274+
if !token.AssertAuthority(serverTarget) {
275+
return token, errors.New("v2 token doesn't authorize this node")
276+
}
277+
278+
cur := s.net.CurrentEpoch()
279+
if !token.ValidAt(cur) {
280+
return token, fmt.Errorf("v2 token is invalid at epoch %d", cur)
281+
}
282+
283+
return token, nil
284+
}
285+
286+
func (s *Server) verifySessionTokenV2AgainstRequest(token session.TokenV2, reqVerb session.ContainerVerb, reqCnr cid.ID) error {
287+
verbV2 := containerVerbToVerbV2(reqVerb)
288+
if verbV2 == 0 {
289+
return fmt.Errorf("unsupported container verb for V2: %v", reqVerb)
290+
}
291+
292+
if !token.AssertContainer(verbV2, reqCnr) {
293+
return errors.New("v2 session token does not authorize this container operation")
294+
}
295+
296+
if reqCnr.IsZero() {
297+
return nil
298+
}
299+
300+
issuer := token.Issuer()
301+
if !issuer.IsOwnerID() {
302+
return errors.New("v2 session token issuer must be OwnerID for container operations")
303+
}
304+
305+
if err := s.checkSessionIssuer(reqCnr, issuer.OwnerID()); err != nil {
306+
return fmt.Errorf("verify v2 session issuer: %w", err)
307+
}
308+
309+
return nil
310+
}
311+
312+
func containerVerbToVerbV2(verb session.ContainerVerb) session.VerbV2 {
313+
switch verb {
314+
case session.VerbContainerPut:
315+
return session.VerbV2ContainerPut
316+
case session.VerbContainerDelete:
317+
return session.VerbV2ContainerDelete
318+
case session.VerbContainerSetEACL:
319+
return session.VerbV2ContainerSetEACL
320+
default:
321+
return session.VerbV2Unspecified
322+
}
323+
}
324+
219325
func (s *Server) makePutResponse(body *protocontainer.PutResponse_Body, st *protostatus.Status) (*protocontainer.PutResponse, error) {
220326
resp := &protocontainer.PutResponse{
221327
Body: body,
@@ -307,9 +413,17 @@ func (s *Server) Put(_ context.Context, req *protocontainer.PutRequest) (*protoc
307413
return s.makeFailedPutResponse(fmt.Errorf("invalid container: %w", err))
308414
}
309415

310-
st, err := s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerPut, cid.ID{})
416+
stV2, err := s.getVerifiedSessionTokenV2(req.GetMetaHeader(), session.VerbContainerPut, cid.ID{})
311417
if err != nil {
312-
return s.makeFailedPutResponse(fmt.Errorf("verify session token: %w", err))
418+
return s.makeFailedPutResponse(fmt.Errorf("verify session token v2: %w", err))
419+
}
420+
421+
var st *session.Container
422+
if stV2 == nil {
423+
st, err = s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerPut, cid.ID{})
424+
if err != nil {
425+
return s.makeFailedPutResponse(fmt.Errorf("verify session token: %w", err))
426+
}
313427
}
314428

315429
id, err := s.contract.Put(cnr, mSig.Key, mSig.Sign, st)
@@ -353,9 +467,17 @@ func (s *Server) Delete(_ context.Context, req *protocontainer.DeleteRequest) (*
353467
return s.makeDeleteResponse(fmt.Errorf("invalid ID: %w", err))
354468
}
355469

356-
st, err := s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerDelete, id)
470+
stV2, err := s.getVerifiedSessionTokenV2(req.GetMetaHeader(), session.VerbContainerDelete, id)
357471
if err != nil {
358-
return s.makeDeleteResponse(fmt.Errorf("verify session token: %w", err))
472+
return s.makeDeleteResponse(fmt.Errorf("verify session token v2: %w", err))
473+
}
474+
475+
var st *session.Container
476+
if stV2 == nil {
477+
st, err = s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerDelete, id)
478+
if err != nil {
479+
return s.makeDeleteResponse(fmt.Errorf("verify session token: %w", err))
480+
}
359481
}
360482

361483
if err := s.contract.Delete(id, mSig.Key, mSig.Sign, st); err != nil {
@@ -491,9 +613,17 @@ func (s *Server) SetExtendedACL(_ context.Context, req *protocontainer.SetExtend
491613
return s.makeSetEACLResponse(errors.New("missing container ID in eACL table"))
492614
}
493615

494-
st, err := s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerSetEACL, cnrID)
616+
stV2, err := s.getVerifiedSessionTokenV2(req.GetMetaHeader(), session.VerbContainerSetEACL, cnrID)
495617
if err != nil {
496-
return s.makeSetEACLResponse(fmt.Errorf("verify session token: %w", err))
618+
return s.makeSetEACLResponse(fmt.Errorf("verify session token v2: %w", err))
619+
}
620+
621+
var st *session.Container
622+
if stV2 == nil {
623+
st, err = s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerSetEACL, cnrID)
624+
if err != nil {
625+
return s.makeSetEACLResponse(fmt.Errorf("verify session token: %w", err))
626+
}
497627
}
498628

499629
if err := s.contract.PutEACL(eACL, mSig.Key, mSig.Sign, st); err != nil {

pkg/services/object/acl/v2/opts.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package v2
22

33
import (
4+
"crypto/ecdsa"
5+
46
"github.com/nspcc-dev/neofs-node/pkg/core/container"
57
"go.uber.org/zap"
68
)
@@ -33,3 +35,10 @@ func WithIRFetcher(v InnerRingFetcher) Option {
3335
c.irFetcher = v
3436
}
3537
}
38+
39+
// WithNodeKey returns option to set node's public key for SessionTokenV2 authority checks.
40+
func WithNodeKey(v *ecdsa.PublicKey) Option {
41+
return func(c *cfg) {
42+
c.nodeKey = v
43+
}
44+
}

0 commit comments

Comments
 (0)