Skip to content

Commit 37fee9b

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

File tree

10 files changed

+352
-37
lines changed

10 files changed

+352
-37
lines changed

pkg/services/container/server.go

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

82+
type sessionTokenV2CommonCheckResult struct {
83+
token session.TokenV2
84+
err error
85+
}
86+
8287
type server struct {
8388
protocontainer.UnimplementedContainerServiceServer
8489
signer *ecdsa.PrivateKey
8590
net netmap.State
8691
contract Contract
8792
historicN3ScriptRunner
8893

89-
sessionTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenCommonCheckResult]
94+
sessionTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenCommonCheckResult]
95+
sessionTokenV2CommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenV2CommonCheckResult]
9096
}
9197

9298
// New provides protocontainer.ContainerServiceServer based on specified
@@ -99,6 +105,10 @@ func New(s *ecdsa.PrivateKey, net netmap.State, fsChain FSChain, c Contract, nc
99105
if err != nil {
100106
panic(fmt.Errorf("unexpected error in lru.New: %w", err))
101107
}
108+
sessionTokenV2CheckCache, err := lru.New[[sha256.Size]byte, sessionTokenV2CommonCheckResult](1000)
109+
if err != nil {
110+
panic(fmt.Errorf("unexpected error in lru.New for v2: %w", err))
111+
}
102112
return &server{
103113
signer: s,
104114
net: net,
@@ -107,7 +117,8 @@ func New(s *ecdsa.PrivateKey, net netmap.State, fsChain FSChain, c Contract, nc
107117
FSChain: fsChain,
108118
NetmapContract: nc,
109119
},
110-
sessionTokenCommonCheckCache: sessionTokenCheckCache,
120+
sessionTokenCommonCheckCache: sessionTokenCheckCache,
121+
sessionTokenV2CommonCheckCache: sessionTokenV2CheckCache,
111122
}
112123
}
113124

@@ -215,6 +226,96 @@ func (s *server) checkSessionIssuer(id cid.ID, issuer user.ID) error {
215226
return nil
216227
}
217228

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

309-
st, err := s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerPut, cid.ID{})
410+
stV2, err := s.getVerifiedSessionTokenV2(req.GetMetaHeader(), session.VerbContainerPut, cid.ID{})
310411
if err != nil {
311-
return s.makeFailedPutResponse(fmt.Errorf("verify session token: %w", err))
412+
return s.makeFailedPutResponse(fmt.Errorf("verify session token v2: %w", err))
413+
}
414+
415+
var st *session.Container
416+
if stV2 == nil {
417+
st, err = s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerPut, cid.ID{})
418+
if err != nil {
419+
return s.makeFailedPutResponse(fmt.Errorf("verify session token: %w", err))
420+
}
312421
}
313422

314423
id, err := s.contract.Put(cnr, mSig.Key, mSig.Sign, st)
@@ -352,9 +461,17 @@ func (s *server) Delete(_ context.Context, req *protocontainer.DeleteRequest) (*
352461
return s.makeDeleteResponse(fmt.Errorf("invalid ID: %w", err))
353462
}
354463

355-
st, err := s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerDelete, id)
464+
stV2, err := s.getVerifiedSessionTokenV2(req.GetMetaHeader(), session.VerbContainerDelete, id)
356465
if err != nil {
357-
return s.makeDeleteResponse(fmt.Errorf("verify session token: %w", err))
466+
return s.makeDeleteResponse(fmt.Errorf("verify session token v2: %w", err))
467+
}
468+
469+
var st *session.Container
470+
if stV2 == nil {
471+
st, err = s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerDelete, id)
472+
if err != nil {
473+
return s.makeDeleteResponse(fmt.Errorf("verify session token: %w", err))
474+
}
358475
}
359476

360477
if err := s.contract.Delete(id, mSig.Key, mSig.Sign, st); err != nil {
@@ -490,9 +607,17 @@ func (s *server) SetExtendedACL(_ context.Context, req *protocontainer.SetExtend
490607
return s.makeSetEACLResponse(errors.New("missing container ID in eACL table"))
491608
}
492609

493-
st, err := s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerSetEACL, cnrID)
610+
stV2, err := s.getVerifiedSessionTokenV2(req.GetMetaHeader(), session.VerbContainerSetEACL, cnrID)
494611
if err != nil {
495-
return s.makeSetEACLResponse(fmt.Errorf("verify session token: %w", err))
612+
return s.makeSetEACLResponse(fmt.Errorf("verify session token v2: %w", err))
613+
}
614+
615+
var st *session.Container
616+
if stV2 == nil {
617+
st, err = s.getVerifiedSessionToken(req.GetMetaHeader(), session.VerbContainerSetEACL, cnrID)
618+
if err != nil {
619+
return s.makeSetEACLResponse(fmt.Errorf("verify session token: %w", err))
620+
}
496621
}
497622

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

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

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ type sessionTokenCommonCheckResult struct {
3131
err error
3232
}
3333

34+
type sessionTokenV2CommonCheckResult struct {
35+
token sessionSDK.TokenV2
36+
err error
37+
}
38+
3439
type bearerTokenCommonCheckResult struct {
3540
token bearer.Token
3641
err error
@@ -42,8 +47,9 @@ type Service struct {
4247

4348
c senderClassifier
4449

45-
sessionTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenCommonCheckResult]
46-
bearerTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, bearerTokenCommonCheckResult]
50+
sessionTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenCommonCheckResult]
51+
sessionTokenV2CommonCheckCache *lru.Cache[[sha256.Size]byte, sessionTokenV2CommonCheckResult]
52+
bearerTokenCommonCheckCache *lru.Cache[[sha256.Size]byte, bearerTokenCommonCheckResult]
4753
}
4854

4955
// Option represents Service constructor option.
@@ -114,6 +120,10 @@ func New(fsChain FSChain, opts ...Option) Service {
114120
if err != nil {
115121
panic(fmt.Errorf("unexpected error in lru.New: %w", err))
116122
}
123+
sessionTokenV2CheckCache, err := lru.New[[sha256.Size]byte, sessionTokenV2CommonCheckResult](1000)
124+
if err != nil {
125+
panic(fmt.Errorf("unexpected error in lru.New: %w", err))
126+
}
117127

118128
return Service{
119129
cfg: cfg,
@@ -122,21 +132,31 @@ func New(fsChain FSChain, opts ...Option) Service {
122132
innerRing: cfg.irFetcher,
123133
fsChain: fsChain,
124134
},
125-
sessionTokenCommonCheckCache: sessionTokenCheckCache,
126-
bearerTokenCommonCheckCache: bearerTokenCheckCache,
135+
sessionTokenCommonCheckCache: sessionTokenCheckCache,
136+
bearerTokenCommonCheckCache: bearerTokenCheckCache,
137+
sessionTokenV2CommonCheckCache: sessionTokenV2CheckCache,
127138
}
128139
}
129140

130141
// ResetTokenCheckCache resets cache of session and bearer tokens' check results.
131142
func (b Service) ResetTokenCheckCache() {
132143
b.sessionTokenCommonCheckCache.Purge()
133144
b.bearerTokenCommonCheckCache.Purge()
145+
b.sessionTokenV2CommonCheckCache.Purge()
134146
}
135147

136148
func (b Service) getVerifiedSessionToken(mh *protosession.RequestMetaHeader, reqVerb sessionSDK.ObjectVerb, reqCnr cid.ID, reqObj oid.ID) (*sessionSDK.Object, error) {
137149
for omh := mh.GetOrigin(); omh != nil; omh = mh.GetOrigin() {
138150
mh = omh
139151
}
152+
153+
mV2 := mh.GetSessionTokenV2()
154+
if mV2 != nil {
155+
b.log.Debug("Verifying V2 session token")
156+
return b.getVerifiedSessionTokenV2(mV2, reqVerb, reqCnr, reqObj)
157+
}
158+
159+
// Fall back to V1 token
140160
m := mh.GetSessionToken()
141161
if m == nil {
142162
return nil, nil
@@ -202,6 +222,98 @@ func (b Service) verifySessionTokenAgainstRequest(token sessionSDK.Object, reqVe
202222
return nil
203223
}
204224

225+
// getVerifiedSessionTokenV2 validates and returns V2 session token, returns nil if V2 doesn't apply.
226+
func (b Service) getVerifiedSessionTokenV2(mV2 *protosession.SessionTokenV2, reqVerb sessionSDK.ObjectVerb, reqCnr cid.ID, reqObj oid.ID) (*sessionSDK.Object, error) {
227+
mb := make([]byte, mV2.MarshaledSize())
228+
mV2.MarshalStable(mb)
229+
230+
cacheKey := sha256.Sum256(mb)
231+
res, ok := b.sessionTokenV2CommonCheckCache.Get(cacheKey)
232+
if !ok {
233+
res.token, res.err = b.decodeAndVerifySessionTokenV2Common(mV2)
234+
b.sessionTokenV2CommonCheckCache.Add(cacheKey, res)
235+
}
236+
if res.err != nil {
237+
return nil, res.err
238+
}
239+
240+
if err := b.verifySessionTokenV2AgainstRequest(res.token, reqVerb, reqCnr, reqObj); err != nil {
241+
return nil, err
242+
}
243+
244+
// V2 tokens work differently - return nil as they don't convert to V1
245+
// The caller needs to handle this properly
246+
return nil, nil
247+
}
248+
249+
func (b Service) decodeAndVerifySessionTokenV2Common(m *protosession.SessionTokenV2) (sessionSDK.TokenV2, error) {
250+
b.log.Debug("Decoding V2 session token")
251+
var token sessionSDK.TokenV2
252+
if err := token.FromProtoMessage(m); err != nil {
253+
return token, fmt.Errorf("invalid V2 session token: %w", err)
254+
}
255+
256+
if err := token.Validate(); err != nil {
257+
return token, fmt.Errorf("invalid V2 session token: %w", err)
258+
}
259+
260+
if !token.VerifySignature() {
261+
return token, errors.New("v2 session token signature verification failed")
262+
}
263+
264+
currentEpoch, err := b.nm.Epoch()
265+
if err != nil {
266+
return token, errors.New("can't fetch current epoch")
267+
}
268+
269+
if !token.ValidAt(currentEpoch) {
270+
return token, fmt.Errorf("%s: V2 token is invalid at %d epoch", invalidRequestMessage, currentEpoch)
271+
}
272+
273+
return token, nil
274+
}
275+
276+
func (b Service) verifySessionTokenV2AgainstRequest(token sessionSDK.TokenV2, reqVerb sessionSDK.ObjectVerb, reqCnr cid.ID, reqObj oid.ID) error {
277+
verbV2 := objectVerbToVerbV2(reqVerb)
278+
if verbV2 == 0 {
279+
return fmt.Errorf("unsupported object verb for V2: %v", reqVerb)
280+
}
281+
282+
if !reqObj.IsZero() {
283+
if !token.AssertObject(verbV2, reqCnr, reqObj) {
284+
return errors.New("V2 session token does not authorize access to the object")
285+
}
286+
} else {
287+
if !token.AssertVerb(verbV2, reqCnr) {
288+
return errInvalidVerb
289+
}
290+
}
291+
292+
return nil
293+
}
294+
295+
// objectVerbToVerbV2 converts V1 ObjectVerb to V2 VerbV2.
296+
func objectVerbToVerbV2(v1Verb sessionSDK.ObjectVerb) sessionSDK.VerbV2 {
297+
switch v1Verb {
298+
case sessionSDK.VerbObjectGet:
299+
return sessionSDK.VerbV2ObjectGet
300+
case sessionSDK.VerbObjectHead:
301+
return sessionSDK.VerbV2ObjectHead
302+
case sessionSDK.VerbObjectPut:
303+
return sessionSDK.VerbV2ObjectPut
304+
case sessionSDK.VerbObjectDelete:
305+
return sessionSDK.VerbV2ObjectDelete
306+
case sessionSDK.VerbObjectSearch:
307+
return sessionSDK.VerbV2ObjectSearch
308+
case sessionSDK.VerbObjectRange:
309+
return sessionSDK.VerbV2ObjectRange
310+
case sessionSDK.VerbObjectRangeHash:
311+
return sessionSDK.VerbV2ObjectRangeHash
312+
default:
313+
return 0
314+
}
315+
}
316+
205317
func (b Service) getVerifiedBearerToken(mh *protosession.RequestMetaHeader, reqCnr cid.ID, ownerCnr user.ID, usrSender user.ID) (*bearer.Token, error) {
206318
for omh := mh.GetOrigin(); omh != nil; omh = mh.GetOrigin() {
207319
mh = omh

pkg/services/object/delete/delete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
func (s *Service) Delete(ctx context.Context, prm Prm) error {
1212
// If session token is not found we will fail during tombstone PUT.
1313
// Here we fail immediately to ensure no unnecessary network communication is done.
14-
if tok := prm.common.SessionToken(); tok != nil {
14+
if tok := prm.common.SessionToken(); tok != nil && prm.common.SessionTokenV2() == nil {
1515
_, err := s.keyStorage.GetKey(&util.SessionInfo{
1616
ID: tok.ID(),
1717
Owner: tok.Issuer(),

pkg/services/object/delete/local.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,20 @@ func (exec *execCtx) formTombstone() (ok bool) {
4343
exec.tombstoneObj.AssociateDeleted(exec.address().Object())
4444

4545
tokenSession := exec.commonParameters().SessionToken()
46-
if tokenSession != nil {
46+
tokenSessionV2 := exec.commonParameters().SessionTokenV2()
47+
48+
if tokenSessionV2 != nil {
49+
issuer := tokenSessionV2.Issuer()
50+
if issuer.IsOwnerID() {
51+
exec.tombstoneObj.SetOwner(issuer.OwnerID())
52+
} else {
53+
// Fallback to local node if issuer is not OwnerID
54+
exec.tombstoneObj.SetOwner(exec.svc.netInfo.LocalNodeID())
55+
}
56+
} else if tokenSession != nil {
4757
exec.tombstoneObj.SetOwner(tokenSession.Issuer())
4858
} else {
49-
// make local node a tombstone object owner
59+
// No token: make local node a tombstone object owner
5060
exec.tombstoneObj.SetOwner(exec.svc.netInfo.LocalNodeID())
5161
}
5262

0 commit comments

Comments
 (0)