Skip to content

Commit 789fb0c

Browse files
committed
session: introduce Reserve->Create pattern
In this commit, we let StateReserved be the new initial state of a session for when NewSession is called. We then do predicate checks for linked sessions along with unique session alias (ID) and priv key derivations all under the same DB transaction in NewSession. CreateSession then moves a session to StateCreated. Only in StateCreated does a session become usable. With this change, we no longer need to ensure atomic session creation by acquiring the `sessRegMu` mutex in the session RPC server.
1 parent 400a129 commit 789fb0c

File tree

4 files changed

+212
-214
lines changed

4 files changed

+212
-214
lines changed

session/interface.go

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ const (
2727
type State uint8
2828

2929
/*
30-
/---> StateExpired
31-
StateCreated ---
32-
\---> StateRevoked
30+
/---> StateExpired
31+
StateReserved ---> StateCreated ---
32+
\---> StateRevoked
3333
*/
3434

3535
const (
3636
// StateCreated is the state of a session once it has been fully
37-
// committed to the Store and is ready to be used. This is the first
38-
// state of a session.
37+
// committed to the BoltStore and is ready to be used. This is the
38+
// first state after StateReserved.
3939
StateCreated State = 0
4040

4141
// StateInUse is the state of a session that is currently being used.
@@ -52,10 +52,10 @@ const (
5252
// date.
5353
StateExpired State = 3
5454

55-
// StateReserved is a temporary initial state of a session. On start-up,
56-
// any sessions in this state should be cleaned up.
57-
//
58-
// NOTE: this isn't used yet.
55+
// StateReserved is a temporary initial state of a session. This is used
56+
// to reserve a unique ID and private key pair for a session before it
57+
// is fully created. On start-up, any sessions in this state should be
58+
// cleaned up.
5959
StateReserved State = 4
6060
)
6161

@@ -123,7 +123,7 @@ func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
123123
sess := &Session{
124124
ID: id,
125125
Label: label,
126-
State: StateCreated,
126+
State: StateReserved,
127127
Type: typ,
128128
Expiry: expiry.UTC(),
129129
CreatedAt: created.UTC(),
@@ -167,22 +167,16 @@ type IDToGroupIndex interface {
167167
// retrieving Terminal Connect sessions.
168168
type Store interface {
169169
// NewSession creates a new session with the given user-defined
170-
// parameters.
171-
//
172-
// NOTE: currently this purely a constructor of the Session type and
173-
// does not make any database calls. This will be changed in a future
174-
// commit.
175-
NewSession(id ID, localPrivKey *btcec.PrivateKey, label string,
176-
typ Type, expiry time.Time, serverAddr string, devServer bool,
177-
perms []bakery.Op, caveats []macaroon.Caveat,
170+
// parameters. The session will remain in the StateReserved state until
171+
// CreateSession is called for the session.
172+
NewSession(label string, typ Type, expiry time.Time, serverAddr string,
173+
devServer bool, perms []bakery.Op, caveats []macaroon.Caveat,
178174
featureConfig FeaturesConfig, privacy bool, linkedGroupID *ID,
179175
flags PrivacyFlags) (*Session, error)
180176

181-
// CreateSession adds a new session to the store. If a session with the
182-
// same local public key already exists an error is returned. This
183-
// can only be called with a Session with an ID that the Store has
184-
// reserved.
185-
CreateSession(*Session) error
177+
// CreateSession moves the given session from the StateReserved state to
178+
// the StateCreated state.
179+
CreateSession(ID) (*Session, error)
186180

187181
// GetSession fetches the session with the given key.
188182
GetSession(key *btcec.PublicKey) (*Session, error)
@@ -206,12 +200,6 @@ type Store interface {
206200
UpdateSessionRemotePubKey(localPubKey,
207201
remotePubKey *btcec.PublicKey) error
208202

209-
// GetUnusedIDAndKeyPair can be used to generate a new, unused, local
210-
// private key and session ID pair. Care must be taken to ensure that no
211-
// other thread calls this before the returned ID and key pair from this
212-
// method are either used or discarded.
213-
GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error)
214-
215203
// GetSessionByID fetches the session with the given ID.
216204
GetSessionByID(id ID) (*Session, error)
217205

session/kvdb_store.go

Lines changed: 87 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -182,41 +182,42 @@ func getSessionKey(session *Session) []byte {
182182
return session.LocalPublicKey.SerializeCompressed()
183183
}
184184

185-
// NewSession creates a new session with the given user-defined parameters.
186-
//
187-
// NOTE: currently this purely a constructor of the Session type and does not
188-
// make any database calls. This will be changed in a future commit.
189-
//
190-
// NOTE: this is part of the Store interface.
191-
func (db *BoltStore) NewSession(id ID, localPrivKey *btcec.PrivateKey,
192-
label string, typ Type, expiry time.Time, serverAddr string,
193-
devServer bool, perms []bakery.Op, caveats []macaroon.Caveat,
194-
featureConfig FeaturesConfig, privacy bool, linkedGroupID *ID,
195-
flags PrivacyFlags) (*Session, error) {
196-
197-
return buildSession(
198-
id, localPrivKey, label, typ, db.clock.Now(), expiry,
199-
serverAddr, devServer, perms, caveats, featureConfig, privacy,
200-
linkedGroupID, flags,
201-
)
202-
}
203-
204-
// CreateSession adds a new session to the store. If a session with the same
205-
// local public key already exists an error is returned.
185+
// NewSession creates and persists a new session with the given user-defined
186+
// parameters. The initial state of the session will be Reserved until
187+
// CreateSession is called.
206188
//
207189
// NOTE: this is part of the Store interface.
208-
func (db *BoltStore) CreateSession(session *Session) error {
209-
sessionKey := getSessionKey(session)
190+
func (db *BoltStore) NewSession(label string, typ Type, expiry time.Time,
191+
serverAddr string, devServer bool, perms []bakery.Op,
192+
caveats []macaroon.Caveat, featureConfig FeaturesConfig, privacy bool,
193+
linkedGroupID *ID, flags PrivacyFlags) (*Session, error) {
210194

211-
return db.Update(func(tx *bbolt.Tx) error {
195+
var session *Session
196+
err := db.Update(func(tx *bbolt.Tx) error {
212197
sessionBucket, err := getBucket(tx, sessionBucketKey)
213198
if err != nil {
214199
return err
215200
}
216201

202+
id, localPrivKey, err := getUnusedIDAndKeyPair(sessionBucket)
203+
if err != nil {
204+
return err
205+
}
206+
207+
session, err = buildSession(
208+
id, localPrivKey, label, typ, db.clock.Now(), expiry,
209+
serverAddr, devServer, perms, caveats, featureConfig,
210+
privacy, linkedGroupID, flags,
211+
)
212+
if err != nil {
213+
return err
214+
}
215+
216+
sessionKey := getSessionKey(session)
217+
217218
if len(sessionBucket.Get(sessionKey)) != 0 {
218-
return fmt.Errorf("session with local public "+
219-
"key(%x) already exists",
219+
return fmt.Errorf("session with local public key(%x) "+
220+
"already exists",
220221
session.LocalPublicKey.SerializeCompressed())
221222
}
222223

@@ -275,6 +276,46 @@ func (db *BoltStore) CreateSession(session *Session) error {
275276

276277
return putSession(sessionBucket, session)
277278
})
279+
if err != nil {
280+
return nil, err
281+
}
282+
283+
return session, nil
284+
}
285+
286+
// CreateSession moves the session with the given ID from the Reserved state to
287+
// the Created state.
288+
//
289+
// NOTE: this is part of the Store interface.
290+
func (db *BoltStore) CreateSession(id ID) (*Session, error) {
291+
var session *Session
292+
err := db.Update(func(tx *bbolt.Tx) error {
293+
sessionBucket, err := getBucket(tx, sessionBucketKey)
294+
if err != nil {
295+
return err
296+
}
297+
298+
session, err = getSessionByID(sessionBucket, id)
299+
if err != nil {
300+
return err
301+
}
302+
303+
// The session MUST be in the Reserved state.
304+
if session.State != StateReserved {
305+
return fmt.Errorf("session must be in the Reserved " +
306+
"state for it to move to the Created state")
307+
}
308+
309+
// Move the session to the CreatedState.
310+
session.State = StateCreated
311+
312+
return putSession(sessionBucket, session)
313+
})
314+
if err != nil {
315+
return nil, err
316+
}
317+
318+
return session, nil
278319
}
279320

280321
// UpdateSessionRemotePubKey can be used to add the given remote pub key
@@ -565,53 +606,35 @@ func (db *BoltStore) GetSessionByID(id ID) (*Session, error) {
565606
return session, nil
566607
}
567608

568-
// GetUnusedIDAndKeyPair can be used to generate a new, unused, local private
609+
// getUnusedIDAndKeyPair can be used to generate a new, unused, local private
569610
// key and session ID pair. Care must be taken to ensure that no other thread
570611
// calls this before the returned ID and key pair from this method are either
571612
// used or discarded.
572-
//
573-
// NOTE: this is part of the Store interface.
574-
func (db *BoltStore) GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error) {
575-
var (
576-
id ID
577-
privKey *btcec.PrivateKey
578-
)
579-
err := db.Update(func(tx *bbolt.Tx) error {
580-
sessionBucket, err := getBucket(tx, sessionBucketKey)
581-
if err != nil {
582-
return err
583-
}
584-
585-
idIndexBkt := sessionBucket.Bucket(idIndexKey)
586-
if idIndexBkt == nil {
587-
return ErrDBInitErr
588-
}
613+
func getUnusedIDAndKeyPair(bucket *bbolt.Bucket) (ID, *btcec.PrivateKey,
614+
error) {
589615

590-
// Spin until we find a key with an ID that does not collide
591-
// with any of our existing IDs.
592-
for {
593-
// Generate a new private key and ID pair.
594-
privKey, id, err = NewSessionPrivKeyAndID()
595-
if err != nil {
596-
return err
597-
}
616+
idIndexBkt := bucket.Bucket(idIndexKey)
617+
if idIndexBkt == nil {
618+
return ID{}, nil, ErrDBInitErr
619+
}
598620

599-
// Check that no such ID exits in our id-to-key index.
600-
idBkt := idIndexBkt.Bucket(id[:])
601-
if idBkt != nil {
602-
continue
603-
}
621+
// Spin until we find a key with an ID that does not collide with any of
622+
// our existing IDs.
623+
for {
624+
// Generate a new private key and ID pair.
625+
privKey, id, err := NewSessionPrivKeyAndID()
626+
if err != nil {
627+
return ID{}, nil, err
628+
}
604629

605-
break
630+
// Check that no such ID exits in our id-to-key index.
631+
idBkt := idIndexBkt.Bucket(id[:])
632+
if idBkt != nil {
633+
continue
606634
}
607635

608-
return nil
609-
})
610-
if err != nil {
611-
return id, nil, err
636+
return id, privKey, nil
612637
}
613-
614-
return id, privKey, nil
615638
}
616639

617640
// GetGroupID will return the group ID for the given session ID.

0 commit comments

Comments
 (0)