Skip to content

Commit ab75080

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. ShiftState 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 88a2bf0 commit ab75080

File tree

4 files changed

+199
-222
lines changed

4 files changed

+199
-222
lines changed

session/interface.go

+19-30
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,16 @@ const (
2727
type State uint8
2828

2929
/*
30-
/---> StateExpired (terminal)
31-
StateCreated ---
32-
\---> StateRevoked (terminal)
30+
/---> StateExpired (terminal)
31+
StateReserved ---> StateCreated ---
32+
| |
33+
\-------------------------+---> StateRevoked (terminal)
3334
*/
3435

3536
const (
3637
// 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.
38+
// committed to the BoltStore and is ready to be used. This is the
39+
// first state after StateReserved.
3940
StateCreated State = 0
4041

4142
// StateInUse is the state of a session that is currently being used.
@@ -52,10 +53,10 @@ const (
5253
// date.
5354
StateExpired State = 3
5455

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.
56+
// StateReserved is a temporary initial state of a session. This is used
57+
// to reserve a unique ID and private key pair for a session before it
58+
// is fully created. On start-up, any sessions in this state should be
59+
// cleaned up.
5960
StateReserved State = 4
6061
)
6162

@@ -67,6 +68,10 @@ func (s State) Terminal() bool {
6768
// legalStateShifts is a map that defines the legal State transitions that a
6869
// Session can be put through.
6970
var legalStateShifts = map[State]map[State]bool{
71+
StateReserved: {
72+
StateCreated: true,
73+
StateRevoked: true,
74+
},
7075
StateCreated: {
7176
StateExpired: true,
7277
StateRevoked: true,
@@ -141,7 +146,7 @@ func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
141146
sess := &Session{
142147
ID: id,
143148
Label: label,
144-
State: StateCreated,
149+
State: StateReserved,
145150
Type: typ,
146151
Expiry: expiry.UTC(),
147152
CreatedAt: created.UTC(),
@@ -185,23 +190,13 @@ type IDToGroupIndex interface {
185190
// retrieving Terminal Connect sessions.
186191
type Store interface {
187192
// NewSession creates a new session with the given user-defined
188-
// parameters.
189-
//
190-
// NOTE: currently this purely a constructor of the Session type and
191-
// does not make any database calls. This will be changed in a future
192-
// commit.
193-
NewSession(id ID, localPrivKey *btcec.PrivateKey, label string,
194-
typ Type, expiry time.Time, serverAddr string, devServer bool,
195-
perms []bakery.Op, caveats []macaroon.Caveat,
193+
// parameters. The session will remain in the StateReserved state until
194+
// ShiftState is called to update the state.
195+
NewSession(label string, typ Type, expiry time.Time, serverAddr string,
196+
devServer bool, perms []bakery.Op, caveats []macaroon.Caveat,
196197
featureConfig FeaturesConfig, privacy bool, linkedGroupID *ID,
197198
flags PrivacyFlags) (*Session, error)
198199

199-
// CreateSession adds a new session to the store. If a session with the
200-
// same local public key already exists an error is returned. This
201-
// can only be called with a Session with an ID that the Store has
202-
// reserved.
203-
CreateSession(*Session) error
204-
205200
// GetSession fetches the session with the given key.
206201
GetSession(key *btcec.PublicKey) (*Session, error)
207202

@@ -220,12 +215,6 @@ type Store interface {
220215
UpdateSessionRemotePubKey(localPubKey,
221216
remotePubKey *btcec.PublicKey) error
222217

223-
// GetUnusedIDAndKeyPair can be used to generate a new, unused, local
224-
// private key and session ID pair. Care must be taken to ensure that no
225-
// other thread calls this before the returned ID and key pair from this
226-
// method are either used or discarded.
227-
GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error)
228-
229218
// GetSessionByID fetches the session with the given ID.
230219
GetSessionByID(id ID) (*Session, error)
231220

session/kvdb_store.go

+52-64
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.
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+
// ShiftState is called with StateCreated.
189188
//
190189
// 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-
}
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) {
203194

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.
206-
//
207-
// NOTE: this is part of the Store interface.
208-
func (db *BoltStore) CreateSession(session *Session) error {
209-
sessionKey := getSessionKey(session)
210-
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,11 @@ 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
278284
}
279285

280286
// UpdateSessionRemotePubKey can be used to add the given remote pub key
@@ -577,53 +583,35 @@ func (db *BoltStore) GetSessionByID(id ID) (*Session, error) {
577583
return session, nil
578584
}
579585

580-
// GetUnusedIDAndKeyPair can be used to generate a new, unused, local private
586+
// getUnusedIDAndKeyPair can be used to generate a new, unused, local private
581587
// key and session ID pair. Care must be taken to ensure that no other thread
582588
// calls this before the returned ID and key pair from this method are either
583589
// used or discarded.
584-
//
585-
// NOTE: this is part of the Store interface.
586-
func (db *BoltStore) GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error) {
587-
var (
588-
id ID
589-
privKey *btcec.PrivateKey
590-
)
591-
err := db.Update(func(tx *bbolt.Tx) error {
592-
sessionBucket, err := getBucket(tx, sessionBucketKey)
593-
if err != nil {
594-
return err
595-
}
596-
597-
idIndexBkt := sessionBucket.Bucket(idIndexKey)
598-
if idIndexBkt == nil {
599-
return ErrDBInitErr
600-
}
590+
func getUnusedIDAndKeyPair(bucket *bbolt.Bucket) (ID, *btcec.PrivateKey,
591+
error) {
601592

602-
// Spin until we find a key with an ID that does not collide
603-
// with any of our existing IDs.
604-
for {
605-
// Generate a new private key and ID pair.
606-
privKey, id, err = NewSessionPrivKeyAndID()
607-
if err != nil {
608-
return err
609-
}
593+
idIndexBkt := bucket.Bucket(idIndexKey)
594+
if idIndexBkt == nil {
595+
return ID{}, nil, ErrDBInitErr
596+
}
610597

611-
// Check that no such ID exits in our id-to-key index.
612-
idBkt := idIndexBkt.Bucket(id[:])
613-
if idBkt != nil {
614-
continue
615-
}
598+
// Spin until we find a key with an ID that does not collide with any of
599+
// our existing IDs.
600+
for {
601+
// Generate a new private key and ID pair.
602+
privKey, id, err := NewSessionPrivKeyAndID()
603+
if err != nil {
604+
return ID{}, nil, err
605+
}
616606

617-
break
607+
// Check that no such ID exits in our id-to-key index.
608+
idBkt := idIndexBkt.Bucket(id[:])
609+
if idBkt != nil {
610+
continue
618611
}
619612

620-
return nil
621-
})
622-
if err != nil {
623-
return id, nil, err
613+
return id, privKey, nil
624614
}
625-
626-
return id, privKey, nil
627615
}
628616

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

0 commit comments

Comments
 (0)