From 92575c8ed7fbe286f1e59284890152208292a651 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 25 Aug 2023 14:20:12 +0200 Subject: [PATCH 01/23] session: update the session Store interface --- session/db.go | 3 +++ session/interface.go | 5 ++++- session/store.go | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/session/db.go b/session/db.go index 52a141145..b261ed220 100644 --- a/session/db.go +++ b/session/db.go @@ -37,6 +37,9 @@ type DB struct { *bbolt.DB } +// A compile-time check to ensure that DB implements the Store interface. +var _ Store = (*DB)(nil) + // NewDB creates a new bolt database that can be found at the given directory. func NewDB(dir, fileName string) (*DB, error) { firstInit := false diff --git a/session/interface.go b/session/interface.go index a87d64f80..d0707422c 100644 --- a/session/interface.go +++ b/session/interface.go @@ -128,8 +128,11 @@ type Store interface { // overwritten instead. StoreSession(*Session) error + // GetSession fetches the session with the given key. + GetSession(key *btcec.PublicKey) (*Session, error) + // ListSessions returns all sessions currently known to the store. - ListSessions() ([]*Session, error) + ListSessions(filterFn func(s *Session) bool) ([]*Session, error) // RevokeSession updates the state of the session with the given local // public key to be revoked. diff --git a/session/store.go b/session/store.go index 82abe2e27..6fe9b2d8f 100644 --- a/session/store.go +++ b/session/store.go @@ -28,6 +28,8 @@ func getSessionKey(session *Session) []byte { // StoreSession stores a session in the store. If a session with the // same local public key already exists, the existing record is updated/ // overwritten instead. +// +// NOTE: this is part of the Store interface. func (db *DB) StoreSession(session *Session) error { var buf bytes.Buffer if err := SerializeSession(&buf, session); err != nil { @@ -46,6 +48,8 @@ func (db *DB) StoreSession(session *Session) error { } // GetSession fetches the session with the given key. +// +// NOTE: this is part of the Store interface. func (db *DB) GetSession(key *btcec.PublicKey) (*Session, error) { var session *Session err := db.View(func(tx *bbolt.Tx) error { @@ -74,6 +78,8 @@ func (db *DB) GetSession(key *btcec.PublicKey) (*Session, error) { } // ListSessions returns all sessions currently known to the store. +// +// NOTE: this is part of the Store interface. func (db *DB) ListSessions(filterFn func(s *Session) bool) ([]*Session, error) { var sessions []*Session err := db.View(func(tx *bbolt.Tx) error { @@ -112,6 +118,8 @@ func (db *DB) ListSessions(filterFn func(s *Session) bool) ([]*Session, error) { // RevokeSession updates the state of the session with the given local // public key to be revoked. +// +// NOTE: this is part of the Store interface. func (db *DB) RevokeSession(key *btcec.PublicKey) error { var session *Session err := db.View(func(tx *bbolt.Tx) error { From d3a262600f1ea498394e969bfcb09e9bb1564115 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 25 Aug 2023 14:36:59 +0200 Subject: [PATCH 02/23] session: separate methods for creating vs updating a session --- session/interface.go | 12 ++++--- session/server.go | 7 ++-- session/store.go | 75 +++++++++++++++++++++++++++++++++------- session/store_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++ session_rpcserver.go | 6 ++-- 5 files changed, 156 insertions(+), 24 deletions(-) create mode 100644 session/store_test.go diff --git a/session/interface.go b/session/interface.go index d0707422c..6ae9da0b0 100644 --- a/session/interface.go +++ b/session/interface.go @@ -123,10 +123,9 @@ func NewSession(label string, typ Type, expiry time.Time, serverAddr string, // Store is the interface a persistent storage must implement for storing and // retrieving Terminal Connect sessions. type Store interface { - // StoreSession stores a session in the store. If a session with the - // same local public key already exists, the existing record is updated/ - // overwritten instead. - StoreSession(*Session) error + // CreateSession adds a new session to the store. If a session with the same + // local public key already exists an error is returned. + CreateSession(*Session) error // GetSession fetches the session with the given key. GetSession(key *btcec.PublicKey) (*Session, error) @@ -137,4 +136,9 @@ type Store interface { // RevokeSession updates the state of the session with the given local // public key to be revoked. RevokeSession(*btcec.PublicKey) error + + // UpdateSessionRemotePubKey can be used to add the given remote pub key + // to the session with the given local pub key. + UpdateSessionRemotePubKey(localPubKey, + remotePubKey *btcec.PublicKey) error } diff --git a/session/server.go b/session/server.go index daa7243ee..ae7a50121 100644 --- a/session/server.go +++ b/session/server.go @@ -33,7 +33,7 @@ func newMailboxSession() *mailboxSession { func (m *mailboxSession) start(session *Session, serverCreator GRPCServerCreator, authData []byte, - onUpdate func(sess *Session) error, + onUpdate func(local, remote *btcec.PublicKey) error, onNewStatus func(s mailbox.ServerStatus)) error { tlsConfig := &tls.Config{} @@ -46,8 +46,7 @@ func (m *mailboxSession) start(session *Session, keys := mailbox.NewConnData( ecdh, session.RemotePublicKey, session.PairingSecret[:], authData, func(key *btcec.PublicKey) error { - session.RemotePublicKey = key - return onUpdate(session) + return onUpdate(session.LocalPublicKey, key) }, nil, ) @@ -105,7 +104,7 @@ func NewServer(serverCreator GRPCServerCreator) *Server { } func (s *Server) StartSession(session *Session, authData []byte, - onUpdate func(sess *Session) error, + onUpdate func(local, remote *btcec.PublicKey) error, onNewStatus func(s mailbox.ServerStatus)) (chan struct{}, error) { s.activeSessionsMtx.Lock() diff --git a/session/store.go b/session/store.go index 6fe9b2d8f..a3bbb1a67 100644 --- a/session/store.go +++ b/session/store.go @@ -3,6 +3,7 @@ package session import ( "bytes" "errors" + "fmt" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -25,12 +26,11 @@ func getSessionKey(session *Session) []byte { return session.LocalPublicKey.SerializeCompressed() } -// StoreSession stores a session in the store. If a session with the -// same local public key already exists, the existing record is updated/ -// overwritten instead. +// CreateSession adds a new session to the store. If a session with the same +// local public key already exists an error is returned. // // NOTE: this is part of the Store interface. -func (db *DB) StoreSession(session *Session) error { +func (db *DB) CreateSession(session *Session) error { var buf bytes.Buffer if err := SerializeSession(&buf, session); err != nil { return err @@ -43,10 +43,55 @@ func (db *DB) StoreSession(session *Session) error { return err } + if len(sessionBucket.Get(sessionKey)) != 0 { + return fmt.Errorf("session with local public "+ + "key(%x) already exists", + session.LocalPublicKey.SerializeCompressed()) + } + return sessionBucket.Put(sessionKey, buf.Bytes()) }) } +// UpdateSessionRemotePubKey can be used to add the given remote pub key +// to the session with the given local pub key. +// +// NOTE: this is part of the Store interface. +func (db *DB) UpdateSessionRemotePubKey(localPubKey, + remotePubKey *btcec.PublicKey) error { + + key := localPubKey.SerializeCompressed() + + return db.Update(func(tx *bbolt.Tx) error { + sessionBucket, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + serialisedSession := sessionBucket.Get(key) + + if len(serialisedSession) == 0 { + return ErrSessionNotFound + } + + session, err := DeserializeSession( + bytes.NewReader(serialisedSession), + ) + if err != nil { + return err + } + + session.RemotePublicKey = remotePubKey + + var buf bytes.Buffer + if err := SerializeSession(&buf, session); err != nil { + return err + } + + return sessionBucket.Put(key, buf.Bytes()) + }) +} + // GetSession fetches the session with the given key. // // NOTE: this is part of the Store interface. @@ -122,7 +167,7 @@ func (db *DB) ListSessions(filterFn func(s *Session) bool) ([]*Session, error) { // NOTE: this is part of the Store interface. func (db *DB) RevokeSession(key *btcec.PublicKey) error { var session *Session - err := db.View(func(tx *bbolt.Tx) error { + return db.Update(func(tx *bbolt.Tx) error { sessionBucket, err := getBucket(tx, sessionBucketKey) if err != nil { return err @@ -134,14 +179,18 @@ func (db *DB) RevokeSession(key *btcec.PublicKey) error { } session, err = DeserializeSession(bytes.NewReader(sessionBytes)) - return err - }) - if err != nil { - return err - } + if err != nil { + return err + } - session.State = StateRevoked - session.RevokedAt = time.Now() + session.State = StateRevoked + session.RevokedAt = time.Now() - return db.StoreSession(session) + var buf bytes.Buffer + if err := SerializeSession(&buf, session); err != nil { + return err + } + + return sessionBucket.Put(key.SerializeCompressed(), buf.Bytes()) + }) } diff --git a/session/store_test.go b/session/store_test.go new file mode 100644 index 000000000..ae7205a1a --- /dev/null +++ b/session/store_test.go @@ -0,0 +1,80 @@ +package session + +import ( + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" +) + +// TestBasicSessionStore tests the basic getters and setters of the session +// store. +func TestBasicSessionStore(t *testing.T) { + // Set up a new DB. + db, err := NewDB(t.TempDir(), "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + // Create a few sessions. + s1 := newSession(t, "session 1") + s2 := newSession(t, "session 2") + s3 := newSession(t, "session 3") + + // Persist session 1. + require.NoError(t, db.CreateSession(s1)) + + // Trying to persist session 1 again should fail. + require.ErrorContains(t, db.CreateSession(s1), "already exists") + + // Persist a few more sessions. + require.NoError(t, db.CreateSession(s2)) + require.NoError(t, db.CreateSession(s3)) + + // Ensure that we can retrieve each session. + for _, s := range []*Session{s1, s2, s3} { + session, err := db.GetSession(s.LocalPublicKey) + require.NoError(t, err) + require.Equal(t, s.Label, session.Label) + } + + // Fetch session 1 and assert that it currently has no remote pub key. + session1, err := db.GetSession(s1.LocalPublicKey) + require.NoError(t, err) + require.Nil(t, session1.RemotePublicKey) + + // Use the update method to add a remote key. + remotePriv, err := btcec.NewPrivateKey() + require.NoError(t, err) + remotePub := remotePriv.PubKey() + + err = db.UpdateSessionRemotePubKey(session1.LocalPublicKey, remotePub) + require.NoError(t, err) + + // Assert that the session now does have the remote pub key. + session1, err = db.GetSession(s1.LocalPublicKey) + require.NoError(t, err) + require.True(t, remotePub.IsEqual(session1.RemotePublicKey)) + + // Check that the session's state is currently StateCreated. + require.Equal(t, session1.State, StateCreated) + + // Now revoke the session and assert that the state is revoked. + require.NoError(t, db.RevokeSession(s1.LocalPublicKey)) + session1, err = db.GetSession(s1.LocalPublicKey) + require.NoError(t, err) + require.Equal(t, session1.State, StateRevoked) +} + +func newSession(t *testing.T, label string) *Session { + session, err := NewSession( + label, TypeMacaroonAdmin, + time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), + "foo.bar.baz:1234", true, nil, nil, nil, true, + ) + require.NoError(t, err) + + return session +} diff --git a/session_rpcserver.go b/session_rpcserver.go index 29e0ef54f..533b102ae 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -323,7 +323,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context, return nil, fmt.Errorf("error creating new session: %v", err) } - if err := s.db.StoreSession(sess); err != nil { + if err := s.db.CreateSession(sess); err != nil { return nil, fmt.Errorf("error storing session: %v", err) } @@ -477,7 +477,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { authData := []byte(fmt.Sprintf("%s: %s", HeaderMacaroon, mac)) sessionClosedSub, err := s.sessionServer.StartSession( - sess, authData, s.db.StoreSession, onNewStatus, + sess, authData, s.db.UpdateSessionRemotePubKey, onNewStatus, ) if err != nil { return err @@ -1015,7 +1015,7 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, // We only persist this session if we successfully retrieved the // autopilot's static key. sess.RemotePublicKey = remoteKey - if err := s.db.StoreSession(sess); err != nil { + if err := s.db.CreateSession(sess); err != nil { return nil, fmt.Errorf("error storing session: %v", err) } From c8b78bd10dd5fee9cb250edc91229aa05d824877 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 29 Aug 2023 14:23:42 +0200 Subject: [PATCH 03/23] session: add new ID-to-key index This commit does a few things: 1. Instead of deriving IDs using the first 4 bytes of the session's serialised local pub key, we instead use bytes [1:5] in order to skip the first byte which is either 0x02 or 0x03. This results in a greater entropy set. 2. We also add a new index from ID to key and we write to this index each time a new session is added. 3. We add a `ReserveNewSessionID` method to the session store which will grind through private keys until it finds one that does not clash with the current ID set. 4. A migration is added to back-fill the ID-to-key index. If any old sessions are found that _do_ have a colliding ID, they are sorted by created time and all but the newest session is revoked. Only an entry for the newest session will be added to the ID-to-key index. --- session/db.go | 8 +- session/interface.go | 38 +- session/macaroon.go | 26 + session/metadata.go | 10 +- session/migration1/id_to_key_index.go | 155 +++++ session/migration1/id_to_key_index_test.go | 118 ++++ session/migration1/interface.go | 121 ++++ session/migration1/macaroon.go | 34 ++ session/migration1/tlv.go | 674 +++++++++++++++++++++ session/migtest/migtest.go | 79 +++ session/migtest/raw_db.go | 175 ++++++ session/migtest/raw_db_test.go | 80 +++ session/store.go | 151 +++++ session/store_test.go | 35 +- session/tlv_test.go | 5 +- session_rpcserver.go | 30 +- 16 files changed, 1707 insertions(+), 32 deletions(-) create mode 100644 session/migration1/id_to_key_index.go create mode 100644 session/migration1/id_to_key_index_test.go create mode 100644 session/migration1/interface.go create mode 100644 session/migration1/macaroon.go create mode 100644 session/migration1/tlv.go create mode 100644 session/migtest/migtest.go create mode 100644 session/migtest/raw_db.go create mode 100644 session/migtest/raw_db_test.go diff --git a/session/db.go b/session/db.go index b261ed220..d723e82f6 100644 --- a/session/db.go +++ b/session/db.go @@ -105,7 +105,13 @@ func initDB(filepath string, firstInit bool) (*bbolt.DB, error) { } } - _, err = tx.CreateBucketIfNotExists(sessionBucketKey) + sessionBkt, err := tx.CreateBucketIfNotExists(sessionBucketKey) + if err != nil { + return err + } + + _, err = sessionBkt.CreateBucketIfNotExists(idIndexKey) + return err }) if err != nil { diff --git a/session/interface.go b/session/interface.go index 6ae9da0b0..f1b713b15 100644 --- a/session/interface.go +++ b/session/interface.go @@ -70,27 +70,20 @@ type MacaroonBaker func(ctx context.Context, rootKeyID uint64, recipe *MacaroonRecipe) (string, error) // NewSession creates a new session with the given user-defined parameters. -func NewSession(label string, typ Type, expiry time.Time, serverAddr string, - devServer bool, perms []bakery.Op, caveats []macaroon.Caveat, - featureConfig FeaturesConfig, privacy bool) (*Session, error) { +func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type, + expiry time.Time, serverAddr string, devServer bool, perms []bakery.Op, + caveats []macaroon.Caveat, featureConfig FeaturesConfig, + privacy bool) (*Session, error) { _, pairingSecret, err := mailbox.NewPassphraseEntropy() if err != nil { return nil, fmt.Errorf("error deriving pairing secret: %v", err) } - privateKey, err := btcec.NewPrivateKey() - if err != nil { - return nil, fmt.Errorf("error deriving private key: %v", err) - } - pubKey := privateKey.PubKey() - - var macRootKeyBase [4]byte - copy(macRootKeyBase[:], pubKey.SerializeCompressed()) - macRootKey := NewSuperMacaroonRootKeyID(macRootKeyBase) + macRootKey := NewSuperMacaroonRootKeyID(id) sess := &Session{ - ID: macRootKeyBase, + ID: id, Label: label, State: StateCreated, Type: typ, @@ -100,8 +93,8 @@ func NewSession(label string, typ Type, expiry time.Time, serverAddr string, DevServer: devServer, MacaroonRootKey: macRootKey, PairingSecret: pairingSecret, - LocalPrivateKey: privateKey, - LocalPublicKey: pubKey, + LocalPrivateKey: localPrivKey, + LocalPublicKey: localPrivKey.PubKey(), RemotePublicKey: nil, WithPrivacyMapper: privacy, } @@ -123,8 +116,10 @@ func NewSession(label string, typ Type, expiry time.Time, serverAddr string, // Store is the interface a persistent storage must implement for storing and // retrieving Terminal Connect sessions. type Store interface { - // CreateSession adds a new session to the store. If a session with the same - // local public key already exists an error is returned. + // CreateSession adds a new session to the store. If a session with the + // same local public key already exists an error is returned. This + // can only be called with a Session with an ID that the Store has + // reserved. CreateSession(*Session) error // GetSession fetches the session with the given key. @@ -141,4 +136,13 @@ type Store interface { // to the session with the given local pub key. UpdateSessionRemotePubKey(localPubKey, remotePubKey *btcec.PublicKey) error + + // GetUnusedIDAndKeyPair can be used to generate a new, unused, local + // private key and session ID pair. Care must be taken to ensure that no + // other thread calls this before the returned ID and key pair from this + // method are either used or discarded. + GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error) + + // GetSessionByID fetches the session with the given ID. + GetSessionByID(id ID) (*Session, error) } diff --git a/session/macaroon.go b/session/macaroon.go index 48a1e07b3..72dad8816 100644 --- a/session/macaroon.go +++ b/session/macaroon.go @@ -8,6 +8,7 @@ import ( "fmt" "strconv" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/lnrpc" "google.golang.org/protobuf/proto" "gopkg.in/macaroon-bakery.v2/bakery" @@ -127,3 +128,28 @@ func RootKeyIDFromMacaroon(mac *macaroon.Macaroon) (uint64, error) { // number. return strconv.ParseUint(string(decodedID.StorageId), 10, 64) } + +// NewSessionPrivKeyAndID randomly derives a new private key and session ID +// pair. +func NewSessionPrivKeyAndID() (*btcec.PrivateKey, ID, error) { + var id ID + + privateKey, err := btcec.NewPrivateKey() + if err != nil { + return nil, id, fmt.Errorf("error deriving private key: %v", + err) + } + + pubKey := privateKey.PubKey() + + // NOTE: we use 4 bytes [1:5] of the serialised public key to create the + // macaroon root key base along with the Session ID. This will provide + // 4 bytes of entropy. Previously, bytes [0:4] where used but this + // resulted in lower entropy due to the first byte always being either + // 0x02 or 0x03. + copy(id[:], pubKey.SerializeCompressed()[1:5]) + + log.Debugf("Generated new Session ID: %x", id) + + return privateKey, id, nil +} diff --git a/session/metadata.go b/session/metadata.go index fe5c52a8d..24575c689 100644 --- a/session/metadata.go +++ b/session/metadata.go @@ -3,7 +3,9 @@ package session import ( "errors" "fmt" + "time" + "github.com/lightninglabs/lightning-terminal/session/migration1" "go.etcd.io/bbolt" ) @@ -29,7 +31,13 @@ var ( // version of the database doesn't match the latest version this list // will be used for retrieving all migration function that are need to // apply to the current db. - dbVersions []migration + dbVersions = []migration{ + func(tx *bbolt.Tx) error { + return migration1.MigrateSessionIDToKeyIndex( + tx, time.Now, + ) + }, + } latestDBVersion = uint32(len(dbVersions)) ) diff --git a/session/migration1/id_to_key_index.go b/session/migration1/id_to_key_index.go new file mode 100644 index 000000000..d6d6fb7a2 --- /dev/null +++ b/session/migration1/id_to_key_index.go @@ -0,0 +1,155 @@ +package migration1 + +import ( + "bytes" + "errors" + "fmt" + "sort" + "time" + + "go.etcd.io/bbolt" +) + +var ( + // sessionBucketKey is the top level bucket where we can find all + // information about sessions. These sessions are indexed by their + // public key. + // + // The session bucket has the following structure: + // session -> -> + // -> id-index -> -> key -> + sessionBucketKey = []byte("session") + + // idIndexKey is the key used to define the id-index sub-bucket within + // the main session bucket. This bucket will be used to store the + // mapping from session ID to various other fields. + idIndexKey = []byte("id-index") + + // sessionKeyKey is the key used within the id-index bucket to store the + // session key (serialised local public key) associated with the given + // session ID. + sessionKeyKey = []byte("key") + + // ErrDBInitErr is returned when a bucket that we expect to have been + // set up during DB initialisation is not found. + ErrDBInitErr = errors.New("db did not initialise properly") +) + +type sessionInfo struct { + key []byte + state State + createdAt time.Time +} + +// MigrateSessionIDToKeyIndex back-fills the session ID to key index so that it +// has an entry for all sessions that the session store is currently aware of. +func MigrateSessionIDToKeyIndex(tx *bbolt.Tx, timeNow func() time.Time) error { + sessionBucket := tx.Bucket(sessionBucketKey) + if sessionBucket == nil { + return fmt.Errorf("session bucket not found") + } + + idIndexBkt := sessionBucket.Bucket(idIndexKey) + if idIndexBkt == nil { + return ErrDBInitErr + } + + // Collect all the index entries. + idToSessionPairs := make(map[ID][]*sessionInfo) + err := sessionBucket.ForEach(func(key, sessionBytes []byte) error { + // The session bucket contains both keys and sub-buckets. So + // here we ensure that we skip any sub-buckets. + if len(sessionBytes) == 0 { + return nil + } + + session, err := DeserializeSession( + bytes.NewReader(sessionBytes), + ) + if err != nil { + return err + } + + var id ID + copy(id[:], key[0:4]) + + idToSessionPairs[id] = append( + idToSessionPairs[id], &sessionInfo{ + key: key, + createdAt: session.CreatedAt, + state: session.State, + }, + ) + + return nil + }) + if err != nil { + return err + } + + addIndexEntry := func(id ID, key []byte) error { + idBkt, err := idIndexBkt.CreateBucket(id[:]) + if err != nil { + return err + } + + return idBkt.Put(sessionKeyKey[:], key) + } + + for id, sessions := range idToSessionPairs { + if len(sessions) == 1 { + err = addIndexEntry(id, sessions[0].key) + if err != nil { + return err + } + + continue + } + + // Sort the sessions from oldest to newest. + sort.Slice(sessions, func(i, j int) bool { + return sessions[i].createdAt.Before( + sessions[j].createdAt, + ) + }) + + // For each session other than the newest one, we ensure that + // the session is revoked. We do this in case there was a + // collision in the ID used for the session since now we want to + // populate the ID-to-key index which should be a one-to-one + // mapping. So there is a small chance that the DB contains a + // session with no entry in this ID-to-key index but at least + // this will not be an active session. + for _, session := range sessions[:len(sessions)-1] { + serialisedSession := sessionBucket.Get(session.key) + + sess, err := DeserializeSession( + bytes.NewReader(serialisedSession), + ) + if err != nil { + return err + } + + sess.State = StateRevoked + sess.RevokedAt = timeNow() + + var buf bytes.Buffer + if err := SerializeSession(&buf, sess); err != nil { + return err + } + + err = sessionBucket.Put(session.key, buf.Bytes()) + if err != nil { + return err + } + } + + // Add an entry for the last session in the set. + err = addIndexEntry(id, sessions[len(sessions)-1].key) + if err != nil { + return err + } + } + + return nil +} diff --git a/session/migration1/id_to_key_index_test.go b/session/migration1/id_to_key_index_test.go new file mode 100644 index 000000000..940342bb0 --- /dev/null +++ b/session/migration1/id_to_key_index_test.go @@ -0,0 +1,118 @@ +package migration1 + +import ( + "bytes" + "testing" + "time" + + "github.com/lightninglabs/lightning-terminal/session/migtest" + "github.com/stretchr/testify/require" + "go.etcd.io/bbolt" +) + +// TestMigrateSessionIDIndex tests that the MigrateSessionIDIndex migration +// correctly back-fills the session-ID to session-key index. +func TestMigrateSessionIDIndex(t *testing.T) { + t.Parallel() + + revokeTime := time.Unix(1000, 20) + + // Make a few sessions. + sess1ID, _, sess1Key, sess1Bytes := newSession(t) + sess2ID, _, sess2Key, sess2Bytes := newSession(t) + sess3ID, _, sess3Key, sess3Bytes := newSession(t) + _, sess4, sess4Key, _ := newSession(t) + + // Assert that the State of session 4 is StateCreated. + require.Equal(t, StateCreated, sess4.State) + + // Overwrite the CreatedAt time of session 4 so that it is definitely + // considered older than session 3. + sess4.CreatedAt = time.Now().Add(-time.Hour) + + // Now re-serialise session 4. + var sessBuff bytes.Buffer + require.NoError(t, SerializeSession(&sessBuff, sess4)) + sess4Bytes := sessBuff.Bytes() + + // Overwrite the first 4 bytes of the key of session 4 so that it is + // the same as session 3. This will mean that they have the same ID. + copy(sess4Key[0:4], sess3Key[0:4]) + + // Put together a sample session DB based on the above. + sessionDBBefore := map[string]interface{}{ + string(sess1Key): string(sess1Bytes), + string(sess2Key): string(sess2Bytes), + string(sess3Key): string(sess3Bytes), + string(sess4Key): string(sess4Bytes), + string(idIndexKey): map[string]interface{}{}, + } + + before := func(tx *bbolt.Tx) error { + return migtest.RestoreDB(tx, sessionBucketKey, sessionDBBefore) + } + + // Put together what we expect the resulting index will look like. + expectedIndex := map[string]interface{}{ + string(sess1ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess1Key), + }, + string(sess2ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess2Key), + }, + string(sess3ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess3Key), + }, + } + + // Since the DB contained two sessions with colliding IDs (session 3 + // and 4), we expect the oldest session of the two to be revoked during + // the migration. So let's construct what we expect session 4 to look + // like after the migration. + sess4.RevokedAt = revokeTime + sess4.State = StateRevoked + + sessBuff.Reset() + require.NoError(t, SerializeSession(&sessBuff, sess4)) + sess4BytesAfter := sessBuff.Bytes() + + sessionDBAfter := map[string]interface{}{ + string(sess1Key): string(sess1Bytes), + string(sess2Key): string(sess2Bytes), + string(sess3Key): string(sess3Bytes), + string(sess4Key): string(sess4BytesAfter), + string(idIndexKey): expectedIndex, + } + + // After the migration, we should have a new index bucket. + after := func(tx *bbolt.Tx) error { + return migtest.VerifyDB(tx, sessionBucketKey, sessionDBAfter) + } + + migrateFn := func(tx *bbolt.Tx) error { + return MigrateSessionIDToKeyIndex(tx, func() time.Time { + return revokeTime + }) + } + + migtest.ApplyMigration(t, before, after, migrateFn, false) +} + +// newSession is a helper function that can be used to generate a random new +// Session. It returns the session ID, key and the serialised session. +func newSession(t *testing.T) (ID, *Session, []byte, []byte) { + session, err := NewSession( + "test-session", TypeMacaroonAdmin, + time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), + "foo.bar.baz:1234", true, nil, nil, nil, false, + ) + require.NoError(t, err) + + var sessBuff bytes.Buffer + err = SerializeSession(&sessBuff, session) + require.NoError(t, err) + + key := session.LocalPublicKey.SerializeCompressed() + + return session.ID, session, key, sessBuff.Bytes() +} diff --git a/session/migration1/interface.go b/session/migration1/interface.go new file mode 100644 index 000000000..0ad434b5d --- /dev/null +++ b/session/migration1/interface.go @@ -0,0 +1,121 @@ +package migration1 + +import ( + "fmt" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightninglabs/lightning-node-connect/mailbox" + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" +) + +// Type represents the type of session. +type Type uint8 + +const ( + TypeMacaroonReadonly Type = 0 + TypeMacaroonAdmin Type = 1 + TypeMacaroonCustom Type = 2 + TypeUIPassword Type = 3 + TypeAutopilot Type = 4 + TypeMacaroonAccount Type = 5 +) + +// State represents the state of a session. +type State uint8 + +const ( + StateCreated State = 0 + StateInUse State = 1 + StateRevoked State = 2 + StateExpired State = 3 +) + +// MacaroonRecipe defines the permissions and caveats that should be used +// to bake a macaroon. +type MacaroonRecipe struct { + Permissions []bakery.Op + Caveats []macaroon.Caveat +} + +// FeaturesConfig is a map from feature name to a raw byte array which stores +// any config feature config options. +type FeaturesConfig map[string][]byte + +// Session is a struct representing a long-term Terminal Connect session. +type Session struct { + ID ID + Label string + State State + Type Type + Expiry time.Time + CreatedAt time.Time + RevokedAt time.Time + ServerAddr string + DevServer bool + MacaroonRootKey uint64 + MacaroonRecipe *MacaroonRecipe + PairingSecret [mailbox.NumPassphraseEntropyBytes]byte + LocalPrivateKey *btcec.PrivateKey + LocalPublicKey *btcec.PublicKey + RemotePublicKey *btcec.PublicKey + FeatureConfig *FeaturesConfig + WithPrivacyMapper bool +} + +// NewSession creates a new session with the given user-defined parameters. +func NewSession(label string, typ Type, expiry time.Time, serverAddr string, + devServer bool, perms []bakery.Op, caveats []macaroon.Caveat, + featureConfig FeaturesConfig, privacy bool) (*Session, error) { + + _, pairingSecret, err := mailbox.NewPassphraseEntropy() + if err != nil { + return nil, fmt.Errorf("error deriving pairing secret: %v", err) + } + + privateKey, err := btcec.NewPrivateKey() + if err != nil { + return nil, fmt.Errorf("error deriving private key: %v", err) + } + pubKey := privateKey.PubKey() + + // NOTE: after this migration, no new sessions use the first 4 bytes of + // the serialised pub key for the macaroon root key. After this + // migration, bytes 1-4 are used (instead of 0-3) this is so that we + // get the full 4 byte entropy instead of wasting one byte on the 0x02 + // and 0x03 prefix of the public key. + var macRootKeyBase [4]byte + copy(macRootKeyBase[:], pubKey.SerializeCompressed()) + macRootKey := NewSuperMacaroonRootKeyID(macRootKeyBase) + + sess := &Session{ + ID: macRootKeyBase, + Label: label, + State: StateCreated, + Type: typ, + Expiry: expiry, + CreatedAt: time.Now(), + ServerAddr: serverAddr, + DevServer: devServer, + MacaroonRootKey: macRootKey, + PairingSecret: pairingSecret, + LocalPrivateKey: privateKey, + LocalPublicKey: pubKey, + RemotePublicKey: nil, + WithPrivacyMapper: privacy, + } + + if perms != nil || caveats != nil { + sess.MacaroonRecipe = &MacaroonRecipe{ + Permissions: perms, + Caveats: caveats, + } + } + + if len(featureConfig) != 0 { + sess.FeatureConfig = &featureConfig + } + + return sess, nil +} diff --git a/session/migration1/macaroon.go b/session/migration1/macaroon.go new file mode 100644 index 000000000..9464abe42 --- /dev/null +++ b/session/migration1/macaroon.go @@ -0,0 +1,34 @@ +package migration1 + +import ( + "encoding/binary" +) + +var ( + // SuperMacaroonRootKeyPrefix is the prefix we set on a super macaroon's + // root key to clearly mark it as such. + SuperMacaroonRootKeyPrefix = [4]byte{0xFF, 0xEE, 0xDD, 0xCC} +) + +// ID represents the id of a session. +type ID [4]byte + +// NewSuperMacaroonRootKeyID returns a new macaroon root key ID that has the +// prefix to mark it as a super macaroon root key. +func NewSuperMacaroonRootKeyID(id [4]byte) uint64 { + rootKeyBytes := make([]byte, 8) + copy(rootKeyBytes[:], SuperMacaroonRootKeyPrefix[:]) + copy(rootKeyBytes[4:], id[:]) + return binary.BigEndian.Uint64(rootKeyBytes) +} + +// IDFromMacRootKeyID converts a macaroon root key ID to a session ID. +func IDFromMacRootKeyID(rootKeyID uint64) ID { + rootKeyBytes := make([]byte, 8) + binary.BigEndian.PutUint64(rootKeyBytes[:], rootKeyID) + + var id ID + copy(id[:], rootKeyBytes[4:]) + + return id +} diff --git a/session/migration1/tlv.go b/session/migration1/tlv.go new file mode 100644 index 000000000..aa3370b71 --- /dev/null +++ b/session/migration1/tlv.go @@ -0,0 +1,674 @@ +package migration1 + +import ( + "bytes" + "fmt" + "io" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/tlv" + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" +) + +const ( + typeLabel tlv.Type = 1 + typeState tlv.Type = 2 + typeType tlv.Type = 3 + typeExpiry tlv.Type = 4 + typeServerAddr tlv.Type = 5 + typeDevServer tlv.Type = 6 + typeMacaroonRootKey tlv.Type = 7 + typePairingSecret tlv.Type = 9 + typeLocalPrivateKey tlv.Type = 10 + typeRemotePublicKey tlv.Type = 11 + typeMacaroonRecipe tlv.Type = 12 + typeCreatedAt tlv.Type = 13 + typeFeaturesConfig tlv.Type = 14 + typeWithPrivacy tlv.Type = 15 + typeRevokedAt tlv.Type = 16 + + // typeMacaroon is no longer used, but we leave it defined for backwards + // compatibility. + typeMacaroon tlv.Type = 8 // nolint + + typeMacPerms tlv.Type = 1 + typeMacCaveats tlv.Type = 2 + + typeCaveatID tlv.Type = 1 + typeCaveatVerificationID tlv.Type = 2 + typeCaveatLocation tlv.Type = 3 + + typePermEntity tlv.Type = 1 + typePermAction tlv.Type = 2 + + typeFeatureName tlv.Type = 1 + typeFeatureConfig tlv.Type = 2 +) + +// SerializeSession binary serializes the given session to the writer using the +// tlv format. +func SerializeSession(w io.Writer, session *Session) error { + if session == nil { + return fmt.Errorf("session cannot be nil") + } + + var ( + label = []byte(session.Label) + state = uint8(session.State) + typ = uint8(session.Type) + expiry = uint64(session.Expiry.Unix()) + serverAddr = []byte(session.ServerAddr) + devServer = uint8(0) + pairingSecret = session.PairingSecret[:] + privateKey = session.LocalPrivateKey.Serialize() + createdAt = uint64(session.CreatedAt.Unix()) + revokedAt uint64 + withPrivacy = uint8(0) + ) + + if !session.RevokedAt.IsZero() { + revokedAt = uint64(session.RevokedAt.Unix()) + } + + if session.WithPrivacyMapper { + withPrivacy = 1 + } + + if session.DevServer { + devServer = 1 + } + + tlvRecords := []tlv.Record{ + tlv.MakePrimitiveRecord(typeLabel, &label), + tlv.MakePrimitiveRecord(typeState, &state), + tlv.MakePrimitiveRecord(typeType, &typ), + tlv.MakePrimitiveRecord(typeExpiry, &expiry), + tlv.MakePrimitiveRecord(typeServerAddr, &serverAddr), + tlv.MakePrimitiveRecord(typeDevServer, &devServer), + tlv.MakePrimitiveRecord( + typeMacaroonRootKey, &session.MacaroonRootKey, + ), + } + + tlvRecords = append( + tlvRecords, + tlv.MakePrimitiveRecord(typePairingSecret, &pairingSecret), + tlv.MakePrimitiveRecord(typeLocalPrivateKey, &privateKey), + ) + + if session.RemotePublicKey != nil { + tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord( + typeRemotePublicKey, &session.RemotePublicKey, + )) + } + + if session.MacaroonRecipe != nil { + tlvRecords = append(tlvRecords, tlv.MakeDynamicRecord( + typeMacaroonRecipe, session.MacaroonRecipe, + func() uint64 { + return recordSize( + macaroonRecipeEncoder, + session.MacaroonRecipe, + ) + }, + macaroonRecipeEncoder, macaroonRecipeDecoder, + )) + } + + tlvRecords = append( + tlvRecords, tlv.MakePrimitiveRecord(typeCreatedAt, &createdAt), + ) + + if session.FeatureConfig != nil && len(*session.FeatureConfig) != 0 { + tlvRecords = append(tlvRecords, tlv.MakeDynamicRecord( + typeFeaturesConfig, session.FeatureConfig, + func() uint64 { + return recordSize( + featureConfigEncoder, + session.FeatureConfig, + ) + }, + featureConfigEncoder, featureConfigDecoder, + )) + } + + tlvRecords = append( + tlvRecords, + tlv.MakePrimitiveRecord(typeWithPrivacy, &withPrivacy), + tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), + ) + + tlvStream, err := tlv.NewStream(tlvRecords...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// DeserializeSession deserializes a session from the given reader, expecting +// the data to be encoded in the tlv format. +func DeserializeSession(r io.Reader) (*Session, error) { + var ( + session = &Session{} + label, serverAddr []byte + pairingSecret, privateKey []byte + state, typ, devServer, privacy uint8 + expiry, createdAt, revokedAt uint64 + macRecipe MacaroonRecipe + featureConfig FeaturesConfig + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord(typeLabel, &label), + tlv.MakePrimitiveRecord(typeState, &state), + tlv.MakePrimitiveRecord(typeType, &typ), + tlv.MakePrimitiveRecord(typeExpiry, &expiry), + tlv.MakePrimitiveRecord(typeServerAddr, &serverAddr), + tlv.MakePrimitiveRecord(typeDevServer, &devServer), + tlv.MakePrimitiveRecord( + typeMacaroonRootKey, &session.MacaroonRootKey, + ), + tlv.MakePrimitiveRecord(typePairingSecret, &pairingSecret), + tlv.MakePrimitiveRecord(typeLocalPrivateKey, &privateKey), + tlv.MakePrimitiveRecord( + typeRemotePublicKey, &session.RemotePublicKey, + ), + tlv.MakeDynamicRecord( + typeMacaroonRecipe, &macRecipe, nil, + macaroonRecipeEncoder, macaroonRecipeDecoder, + ), + tlv.MakePrimitiveRecord(typeCreatedAt, &createdAt), + tlv.MakeDynamicRecord( + typeFeaturesConfig, &featureConfig, nil, + featureConfigEncoder, featureConfigDecoder, + ), + tlv.MakePrimitiveRecord(typeWithPrivacy, &privacy), + tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), + ) + if err != nil { + return nil, err + } + + parsedTypes, err := tlvStream.DecodeWithParsedTypes(r) + if err != nil { + return nil, err + } + + session.ID = IDFromMacRootKeyID(session.MacaroonRootKey) + session.Label = string(label) + session.State = State(state) + session.Type = Type(typ) + session.Expiry = time.Unix(int64(expiry), 0) + session.CreatedAt = time.Unix(int64(createdAt), 0) + session.ServerAddr = string(serverAddr) + session.DevServer = devServer == 1 + session.WithPrivacyMapper = privacy == 1 + + if revokedAt != 0 { + session.RevokedAt = time.Unix(int64(revokedAt), 0) + } + + if t, ok := parsedTypes[typeMacaroonRecipe]; ok && t == nil { + session.MacaroonRecipe = &macRecipe + } + + if t, ok := parsedTypes[typePairingSecret]; ok && t == nil { + copy(session.PairingSecret[:], pairingSecret) + } + + if t, ok := parsedTypes[typeLocalPrivateKey]; ok && t == nil { + session.LocalPrivateKey, session.LocalPublicKey = btcec.PrivKeyFromBytes( + privateKey, + ) + } + + if t, ok := parsedTypes[typeFeaturesConfig]; ok && t == nil { + session.FeatureConfig = &featureConfig + } + + return session, nil +} + +func featureConfigEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*FeaturesConfig); ok { + for n, config := range *v { + name := []byte(n) + config := config + + var permsTLVBytes bytes.Buffer + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord(typeFeatureName, &name), + tlv.MakePrimitiveRecord( + typeFeatureConfig, &config, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Encode(&permsTLVBytes) + if err != nil { + return err + } + + // We encode the record with a varint length followed by + // the _raw_ TLV bytes. + tlvLen := uint64(len(permsTLVBytes.Bytes())) + if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil { + return err + } + + _, err = w.Write(permsTLVBytes.Bytes()) + if err != nil { + return err + } + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "FeaturesConfig") +} + +func featureConfigDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*FeaturesConfig); ok { + featureConfig := make(map[string][]byte) + + // Using this information, we'll create a new limited + // reader that'll return an EOF once the end has been + // reached so the stream stops consuming bytes. + innerTlvReader := io.LimitedReader{ + R: r, + N: int64(l), + } + + for { + // Read out the varint that encodes the size of this + // inner TLV record. + blobSize, err := tlv.ReadVarInt(&innerTlvReader, buf) + if err == io.EOF { + break + } else if err != nil { + return err + } + + innerInnerTlvReader := io.LimitedReader{ + R: &innerTlvReader, + N: int64(blobSize), + } + + var ( + name []byte + config []byte + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord( + typeFeatureName, &name, + ), + tlv.MakePrimitiveRecord( + typeFeatureConfig, &config, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Decode(&innerInnerTlvReader) + if err != nil { + return err + } + + featureConfig[string(name)] = config + } + + *v = featureConfig + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "FeaturesConfig") +} + +// macaroonRecipeEncoder is a custom TLV encoder for a MacaroonRecipe record. +func macaroonRecipeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*MacaroonRecipe); ok { + var recipeTLVBytes bytes.Buffer + tlvStream, err := tlv.NewStream( + tlv.MakeDynamicRecord( + typeMacPerms, &v.Permissions, func() uint64 { + return recordSize( + permsEncoder, &v.Permissions, + ) + }, permsEncoder, permsDecoder, + ), + tlv.MakeDynamicRecord( + typeMacCaveats, &v.Caveats, func() uint64 { + return recordSize( + caveatsEncoder, &v.Caveats, + ) + }, caveatsEncoder, caveatsDecoder, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Encode(&recipeTLVBytes) + if err != nil { + return err + } + + _, err = w.Write(recipeTLVBytes.Bytes()) + if err != nil { + return err + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "MacaroonRecipe") +} + +// macaroonRecipeDecoder is a custom TLV decoder for a MacaroonRecipe record. +func macaroonRecipeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*MacaroonRecipe); ok { + // Using this information, we'll create a new limited + // reader that'll return an EOF once the end has been + // reached so the stream stops consuming bytes. + innerTlvReader := io.LimitedReader{ + R: r, + N: int64(l), + } + + var ( + perms []bakery.Op + caveats []macaroon.Caveat + ) + tlvStream, err := tlv.NewStream( + tlv.MakeDynamicRecord( + typeMacPerms, &perms, nil, permsEncoder, + permsDecoder, + ), + tlv.MakeDynamicRecord( + typeMacCaveats, &caveats, nil, caveatsEncoder, + caveatsDecoder, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Decode(&innerTlvReader) + if err != nil { + return err + } + + *v = MacaroonRecipe{ + Permissions: perms, + Caveats: caveats, + } + + return nil + } + + return tlv.NewTypeForDecodingErr(val, "MacaroonRecipe", l, l) +} + +// permsEncoder is a custom TLV encoder for macaroon Permission records. +func permsEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*[]bakery.Op); ok { + for _, c := range *v { + entity := []byte(c.Entity) + action := []byte(c.Action) + + var permsTLVBytes bytes.Buffer + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord( + typePermEntity, &entity, + ), + tlv.MakePrimitiveRecord( + typePermAction, &action, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Encode(&permsTLVBytes) + if err != nil { + return err + } + + // We encode the record with a varint length followed by + // the _raw_ TLV bytes. + tlvLen := uint64(len(permsTLVBytes.Bytes())) + if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil { + return err + } + + _, err = w.Write(permsTLVBytes.Bytes()) + if err != nil { + return err + } + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "MacaroonPermission") +} + +// permsDecoder is a custom TLV decoder for macaroon Permission records. +func permsDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { + if v, ok := val.(*[]bakery.Op); ok { + var perms []bakery.Op + + // Using this information, we'll create a new limited + // reader that'll return an EOF once the end has been + // reached so the stream stops consuming bytes. + innerTlvReader := io.LimitedReader{ + R: r, + N: int64(l), + } + + for { + // Read out the varint that encodes the size of this + // inner TLV record. + blobSize, err := tlv.ReadVarInt(&innerTlvReader, buf) + if err == io.EOF { + break + } else if err != nil { + return err + } + + innerInnerTlvReader := io.LimitedReader{ + R: &innerTlvReader, + N: int64(blobSize), + } + + var ( + entity []byte + action []byte + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord( + typePermEntity, &entity, + ), + tlv.MakePrimitiveRecord( + typePermAction, &action, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Decode(&innerInnerTlvReader) + if err != nil { + return err + } + + perms = append(perms, bakery.Op{ + Entity: string(entity), + Action: string(action), + }) + } + + *v = perms + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "MacaroonPermission") +} + +// caveatsEncoder is a custom TLV decoder for macaroon Caveat records. +func caveatsEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*[]macaroon.Caveat); ok { + for _, c := range *v { + tlvRecords := []tlv.Record{ + tlv.MakePrimitiveRecord(typeCaveatID, &c.Id), + } + + if c.VerificationId != nil { + tlvRecords = append(tlvRecords, + tlv.MakePrimitiveRecord( + typeCaveatVerificationID, + &c.VerificationId, + ), + ) + } + + location := []byte(c.Location) + if location != nil { + tlvRecords = append(tlvRecords, + tlv.MakePrimitiveRecord( + typeCaveatLocation, &location, + ), + ) + } + + tlvStream, err := tlv.NewStream(tlvRecords...) + if err != nil { + return err + } + + var caveatTLVBytes bytes.Buffer + err = tlvStream.Encode(&caveatTLVBytes) + if err != nil { + return err + } + + // We encode the record with a varint length followed by + // the _raw_ TLV bytes. + tlvLen := uint64(len(caveatTLVBytes.Bytes())) + if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil { + return err + } + + _, err = w.Write(caveatTLVBytes.Bytes()) + if err != nil { + return err + } + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "MacaroonCaveat") +} + +// caveatsDecoder is a custom TLV decoder for the macaroon Caveat record. +func caveatsDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*[]macaroon.Caveat); ok { + var caveats []macaroon.Caveat + + // Using this information, we'll create a new limited + // reader that'll return an EOF once the end has been + // reached so the stream stops consuming bytes. + innerTlvReader := io.LimitedReader{ + R: r, + N: int64(l), + } + + for { + // Read out the varint that encodes the size of this + // inner TLV record. + blobSize, err := tlv.ReadVarInt(r, buf) + if err == io.EOF { + break + } else if err != nil { + return err + } + + innerInnerTlvReader := io.LimitedReader{ + R: &innerTlvReader, + N: int64(blobSize), + } + + var ( + id []byte + verificationID []byte + location []byte + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord( + typeCaveatID, &id, + ), + tlv.MakePrimitiveRecord( + typeCaveatVerificationID, + &verificationID, + ), + tlv.MakePrimitiveRecord( + typeCaveatLocation, &location, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Decode(&innerInnerTlvReader) + if err != nil { + return err + } + + caveats = append(caveats, macaroon.Caveat{ + Id: id, + VerificationId: verificationID, + Location: string(location), + }) + } + + *v = caveats + return nil + } + + return tlv.NewTypeForDecodingErr(val, "MacaroonCaveat", l, l) +} + +// recordSize returns the amount of bytes this TLV record will occupy when +// encoded. +func recordSize(encoder tlv.Encoder, v interface{}) uint64 { + var ( + b bytes.Buffer + buf [8]byte + ) + + // We know that encoding works since the tests pass in the build this + // file is checked into, so we'll simplify things and simply encode it + // ourselves then report the total amount of bytes used. + if err := encoder(&b, v, &buf); err != nil { + // This should never error out, but we log it just in case it + // does. + // log.Errorf("encoding the amp invoice state failed: %v", err) + } + + return uint64(len(b.Bytes())) +} diff --git a/session/migtest/migtest.go b/session/migtest/migtest.go new file mode 100644 index 000000000..34f929b7f --- /dev/null +++ b/session/migtest/migtest.go @@ -0,0 +1,79 @@ +package migtest + +import ( + "fmt" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.etcd.io/bbolt" +) + +const ( + // dbFilePermission is the default permission the rules' database file + // is created with. + dbFilePermission = 0600 +) + +// MakeDB creates a new instance of the firewall DB for testing purposes. +func MakeDB(t *testing.T) *bbolt.DB { + dir := t.TempDir() + path := filepath.Join(dir, "test.db") + + db, err := bbolt.Open(path, dbFilePermission, &bbolt.Options{ + Timeout: time.Second * 5, + }) + require.NoError(t, err) + + return db +} + +// ApplyMigration is a helper test function that encapsulates the general steps +// which are needed to properly check the result of applying migration function. +func ApplyMigration(t *testing.T, beforeMigration, afterMigration, + migrationFunc func(tx *bbolt.Tx) error, shouldFail bool) { + + t.Helper() + + db := MakeDB(t) + + // beforeMigration is usually used for populating the database with + // test data. + require.NoError(t, db.Update(beforeMigration)) + + defer func() { + t.Helper() + + var err error + if r := recover(); r != nil { + err = newError(r) + } + + if shouldFail { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + // afterMigration usually used for checking the database state + // and throwing the error if something went wrong. + err = db.Update(afterMigration) + require.NoError(t, err) + }() + + // Apply migration. + require.NoError(t, db.Update(migrationFunc)) +} + +func newError(e interface{}) error { + var err error + switch e := e.(type) { + case error: + err = e + default: + err = fmt.Errorf("%v", e) + } + + return err +} diff --git a/session/migtest/raw_db.go b/session/migtest/raw_db.go new file mode 100644 index 000000000..7227e41bb --- /dev/null +++ b/session/migtest/raw_db.go @@ -0,0 +1,175 @@ +package migtest + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "strings" + + "go.etcd.io/bbolt" +) + +// DumpDB dumps go code describing the contents of the database to stdout. This +// function is only intended for use during development. +// +// Example output: +// +// map[string]interface{}{ +// hex("1234"): map[string]interface{}{ +// "human-readable": hex("102030"), +// hex("1111"): hex("5783492373"), +// }, +// } +func DumpDB(tx *bbolt.Tx, rootKey []byte) error { + bucket := tx.Bucket(rootKey) + if bucket == nil { + return fmt.Errorf("bucket %v not found", string(rootKey)) + } + + return dumpBucket(bucket) +} + +func dumpBucket(bucket *bbolt.Bucket) error { + fmt.Printf("map[string]interface{} {\n") + err := bucket.ForEach(func(k, v []byte) error { + key := toString(k) + fmt.Printf("%v: ", key) + + subBucket := bucket.Bucket(k) + if subBucket != nil { + err := dumpBucket(subBucket) + if err != nil { + return err + } + } else { + fmt.Print(toHex(v)) + } + fmt.Printf(",\n") + + return nil + }) + if err != nil { + return err + } + fmt.Printf("}") + + return nil +} + +// RestoreDB primes the database with the given data set. +func RestoreDB(tx *bbolt.Tx, rootKey []byte, data map[string]interface{}) error { + bucket, err := tx.CreateBucket(rootKey) + if err != nil { + return err + } + + return restoreDB(bucket, data) +} + +func restoreDB(bucket *bbolt.Bucket, data map[string]interface{}) error { + for k, v := range data { + key := []byte(k) + + switch value := v.(type) { + // Key contains value. + case string: + err := bucket.Put(key, []byte(value)) + if err != nil { + return err + } + + // Key contains a sub-bucket. + case map[string]interface{}: + subBucket, err := bucket.CreateBucket(key) + if err != nil { + return err + } + + if err := restoreDB(subBucket, value); err != nil { + return err + } + + default: + return errors.New("invalid type") + } + } + + return nil +} + +// VerifyDB verifies the database against the given data set. +func VerifyDB(tx *bbolt.Tx, rootKey []byte, data map[string]interface{}) error { + bucket := tx.Bucket(rootKey) + if bucket == nil { + return fmt.Errorf("bucket %v not found", string(rootKey)) + } + + return verifyDB(bucket, data) +} + +func verifyDB(bucket *bbolt.Bucket, data map[string]interface{}) error { + for k, v := range data { + key := []byte(k) + + switch value := v.(type) { + // Key contains value. + case string: + expectedValue := []byte(value) + dbValue := bucket.Get(key) + + if !bytes.Equal(dbValue, expectedValue) { + return errors.New("value mismatch") + } + + // Key contains a sub-bucket. + case map[string]interface{}: + subBucket := bucket.Bucket(key) + if subBucket == nil { + return fmt.Errorf("bucket %v not found", k) + } + + err := verifyDB(subBucket, value) + if err != nil { + return err + } + + default: + return errors.New("invalid type") + } + } + + keyCount := 0 + err := bucket.ForEach(func(k, v []byte) error { + keyCount++ + return nil + }) + if err != nil { + return err + } + if keyCount != len(data) { + return errors.New("unexpected keys in database") + } + + return nil +} + +func toHex(v []byte) string { + if len(v) == 0 { + return "nil" + } + + return "hex(\"" + hex.EncodeToString(v) + "\")" +} + +func toString(v []byte) string { + readableChars := "abcdefghijklmnopqrstuvwxyz0123456789-" + + for _, c := range v { + if !strings.Contains(readableChars, string(c)) { + return toHex(v) + } + } + + return "\"" + string(v) + "\"" +} diff --git a/session/migtest/raw_db_test.go b/session/migtest/raw_db_test.go new file mode 100644 index 000000000..7f4a4083e --- /dev/null +++ b/session/migtest/raw_db_test.go @@ -0,0 +1,80 @@ +package migtest + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.etcd.io/bbolt" +) + +// TestRestoreAndVerifyDB tests that the RestoreDB and VerifyDB helper methods +// works as expected. +func TestRestoreAndVerifyDB(t *testing.T) { + // Define the top leve key name and the DB structure. + topLevelKey := []byte("top-level") + + dbStructure := map[string]interface{}{ + "key1": "value1", + "bucket1": map[string]interface{}{ + "key2": "value2", + "bucket2": map[string]interface{}{ + "key3": "value3", + }, + "bucket3": map[string]interface{}{ + "key4": "value4", + }, + }, + } + + // Create the DB and attempt to restore the DB structure. + db := MakeDB(t) + + err := db.Update(func(tx *bbolt.Tx) error { + return RestoreDB(tx, topLevelKey, dbStructure) + }) + require.NoError(t, err) + + // Check that the VerifyDB method passes. + err = db.View(func(tx *bbolt.Tx) error { + return VerifyDB(tx, topLevelKey, dbStructure) + }) + require.NoError(t, err) + + // Now manually do some checks based on what we know about the db + // structure. + err = db.View(func(tx *bbolt.Tx) error { + // Query for a bucket we did not create and ensure that nil is + // returned. + bucket := tx.Bucket([]byte("top-level-2")) + require.Nil(t, bucket) + + // Query for the actual top level bucket. + bucket = tx.Bucket(topLevelKey) + require.NotNil(t, bucket) + + // Test the first bucket level. + require.Equal(t, []byte("value1"), bucket.Get([]byte("key1"))) + require.Nil(t, bucket.Get([]byte("bucket1"))) + require.Nil(t, bucket.Get([]byte("key2"))) + + // Test the second bucket level. + bucket1 := bucket.Bucket([]byte("bucket1")) + require.NotNil(t, bucket1) + + require.Equal(t, []byte("value2"), bucket1.Get([]byte("key2"))) + require.Nil(t, bucket1.Get([]byte("key1"))) + require.Nil(t, bucket1.Get([]byte("bucket1"))) + require.Nil(t, bucket1.Get([]byte("bucket2"))) + + bucket2 := bucket1.Bucket([]byte("bucket2")) + require.NotNil(t, bucket2) + require.Equal(t, []byte("value3"), bucket2.Get([]byte("key3"))) + + bucket3 := bucket1.Bucket([]byte("bucket3")) + require.NotNil(t, bucket3) + require.Equal(t, []byte("value4"), bucket3.Get([]byte("key4"))) + + return nil + }) + require.NoError(t, err) +} diff --git a/session/store.go b/session/store.go index a3bbb1a67..3eaad4266 100644 --- a/session/store.go +++ b/session/store.go @@ -14,11 +14,29 @@ var ( // sessionBucketKey is the top level bucket where we can find all // information about sessions. These sessions are indexed by their // public key. + // + // The session bucket has the following structure: + // session -> -> + // -> id-index -> -> key -> sessionBucketKey = []byte("session") + // idIndexKey is the key used to define the id-index sub-bucket within + // the main session bucket. This bucket will be used to store the + // mapping from session ID to various other fields. + idIndexKey = []byte("id-index") + + // sessionKeyKey is the key used within the id-index bucket to store the + // session key (serialised local public key) associated with the given + // session ID. + sessionKeyKey = []byte("key") + // ErrSessionNotFound is an error returned when we attempt to retrieve // information about a session but it is not found. ErrSessionNotFound = errors.New("session not found") + + // ErrDBInitErr is returned when a bucket that we expect to have been + // set up during DB initialisation is not found. + ErrDBInitErr = errors.New("db did not initialise properly") ) // getSessionKey returns the key for a session. @@ -49,6 +67,13 @@ func (db *DB) CreateSession(session *Session) error { session.LocalPublicKey.SerializeCompressed()) } + // Add the mapping from session ID to session key to the ID + // index. + err = addIDToKeyPair(sessionBucket, session.ID, sessionKey) + if err != nil { + return err + } + return sessionBucket.Put(sessionKey, buf.Bytes()) }) } @@ -194,3 +219,129 @@ func (db *DB) RevokeSession(key *btcec.PublicKey) error { return sessionBucket.Put(key.SerializeCompressed(), buf.Bytes()) }) } + +// GetSessionByID fetches the session with the given ID. +// +// NOTE: this is part of the Store interface. +func (db *DB) GetSessionByID(id ID) (*Session, error) { + var session *Session + err := db.View(func(tx *bbolt.Tx) error { + sessionBucket, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + keyBytes, err := getKeyForID(sessionBucket, id) + if err != nil { + return err + } + + v := sessionBucket.Get(keyBytes) + if len(v) == 0 { + return ErrSessionNotFound + } + + session, err = DeserializeSession(bytes.NewReader(v)) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return session, nil +} + +// GetUnusedIDAndKeyPair can be used to generate a new, unused, local private +// key and session ID pair. Care must be taken to ensure that no other thread +// calls this before the returned ID and key pair from this method are either +// used or discarded. +// +// NOTE: this is part of the Store interface. +func (db *DB) GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error) { + var ( + id ID + privKey *btcec.PrivateKey + ) + err := db.Update(func(tx *bbolt.Tx) error { + sessionBucket, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + idIndexBkt := sessionBucket.Bucket(idIndexKey) + if idIndexBkt == nil { + return ErrDBInitErr + } + + // Spin until we find a key with an ID that does not collide + // with any of our existing IDs. + for { + // Generate a new private key and ID pair. + privKey, id, err = NewSessionPrivKeyAndID() + if err != nil { + return err + } + + // Check that no such ID exits in our id-to-key index. + idBkt := idIndexBkt.Bucket(id[:]) + if idBkt != nil { + continue + } + + break + } + + return nil + }) + if err != nil { + return id, nil, err + } + + return id, privKey, nil +} + +// addIdToKeyPair inserts the mapping from session ID to session key into the +// id-index bucket. An error is returned if an entry for this ID already exists. +func addIDToKeyPair(sessionBkt *bbolt.Bucket, id ID, sessionKey []byte) error { + idIndexBkt := sessionBkt.Bucket(idIndexKey) + if idIndexBkt == nil { + return ErrDBInitErr + } + + idBkt, err := idIndexBkt.CreateBucketIfNotExists(id[:]) + if err != nil { + return err + } + + if len(idBkt.Get(sessionKeyKey)) != 0 { + return fmt.Errorf("a session with the given ID already exists") + } + + return idBkt.Put(sessionKeyKey[:], sessionKey) +} + +// getKeyForID fetches the session key associated with the given session ID. +func getKeyForID(sessionBkt *bbolt.Bucket, id ID) ([]byte, error) { + idIndexBkt := sessionBkt.Bucket(idIndexKey) + if idIndexBkt == nil { + return nil, ErrDBInitErr + } + + idBkt := idIndexBkt.Bucket(id[:]) + if idBkt == nil { + return nil, fmt.Errorf("no entry found in the ID index for "+ + "ID: %x", id) + } + + sessionKeyBytes := idBkt.Get(sessionKeyKey) + if len(sessionKeyKey) == 0 { + return nil, fmt.Errorf("no session key found in the ID "+ + "index for ID: %x", id) + } + + return sessionKeyBytes, nil +} diff --git a/session/store_test.go b/session/store_test.go index ae7205a1a..14178f1ca 100644 --- a/session/store_test.go +++ b/session/store_test.go @@ -19,25 +19,41 @@ func TestBasicSessionStore(t *testing.T) { }) // Create a few sessions. - s1 := newSession(t, "session 1") - s2 := newSession(t, "session 2") - s3 := newSession(t, "session 3") + s1 := newSession(t, db, "session 1") + s2 := newSession(t, db, "session 2") + s3 := newSession(t, db, "session 3") + s4 := newSession(t, db, "session 3") - // Persist session 1. + // Persist session 1. This should now succeed. require.NoError(t, db.CreateSession(s1)) - // Trying to persist session 1 again should fail. + // Trying to persist session 1 again should fail due to a session with + // the given pub key already existing. require.ErrorContains(t, db.CreateSession(s1), "already exists") + // Change the local pub key of session 4 such that it has the same + // ID as session 1. + s4.ID = s1.ID + + // Now try to insert session 4. This should fail due to an entry for + // the ID already existing. + require.ErrorContains(t, db.CreateSession(s4), "a session with the "+ + "given ID already exists") + // Persist a few more sessions. require.NoError(t, db.CreateSession(s2)) require.NoError(t, db.CreateSession(s3)) - // Ensure that we can retrieve each session. + // Ensure that we can retrieve each session by both its local pub key + // and by its ID. for _, s := range []*Session{s1, s2, s3} { session, err := db.GetSession(s.LocalPublicKey) require.NoError(t, err) require.Equal(t, s.Label, session.Label) + + session, err = db.GetSessionByID(s.ID) + require.NoError(t, err) + require.Equal(t, s.Label, session.Label) } // Fetch session 1 and assert that it currently has no remote pub key. @@ -68,9 +84,12 @@ func TestBasicSessionStore(t *testing.T) { require.Equal(t, session1.State, StateRevoked) } -func newSession(t *testing.T, label string) *Session { +func newSession(t *testing.T, db Store, label string) *Session { + id, priv, err := db.GetUnusedIDAndKeyPair() + require.NoError(t, err) + session, err := NewSession( - label, TypeMacaroonAdmin, + id, priv, label, TypeMacaroonAdmin, time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), "foo.bar.baz:1234", true, nil, nil, nil, true, ) diff --git a/session/tlv_test.go b/session/tlv_test.go index f1d211dd8..68833aadf 100644 --- a/session/tlv_test.go +++ b/session/tlv_test.go @@ -83,8 +83,11 @@ func TestSerializeDeserializeSession(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + priv, id, err := NewSessionPrivKeyAndID() + require.NoError(t, err) + session, err := NewSession( - test.name, test.sessType, + id, priv, test.name, test.sessType, time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), "foo.bar.baz:1234", true, test.perms, test.caveats, test.featureConfig, true, diff --git a/session_rpcserver.go b/session_rpcserver.go index 533b102ae..3db67fae3 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -44,6 +44,11 @@ type sessionRpcServer struct { db *session.DB sessionServer *session.Server + // sessRegMu is a mutex that should be held between acquiring an unused + // session ID and key pair from the session store and persisting that + // new session. + sessRegMu sync.Mutex + quit chan struct{} wg sync.WaitGroup stopOnce sync.Once @@ -315,9 +320,17 @@ func (s *sessionRpcServer) AddSession(_ context.Context, } } + s.sessRegMu.Lock() + defer s.sessRegMu.Unlock() + + id, localPrivKey, err := s.db.GetUnusedIDAndKeyPair() + if err != nil { + return nil, err + } + sess, err := session.NewSession( - req.Label, typ, expiry, req.MailboxServerAddr, req.DevServer, - uniquePermissions, caveats, nil, false, + id, localPrivKey, req.Label, typ, expiry, req.MailboxServerAddr, + req.DevServer, uniquePermissions, caveats, nil, false, ) if err != nil { return nil, fmt.Errorf("error creating new session: %v", err) @@ -979,9 +992,18 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, caveats = append(caveats, firewall.MetaPrivacyCaveat) } + s.sessRegMu.Lock() + defer s.sessRegMu.Unlock() + + id, localPrivKey, err := s.db.GetUnusedIDAndKeyPair() + if err != nil { + return nil, err + } + sess, err := session.NewSession( - req.Label, session.TypeAutopilot, expiry, req.MailboxServerAddr, - req.DevServer, perms, caveats, featureConfig, privacy, + id, localPrivKey, req.Label, session.TypeAutopilot, expiry, + req.MailboxServerAddr, req.DevServer, perms, caveats, + featureConfig, privacy, ) if err != nil { return nil, fmt.Errorf("error creating new session: %v", err) From 9098253d73f125da4249de429bff26835ccecedd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 18 Jun 2023 12:32:06 +0200 Subject: [PATCH 04/23] autopilotserverrpc: update with session linking fields Update the autopilot rpc protos to include the new fields that will allow the client to link autopilot sessions. --- autopilotserverrpc/autopilotserver.pb.go | 30 ++++++++++++++++++++++-- autopilotserverrpc/autopilotserver.proto | 12 ++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/autopilotserverrpc/autopilotserver.pb.go b/autopilotserverrpc/autopilotserver.pb.go index ae08d9d0b..ac75a2f84 100644 --- a/autopilotserverrpc/autopilotserver.pb.go +++ b/autopilotserverrpc/autopilotserver.pb.go @@ -644,6 +644,12 @@ type RegisterSessionRequest struct { LitVersion *Version `protobuf:"bytes,5,opt,name=lit_version,json=litVersion,proto3" json:"lit_version,omitempty"` // The version of the LND that the Lit client is using. LndVersion *Version `protobuf:"bytes,6,opt,name=lnd_version,json=lndVersion,proto3" json:"lnd_version,omitempty"` + // If set, this is the responder public key of a first session in the group + // that this new session should be linked to. + GroupResponderKey []byte `protobuf:"bytes,7,opt,name=group_responder_key,json=groupResponderKey,proto3" json:"group_responder_key,omitempty"` + // The signature by the first responder key of the serialised new responder + // key. + GroupResponderSig []byte `protobuf:"bytes,8,opt,name=group_responder_sig,json=groupResponderSig,proto3" json:"group_responder_sig,omitempty"` } func (x *RegisterSessionRequest) Reset() { @@ -720,6 +726,20 @@ func (x *RegisterSessionRequest) GetLndVersion() *Version { return nil } +func (x *RegisterSessionRequest) GetGroupResponderKey() []byte { + if x != nil { + return x.GroupResponderKey + } + return nil +} + +func (x *RegisterSessionRequest) GetGroupResponderSig() []byte { + if x != nil { + return x.GroupResponderSig + } + return nil +} + type RegisterSessionResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -930,7 +950,7 @@ var file_autopilotserver_proto_rawDesc = []byte{ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xae, 0x03, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x8e, 0x04, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x65, @@ -953,7 +973,13 @@ var file_autopilotserver_proto_rawDesc = []byte{ 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6c, 0x6e, 0x64, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x41, 0x0a, 0x13, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x11, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, + 0x72, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x11, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, + 0x72, 0x53, 0x69, 0x67, 0x1a, 0x41, 0x0a, 0x13, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, diff --git a/autopilotserverrpc/autopilotserver.proto b/autopilotserverrpc/autopilotserver.proto index 8bb8bce67..5a5ad7467 100644 --- a/autopilotserverrpc/autopilotserver.proto +++ b/autopilotserverrpc/autopilotserver.proto @@ -170,6 +170,18 @@ message RegisterSessionRequest { The version of the LND that the Lit client is using. */ Version lnd_version = 6; + + /* + If set, this is the responder public key of a first session in the group + that this new session should be linked to. + */ + bytes group_responder_key = 7; + + /* + The signature by the first responder key of the serialised new responder + key. + */ + bytes group_responder_sig = 8; } message RegisterSessionResponse { From c9c396d89d01fa641563904f6d699be928f30165 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 18 Jun 2023 12:38:46 +0200 Subject: [PATCH 05/23] autopilotserver: update to send linked session info Update the autopilot server client to send the new session linking fields if they are provided. Currently, they are not yet provided. --- autopilotserver/client.go | 29 +++++++++++++++++++---------- autopilotserver/client_test.go | 2 +- autopilotserver/interface.go | 3 ++- session_rpcserver.go | 2 +- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/autopilotserver/client.go b/autopilotserver/client.go index 599c0466a..7e2eabb61 100644 --- a/autopilotserver/client.go +++ b/autopilotserver/client.go @@ -376,11 +376,12 @@ func (c *Client) ListFeatures(ctx context.Context) (map[string]*Feature, // // Note: this is part of the Autopilot interface. func (c *Client) RegisterSession(ctx context.Context, pubKey *btcec.PublicKey, - mailboxAddr string, devServer bool, featureConf map[string][]byte) ( - *btcec.PublicKey, error) { + mailboxAddr string, devServer bool, featureConf map[string][]byte, + groupKey *btcec.PublicKey, linkSig []byte) (*btcec.PublicKey, error) { remotePub, err := c.registerSession( ctx, pubKey, mailboxAddr, devServer, featureConf, + groupKey, linkSig, ) if err != nil { log.Errorf("unsuccessful registration of session %x", @@ -425,8 +426,9 @@ func (c *Client) trackClient(pubKey *btcec.PublicKey) { // registerSession attempts to register a session with the given local static // public key with the autopilot server. func (c *Client) registerSession(ctx context.Context, pubKey *btcec.PublicKey, - mailboxAddr string, devServer bool, - featureConfig map[string][]byte) (*btcec.PublicKey, error) { + mailboxAddr string, devServer bool, featureConfig map[string][]byte, + groupLocalPub *btcec.PublicKey, linkSig []byte) (*btcec.PublicKey, + error) { client, cleanup, err := c.getClientConn() if err != nil { @@ -434,14 +436,21 @@ func (c *Client) registerSession(ctx context.Context, pubKey *btcec.PublicKey, } defer cleanup() + var groupKey []byte + if groupLocalPub != nil { + groupKey = groupLocalPub.SerializeCompressed() + } + resp, err := client.RegisterSession( ctx, &autopilotserverrpc.RegisterSessionRequest{ - ResponderPubKey: pubKey.SerializeCompressed(), - MailboxAddr: mailboxAddr, - DevServer: devServer, - FeatureConfigs: featureConfig, - LitVersion: marshalVersion(c.cfg.LitVersion), - LndVersion: marshalVersion(c.cfg.LndVersion), + ResponderPubKey: pubKey.SerializeCompressed(), + MailboxAddr: mailboxAddr, + DevServer: devServer, + FeatureConfigs: featureConfig, + LitVersion: marshalVersion(c.cfg.LitVersion), + LndVersion: marshalVersion(c.cfg.LndVersion), + GroupResponderKey: groupKey, + GroupResponderSig: linkSig, }, ) if err != nil { diff --git a/autopilotserver/client_test.go b/autopilotserver/client_test.go index 755b3a640..ad616dea2 100644 --- a/autopilotserver/client_test.go +++ b/autopilotserver/client_test.go @@ -45,7 +45,7 @@ func TestAutopilotClient(t *testing.T) { require.ErrorContains(t, err, "no such client") // Register the client. - _, err = client.RegisterSession(ctx, pubKey, "", false, nil) + _, err = client.RegisterSession(ctx, pubKey, "", false, nil, nil, nil) require.NoError(t, err) // Assert that the server sees the new client and has it in the Active diff --git a/autopilotserver/interface.go b/autopilotserver/interface.go index 3c0f4bcbe..e6bd8b210 100644 --- a/autopilotserver/interface.go +++ b/autopilotserver/interface.go @@ -29,7 +29,8 @@ type Autopilot interface { // remains active. RegisterSession(ctx context.Context, pubKey *btcec.PublicKey, mailboxAddr string, devServer bool, - featureConf map[string][]byte) (*btcec.PublicKey, error) + featureConf map[string][]byte, prevLocal *btcec.PublicKey, + linkSig []byte) (*btcec.PublicKey, error) // ActivateSession attempts to inform the autopilot server that the // given session is still active. After this is called, the autopilot diff --git a/session_rpcserver.go b/session_rpcserver.go index 3db67fae3..f05a9af8c 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -1027,7 +1027,7 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, // Attempt to register the session with the Autopilot server. remoteKey, err := s.cfg.autopilot.RegisterSession( ctx, sess.LocalPublicKey, sess.ServerAddr, sess.DevServer, - featureConfig, + featureConfig, nil, nil, ) if err != nil { return nil, fmt.Errorf("error registering session with "+ From 4bd8a5c03add5fc71fa72b5ff76911e5257e62a7 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 18 Jun 2023 13:04:56 +0200 Subject: [PATCH 06/23] session: add GroupID field to Session Add the new `GroupID1` field to the `Session` struct and ensure that the new fields are properly serialised and deserialised. The `GroupID` is the ID of the first Session in a set of linked sessions. --- session/interface.go | 17 +++++- session/store.go | 13 +++++ session/store_test.go | 40 +++++++++++-- session/tlv.go | 44 +++++++++++++-- session/tlv_test.go | 128 +++++++++++++++++++++++++++++++++++++++++- session_rpcserver.go | 4 +- 6 files changed, 230 insertions(+), 16 deletions(-) diff --git a/session/interface.go b/session/interface.go index f1b713b15..bfb593fe9 100644 --- a/session/interface.go +++ b/session/interface.go @@ -63,6 +63,11 @@ type Session struct { RemotePublicKey *btcec.PublicKey FeatureConfig *FeaturesConfig WithPrivacyMapper bool + + // GroupID is the Session ID of the very first Session in the linked + // group of sessions. If this is the very first session in the group + // then this will be the same as ID. + GroupID ID } // MacaroonBaker is a function type for baking a super macaroon. @@ -73,7 +78,7 @@ type MacaroonBaker func(ctx context.Context, rootKeyID uint64, func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type, expiry time.Time, serverAddr string, devServer bool, perms []bakery.Op, caveats []macaroon.Caveat, featureConfig FeaturesConfig, - privacy bool) (*Session, error) { + privacy bool, linkedGroupID *ID) (*Session, error) { _, pairingSecret, err := mailbox.NewPassphraseEntropy() if err != nil { @@ -82,6 +87,15 @@ func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type, macRootKey := NewSuperMacaroonRootKeyID(id) + // The group ID will by default be the same as the Session ID + // unless this session links to a previous session. + groupID := id + if linkedGroupID != nil { + // If this session is linked to a previous session, then the + // group ID is the same as the linked session's group ID. + groupID = *linkedGroupID + } + sess := &Session{ ID: id, Label: label, @@ -97,6 +111,7 @@ func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type, LocalPublicKey: localPrivKey.PubKey(), RemotePublicKey: nil, WithPrivacyMapper: privacy, + GroupID: groupID, } if perms != nil || caveats != nil { diff --git a/session/store.go b/session/store.go index 3eaad4266..e32dfcc21 100644 --- a/session/store.go +++ b/session/store.go @@ -67,6 +67,19 @@ func (db *DB) CreateSession(session *Session) error { session.LocalPublicKey.SerializeCompressed()) } + // If this is a linked session (meaning the group ID is + // different from the ID) the make sure that the Group ID of + // this session is an ID known by the store. We can do this by + // checking that an entry for this ID exists in the id-to-key + // index. + if session.ID != session.GroupID { + _, err = getKeyForID(sessionBucket, session.GroupID) + if err != nil { + return fmt.Errorf("unknown linked session "+ + "%x: %w", session.GroupID, err) + } + } + // Add the mapping from session ID to session key to the ID // index. err = addIDToKeyPair(sessionBucket, session.ID, sessionKey) diff --git a/session/store_test.go b/session/store_test.go index 14178f1ca..01ce8b5c3 100644 --- a/session/store_test.go +++ b/session/store_test.go @@ -19,10 +19,10 @@ func TestBasicSessionStore(t *testing.T) { }) // Create a few sessions. - s1 := newSession(t, db, "session 1") - s2 := newSession(t, db, "session 2") - s3 := newSession(t, db, "session 3") - s4 := newSession(t, db, "session 3") + s1 := newSession(t, db, "session 1", nil) + s2 := newSession(t, db, "session 2", nil) + s3 := newSession(t, db, "session 3", nil) + s4 := newSession(t, db, "session 4", nil) // Persist session 1. This should now succeed. require.NoError(t, db.CreateSession(s1)) @@ -34,6 +34,7 @@ func TestBasicSessionStore(t *testing.T) { // Change the local pub key of session 4 such that it has the same // ID as session 1. s4.ID = s1.ID + s4.GroupID = s1.GroupID // Now try to insert session 4. This should fail due to an entry for // the ID already existing. @@ -84,14 +85,41 @@ func TestBasicSessionStore(t *testing.T) { require.Equal(t, session1.State, StateRevoked) } -func newSession(t *testing.T, db Store, label string) *Session { +// TestLinkingSessions tests that session linking works as expected. +func TestLinkingSessions(t *testing.T) { + // Set up a new DB. + db, err := NewDB(t.TempDir(), "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + // Create a new session with no previous link. + s1 := newSession(t, db, "session 1", nil) + + // Create another session and link it to the first. + s2 := newSession(t, db, "session 2", &s1.GroupID) + + // Try to persist the second session and assert that it fails due to the + // linked session not existing in the DB yet. + require.ErrorContains(t, db.CreateSession(s2), "unknown linked session") + + // Now persist the first session and retry persisting the second one + // and assert that this now works. + require.NoError(t, db.CreateSession(s1)) + require.NoError(t, db.CreateSession(s2)) +} + +func newSession(t *testing.T, db Store, label string, + linkedGroupID *ID) *Session { + id, priv, err := db.GetUnusedIDAndKeyPair() require.NoError(t, err) session, err := NewSession( id, priv, label, TypeMacaroonAdmin, time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), - "foo.bar.baz:1234", true, nil, nil, nil, true, + "foo.bar.baz:1234", true, nil, nil, nil, true, linkedGroupID, ) require.NoError(t, err) diff --git a/session/tlv.go b/session/tlv.go index f2dbb88c8..3e0feb02d 100644 --- a/session/tlv.go +++ b/session/tlv.go @@ -28,6 +28,7 @@ const ( typeFeaturesConfig tlv.Type = 14 typeWithPrivacy tlv.Type = 15 typeRevokedAt tlv.Type = 16 + typeGroupID tlv.Type = 17 // typeMacaroon is no longer used, but we leave it defined for backwards // compatibility. @@ -54,6 +55,28 @@ func SerializeSession(w io.Writer, session *Session) error { return fmt.Errorf("session cannot be nil") } + tlvRecords, err := constructSessionTLVRecords(session, true) + if err != nil { + return err + } + + tlvStream, err := tlv.NewStream(tlvRecords...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// constructSessionTlvRecords takes a Session and gathers all the TLV records +// that need to be serialised for the session. The withGroupID boolean can be +// used to exclude the tlv record for the GroupID of the session. This should +// only ever be set to true for testing purposes to ensure that older sessions +// which did do not have the GroupID serialised still correctly deserialize and +// set the correct GroupID in the Session struct. +func constructSessionTLVRecords(session *Session, withGroupID bool) ( + []tlv.Record, error) { + var ( label = []byte(session.Label) state = uint8(session.State) @@ -140,12 +163,14 @@ func SerializeSession(w io.Writer, session *Session) error { tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), ) - tlvStream, err := tlv.NewStream(tlvRecords...) - if err != nil { - return err + if withGroupID { + groupID := session.GroupID[:] + tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord( + typeGroupID, &groupID, + )) } - return tlvStream.Encode(w) + return tlvRecords, nil } // DeserializeSession deserializes a session from the given reader, expecting @@ -159,6 +184,7 @@ func DeserializeSession(r io.Reader) (*Session, error) { expiry, createdAt, revokedAt uint64 macRecipe MacaroonRecipe featureConfig FeaturesConfig + groupID []byte ) tlvStream, err := tlv.NewStream( tlv.MakePrimitiveRecord(typeLabel, &label), @@ -186,6 +212,7 @@ func DeserializeSession(r io.Reader) (*Session, error) { ), tlv.MakePrimitiveRecord(typeWithPrivacy, &privacy), tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), + tlv.MakePrimitiveRecord(typeGroupID, &groupID), ) if err != nil { return nil, err @@ -228,6 +255,15 @@ func DeserializeSession(r io.Reader) (*Session, error) { session.FeatureConfig = &featureConfig } + // The GroupID field might not be set for older sessions. So we attempt + // to read it from the DB, otherwise we set the group ID to the session + // ID. + if t, ok := parsedTypes[typeGroupID]; ok && t == nil { + copy(session.GroupID[:], groupID) + } else { + session.GroupID = session.ID + } + return session, nil } diff --git a/session/tlv_test.go b/session/tlv_test.go index 68833aadf..631663758 100644 --- a/session/tlv_test.go +++ b/session/tlv_test.go @@ -45,11 +45,15 @@ var ( Id: []byte("caveat id3 here"), }, } + + groupID = ID{0, 1, 3, 4} ) // TestSerializeDeserializeSession makes sure that a session can be serialized // and deserialized from and to the tlv binary format successfully. func TestSerializeDeserializeSession(t *testing.T) { + t.Parallel() + tests := []struct { name string sessType Type @@ -57,32 +61,71 @@ func TestSerializeDeserializeSession(t *testing.T) { perms []bakery.Op caveats []macaroon.Caveat featureConfig map[string][]byte + linkedGroupID *ID }{ { - name: "session 1", + name: "revoked-at field", sessType: TypeMacaroonCustom, revokedAt: time.Date( 2023, 1, 10, 10, 10, 0, 0, time.UTC, ), }, { - name: "session 2", + name: "permissions and caveats", sessType: TypeMacaroonCustom, perms: perms, caveats: caveats, }, { - name: "session 3", + name: "feature configuration bytes", + sessType: TypeMacaroonCustom, + featureConfig: map[string][]byte{ + "AutoFees": {1, 2, 3, 4}, + "AutoSomething": {4, 3, 4, 5, 6, 6}, + }, + }, + { + name: "linked session with no group ID", + sessType: TypeMacaroonCustom, + featureConfig: map[string][]byte{ + "AutoFees": {1, 2, 3, 4}, + "AutoSomething": {4, 3, 4, 5, 6, 6}, + }, + linkedGroupID: &groupID, + }, + { + name: "linked session with group ID", sessType: TypeMacaroonCustom, featureConfig: map[string][]byte{ "AutoFees": {1, 2, 3, 4}, "AutoSomething": {4, 3, 4, 5, 6, 6}, }, + linkedGroupID: &groupID, + }, + { + name: "session with no optional fields", + sessType: TypeMacaroonCustom, + }, + { + name: "session with all optional fields", + sessType: TypeMacaroonCustom, + revokedAt: time.Date( + 2023, 1, 10, 10, 10, 0, 0, time.UTC, + ), + perms: perms, + caveats: caveats, + featureConfig: map[string][]byte{ + "AutoFees": {1, 2, 3, 4}, + "AutoSomething": {4, 3, 4, 5, 6, 6}, + }, }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { + t.Parallel() + priv, id, err := NewSessionPrivKeyAndID() require.NoError(t, err) @@ -91,6 +134,7 @@ func TestSerializeDeserializeSession(t *testing.T) { time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), "foo.bar.baz:1234", true, test.perms, test.caveats, test.featureConfig, true, + test.linkedGroupID, ) require.NoError(t, err) @@ -129,9 +173,83 @@ func TestSerializeDeserializeSession(t *testing.T) { } } +// TestGroupIDForOlderSessions tests that older sessions that were added before +// the GroupID was introduced still deserialize correctly by using the session's +// ID as the GroupID. +func TestGroupIDForOlderSessions(t *testing.T) { + t.Parallel() + + priv, id, err := NewSessionPrivKeyAndID() + require.NoError(t, err) + + session, err := NewSession( + id, priv, "test-session", TypeMacaroonAdmin, + time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), + "foo.bar.baz:1234", true, nil, nil, nil, false, nil, + ) + require.NoError(t, err) + + // Gather all the tlv records for the session _except for the group ID_. + records, err := constructSessionTLVRecords(session, false) + require.NoError(t, err) + + stream, err := tlv.NewStream(records...) + require.NoError(t, err) + + // Serialise the TLV stream. + var buf bytes.Buffer + require.NoError(t, stream.Encode(&buf)) + + // Now deserialize the session and ensure that the group ID _does_ get + // set correctly the session's ID. + sess, err := DeserializeSession(&buf) + require.NoError(t, err) + require.Equal(t, session.ID, sess.GroupID) +} + +// TestGroupID tests that a Session's GroupID member gets correctly set +// depending on if the Session is linked to a previous one. +func TestGroupID(t *testing.T) { + t.Parallel() + + priv, id, err := NewSessionPrivKeyAndID() + require.NoError(t, err) + + // Create session 1 which is not linked to any previous session. + session1, err := NewSession( + id, priv, "test-session", TypeMacaroonAdmin, + time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), + "foo.bar.baz:1234", true, nil, nil, nil, false, nil, + ) + require.NoError(t, err) + + // The group ID of this session should be the same as the session ID. + require.Equal(t, session1.ID, session1.GroupID) + + // Create session 2 and link it to session 1. + priv, id, err = NewSessionPrivKeyAndID() + require.NoError(t, err) + session2, err := NewSession( + id, priv, "test-session", TypeMacaroonAdmin, + time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), + "foo.bar.baz:1234", true, nil, nil, nil, false, + &session1.GroupID, + ) + require.NoError(t, err) + + // The group ID of this session should _not_ the same as its session ID. + require.NotEqual(t, session2.ID, session2.GroupID) + + // Instead, the group ID should match the session ID of session 1. + require.Equal(t, session1.ID, session2.GroupID) + +} + // TestSerializeDeserializeCaveats makes sure that a list of caveats can be // serialized and deserialized from and to the tlv binary format successfully. func TestSerializeDeserializeCaveats(t *testing.T) { + t.Parallel() + // We'll now make a sample invoice stream, and use that to encode the // amp state we created above. tlvStream, err := tlv.NewStream( @@ -173,6 +291,8 @@ func TestSerializeDeserializeCaveats(t *testing.T) { // TestSerializeDeserializePerms makes sure that a list of perms can be // serialized and deserialized from and to the tlv binary format successfully. func TestSerializeDeserializePerms(t *testing.T) { + t.Parallel() + // We'll now make a sample invoice stream, and use that to encode the // amp state we created above. tlvStream, err := tlv.NewStream( @@ -212,6 +332,8 @@ func TestSerializeDeserializePerms(t *testing.T) { // recipes can be serialized and deserialized from and to the tlv binary format // successfully. func TestSerializeDeserializeMacaroonRecipe(t *testing.T) { + t.Parallel() + recipe := MacaroonRecipe{ Permissions: perms, Caveats: caveats, diff --git a/session_rpcserver.go b/session_rpcserver.go index f05a9af8c..7f8747c68 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -330,7 +330,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context, sess, err := session.NewSession( id, localPrivKey, req.Label, typ, expiry, req.MailboxServerAddr, - req.DevServer, uniquePermissions, caveats, nil, false, + req.DevServer, uniquePermissions, caveats, nil, false, nil, ) if err != nil { return nil, fmt.Errorf("error creating new session: %v", err) @@ -1003,7 +1003,7 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, sess, err := session.NewSession( id, localPrivKey, req.Label, session.TypeAutopilot, expiry, req.MailboxServerAddr, req.DevServer, perms, caveats, - featureConfig, privacy, + featureConfig, privacy, nil, ) if err != nil { return nil, fmt.Errorf("error creating new session: %v", err) From e92350254f785d4b85b9145bca0b0f89977288b9 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 18 Jun 2023 13:08:18 +0200 Subject: [PATCH 07/23] litrpc: add session link field to AddAutopilotSessionRequest Update the Autopilot service AddAutopilotSessionRequest message to include a new `group_id` field. Users may set this field to the ID of a sessino group in order to link the new session to the older ones. The Session message is also updated with new `group_id` field which will be populated by the group a session. --- app/src/types/generated/lit-autopilot_pb.d.ts | 6 + app/src/types/generated/lit-autopilot_pb.js | 53 ++- app/src/types/generated/lit-sessions_pb.d.ts | 6 + app/src/types/generated/lit-sessions_pb.js | 53 ++- litrpc/lit-autopilot.pb.go | 231 ++++++------ litrpc/lit-autopilot.proto | 5 + litrpc/lit-autopilot.swagger.json | 10 + litrpc/lit-sessions.pb.go | 329 +++++++++--------- litrpc/lit-sessions.proto | 7 + litrpc/lit-sessions.swagger.json | 5 + proto/lit-autopilot.proto | 5 + proto/lit-sessions.proto | 7 + 12 files changed, 447 insertions(+), 270 deletions(-) diff --git a/app/src/types/generated/lit-autopilot_pb.d.ts b/app/src/types/generated/lit-autopilot_pb.d.ts index f32731d68..ddcd56cd5 100644 --- a/app/src/types/generated/lit-autopilot_pb.d.ts +++ b/app/src/types/generated/lit-autopilot_pb.d.ts @@ -27,6 +27,11 @@ export class AddAutopilotSessionRequest extends jspb.Message { getNoPrivacyMapper(): boolean; setNoPrivacyMapper(value: boolean): void; + getLinkedGroupId(): Uint8Array | string; + getLinkedGroupId_asU8(): Uint8Array; + getLinkedGroupId_asB64(): string; + setLinkedGroupId(value: Uint8Array | string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): AddAutopilotSessionRequest.AsObject; static toObject(includeInstance: boolean, msg: AddAutopilotSessionRequest): AddAutopilotSessionRequest.AsObject; @@ -46,6 +51,7 @@ export namespace AddAutopilotSessionRequest { featuresMap: Array<[string, FeatureConfig.AsObject]>, sessionRules?: lit_sessions_pb.RulesMap.AsObject, noPrivacyMapper: boolean, + linkedGroupId: Uint8Array | string, } } diff --git a/app/src/types/generated/lit-autopilot_pb.js b/app/src/types/generated/lit-autopilot_pb.js index 10fc96a0e..44c47f02c 100644 --- a/app/src/types/generated/lit-autopilot_pb.js +++ b/app/src/types/generated/lit-autopilot_pb.js @@ -80,7 +80,8 @@ proto.litrpc.AddAutopilotSessionRequest.toObject = function(includeInstance, msg devServer: jspb.Message.getFieldWithDefault(msg, 4, false), featuresMap: (f = msg.getFeaturesMap()) ? f.toObject(includeInstance, proto.litrpc.FeatureConfig.toObject) : [], sessionRules: (f = msg.getSessionRules()) && lit$sessions_pb.RulesMap.toObject(includeInstance, f), - noPrivacyMapper: jspb.Message.getFieldWithDefault(msg, 7, false) + noPrivacyMapper: jspb.Message.getFieldWithDefault(msg, 7, false), + linkedGroupId: msg.getLinkedGroupId_asB64() }; if (includeInstance) { @@ -148,6 +149,10 @@ proto.litrpc.AddAutopilotSessionRequest.deserializeBinaryFromReader = function(m var value = /** @type {boolean} */ (reader.readBool()); msg.setNoPrivacyMapper(value); break; + case 8: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setLinkedGroupId(value); + break; default: reader.skipField(); break; @@ -224,6 +229,13 @@ proto.litrpc.AddAutopilotSessionRequest.serializeBinaryToWriter = function(messa f ); } + f = message.getLinkedGroupId_asU8(); + if (f.length > 0) { + writer.writeBytes( + 8, + f + ); + } }; @@ -354,6 +366,45 @@ proto.litrpc.AddAutopilotSessionRequest.prototype.setNoPrivacyMapper = function( }; +/** + * optional bytes linked_group_id = 8; + * @return {!(string|Uint8Array)} + */ +proto.litrpc.AddAutopilotSessionRequest.prototype.getLinkedGroupId = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 8, "")); +}; + + +/** + * optional bytes linked_group_id = 8; + * This is a type-conversion wrapper around `getLinkedGroupId()` + * @return {string} + */ +proto.litrpc.AddAutopilotSessionRequest.prototype.getLinkedGroupId_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getLinkedGroupId())); +}; + + +/** + * optional bytes linked_group_id = 8; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getLinkedGroupId()` + * @return {!Uint8Array} + */ +proto.litrpc.AddAutopilotSessionRequest.prototype.getLinkedGroupId_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getLinkedGroupId())); +}; + + +/** @param {!(string|Uint8Array)} value */ +proto.litrpc.AddAutopilotSessionRequest.prototype.setLinkedGroupId = function(value) { + jspb.Message.setProto3BytesField(this, 8, value); +}; + + /** * Generated by JsPbCodeGenerator. diff --git a/app/src/types/generated/lit-sessions_pb.d.ts b/app/src/types/generated/lit-sessions_pb.d.ts index c9ce8cc50..b3ff42c35 100644 --- a/app/src/types/generated/lit-sessions_pb.d.ts +++ b/app/src/types/generated/lit-sessions_pb.d.ts @@ -153,6 +153,11 @@ export class Session extends jspb.Message { getRevokedAt(): string; setRevokedAt(value: string): void; + getGroupId(): Uint8Array | string; + getGroupId_asU8(): Uint8Array; + getGroupId_asB64(): string; + setGroupId(value: Uint8Array | string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Session.AsObject; static toObject(includeInstance: boolean, msg: Session): Session.AsObject; @@ -181,6 +186,7 @@ export namespace Session { accountId: string, autopilotFeatureInfoMap: Array<[string, RulesMap.AsObject]>, revokedAt: string, + groupId: Uint8Array | string, } } diff --git a/app/src/types/generated/lit-sessions_pb.js b/app/src/types/generated/lit-sessions_pb.js index e09f51593..39d427029 100644 --- a/app/src/types/generated/lit-sessions_pb.js +++ b/app/src/types/generated/lit-sessions_pb.js @@ -758,7 +758,8 @@ proto.litrpc.Session.toObject = function(includeInstance, msg) { macaroonRecipe: (f = msg.getMacaroonRecipe()) && proto.litrpc.MacaroonRecipe.toObject(includeInstance, f), accountId: jspb.Message.getFieldWithDefault(msg, 13, ""), autopilotFeatureInfoMap: (f = msg.getAutopilotFeatureInfoMap()) ? f.toObject(includeInstance, proto.litrpc.RulesMap.toObject) : [], - revokedAt: jspb.Message.getFieldWithDefault(msg, 16, "0") + revokedAt: jspb.Message.getFieldWithDefault(msg, 16, "0"), + groupId: msg.getGroupId_asB64() }; if (includeInstance) { @@ -862,6 +863,10 @@ proto.litrpc.Session.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {string} */ (reader.readUint64String()); msg.setRevokedAt(value); break; + case 17: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setGroupId(value); + break; default: reader.skipField(); break; @@ -1001,6 +1006,13 @@ proto.litrpc.Session.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getGroupId_asU8(); + if (f.length > 0) { + writer.writeBytes( + 17, + f + ); + } }; @@ -1360,6 +1372,45 @@ proto.litrpc.Session.prototype.setRevokedAt = function(value) { }; +/** + * optional bytes group_id = 17; + * @return {!(string|Uint8Array)} + */ +proto.litrpc.Session.prototype.getGroupId = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 17, "")); +}; + + +/** + * optional bytes group_id = 17; + * This is a type-conversion wrapper around `getGroupId()` + * @return {string} + */ +proto.litrpc.Session.prototype.getGroupId_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getGroupId())); +}; + + +/** + * optional bytes group_id = 17; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getGroupId()` + * @return {!Uint8Array} + */ +proto.litrpc.Session.prototype.getGroupId_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getGroupId())); +}; + + +/** @param {!(string|Uint8Array)} value */ +proto.litrpc.Session.prototype.setGroupId = function(value) { + jspb.Message.setProto3BytesField(this, 17, value); +}; + + /** * Generated by JsPbCodeGenerator. diff --git a/litrpc/lit-autopilot.pb.go b/litrpc/lit-autopilot.pb.go index 6b630e9dc..1417a5bf6 100644 --- a/litrpc/lit-autopilot.pb.go +++ b/litrpc/lit-autopilot.pb.go @@ -41,6 +41,8 @@ type AddAutopilotSessionRequest struct { SessionRules *RulesMap `protobuf:"bytes,6,opt,name=session_rules,json=sessionRules,proto3" json:"session_rules,omitempty"` // Set to true of the session should not make use of the privacy mapper. NoPrivacyMapper bool `protobuf:"varint,7,opt,name=no_privacy_mapper,json=noPrivacyMapper,proto3" json:"no_privacy_mapper,omitempty"` + // Set to the ID of the group to link this session to, if any. + LinkedGroupId []byte `protobuf:"bytes,8,opt,name=linked_group_id,json=linkedGroupId,proto3" json:"linked_group_id,omitempty"` } func (x *AddAutopilotSessionRequest) Reset() { @@ -124,6 +126,13 @@ func (x *AddAutopilotSessionRequest) GetNoPrivacyMapper() bool { return false } +func (x *AddAutopilotSessionRequest) GetLinkedGroupId() []byte { + if x != nil { + return x.LinkedGroupId + } + return nil +} + type FeatureConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -717,7 +726,7 @@ var file_lit_autopilot_proto_rawDesc = []byte{ 0x0a, 0x13, 0x6c, 0x69, 0x74, 0x2d, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x12, 0x6c, 0x69, 0x74, 0x2d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0xc4, 0x03, 0x0a, 0x1a, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, + 0x6f, 0x22, 0xec, 0x03, 0x0a, 0x1a, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x18, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, @@ -740,116 +749,118 @@ var file_lit_autopilot_proto_rawDesc = []byte{ 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6e, 0x6f, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6e, 0x6f, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, - 0x70, 0x70, 0x65, 0x72, 0x1a, 0x52, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x05, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4c, 0x0a, 0x1d, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x41, 0x75, - 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, - 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xbe, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, - 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4c, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0x49, 0x0a, 0x1d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, - 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, - 0x1e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0xaa, 0x02, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x72, 0x75, - 0x6c, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x10, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, - 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x0f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, - 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x1a, 0x4c, - 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, - 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb1, 0x01, 0x0a, - 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6b, - 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6b, 0x6e, 0x6f, 0x77, - 0x6e, 0x12, 0x2d, 0x0a, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, - 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, - 0x12, 0x2e, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, - 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x2e, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, - 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x22, 0x61, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x3a, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x32, 0xa0, 0x03, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, - 0x74, 0x12, 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, - 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x6c, 0x69, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, - 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, - 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x41, 0x75, - 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, - 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, - 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, + 0x70, 0x70, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6c, + 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x1a, 0x52, 0x0a, 0x0d, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x4f, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x26, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, + 0x61, 0x70, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, + 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x4c, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, + 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x48, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, + 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbe, 0x01, 0x0a, 0x1d, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x08, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, + 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4c, 0x0a, 0x0d, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x49, 0x0a, 0x1d, 0x52, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, + 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xaa, 0x02, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x75, 0x6c, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x10, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0f, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x55, + 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x1a, 0x4c, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb1, 0x01, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x12, 0x2d, 0x0a, 0x08, 0x64, 0x65, 0x66, + 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, + 0x6d, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2e, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, + 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x61, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, + 0x3a, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0xa0, 0x03, 0x0a, 0x09, + 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x12, 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x12, 0x24, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x5e, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x24, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, + 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, + 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, + 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x24, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, - 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, - 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, - 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, - 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, - 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, - 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, + 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, + 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/litrpc/lit-autopilot.proto b/litrpc/lit-autopilot.proto index 174940d4b..169955a20 100644 --- a/litrpc/lit-autopilot.proto +++ b/litrpc/lit-autopilot.proto @@ -73,6 +73,11 @@ message AddAutopilotSessionRequest { Set to true of the session should not make use of the privacy mapper. */ bool no_privacy_mapper = 7; + + /* + Set to the ID of the group to link this session to, if any. + */ + bytes linked_group_id = 8; } message FeatureConfig { diff --git a/litrpc/lit-autopilot.swagger.json b/litrpc/lit-autopilot.swagger.json index c0c9b3d28..8f65810d0 100644 --- a/litrpc/lit-autopilot.swagger.json +++ b/litrpc/lit-autopilot.swagger.json @@ -162,6 +162,11 @@ "no_privacy_mapper": { "type": "boolean", "description": "Set to true of the session should not make use of the privacy mapper." + }, + "linked_group_id": { + "type": "string", + "format": "byte", + "description": "Set to the ID of the group to link this session to, if any." } } }, @@ -578,6 +583,11 @@ "type": "string", "format": "uint64", "description": "The unix timestamp indicating the time at which the session was revoked.\nNote that this field has not been around since the beginning and so it\ncould be the case that a session has been revoked but that this field\nwill not have been set for that session. Therefore, it is suggested that\nreaders should not assume that if this field is zero that the session is\nnot revoked. Readers should instead first check the session_state field." + }, + "group_id": { + "type": "string", + "format": "byte", + "description": "The ID of the group of Session's that this Session is linked to. If this\nsession is not linked to any older Session, then this value will be the\nsame as the ID." } } }, diff --git a/litrpc/lit-sessions.pb.go b/litrpc/lit-sessions.pb.go index bd8e26680..7b882f04f 100644 --- a/litrpc/lit-sessions.pb.go +++ b/litrpc/lit-sessions.pb.go @@ -397,6 +397,10 @@ type Session struct { // readers should not assume that if this field is zero that the session is // not revoked. Readers should instead first check the session_state field. RevokedAt uint64 `protobuf:"varint,16,opt,name=revoked_at,json=revokedAt,proto3" json:"revoked_at,omitempty"` + // The ID of the group of Session's that this Session is linked to. If this + // session is not linked to any older Session, then this value will be the + // same as the ID. + GroupId []byte `protobuf:"bytes,17,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` } func (x *Session) Reset() { @@ -543,6 +547,13 @@ func (x *Session) GetRevokedAt() uint64 { return 0 } +func (x *Session) GetGroupId() []byte { + if x != nil { + return x.GroupId + } + return nil +} + type MacaroonRecipe struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1556,7 +1567,7 @@ var file_lit_sessions_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, - 0xc6, 0x06, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0xe1, 0x06, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, @@ -1602,164 +1613,166 @@ var file_lit_sessions_proto_rawDesc = []byte{ 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0a, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, - 0x30, 0x01, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x59, 0x0a, - 0x19, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x69, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x61, 0x76, 0x65, - 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, - 0x74, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x14, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x40, - 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x08, 0x52, 0x75, - 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x12, 0x31, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, - 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0a, 0x52, 0x75, 0x6c, - 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x92, 0x04, 0x0a, 0x09, 0x52, 0x75, 0x6c, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x32, 0x0a, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x48, 0x00, 0x52, 0x09, 0x72, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x6f, 0x75, 0x6e, 0x64, - 0x73, 0x48, 0x00, 0x52, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, - 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x3b, 0x0a, 0x0d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x12, 0x42, 0x0a, 0x10, 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, - 0x64, 0x67, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x5f, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x36, 0x0a, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x5f, - 0x74, 0x6f, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, - 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, 0x65, 0x6c, - 0x66, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, 0x65, 0x6c, 0x66, 0x12, - 0x44, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x72, - 0x69, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, - 0x74, 0x72, 0x69, 0x63, 0x74, 0x12, 0x3b, 0x0a, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x72, 0x65, - 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x67, 0x0a, 0x09, 0x52, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2b, 0x0a, 0x0a, 0x72, 0x65, 0x61, 0x64, - 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x09, 0x72, 0x65, 0x61, 0x64, - 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x0b, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, - 0x69, 0x6d, 0x69, 0x74, 0x22, 0x43, 0x0a, 0x04, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, - 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, - 0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x08, 0x6e, 0x75, 0x6d, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x22, 0x51, 0x0a, 0x0c, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x21, 0x0a, 0x0a, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, - 0x01, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x08, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, - 0x30, 0x01, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc5, 0x02, 0x0a, - 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x6f, - 0x75, 0x6e, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x0b, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, - 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x73, 0x65, - 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x52, - 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x61, - 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, - 0x78, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x6e, 0x5f, - 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x24, - 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x43, 0x6c, 0x74, 0x76, 0x44, - 0x65, 0x6c, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, - 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, - 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, - 0x4d, 0x73, 0x61, 0x74, 0x22, 0x5e, 0x0a, 0x0e, 0x4f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6d, - 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, - 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, - 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x73, - 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6f, 0x0a, 0x0d, 0x4f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, - 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x2e, 0x0a, 0x11, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, - 0x65, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x42, 0x02, 0x30, 0x01, 0x52, 0x0f, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x41, 0x6d, - 0x74, 0x53, 0x61, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x61, 0x74, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, - 0x56, 0x42, 0x79, 0x74, 0x65, 0x22, 0x0c, 0x0a, 0x0a, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, - 0x65, 0x6c, 0x66, 0x22, 0x36, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, - 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x73, 0x22, 0x29, 0x0a, 0x0c, 0x50, - 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, - 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, - 0x65, 0x65, 0x72, 0x49, 0x64, 0x73, 0x2a, 0xa1, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, - 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x4f, 0x4e, 0x4c, 0x59, - 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, - 0x4f, 0x4f, 0x4e, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x43, 0x55, 0x53, - 0x54, 0x4f, 0x4d, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, - 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x41, 0x55, 0x54, 0x4f, 0x50, 0x49, 0x4c, 0x4f, 0x54, 0x10, 0x04, 0x12, - 0x19, 0x0a, 0x15, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, - 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x05, 0x2a, 0x59, 0x0a, 0x0c, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, - 0x41, 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, - 0x0c, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x01, 0x12, - 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, - 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, - 0x52, 0x45, 0x44, 0x10, 0x03, 0x32, 0xe8, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x19, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, + 0x30, 0x01, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x1a, 0x59, 0x0a, 0x19, 0x41, 0x75, 0x74, 0x6f, + 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, + 0x65, 0x63, 0x69, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x73, 0x22, 0x15, 0x0a, + 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x08, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, - 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, - 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, - 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, - 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x17, 0x0a, 0x15, 0x52, + 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x08, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, + 0x70, 0x12, 0x31, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, + 0x61, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x72, + 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x92, 0x04, 0x0a, 0x09, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x32, 0x0a, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x48, 0x00, 0x52, 0x09, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, + 0x6d, 0x69, 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x48, 0x00, 0x52, 0x10, + 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, + 0x12, 0x3b, 0x0a, 0x0d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x48, 0x00, 0x52, + 0x0c, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x42, 0x0a, + 0x10, 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x62, 0x75, 0x64, 0x67, 0x65, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x48, + 0x00, 0x52, 0x0e, 0x6f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, 0x64, 0x67, 0x65, + 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x62, 0x75, + 0x64, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, 0x64, 0x67, 0x65, + 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, 0x64, 0x67, + 0x65, 0x74, 0x12, 0x36, 0x0a, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x73, 0x65, + 0x6c, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, 0x65, 0x6c, 0x66, 0x48, 0x00, 0x52, 0x0a, + 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x48, 0x00, 0x52, + 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, + 0x12, 0x3b, 0x0a, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x48, 0x00, 0x52, + 0x0c, 0x70, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x42, 0x07, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x67, 0x0a, 0x09, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, + 0x6d, 0x69, 0x74, 0x12, 0x2b, 0x0a, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x09, 0x72, 0x65, 0x61, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x2d, 0x0a, 0x0b, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x61, 0x74, 0x65, 0x52, 0x0a, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x22, + 0x43, 0x0a, 0x04, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x74, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x68, + 0x6f, 0x75, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x48, + 0x6f, 0x75, 0x72, 0x73, 0x22, 0x51, 0x0a, 0x0c, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x12, 0x21, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc5, 0x02, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, + 0x26, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x42, + 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x62, + 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, + 0x20, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, + 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, + 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x52, 0x61, 0x74, 0x65, + 0x50, 0x70, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, + 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x69, 0x6e, + 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, + 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, + 0x26, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, + 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x22, + 0x5e, 0x0a, 0x0e, 0x4f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, 0x64, 0x67, 0x65, + 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0a, 0x6d, 0x61, 0x78, + 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x66, + 0x65, 0x65, 0x73, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x73, 0x4d, 0x73, 0x61, 0x74, 0x22, + 0x6f, 0x0a, 0x0d, 0x4f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, + 0x12, 0x2e, 0x0a, 0x11, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x5f, 0x61, 0x6d, 0x74, + 0x5f, 0x73, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, + 0x0f, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x73, + 0x12, 0x2e, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x76, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, + 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x42, 0x79, 0x74, 0x65, + 0x22, 0x0c, 0x0a, 0x0a, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, 0x65, 0x6c, 0x66, 0x22, 0x36, + 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, + 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x73, 0x22, 0x29, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, + 0x73, 0x2a, 0xa1, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, + 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x17, 0x0a, + 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x41, + 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, + 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x02, + 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x5f, 0x50, 0x41, 0x53, 0x53, + 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, + 0x55, 0x54, 0x4f, 0x50, 0x49, 0x4c, 0x4f, 0x54, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x43, 0x4f, + 0x55, 0x4e, 0x54, 0x10, 0x05, 0x2a, 0x59, 0x0a, 0x0c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, + 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, + 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, + 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, + 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x03, + 0x32, 0xe8, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, + 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, + 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, + 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, + 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/litrpc/lit-sessions.proto b/litrpc/lit-sessions.proto index a3f47db83..dfbd8a8c9 100644 --- a/litrpc/lit-sessions.proto +++ b/litrpc/lit-sessions.proto @@ -199,6 +199,13 @@ message Session { not revoked. Readers should instead first check the session_state field. */ uint64 revoked_at = 16 [jstype = JS_STRING]; + + /* + The ID of the group of Session's that this Session is linked to. If this + session is not linked to any older Session, then this value will be the + same as the ID. + */ + bytes group_id = 17; } message MacaroonRecipe { diff --git a/litrpc/lit-sessions.swagger.json b/litrpc/lit-sessions.swagger.json index b29b9b6b2..106944dcd 100644 --- a/litrpc/lit-sessions.swagger.json +++ b/litrpc/lit-sessions.swagger.json @@ -461,6 +461,11 @@ "type": "string", "format": "uint64", "description": "The unix timestamp indicating the time at which the session was revoked.\nNote that this field has not been around since the beginning and so it\ncould be the case that a session has been revoked but that this field\nwill not have been set for that session. Therefore, it is suggested that\nreaders should not assume that if this field is zero that the session is\nnot revoked. Readers should instead first check the session_state field." + }, + "group_id": { + "type": "string", + "format": "byte", + "description": "The ID of the group of Session's that this Session is linked to. If this\nsession is not linked to any older Session, then this value will be the\nsame as the ID." } } }, diff --git a/proto/lit-autopilot.proto b/proto/lit-autopilot.proto index 174940d4b..169955a20 100644 --- a/proto/lit-autopilot.proto +++ b/proto/lit-autopilot.proto @@ -73,6 +73,11 @@ message AddAutopilotSessionRequest { Set to true of the session should not make use of the privacy mapper. */ bool no_privacy_mapper = 7; + + /* + Set to the ID of the group to link this session to, if any. + */ + bytes linked_group_id = 8; } message FeatureConfig { diff --git a/proto/lit-sessions.proto b/proto/lit-sessions.proto index a3f47db83..dfbd8a8c9 100644 --- a/proto/lit-sessions.proto +++ b/proto/lit-sessions.proto @@ -199,6 +199,13 @@ message Session { not revoked. Readers should instead first check the session_state field. */ uint64 revoked_at = 16 [jstype = JS_STRING]; + + /* + The ID of the group of Session's that this Session is linked to. If this + session is not linked to any older Session, then this value will be the + same as the ID. + */ + bytes group_id = 17; } message MacaroonRecipe { From 477a5115dd66919e0c1bec8a9dc9c4c227ebfe8d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 18 Jun 2023 13:26:39 +0200 Subject: [PATCH 08/23] session_rpcserver: marshal new Session fields Marshal the new Session fields into the proto Session struct. --- session_rpcserver.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/session_rpcserver.go b/session_rpcserver.go index 7f8747c68..74c345c7d 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -1228,7 +1228,10 @@ func (s *sessionRpcServer) marshalRPCSession(sess *session.Session) ( revokedAt = uint64(sess.RevokedAt.Unix()) } - featureInfo := make(map[string]*litrpc.RulesMap) + var ( + featureInfo = make(map[string]*litrpc.RulesMap) + initRuleValues = s.cfg.ruleMgrs.InitRuleValues + ) if sess.MacaroonRecipe != nil { for _, cav := range sess.MacaroonRecipe.Caveats { info, err := firewall.ParseRuleCaveat(string(cav.Id)) @@ -1241,13 +1244,17 @@ func (s *sessionRpcServer) marshalRPCSession(sess *session.Session) ( for feature, rules := range info.FeatureRules { ruleMap := make(map[string]*litrpc.RuleValue) for name, rule := range rules { - val, err := s.cfg.ruleMgrs.InitRuleValues(name, []byte(rule)) + val, err := initRuleValues( + name, []byte(rule), + ) if err != nil { return nil, err } if sess.WithPrivacyMapper { - db := s.cfg.privMap(sess.ID) + db := s.cfg.privMap( + sess.GroupID, + ) val, err = val.PseudoToReal(db) if err != nil { return nil, err @@ -1257,7 +1264,9 @@ func (s *sessionRpcServer) marshalRPCSession(sess *session.Session) ( ruleMap[name] = val.ToProto() } - featureInfo[feature] = &litrpc.RulesMap{Rules: ruleMap} + featureInfo[feature] = &litrpc.RulesMap{ + Rules: ruleMap, + } } } } @@ -1278,6 +1287,7 @@ func (s *sessionRpcServer) marshalRPCSession(sess *session.Session) ( RevokedAt: revokedAt, MacaroonRecipe: macRecipe, AutopilotFeatureInfo: featureInfo, + GroupId: sess.GroupID[:], }, nil } From a802759f4e1a6059b40b93ab9958ff1018c152f7 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 24 Aug 2023 10:46:35 +0200 Subject: [PATCH 09/23] session: add session ID to group ID indexes This commit adds two new indexes to the session store. One from session ID to group ID and one from groupID to a set of session IDs. This commit only adds the new logic and starts writing the the new indexes from the `StoreSession` method. The next commit will add a migration to back-fill the new indexes. --- session/db.go | 5 ++++ session/store.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/session/db.go b/session/db.go index d723e82f6..0aa3417af 100644 --- a/session/db.go +++ b/session/db.go @@ -111,6 +111,11 @@ func initDB(filepath string, firstInit bool) (*bbolt.DB, error) { } _, err = sessionBkt.CreateBucketIfNotExists(idIndexKey) + if err != nil { + return err + } + + _, err = sessionBkt.CreateBucketIfNotExists(groupIDIndexKey) return err }) diff --git a/session/store.go b/session/store.go index e32dfcc21..0a7576d7c 100644 --- a/session/store.go +++ b/session/store.go @@ -18,6 +18,8 @@ var ( // The session bucket has the following structure: // session -> -> // -> id-index -> -> key -> + // -> group -> + // -> group-id-index -> -> session-id -> sequence -> sessionBucketKey = []byte("session") // idIndexKey is the key used to define the id-index sub-bucket within @@ -30,6 +32,20 @@ var ( // session ID. sessionKeyKey = []byte("key") + // groupIDKey is the key used within the id-index bucket to store the + // group ID associated with the given session ID. + groupIDKey = []byte("group") + + // groupIDIndexKey is the key used to define the group-id-index + // sub-bucket within the main session bucket. This bucket will be used + // to store the mapping from group ID to various other fields. + groupIDIndexKey = []byte("group-id-index") + + // sessionIDKey is a key used in the group-id-index under a sub-bucket + // defined by a specific group ID. It will be used to store the session + // IDs associated with the given group ID. + sessionIDKey = []byte("session-id") + // ErrSessionNotFound is an error returned when we attempt to retrieve // information about a session but it is not found. ErrSessionNotFound = errors.New("session not found") @@ -87,6 +103,14 @@ func (db *DB) CreateSession(session *Session) error { return err } + // Add the mapping from session ID to group ID and vice versa. + err = addIDToGroupIDPair( + sessionBucket, session.ID, session.GroupID, + ) + if err != nil { + return err + } + return sessionBucket.Put(sessionKey, buf.Bytes()) }) } @@ -358,3 +382,50 @@ func getKeyForID(sessionBkt *bbolt.Bucket, id ID) ([]byte, error) { return sessionKeyBytes, nil } + +// addIDToGroupIDPair inserts the mapping from session ID to group ID into the +// id-index bucket and also inserts the mapping from group ID to session ID into +// the group-id-index bucket. +func addIDToGroupIDPair(sessionBkt *bbolt.Bucket, id, groupID ID) error { + // First we will add the mapping from session ID to group ID. + idIndexBkt := sessionBkt.Bucket(idIndexKey) + if idIndexBkt == nil { + return ErrDBInitErr + } + + idBkt, err := idIndexBkt.CreateBucketIfNotExists(id[:]) + if err != nil { + return err + } + + err = idBkt.Put(groupIDKey, groupID[:]) + if err != nil { + return err + } + + // Now we add the mapping from group ID to session. + groupIdIndexBkt := sessionBkt.Bucket(groupIDIndexKey) + if groupIdIndexBkt == nil { + return ErrDBInitErr + } + + groupBkt, err := groupIdIndexBkt.CreateBucketIfNotExists(groupID[:]) + if err != nil { + return err + } + + sessionIDsBkt, err := groupBkt.CreateBucketIfNotExists(sessionIDKey) + if err != nil { + return err + } + + nextSeq, err := sessionIDsBkt.NextSequence() + if err != nil { + return err + } + + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], nextSeq) + + return sessionIDsBkt.Put(seqNoBytes[:], id[:]) +} From e90df06d587230c511ea9a85224e34c978b404b5 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 24 Aug 2023 11:20:32 +0200 Subject: [PATCH 10/23] session: migration to populate group ID to session ID indexes This commit adds a migration to the session store to back-fill the new session ID to group ID and group ID to session IDs indexes. --- session/metadata.go | 2 + session/migration2/id_to_group_index.go | 119 ++++++++++++++++++ session/migration2/id_to_group_index_test.go | 122 +++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 session/migration2/id_to_group_index.go create mode 100644 session/migration2/id_to_group_index_test.go diff --git a/session/metadata.go b/session/metadata.go index 24575c689..407fad3b9 100644 --- a/session/metadata.go +++ b/session/metadata.go @@ -6,6 +6,7 @@ import ( "time" "github.com/lightninglabs/lightning-terminal/session/migration1" + "github.com/lightninglabs/lightning-terminal/session/migration2" "go.etcd.io/bbolt" ) @@ -37,6 +38,7 @@ var ( tx, time.Now, ) }, + migration2.MigrateSessionIDToGroupIndex, } latestDBVersion = uint32(len(dbVersions)) diff --git a/session/migration2/id_to_group_index.go b/session/migration2/id_to_group_index.go new file mode 100644 index 000000000..382a352fd --- /dev/null +++ b/session/migration2/id_to_group_index.go @@ -0,0 +1,119 @@ +package migration2 + +import ( + "encoding/binary" + "errors" + "fmt" + + "go.etcd.io/bbolt" +) + +var ( + // sessionBucketKey is the top level bucket where we can find all + // information about sessions. These sessions are indexed by their + // public key. + // + // The session bucket has the following structure: + // session -> -> + // -> id-index -> -> key -> + // -> group -> + // -> group-id-index -> -> session-id -> sequence -> + sessionBucketKey = []byte("session") + + // idIndexKey is the key used to define the id-index sub-bucket within + // the main session bucket. This bucket will be used to store the + // mapping from session ID to various other fields. + idIndexKey = []byte("id-index") + + // sessionKeyKey is the key used within the id-index bucket to store the + // session key (serialised local public key) associated with the given + // session ID. + sessionKeyKey = []byte("key") + + // groupIDKey is the key used within the id-index bucket to store the + // group ID associated with the given session ID. + groupIDKey = []byte("group") + + // groupIDIndexKey is the key used to define the group-id-index + // sub-bucket within the main session bucket. This bucket will be used + // to store the mapping from group ID to various other fields. + groupIDIndexKey = []byte("group-id-index") + + // sessionIDKey is a key used in the group-id-index under a sub-bucket + // defined by a specific group ID. It will be used to store the session + // IDs associated with the given group ID. + sessionIDKey = []byte("session-id") + + // ErrDBInitErr is returned when a bucket that we expect to have been + // set up during DB initialisation is not found. + ErrDBInitErr = errors.New("db did not initialise properly") + + // byteOrder is the default byte order we'll use for serialization + // within the database. + byteOrder = binary.BigEndian +) + +// MigrateSessionIDToGroupIndex back-fills the session ID to group index so that +// it has an entry for all sessions that the session store is currently aware of. +func MigrateSessionIDToGroupIndex(tx *bbolt.Tx) error { + sessionBucket := tx.Bucket(sessionBucketKey) + if sessionBucket == nil { + return fmt.Errorf("session bucket not found") + } + + idIndexBkt := sessionBucket.Bucket(idIndexKey) + if idIndexBkt == nil { + return ErrDBInitErr + } + + groupIndexBkt := sessionBucket.Bucket(groupIDIndexKey) + if groupIndexBkt == nil { + return ErrDBInitErr + } + + // Collect all the index entries. + return idIndexBkt.ForEach(func(sessionID, _ []byte) error { + // This migration is done before the logic in LiT is added that + // would allow groupIDs to differ from session IDs. And so all + // this migration needs to do is add the current 1:1 mapping + // from group ID to session ID and vice versa where group ID is + // equal to the session ID. + groupID := sessionID + + // First we add the session ID to group ID mapping. + sessionIDBkt := idIndexBkt.Bucket(sessionID) + if sessionIDBkt == nil { + return fmt.Errorf("unexpected non-bucket entry in " + + "the id-index bucket") + } + + err := sessionIDBkt.Put(groupIDKey, groupID) + if err != nil { + return err + } + + // Now we will add the group ID to session ID mapping. + groupIDBkt, err := groupIndexBkt.CreateBucketIfNotExists( + groupID, + ) + if err != nil { + return err + } + + groupSessionIDBkt, err := groupIDBkt.CreateBucketIfNotExists( + sessionIDKey, + ) + if err != nil { + return err + } + + nextSeq, err := groupSessionIDBkt.NextSequence() + if err != nil { + return err + } + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], nextSeq) + + return groupSessionIDBkt.Put(seqNoBytes[:], groupID[:]) + }) +} diff --git a/session/migration2/id_to_group_index_test.go b/session/migration2/id_to_group_index_test.go new file mode 100644 index 000000000..c20352164 --- /dev/null +++ b/session/migration2/id_to_group_index_test.go @@ -0,0 +1,122 @@ +package migration2 + +import ( + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightninglabs/lightning-terminal/session/migtest" + "github.com/stretchr/testify/require" + "go.etcd.io/bbolt" +) + +// ID represents the id of a session. +type ID [4]byte + +// TestMigrateSessionIDToGroupIDIndex tests that the +// MigrateSessionIDToGroupIDIndex migration correctly back-fills the session ID +// to group ID index along with the group ID to session ID index. +func TestMigrateSessionIDToGroupIDIndex(t *testing.T) { + t.Parallel() + + // Make a few session IDs. + sess1ID, sess1Key := newSessionID(t) + sess2ID, sess2Key := newSessionID(t) + sess3ID, sess3Key := newSessionID(t) + + // Put together a sample session ID index DB based on the above. + idIndexBefore := map[string]interface{}{ + string(sess1ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess1Key), + }, + string(sess2ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess2Key), + }, + string(sess3ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess3Key), + }, + } + + // sessionDBBefore is what our session DB will look like before the + // migration. + sessionDBBefore := map[string]interface{}{ + string(idIndexKey): idIndexBefore, + string(groupIDIndexKey): map[string]interface{}{}, + } + + before := func(tx *bbolt.Tx) error { + return migtest.RestoreDB(tx, sessionBucketKey, sessionDBBefore) + } + + // Put together what we expect the resulting id-index bucket to look + // like after the migration. + idIndexAfter := map[string]interface{}{ + string(sess1ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess1Key), + string(groupIDKey): string(sess1ID[:]), + }, + string(sess2ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess2Key), + string(groupIDKey): string(sess2ID[:]), + }, + string(sess3ID[:]): map[string]interface{}{ + string(sessionKeyKey): string(sess3Key), + string(groupIDKey): string(sess3ID[:]), + }, + } + + // Put together what we expect the resulting group-ID-index bucket to + // look like after the migration. + groupIDIndexAfter := map[string]interface{}{ + string(sess1ID[:]): map[string]interface{}{ + string(sessionIDKey): map[string]interface{}{ + sequenceString(1): string(sess1ID[:]), + }, + }, + string(sess2ID[:]): map[string]interface{}{ + string(sessionIDKey): map[string]interface{}{ + sequenceString(1): string(sess2ID[:]), + }, + }, + string(sess3ID[:]): map[string]interface{}{ + string(sessionIDKey): map[string]interface{}{ + sequenceString(1): string(sess3ID[:]), + }, + }, + } + + // sessionDBAfter is what our session DB will look like after the + // migration. + sessionDBAfter := map[string]interface{}{ + string(idIndexKey): idIndexAfter, + string(groupIDIndexKey): groupIDIndexAfter, + } + + after := func(tx *bbolt.Tx) error { + return migtest.VerifyDB(tx, sessionBucketKey, sessionDBAfter) + } + + migtest.ApplyMigration( + t, before, after, MigrateSessionIDToGroupIndex, false, + ) +} + +// newSessionID is a helper function that can be used to generate a new session +// ID and key. +func newSessionID(t *testing.T) (ID, []byte) { + privateKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + key := privateKey.PubKey().SerializeCompressed() + + var id ID + copy(id[:], key) + + return id, key +} + +func sequenceString(id uint64) string { + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], id) + + return string(seqNoBytes[:]) +} From cc95a0192306b83b859797b9ca87955f0386e12f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 24 Aug 2023 11:25:12 +0200 Subject: [PATCH 11/23] session: add GetGroupID and GetSessionIDs methods This commit adds new getters: `GetGroupID` and `GetSessionIDs` to the session store which can be used to query the newly added indexes to get the associated group ID for a session ID or the associated set of session IDs for a group ID. --- session/interface.go | 12 ++++++ session/store.go | 85 +++++++++++++++++++++++++++++++++++++++++++ session/store_test.go | 62 +++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/session/interface.go b/session/interface.go index bfb593fe9..9374fb5c9 100644 --- a/session/interface.go +++ b/session/interface.go @@ -128,6 +128,16 @@ func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type, return sess, nil } +// IDToGroupIndex defines an interface for the session ID to group ID index. +type IDToGroupIndex interface { + // GetGroupID will return the group ID for the given session ID. + GetGroupID(sessionID ID) (ID, error) + + // GetSessionIDs will return the set of session IDs that are in the + // group with the given ID. + GetSessionIDs(groupID ID) ([]ID, error) +} + // Store is the interface a persistent storage must implement for storing and // retrieving Terminal Connect sessions. type Store interface { @@ -160,4 +170,6 @@ type Store interface { // GetSessionByID fetches the session with the given ID. GetSessionByID(id ID) (*Session, error) + + IDToGroupIndex } diff --git a/session/store.go b/session/store.go index 0a7576d7c..7a49572b8 100644 --- a/session/store.go +++ b/session/store.go @@ -341,6 +341,91 @@ func (db *DB) GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error) { return id, privKey, nil } +// GetGroupID will return the group ID for the given session ID. +// +// NOTE: this is part of the IDToGroupIndex interface. +func (db *DB) GetGroupID(sessionID ID) (ID, error) { + var groupID ID + err := db.View(func(tx *bbolt.Tx) error { + sessionBkt, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + idIndex := sessionBkt.Bucket(idIndexKey) + if idIndex == nil { + return ErrDBInitErr + } + + sessionIDBkt := idIndex.Bucket(sessionID[:]) + if sessionIDBkt == nil { + return fmt.Errorf("no index entry for session ID: %x", + sessionID) + } + + groupIDBytes := sessionIDBkt.Get(groupIDKey) + if len(groupIDBytes) == 0 { + return fmt.Errorf("group ID not found for session "+ + "ID %x", sessionID) + } + + copy(groupID[:], groupIDBytes) + + return nil + }) + if err != nil { + return groupID, err + } + + return groupID, nil +} + +// GetSessionIDs will return the set of session IDs that are in the +// group with the given ID. +// +// NOTE: this is part of the IDToGroupIndex interface. +func (db *DB) GetSessionIDs(groupID ID) ([]ID, error) { + var sessionIDs []ID + err := db.View(func(tx *bbolt.Tx) error { + sessionBkt, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + groupIndexBkt := sessionBkt.Bucket(groupIDIndexKey) + if groupIndexBkt == nil { + return ErrDBInitErr + } + + groupIDBkt := groupIndexBkt.Bucket(groupID[:]) + if groupIDBkt == nil { + return fmt.Errorf("no sessions for group ID %v", + groupID) + } + + sessionIDsBkt := groupIDBkt.Bucket(sessionIDKey) + if sessionIDsBkt == nil { + return fmt.Errorf("no sessions for group ID %v", + groupID) + } + + return sessionIDsBkt.ForEach(func(_, + sessionIDBytes []byte) error { + + var sessionID ID + copy(sessionID[:], sessionIDBytes) + sessionIDs = append(sessionIDs, sessionID) + + return nil + }) + }) + if err != nil { + return nil, err + } + + return sessionIDs, nil +} + // addIdToKeyPair inserts the mapping from session ID to session key into the // id-index bucket. An error is returned if an entry for this ID already exists. func addIDToKeyPair(sessionBkt *bbolt.Bucket, id ID, sessionKey []byte) error { diff --git a/session/store_test.go b/session/store_test.go index 01ce8b5c3..164709deb 100644 --- a/session/store_test.go +++ b/session/store_test.go @@ -110,6 +110,68 @@ func TestLinkingSessions(t *testing.T) { require.NoError(t, db.CreateSession(s2)) } +// TestIDToGroupIDIndex tests that the session-ID-to-group-ID and +// group-ID-to-session-ID indexes work as expected by asserting the behaviour +// of the GetGroupID and GetSessionIDs methods. +func TestLinkedSessions(t *testing.T) { + // Set up a new DB. + db, err := NewDB(t.TempDir(), "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + // Create a few sessions. The first one is a new session and the two + // after are all linked to the prior one. All these sessions belong to + // the same group. The group ID is equivalent to the session ID of the + // first session. + s1 := newSession(t, db, "session 1", nil) + s2 := newSession(t, db, "session 2", &s1.GroupID) + s3 := newSession(t, db, "session 3", &s2.GroupID) + + // Persist the sessions. + require.NoError(t, db.CreateSession(s1)) + require.NoError(t, db.CreateSession(s2)) + require.NoError(t, db.CreateSession(s3)) + + // Assert that the session ID to group ID index works as expected. + for _, s := range []*Session{s1, s2, s3} { + groupID, err := db.GetGroupID(s.ID) + require.NoError(t, err) + require.Equal(t, s1.ID, groupID) + require.Equal(t, s.GroupID, groupID) + } + + // Assert that the group ID to session ID index works as expected. + sIDs, err := db.GetSessionIDs(s1.GroupID) + require.NoError(t, err) + require.EqualValues(t, []ID{s1.ID, s2.ID, s3.ID}, sIDs) + + // To ensure that different groups don't interfere with each other, + // let's add another set of linked sessions not linked to the first. + s4 := newSession(t, db, "session 4", nil) + s5 := newSession(t, db, "session 5", &s4.GroupID) + + require.NotEqual(t, s4.GroupID, s1.GroupID) + + // Persist the sessions. + require.NoError(t, db.CreateSession(s4)) + require.NoError(t, db.CreateSession(s5)) + + // Assert that the session ID to group ID index works as expected. + for _, s := range []*Session{s4, s5} { + groupID, err := db.GetGroupID(s.ID) + require.NoError(t, err) + require.Equal(t, s4.ID, groupID) + require.Equal(t, s.GroupID, groupID) + } + + // Assert that the group ID to session ID index works as expected. + sIDs, err = db.GetSessionIDs(s5.GroupID) + require.NoError(t, err) + require.EqualValues(t, []ID{s4.ID, s5.ID}, sIDs) +} + func newSession(t *testing.T, db Store, label string, linkedGroupID *ID) *Session { From 7c7e467d03a5a3ca356b3f17bc891b26bcf39357 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 15:05:31 +0200 Subject: [PATCH 12/23] firewalldb: update comments and var names Change all comments and variable names mentioning "session ID" to "group ID" instead. As of this commit, session linking is not yet possible and so what ever session IDs are passed in are also group IDs. --- firewalldb/kvstores.go | 41 ++++++++++++++++++------------------ firewalldb/kvstores_test.go | 32 +++++++++++++++++----------- firewalldb/privacy_mapper.go | 24 ++++++++++----------- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/firewalldb/kvstores.go b/firewalldb/kvstores.go index caf44e9b7..76e4b9d88 100644 --- a/firewalldb/kvstores.go +++ b/firewalldb/kvstores.go @@ -13,11 +13,11 @@ the `perm` and `temp` buckets are identical in structure. The only difference is that the `temp` bucket is cleared on restart of the db. rules -> perm -> rule-name -> global -> {k:v} - -> sessions -> sessionID -> session-kv-store -> {k:v} + -> sessions -> group ID -> session-kv-store -> {k:v} -> feature-kv-stores -> feature-name -> {k:v} -> temp -> rule-name -> global -> {k:v} - -> sessions -> sessionID -> session-kv-store -> {k:v} + -> sessions -> group ID -> session-kv-store -> {k:v} -> feature-kv-stores -> feature-name -> {k:v} */ @@ -44,7 +44,7 @@ var ( sessKVStoreBucketKey = []byte("session-kv-store") // featureKVStoreBucketKey is the kye under which a kv store specific - // the session id and feature name is stored. + // the group id and feature name is stored. featureKVStoreBucketKey = []byte("feature-kv-store") ) @@ -72,12 +72,12 @@ type KVStores interface { type KVStoreTx interface { // Global returns a persisted global, rule-name indexed, kv store. A // rule with a given name will have access to this store independent of - // session ID or feature. + // group ID or feature. Global() KVStore // Local returns a persisted local kv store for the rule. Depending on // how the implementation is initialised, this will either be under the - // session ID namespace or the session ID _and_ feature name namespace. + // group ID namespace or the group ID _and_ feature name namespace. Local() KVStore // GlobalTemp is similar to the Global store except that its contents @@ -105,17 +105,17 @@ type KVStore interface { // RulesDB can be used to initialise a new rules.KVStores. type RulesDB interface { - GetKVStores(rule string, sessionID session.ID, feature string) KVStores + GetKVStores(rule string, groupID session.ID, feature string) KVStores } // GetKVStores constructs a new rules.KVStores backed by a bbolt db. -func (db *DB) GetKVStores(rule string, sessionID session.ID, +func (db *DB) GetKVStores(rule string, groupID session.ID, feature string) KVStores { return &kvStores{ DB: db, ruleName: rule, - sessionID: sessionID, + groupID: groupID, featureName: feature, } } @@ -124,7 +124,7 @@ func (db *DB) GetKVStores(rule string, sessionID session.ID, type kvStores struct { *DB ruleName string - sessionID session.ID + groupID session.ID featureName string } @@ -237,10 +237,10 @@ func (tx *kvStoreTx) Global() KVStore { // // NOTE: this is part of the KVStoreTx interface. func (tx *kvStoreTx) Local() KVStore { - fn := getSessionRuleBucket(true, tx.ruleName, tx.sessionID) + fn := getSessionRuleBucket(true, tx.ruleName, tx.groupID) if tx.featureName != "" { fn = getSessionFeatureRuleBucket( - true, tx.ruleName, tx.sessionID, tx.featureName, + true, tx.ruleName, tx.groupID, tx.featureName, ) } @@ -268,10 +268,10 @@ func (tx *kvStoreTx) GlobalTemp() KVStore { // // NOTE: this is part of the KVStoreTx interface. func (tx *kvStoreTx) LocalTemp() KVStore { - fn := getSessionRuleBucket(true, tx.ruleName, tx.sessionID) + fn := getSessionRuleBucket(true, tx.ruleName, tx.groupID) if tx.featureName != "" { fn = getSessionFeatureRuleBucket( - false, tx.ruleName, tx.sessionID, tx.featureName, + false, tx.ruleName, tx.groupID, tx.featureName, ) } @@ -390,11 +390,11 @@ func getGlobalRuleBucket(perm bool, ruleName string) getBucketFunc { } // getSessionRuleBucket returns a function that can be used to fetch the -// bucket under which a kv store for a specific rule-name and session ID is +// bucket under which a kv store for a specific rule-name and group ID is // stored. The `perm` param determines if the temporary or permanent store is // used. func getSessionRuleBucket(perm bool, ruleName string, - sessionID session.ID) getBucketFunc { + groupID session.ID) getBucketFunc { return func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error) { ruleBucket, err := getRuleBucket(perm, ruleName)(tx, create) @@ -414,27 +414,28 @@ func getSessionRuleBucket(perm bool, ruleName string, return nil, err } - return sessBucket.CreateBucketIfNotExists(sessionID[:]) + return sessBucket.CreateBucketIfNotExists(groupID[:]) } sessBucket := ruleBucket.Bucket(sessKVStoreBucketKey) if sessBucket == nil { return nil, nil } - return sessBucket.Bucket(sessionID[:]), nil + return sessBucket.Bucket(groupID[:]), nil } } // getSessionFeatureRuleBucket returns a function that can be used to fetch the -// bucket under which a kv store for a specific rule-name, session ID and +// bucket under which a kv store for a specific rule-name, group ID and // feature name is stored. The `perm` param determines if the temporary or // permanent store is used. func getSessionFeatureRuleBucket(perm bool, ruleName string, - sessionID session.ID, featureName string) getBucketFunc { + groupID session.ID, featureName string) getBucketFunc { return func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error) { sessBucket, err := getSessionRuleBucket( - perm, ruleName, sessionID)(tx, create) + perm, ruleName, groupID, + )(tx, create) if err != nil { return nil, err } diff --git a/firewalldb/kvstores_test.go b/firewalldb/kvstores_test.go index dc5076439..21b3ab17f 100644 --- a/firewalldb/kvstores_test.go +++ b/firewalldb/kvstores_test.go @@ -7,6 +7,7 @@ import ( "os" "testing" + "github.com/lightninglabs/lightning-terminal/session" "github.com/stretchr/testify/require" ) @@ -153,17 +154,17 @@ func TestKVStoreNameSpaces(t *testing.T) { }) var ( - sessionID1 = [4]byte{1, 1, 1, 1} - sessionID2 = [4]byte{2, 2, 2, 2} + groupID1 = intToSessionID(1) + groupID2 = intToSessionID(2) ) - // Two DBs for same session but different features. - rulesDB1 := db.GetKVStores("test-rule", sessionID1, "auto-fees") - rulesDB2 := db.GetKVStores("test-rule", sessionID1, "re-balance") + // Two DBs for same group but different features. + rulesDB1 := db.GetKVStores("test-rule", groupID1, "auto-fees") + rulesDB2 := db.GetKVStores("test-rule", groupID1, "re-balance") - // The third DB is for the same rule but a different session. It is + // The third DB is for the same rule but a different group. It is // for the same feature as db 2. - rulesDB3 := db.GetKVStores("test-rule", sessionID2, "re-balance") + rulesDB3 := db.GetKVStores("test-rule", groupID2, "re-balance") // Test that the three ruleDBs share the same global space. err = rulesDB1.Update(func(tx KVStoreTx) error { @@ -270,12 +271,12 @@ func TestKVStoreNameSpaces(t *testing.T) { require.NoError(t, err) require.True(t, bytes.Equal(v, []byte("3"))) - // Test that the session space is shared by the first two dbs but not + // Test that the group space is shared by the first two dbs but not // the third. To do this, we re-init the DB's but leave the feature - // names out. This way, we will access the session storage. - rulesDB1 = db.GetKVStores("test-rule", sessionID1, "") - rulesDB2 = db.GetKVStores("test-rule", sessionID1, "") - rulesDB3 = db.GetKVStores("test-rule", sessionID2, "") + // names out. This way, we will access the group storage. + rulesDB1 = db.GetKVStores("test-rule", groupID1, "") + rulesDB2 = db.GetKVStores("test-rule", groupID1, "") + rulesDB3 = db.GetKVStores("test-rule", groupID2, "") err = rulesDB1.Update(func(tx KVStoreTx) error { return tx.Local().Set(ctx, "test", []byte("thing 1")) @@ -325,3 +326,10 @@ func TestKVStoreNameSpaces(t *testing.T) { require.NoError(t, err) require.True(t, bytes.Equal(v, []byte("thing 3"))) } + +func intToSessionID(i uint32) session.ID { + var id session.ID + byteOrder.PutUint32(id[:], i) + + return id +} diff --git a/firewalldb/privacy_mapper.go b/firewalldb/privacy_mapper.go index 7d96060fc..12a7541c6 100644 --- a/firewalldb/privacy_mapper.go +++ b/firewalldb/privacy_mapper.go @@ -16,8 +16,8 @@ import ( /* The PrivacyMapper data is stored in the following structure in the db: - privacy -> session id -> real-to-pseudo -> {k:v} - -> pseudo-to-real -> {k:v} + privacy -> group id -> real-to-pseudo -> {k:v} + -> pseudo-to-real -> {k:v} */ const ( @@ -33,16 +33,16 @@ var ( pseudoStrAlphabetLen = len(pseudoStrAlphabet) ) -// NewPrivacyMapDB is a function type that takes a session ID and uses it to +// NewPrivacyMapDB is a function type that takes a group ID and uses it to // construct a new PrivacyMapDB. -type NewPrivacyMapDB func(sessionID session.ID) PrivacyMapDB +type NewPrivacyMapDB func(groupID session.ID) PrivacyMapDB // PrivacyDB constructs a PrivacyMapDB that will be indexed under the given -// sessionID key. -func (db *DB) PrivacyDB(sessionID session.ID) PrivacyMapDB { +// group ID key. +func (db *DB) PrivacyDB(groupID session.ID) PrivacyMapDB { return &privacyMapDB{ - DB: db, - sessionID: sessionID, + DB: db, + groupID: groupID, } } @@ -83,7 +83,7 @@ type PrivacyMapTx interface { // privacyMapDB is an implementation of PrivacyMapDB. type privacyMapDB struct { *DB - sessionID session.ID + groupID session.ID } // beginTx starts db transaction. The transaction will be a read or read-write @@ -175,7 +175,7 @@ func (p *privacyMapTx) NewPair(real, pseudo string) error { return err } - sessBucket, err := privacyBucket.CreateBucketIfNotExists(p.sessionID[:]) + sessBucket, err := privacyBucket.CreateBucketIfNotExists(p.groupID[:]) if err != nil { return err } @@ -210,7 +210,7 @@ func (p *privacyMapTx) PseudoToReal(pseudo string) (string, error) { return "", err } - sessBucket := privacyBucket.Bucket(p.sessionID[:]) + sessBucket := privacyBucket.Bucket(p.groupID[:]) if sessBucket == nil { return "", ErrNoSuchKeyFound } @@ -236,7 +236,7 @@ func (p *privacyMapTx) RealToPseudo(real string) (string, error) { return "", err } - sessBucket := privacyBucket.Bucket(p.sessionID[:]) + sessBucket := privacyBucket.Bucket(p.groupID[:]) if sessBucket == nil { return "", ErrNoSuchKeyFound } From 73110b6472000fee098af2215da78b50901c8a68 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 24 Aug 2023 11:30:20 +0200 Subject: [PATCH 13/23] multi: give firewallDB access to session ID index --- firewalldb/actions_test.go | 4 ++-- firewalldb/db.go | 12 ++++++++-- firewalldb/kvstores_test.go | 8 +++---- firewalldb/privacy_mapper_test.go | 4 ++-- session_rpcserver.go | 40 +++++++++++++------------------ terminal.go | 13 ++++++++-- 6 files changed, 45 insertions(+), 36 deletions(-) diff --git a/firewalldb/actions_test.go b/firewalldb/actions_test.go index e0502903d..53fdc4735 100644 --- a/firewalldb/actions_test.go +++ b/firewalldb/actions_test.go @@ -12,7 +12,7 @@ import ( func TestActionStorage(t *testing.T) { tmpDir := t.TempDir() - db, err := NewDB(tmpDir, "test.db") + db, err := NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() @@ -147,7 +147,7 @@ func TestActionStorage(t *testing.T) { func TestListActions(t *testing.T) { tmpDir := t.TempDir() - db, err := NewDB(tmpDir, "test.db") + db, err := NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() diff --git a/firewalldb/db.go b/firewalldb/db.go index 7c862d5a4..a79fcb478 100644 --- a/firewalldb/db.go +++ b/firewalldb/db.go @@ -8,6 +8,7 @@ import ( "path/filepath" "time" + "github.com/lightninglabs/lightning-terminal/session" "go.etcd.io/bbolt" ) @@ -40,10 +41,14 @@ var ( // DB is a bolt-backed persistent store. type DB struct { *bbolt.DB + + sessionIDIndex session.IDToGroupIndex } // NewDB creates a new bolt database that can be found at the given directory. -func NewDB(dir, fileName string) (*DB, error) { +func NewDB(dir, fileName string, sessionIDIndex session.IDToGroupIndex) (*DB, + error) { + firstInit := false path := filepath.Join(dir, fileName) @@ -66,7 +71,10 @@ func NewDB(dir, fileName string) (*DB, error) { return nil, err } - return &DB{DB: db}, nil + return &DB{ + DB: db, + sessionIDIndex: sessionIDIndex, + }, nil } // fileExists reports whether the named file or directory exists. diff --git a/firewalldb/kvstores_test.go b/firewalldb/kvstores_test.go index 21b3ab17f..607dc233a 100644 --- a/firewalldb/kvstores_test.go +++ b/firewalldb/kvstores_test.go @@ -18,7 +18,7 @@ func TestKVStoreTxs(t *testing.T) { ctx := context.Background() tmpDir := t.TempDir() - db, err := NewDB(tmpDir, "test.db") + db, err := NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() @@ -65,7 +65,7 @@ func TestTempAndPermStores(t *testing.T) { ctx := context.Background() tmpDir := t.TempDir() - db, err := NewDB(tmpDir, "test.db") + db, err := NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() @@ -113,7 +113,7 @@ func TestTempAndPermStores(t *testing.T) { require.NoError(t, db.Close()) // Restart it. - db, err = NewDB(tmpDir, "test.db") + db, err = NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() @@ -147,7 +147,7 @@ func TestKVStoreNameSpaces(t *testing.T) { ctx := context.Background() tmpDir := t.TempDir() - db, err := NewDB(tmpDir, "test.db") + db, err := NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() diff --git a/firewalldb/privacy_mapper_test.go b/firewalldb/privacy_mapper_test.go index 827d42c42..0a39a40b1 100644 --- a/firewalldb/privacy_mapper_test.go +++ b/firewalldb/privacy_mapper_test.go @@ -10,7 +10,7 @@ import ( // TestPrivacyMapStorage tests the privacy mapper CRUD logic. func TestPrivacyMapStorage(t *testing.T) { tmpDir := t.TempDir() - db, err := NewDB(tmpDir, "test.db") + db, err := NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() @@ -68,7 +68,7 @@ func TestPrivacyMapStorage(t *testing.T) { // `Update` function, then all the changes prior should be rolled back. func TestPrivacyMapTxs(t *testing.T) { tmpDir := t.TempDir() - db, err := NewDB(tmpDir, "test.db") + db, err := NewDB(tmpDir, "test.db", nil) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() diff --git a/session_rpcserver.go b/session_rpcserver.go index 74c345c7d..e6987cfc4 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -41,7 +41,6 @@ type sessionRpcServer struct { litrpc.UnimplementedAutopilotServer cfg *sessionRpcServerConfig - db *session.DB sessionServer *session.Server // sessRegMu is a mutex that should be held between acquiring an unused @@ -57,8 +56,8 @@ type sessionRpcServer struct { // sessionRpcServerConfig holds the values used to configure the // sessionRpcServer. type sessionRpcServerConfig struct { + db *session.DB basicAuth string - dbDir string grpcOptions []grpc.ServerOption registerGrpcServers func(server *grpc.Server) superMacBaker session.MacaroonBaker @@ -74,12 +73,6 @@ type sessionRpcServerConfig struct { func newSessionRPCServer(cfg *sessionRpcServerConfig) (*sessionRpcServer, error) { - // Create an instance of the local Terminal Connect session store DB. - db, err := session.NewDB(cfg.dbDir, session.DBFilename) - if err != nil { - return nil, fmt.Errorf("error creating session DB: %v", err) - } - // Create the gRPC server that handles adding/removing sessions and the // actual mailbox server that spins up the Terminal Connect server // interface. @@ -96,7 +89,6 @@ func newSessionRPCServer(cfg *sessionRpcServerConfig) (*sessionRpcServer, return &sessionRpcServer{ cfg: cfg, - db: db, sessionServer: server, quit: make(chan struct{}), }, nil @@ -106,7 +98,7 @@ func newSessionRPCServer(cfg *sessionRpcServerConfig) (*sessionRpcServer, // requests. This includes resuming all non-revoked sessions. func (s *sessionRpcServer) start() error { // Start up all previously created sessions. - sessions, err := s.db.ListSessions(nil) + sessions, err := s.cfg.db.ListSessions(nil) if err != nil { return fmt.Errorf("error listing sessions: %v", err) } @@ -157,7 +149,7 @@ func (s *sessionRpcServer) start() error { err) if perm { - err := s.db.RevokeSession( + err := s.cfg.db.RevokeSession( sess.LocalPublicKey, ) if err != nil { @@ -182,7 +174,7 @@ func (s *sessionRpcServer) start() error { func (s *sessionRpcServer) stop() error { var returnErr error s.stopOnce.Do(func() { - if err := s.db.Close(); err != nil { + if err := s.cfg.db.Close(); err != nil { log.Errorf("Error closing session DB: %v", err) returnErr = err } @@ -323,7 +315,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context, s.sessRegMu.Lock() defer s.sessRegMu.Unlock() - id, localPrivKey, err := s.db.GetUnusedIDAndKeyPair() + id, localPrivKey, err := s.cfg.db.GetUnusedIDAndKeyPair() if err != nil { return nil, err } @@ -336,7 +328,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context, return nil, fmt.Errorf("error creating new session: %v", err) } - if err := s.db.CreateSession(sess); err != nil { + if err := s.cfg.db.CreateSession(sess); err != nil { return nil, fmt.Errorf("error storing session: %v", err) } @@ -375,7 +367,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { log.Debugf("Not resuming session %x with expiry %s", pubKeyBytes, sess.Expiry) - if err := s.db.RevokeSession(pubKey); err != nil { + if err := s.cfg.db.RevokeSession(pubKey); err != nil { return fmt.Errorf("error revoking session: %v", err) } @@ -455,7 +447,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { log.Debugf("Deadline for session %x has already "+ "passed. Revoking session", pubKeyBytes) - return s.db.RevokeSession(pubKey) + return s.cfg.db.RevokeSession(pubKey) } // Start the deadline timer. @@ -490,7 +482,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { authData := []byte(fmt.Sprintf("%s: %s", HeaderMacaroon, mac)) sessionClosedSub, err := s.sessionServer.StartSession( - sess, authData, s.db.UpdateSessionRemotePubKey, onNewStatus, + sess, authData, s.cfg.db.UpdateSessionRemotePubKey, onNewStatus, ) if err != nil { return err @@ -535,7 +527,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { log.Debugf("Error stopping session: %v", err) } - err = s.db.RevokeSession(pubKey) + err = s.cfg.db.RevokeSession(pubKey) if err != nil { log.Debugf("error revoking session: %v", err) } @@ -548,7 +540,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { func (s *sessionRpcServer) ListSessions(_ context.Context, _ *litrpc.ListSessionsRequest) (*litrpc.ListSessionsResponse, error) { - sessions, err := s.db.ListSessions(nil) + sessions, err := s.cfg.db.ListSessions(nil) if err != nil { return nil, fmt.Errorf("error fetching sessions: %v", err) } @@ -577,7 +569,7 @@ func (s *sessionRpcServer) RevokeSession(ctx context.Context, return nil, fmt.Errorf("error parsing public key: %v", err) } - if err := s.db.RevokeSession(pubKey); err != nil { + if err := s.cfg.db.RevokeSession(pubKey); err != nil { return nil, fmt.Errorf("error revoking session: %v", err) } @@ -995,7 +987,7 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, s.sessRegMu.Lock() defer s.sessRegMu.Unlock() - id, localPrivKey, err := s.db.GetUnusedIDAndKeyPair() + id, localPrivKey, err := s.cfg.db.GetUnusedIDAndKeyPair() if err != nil { return nil, err } @@ -1037,7 +1029,7 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, // We only persist this session if we successfully retrieved the // autopilot's static key. sess.RemotePublicKey = remoteKey - if err := s.db.CreateSession(sess); err != nil { + if err := s.cfg.db.CreateSession(sess); err != nil { return nil, fmt.Errorf("error storing session: %v", err) } @@ -1061,7 +1053,7 @@ func (s *sessionRpcServer) ListAutopilotSessions(_ context.Context, _ *litrpc.ListAutopilotSessionsRequest) ( *litrpc.ListAutopilotSessionsResponse, error) { - sessions, err := s.db.ListSessions(func(s *session.Session) bool { + sessions, err := s.cfg.db.ListSessions(func(s *session.Session) bool { return s.Type == session.TypeAutopilot }) if err != nil { @@ -1092,7 +1084,7 @@ func (s *sessionRpcServer) RevokeAutopilotSession(ctx context.Context, return nil, fmt.Errorf("error parsing public key: %v", err) } - sess, err := s.db.GetSession(pubKey) + sess, err := s.cfg.db.GetSession(pubKey) if err != nil { return nil, err } diff --git a/terminal.go b/terminal.go index cea739f4c..d92a134be 100644 --- a/terminal.go +++ b/terminal.go @@ -184,6 +184,7 @@ type LightningTerminal struct { accountRpcServer *accounts.RPCServer firewallDB *firewalldb.DB + sessionDB *session.DB restHandler http.Handler restCancel func() @@ -317,12 +318,20 @@ func (g *LightningTerminal) start() error { g.ruleMgrs = rules.NewRuleManagerSet() + // Create an instance of the local Terminal Connect session store DB. networkDir := filepath.Join(g.cfg.LitDir, g.cfg.Network) - g.firewallDB, err = firewalldb.NewDB(networkDir, firewalldb.DBFilename) + g.sessionDB, err = session.NewDB(networkDir, session.DBFilename) if err != nil { return fmt.Errorf("error creating session DB: %v", err) } + g.firewallDB, err = firewalldb.NewDB( + networkDir, firewalldb.DBFilename, g.sessionDB, + ) + if err != nil { + return fmt.Errorf("error creating firewall DB: %v", err) + } + if !g.cfg.Autopilot.Disable { if g.cfg.Autopilot.Address == "" && len(g.cfg.Autopilot.DialOpts) == 0 { @@ -353,8 +362,8 @@ func (g *LightningTerminal) start() error { } g.sessionRpcServer, err = newSessionRPCServer(&sessionRpcServerConfig{ + db: g.sessionDB, basicAuth: g.rpcProxy.basicAuth, - dbDir: filepath.Join(g.cfg.LitDir, g.cfg.Network), grpcOptions: []grpc.ServerOption{ grpc.CustomCodec(grpcProxy.Codec()), // nolint: staticcheck, grpc.ChainStreamInterceptor( From c12e643a43d2bd7287f6f3e9cadec0dba4085d5f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 15:31:21 +0200 Subject: [PATCH 14/23] firewalldb: add ListGroupActions method This method can be used to list actions in a session Group. Note that actions are still stored under session IDs. --- firewalldb/actions.go | 74 +++++++++++++++++++++++ firewalldb/actions_test.go | 118 +++++++++++++++++++++++++++++++++---- 2 files changed, 179 insertions(+), 13 deletions(-) diff --git a/firewalldb/actions.go b/firewalldb/actions.go index 0e491af86..e6ba0767e 100644 --- a/firewalldb/actions.go +++ b/firewalldb/actions.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/binary" + "errors" "fmt" "io" "time" @@ -386,6 +387,79 @@ func (db *DB) ListSessionActions(sessionID session.ID, return actions, lastIndex, totalCount, nil } +// ListGroupActions returns a list of the given session group's Actions that +// pass the filterFn requirements. +// +// TODO: update to allow for pagination. +func (db *DB) ListGroupActions(groupID session.ID, + filterFn ListActionsFilterFn) ([]*Action, error) { + + if filterFn == nil { + filterFn = func(a *Action, reversed bool) (bool, bool) { + return true, true + } + } + + sessionIDs, err := db.sessionIDIndex.GetSessionIDs(groupID) + if err != nil { + return nil, err + } + + var ( + actions []*Action + errDone = errors.New("done iterating") + ) + err = db.View(func(tx *bbolt.Tx) error { + mainActionsBucket, err := getBucket(tx, actionsBucketKey) + if err != nil { + return err + } + + actionsBucket := mainActionsBucket.Bucket(actionsKey) + if actionsBucket == nil { + return ErrNoSuchKeyFound + } + + // Iterate over each session ID in this group. + for _, sessionID := range sessionIDs { + sessionsBucket := actionsBucket.Bucket(sessionID[:]) + if sessionsBucket == nil { + return nil + } + + err = sessionsBucket.ForEach(func(_, v []byte) error { + action, err := DeserializeAction( + bytes.NewReader(v), sessionID, + ) + if err != nil { + return err + } + + include, cont := filterFn(action, false) + if include { + actions = append(actions, action) + } + + if !cont { + return errDone + } + + return nil + }) + if err != nil { + return err + } + } + + return nil + }) + if err != nil && !errors.Is(err, errDone) { + return nil, err + } + + return actions, nil +} + // SerializeAction binary serializes the given action to the writer using the // tlv format. func SerializeAction(w io.Writer, action *Action) error { diff --git a/firewalldb/actions_test.go b/firewalldb/actions_test.go index 53fdc4735..5a9c4f41f 100644 --- a/firewalldb/actions_test.go +++ b/firewalldb/actions_test.go @@ -5,21 +5,15 @@ import ( "testing" "time" + "github.com/lightninglabs/lightning-terminal/session" "github.com/stretchr/testify/require" ) -// TestActionStorage tests that the ActionsDB CRUD logic. -func TestActionStorage(t *testing.T) { - tmpDir := t.TempDir() +var ( + sessionID1 = intToSessionID(1) + sessionID2 = intToSessionID(2) - db, err := NewDB(tmpDir, "test.db", nil) - require.NoError(t, err) - t.Cleanup(func() { - _ = db.Close() - }) - - sessionID1 := [4]byte{1, 1, 1, 1} - action1 := &Action{ + action1 = &Action{ SessionID: sessionID1, ActorName: "Autopilot", FeatureName: "auto-fees", @@ -32,8 +26,7 @@ func TestActionStorage(t *testing.T) { State: ActionStateDone, } - sessionID2 := [4]byte{2, 2, 2, 2} - action2 := &Action{ + action2 = &Action{ SessionID: sessionID2, ActorName: "Autopilot", FeatureName: "rebalancer", @@ -44,6 +37,17 @@ func TestActionStorage(t *testing.T) { AttemptedAt: time.Unix(12300, 0), State: ActionStateInit, } +) + +// TestActionStorage tests that the ActionsDB CRUD logic. +func TestActionStorage(t *testing.T) { + tmpDir := t.TempDir() + + db, err := NewDB(tmpDir, "test.db", nil) + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) actionsStateFilterFn := func(state ActionState) ListActionsFilterFn { return func(a *Action, _ bool) (bool, bool) { @@ -335,3 +339,91 @@ func TestListActions(t *testing.T) { {sessionID2, "6"}, }) } + +// TestListGroupActions tests that the ListGroupActions correctly returns all +// actions in a particular session group. +func TestListGroupActions(t *testing.T) { + group1 := intToSessionID(0) + + // Link session 1 and session 2 to group 1. + index := newMockSessionIDIndex() + index.addPair(sessionID1, group1) + index.addPair(sessionID2, group1) + + db, err := NewDB(t.TempDir(), "test.db", index) + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + // There should not be any actions in group 1 yet. + al, err := db.ListGroupActions(group1, nil) + require.NoError(t, err) + require.Empty(t, al) + + // Add an action under session 1. + _, err = db.AddAction(sessionID1, action1) + require.NoError(t, err) + + // There should now be one action in the group. + al, err = db.ListGroupActions(group1, nil) + require.NoError(t, err) + require.Len(t, al, 1) + require.Equal(t, sessionID1, al[0].SessionID) + + // Add an action under session 2. + _, err = db.AddAction(sessionID2, action2) + require.NoError(t, err) + + // There should now be actions in the group. + al, err = db.ListGroupActions(group1, nil) + require.NoError(t, err) + require.Len(t, al, 2) + require.Equal(t, sessionID1, al[0].SessionID) + require.Equal(t, sessionID2, al[1].SessionID) +} + +type mockSessionIDIndex struct { + sessionToGroupID map[session.ID]session.ID + groupToSessionIDs map[session.ID][]session.ID +} + +var _ session.IDToGroupIndex = (*mockSessionIDIndex)(nil) + +func newMockSessionIDIndex() *mockSessionIDIndex { + return &mockSessionIDIndex{ + sessionToGroupID: make(map[session.ID]session.ID), + groupToSessionIDs: make(map[session.ID][]session.ID), + } +} + +func (m *mockSessionIDIndex) addPair(sessionID, groupID session.ID) { + m.sessionToGroupID[sessionID] = groupID + + m.groupToSessionIDs[groupID] = append( + m.groupToSessionIDs[groupID], sessionID, + ) +} + +func (m *mockSessionIDIndex) GetGroupID(sessionID session.ID) (session.ID, + error) { + + id, ok := m.sessionToGroupID[sessionID] + if !ok { + return session.ID{}, fmt.Errorf("no group ID found for " + + "session ID") + } + + return id, nil +} + +func (m *mockSessionIDIndex) GetSessionIDs(groupID session.ID) ([]session.ID, + error) { + + ids, ok := m.groupToSessionIDs[groupID] + if !ok { + return nil, fmt.Errorf("no session IDs found for group ID") + } + + return ids, nil +} From 921874608c2e69871b9f5bc51dd6a262f7fcd963 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 15:51:06 +0200 Subject: [PATCH 15/23] firewall: update ActionsReadDB to get group actions --- firewall/rule_enforcer.go | 4 +-- firewalldb/actions.go | 72 +++++++++++++++++++-------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/firewall/rule_enforcer.go b/firewall/rule_enforcer.go index 8b2c03060..c51d2f9fa 100644 --- a/firewall/rule_enforcer.go +++ b/firewall/rule_enforcer.go @@ -363,11 +363,11 @@ func (r *RuleEnforcer) initRule(reqID uint64, name string, value []byte, } allActionsDB := r.actionsDB.GetActionsReadDB(sessionID, featureName) - actionsDB := allActionsDB.FeatureActionsDB() + actionsDB := allActionsDB.GroupFeatureActionsDB() rulesDB := r.ruleDB.GetKVStores(name, sessionID, featureName) if sessionRule { - actionsDB = allActionsDB.SessionActionsDB() + actionsDB = allActionsDB.GroupActionsDB() rulesDB = r.ruleDB.GetKVStores(name, sessionID, "") } diff --git a/firewalldb/actions.go b/firewalldb/actions.go index e6ba0767e..8ddd1e3b5 100644 --- a/firewalldb/actions.go +++ b/firewalldb/actions.go @@ -575,26 +575,26 @@ type ActionsDB interface { ListActions(ctx context.Context) ([]*RuleAction, error) } -// ActionsReadDB is an abstraction gives a caller access to either a session -// specific or feature specific rules.ActionDB +// ActionsReadDB is an abstraction gives a caller access to either a group +// specific or group and feature specific rules.ActionDB. type ActionsReadDB interface { - SessionActionsDB() ActionsDB - FeatureActionsDB() ActionsDB + GroupActionsDB() ActionsDB + GroupFeatureActionsDB() ActionsDB } // ActionReadDBGetter represents a function that can be used to construct // an ActionsReadDB. type ActionReadDBGetter interface { - GetActionsReadDB(sessionID session.ID, featureName string) ActionsReadDB + GetActionsReadDB(groupID session.ID, featureName string) ActionsReadDB } // GetActionsReadDB is a method on DB that constructs an ActionsReadDB. -func (db *DB) GetActionsReadDB(sessionID session.ID, +func (db *DB) GetActionsReadDB(groupID session.ID, featureName string) ActionsReadDB { return &allActionsReadDB{ db: db, - sessionID: sessionID, + groupID: groupID, featureName: featureName, } } @@ -602,40 +602,40 @@ func (db *DB) GetActionsReadDB(sessionID session.ID, // allActionsReadDb is an implementation of the ActionsReadDB. type allActionsReadDB struct { db *DB - sessionID session.ID + groupID session.ID featureName string } var _ ActionsReadDB = (*allActionsReadDB)(nil) -// SessionActionsDB returns a rules.ActionsDB that will give the caller access -// to all of a sessions Actions. -func (a *allActionsReadDB) SessionActionsDB() ActionsDB { - return &sessionActionsReadDB{a} +// GroupActionsDB returns a rules.ActionsDB that will give the caller access +// to all of a groups Actions. +func (a *allActionsReadDB) GroupActionsDB() ActionsDB { + return &groupActionsReadDB{a} } -// FeatureActionsDB returns an rules.ActionsDB that will give the caller access -// to only a specific features Actions in a specific session. -func (a *allActionsReadDB) FeatureActionsDB() ActionsDB { - return &featureActionsReadDB{a} +// GroupFeatureActionsDB returns a rules.ActionsDB that will give the caller +// access to only a specific features Actions in a specific group. +func (a *allActionsReadDB) GroupFeatureActionsDB() ActionsDB { + return &groupFeatureActionsReadDB{a} } -// sessionActionReadDB is an implementation of the rules.ActionsDB that will -// provide read access to all the Actions of a particular session. -type sessionActionsReadDB struct { +// groupActionsReadDB is an implementation of the rules.ActionsDB that will +// provide read access to all the Actions of a particular group. +type groupActionsReadDB struct { *allActionsReadDB } -var _ ActionsDB = (*sessionActionsReadDB)(nil) +var _ ActionsDB = (*groupActionsReadDB)(nil) -// ListActions will return all the Actions for a particular session. -func (s *sessionActionsReadDB) ListActions(_ context.Context) ([]*RuleAction, +// ListActions will return all the Actions for a particular group. +func (s *groupActionsReadDB) ListActions(_ context.Context) ([]*RuleAction, error) { - sessionActions, _, _, err := s.db.ListSessionActions( - s.sessionID, func(a *Action, _ bool) (bool, bool) { + sessionActions, err := s.db.ListGroupActions( + s.groupID, func(a *Action, _ bool) (bool, bool) { return a.State == ActionStateDone, true - }, nil, + }, ) if err != nil { return nil, err @@ -649,25 +649,25 @@ func (s *sessionActionsReadDB) ListActions(_ context.Context) ([]*RuleAction, return actions, nil } -// featureActionReadDB is an implementation of the rules.ActionsDB that will -// provide read access to all the Actions of a feature within a particular -// session. -type featureActionsReadDB struct { +// groupFeatureActionsReadDB is an implementation of the rules.ActionsDB that +// will provide read access to all the Actions of a feature within a particular +// group. +type groupFeatureActionsReadDB struct { *allActionsReadDB } -var _ ActionsDB = (*featureActionsReadDB)(nil) +var _ ActionsDB = (*groupFeatureActionsReadDB)(nil) -// ListActions will return all the Actions for a particular session that were +// ListActions will return all the Actions for a particular group that were // executed by a particular feature. -func (a *featureActionsReadDB) ListActions(_ context.Context) ([]*RuleAction, - error) { +func (a *groupFeatureActionsReadDB) ListActions(_ context.Context) ( + []*RuleAction, error) { - featureActions, _, _, err := a.db.ListSessionActions( - a.sessionID, func(action *Action, _ bool) (bool, bool) { + featureActions, err := a.db.ListGroupActions( + a.groupID, func(action *Action, _ bool) (bool, bool) { return action.State == ActionStateDone && action.FeatureName == a.featureName, true - }, nil, + }, ) if err != nil { return nil, err From 3c837a9bc6098febfb8a56ec47236a0f797d2481 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 15:56:16 +0200 Subject: [PATCH 16/23] firewall: map session ID to group ID --- firewall/rule_enforcer.go | 42 ++++++++++++++++++++++++++++----------- terminal.go | 2 +- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/firewall/rule_enforcer.go b/firewall/rule_enforcer.go index c51d2f9fa..db304b7a6 100644 --- a/firewall/rule_enforcer.go +++ b/firewall/rule_enforcer.go @@ -30,6 +30,7 @@ var _ mid.RequestInterceptor = (*RuleEnforcer)(nil) type RuleEnforcer struct { ruleDB firewalldb.RulesDB actionsDB firewalldb.ActionReadDBGetter + sessionIDIndexDB session.IDToGroupIndex markActionErrored func(reqID uint64, reason string) error newPrivMap firewalldb.NewPrivacyMapDB @@ -50,8 +51,9 @@ type featurePerms func(ctx context.Context) (map[string]map[string]bool, error) // NewRuleEnforcer constructs a new RuleEnforcer instance. func NewRuleEnforcer(ruleDB firewalldb.RulesDB, - actionsDB firewalldb.ActionReadDBGetter, getFeaturePerms featurePerms, - permsMgr *perms.Manager, nodeID [33]byte, + actionsDB firewalldb.ActionReadDBGetter, + sessionIDIndex session.IDToGroupIndex, + getFeaturePerms featurePerms, permsMgr *perms.Manager, nodeID [33]byte, routerClient lndclient.RouterClient, lndClient lndclient.LightningClient, ruleMgrs rules.ManagerSet, markActionErrored func(reqID uint64, reason string) error, @@ -68,6 +70,7 @@ func NewRuleEnforcer(ruleDB firewalldb.RulesDB, ruleMgrs: ruleMgrs, markActionErrored: markActionErrored, newPrivMap: privMap, + sessionIDIndexDB: sessionIDIndex, } } @@ -221,7 +224,12 @@ func (r *RuleEnforcer) handleRequest(ctx context.Context, return nil, fmt.Errorf("could not extract ID from macaroon") } - rules, err := r.collectEnforcers(ri, sessionID) + groupID, err := r.sessionIDIndexDB.GetGroupID(sessionID) + if err != nil { + return nil, err + } + + rules, err := r.collectEnforcers(ri, groupID) if err != nil { return nil, fmt.Errorf("error parsing rules: %v", err) } @@ -261,7 +269,12 @@ func (r *RuleEnforcer) handleResponse(ctx context.Context, return nil, fmt.Errorf("could not extract ID from macaroon") } - enforcers, err := r.collectEnforcers(ri, sessionID) + groupID, err := r.sessionIDIndexDB.GetGroupID(sessionID) + if err != nil { + return nil, err + } + + enforcers, err := r.collectEnforcers(ri, groupID) if err != nil { return nil, fmt.Errorf("error parsing rules: %v", err) } @@ -295,7 +308,12 @@ func (r *RuleEnforcer) handleErrorResponse(ctx context.Context, return nil, fmt.Errorf("could not extract ID from macaroon") } - enforcers, err := r.collectEnforcers(ri, sessionID) + groupID, err := r.sessionIDIndexDB.GetGroupID(sessionID) + if err != nil { + return nil, err + } + + enforcers, err := r.collectEnforcers(ri, groupID) if err != nil { return nil, fmt.Errorf("error parsing rules: %v", err) } @@ -320,7 +338,7 @@ func (r *RuleEnforcer) handleErrorResponse(ctx context.Context, // collectRule initialises and returns all the Rules that need to be enforced // for the given request. -func (r *RuleEnforcer) collectEnforcers(ri *RequestInfo, sessionID session.ID) ( +func (r *RuleEnforcer) collectEnforcers(ri *RequestInfo, groupID session.ID) ( []rules.Enforcer, error) { ruleEnforcers := make( @@ -331,7 +349,7 @@ func (r *RuleEnforcer) collectEnforcers(ri *RequestInfo, sessionID session.ID) ( for rule, value := range ri.Rules.FeatureRules[ri.MetaInfo.Feature] { r, err := r.initRule( ri.RequestID, rule, []byte(value), ri.MetaInfo.Feature, - sessionID, false, ri.WithPrivacy, + groupID, false, ri.WithPrivacy, ) if err != nil { return nil, err @@ -345,7 +363,7 @@ func (r *RuleEnforcer) collectEnforcers(ri *RequestInfo, sessionID session.ID) ( // initRule initialises a rule.Rule with any required config values. func (r *RuleEnforcer) initRule(reqID uint64, name string, value []byte, - featureName string, sessionID session.ID, sessionRule, + featureName string, groupID session.ID, sessionRule, privacy bool) (rules.Enforcer, error) { ruleValues, err := r.ruleMgrs.InitRuleValues(name, value) @@ -354,7 +372,7 @@ func (r *RuleEnforcer) initRule(reqID uint64, name string, value []byte, } if privacy { - privMap := r.newPrivMap(sessionID) + privMap := r.newPrivMap(groupID) ruleValues, err = ruleValues.PseudoToReal(privMap) if err != nil { return nil, fmt.Errorf("could not prepare rule "+ @@ -362,13 +380,13 @@ func (r *RuleEnforcer) initRule(reqID uint64, name string, value []byte, } } - allActionsDB := r.actionsDB.GetActionsReadDB(sessionID, featureName) + allActionsDB := r.actionsDB.GetActionsReadDB(groupID, featureName) actionsDB := allActionsDB.GroupFeatureActionsDB() - rulesDB := r.ruleDB.GetKVStores(name, sessionID, featureName) + rulesDB := r.ruleDB.GetKVStores(name, groupID, featureName) if sessionRule { actionsDB = allActionsDB.GroupActionsDB() - rulesDB = r.ruleDB.GetKVStores(name, sessionID, "") + rulesDB = r.ruleDB.GetKVStores(name, groupID, "") } cfg := &rules.ConfigImpl{ diff --git a/terminal.go b/terminal.go index d92a134be..8125ca1d8 100644 --- a/terminal.go +++ b/terminal.go @@ -824,7 +824,7 @@ func (g *LightningTerminal) startInternalSubServers( if !g.cfg.Autopilot.Disable { ruleEnforcer := firewall.NewRuleEnforcer( - g.firewallDB, g.firewallDB, + g.firewallDB, g.firewallDB, g.sessionDB, g.autopilotClient.ListFeaturePerms, g.permsMgr, g.lndClient.NodePubkey, g.lndClient.Router, From bd9a99c5d45b95e0f30dba79a356d7f734ef2b1d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 16:02:59 +0200 Subject: [PATCH 17/23] multi: allow querying actions by group ID --- cmd/litcli/actions.go | 25 +++++++- litrpc/firewall.pb.go | 121 +++++++++++++++++++---------------- litrpc/firewall.proto | 5 ++ litrpc/firewall.swagger.json | 5 ++ session_rpcserver.go | 10 +++ 5 files changed, 108 insertions(+), 58 deletions(-) diff --git a/cmd/litcli/actions.go b/cmd/litcli/actions.go index d520c4522..27d6b4e71 100644 --- a/cmd/litcli/actions.go +++ b/cmd/litcli/actions.go @@ -35,9 +35,9 @@ var listActionsCommand = cli.Command{ }, cli.StringFlag{ Name: "session_id", - Usage: "The session ID to filter the actions by. If " + - "left empty, then all actions will be " + - "returned.", + Usage: "The hex encoded session ID to filter the " + + "actions by. If left empty, then all actions " + + "will be returned.", }, cli.Uint64Flag{ Name: "start_timestamp", @@ -84,6 +84,16 @@ var listActionsCommand = cli.Command{ "index_offset is the index of the action in " + "the db regardless of filter.", }, + cli.StringFlag{ + Name: "group_id", + Usage: "The hex encoded group ID to filter the " + + "actions by. The group ID is the same for " + + "all sessions that have been linked. If a " + + "session has no linked sessions then the " + + "group ID will be the same as the " + + "session ID. This flag will be ignored if " + + "the `session_id` flag is set.", + }, }, } @@ -109,6 +119,14 @@ func listActions(ctx *cli.Context) error { } } + var groupID []byte + if ctx.String("group_id") != "" { + groupID, err = hex.DecodeString(ctx.String("group_id")) + if err != nil { + return err + } + } + resp, err := client.ListActions( ctxb, &litrpc.ListActionsRequest{ SessionId: sessionID, @@ -122,6 +140,7 @@ func listActions(ctx *cli.Context) error { CountTotal: ctx.Bool("count_total"), StartTimestamp: ctx.Uint64("start_timestamp"), EndTimestamp: ctx.Uint64("end_timestamp"), + GroupId: groupID, }, ) if err != nil { diff --git a/litrpc/firewall.pb.go b/litrpc/firewall.pb.go index 5a1e312ea..4d590c5e4 100644 --- a/litrpc/firewall.pb.go +++ b/litrpc/firewall.pb.go @@ -234,6 +234,8 @@ type ListActionsRequest struct { // If specified, then only actions created before the given timestamp will be // considered. EndTimestamp uint64 `protobuf:"varint,11,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"` + // If specified, then only actions under the given group will be queried. + GroupId []byte `protobuf:"bytes,12,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` } func (x *ListActionsRequest) Reset() { @@ -345,6 +347,13 @@ func (x *ListActionsRequest) GetEndTimestamp() uint64 { return 0 } +func (x *ListActionsRequest) GetGroupId() []byte { + if x != nil { + return x.GroupId + } + return nil +} + type ListActionsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -568,7 +577,7 @@ var file_firewall_proto_rawDesc = []byte{ 0x75, 0x74, 0x22, 0x36, 0x0a, 0x1c, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9f, 0x03, 0x0a, 0x12, 0x4c, + 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0xba, 0x03, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, @@ -594,61 +603,63 @@ var file_firewall_proto_rawDesc = []byte{ 0x02, 0x30, 0x01, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x27, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0c, - 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x8c, 0x01, 0x0a, - 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, - 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x03, 0x0a, 0x06, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, - 0x67, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, - 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x74, - 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x64, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, - 0x75, 0x72, 0x65, 0x64, 0x4a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x70, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x72, - 0x70, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x70, 0x63, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x4a, - 0x73, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x49, 0x64, 0x2a, 0x54, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, - 0x5f, 0x44, 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x32, 0xb5, 0x01, 0x0a, 0x08, 0x46, 0x69, 0x72, - 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x12, 0x46, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, - 0x14, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x19, 0x0a, 0x08, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x03, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x12, 0x16, 0x0a, + 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, + 0x72, 0x65, 0x64, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x64, 0x4a, + 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x70, 0x63, 0x5f, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x70, 0x63, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x70, 0x63, 0x5f, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x72, 0x70, 0x63, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x20, + 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x2a, 0x54, 0x0a, + 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, + 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x4f, 0x4e, 0x45, + 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x03, 0x32, 0xb5, 0x01, 0x0a, 0x08, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, + 0x12, 0x46, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x14, 0x50, 0x72, 0x69, 0x76, + 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, + 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6c, 0x69, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, - 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, - 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, - 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, - 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, + 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/litrpc/firewall.proto b/litrpc/firewall.proto index 6631755a1..60ad60370 100644 --- a/litrpc/firewall.proto +++ b/litrpc/firewall.proto @@ -120,6 +120,11 @@ message ListActionsRequest { considered. */ uint64 end_timestamp = 11 [jstype = JS_STRING]; + + /* + If specified, then only actions under the given group will be queried. + */ + bytes group_id = 12; } message ListActionsResponse { diff --git a/litrpc/firewall.swagger.json b/litrpc/firewall.swagger.json index e7bab1aaf..1eb03c2de 100644 --- a/litrpc/firewall.swagger.json +++ b/litrpc/firewall.swagger.json @@ -197,6 +197,11 @@ "type": "string", "format": "uint64", "description": "If specified, then only actions created before the given timestamp will be\nconsidered." + }, + "group_id": { + "type": "string", + "format": "byte", + "description": "If specified, then only actions under the given group will be queried." } } }, diff --git a/session_rpcserver.go b/session_rpcserver.go index e6987cfc4..401c1d7bc 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -725,6 +725,16 @@ func (s *sessionRpcServer) ListActions(_ context.Context, if err != nil { return nil, err } + } else if req.GroupId != nil { + groupID, err := session.IDFromBytes(req.GroupId) + if err != nil { + return nil, err + } + + actions, err = db.ListGroupActions(groupID, filterFn) + if err != nil { + return nil, err + } } else { actions, lastIndex, totalCount, err = db.ListActions( filterFn, query, From 60c10bd91d43aa29950f8dee0999e0a532814da2 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 16:05:17 +0200 Subject: [PATCH 18/23] multi: use group ID for PrivacyMapConversion query --- cmd/litcli/privacy_map.go | 47 +++++++++++++++++++++++++++--------- litrpc/firewall.pb.go | 33 ++++++++++++++++++------- litrpc/firewall.proto | 8 +++++- litrpc/firewall.swagger.json | 7 +++++- session_rpcserver.go | 24 +++++++++++++++--- 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/cmd/litcli/privacy_map.go b/cmd/litcli/privacy_map.go index e080bd371..6bf0c9f7a 100644 --- a/cmd/litcli/privacy_map.go +++ b/cmd/litcli/privacy_map.go @@ -18,9 +18,9 @@ var privacyMapCommands = cli.Command{ Category: "Privacy", Flags: []cli.Flag{ cli.StringFlag{ - Name: "session_id", - Usage: "The id of the session in question", - Required: true, + Name: "session_id", + Usage: "Deprecated, use group_id instead.", + Hidden: true, }, cli.BoolFlag{ Name: "realtopseudo", @@ -30,6 +30,11 @@ var privacyMapCommands = cli.Command{ "as the pseudo value that should be " + "mapped to its real counterpart.", }, + cli.StringFlag{ + Name: "group_id", + Usage: "The ID of the session group who's privacy " + + "map DB should be queried.", + }, }, Subcommands: []cli.Command{ privacyMapConvertStrCommand, @@ -60,16 +65,26 @@ func privacyMapConvertStr(ctx *cli.Context) error { defer cleanup() client := litrpc.NewFirewallClient(clientConn) - id, err := hex.DecodeString(ctx.GlobalString("session_id")) - if err != nil { - return err + var groupID []byte + if ctx.GlobalIsSet("group_id") { + groupID, err = hex.DecodeString(ctx.GlobalString("group_id")) + if err != nil { + return err + } + } else if ctx.GlobalIsSet("session_id") { + groupID, err = hex.DecodeString(ctx.GlobalString("session_id")) + if err != nil { + return err + } + } else { + return fmt.Errorf("must set group_id") } resp, err := client.PrivacyMapConversion( ctxb, &litrpc.PrivacyMapConversionRequest{ - SessionId: id, RealToPseudo: ctx.GlobalBool("realtopseudo"), Input: ctx.String("input"), + GroupId: groupID, }, ) if err != nil { @@ -104,18 +119,28 @@ func privacyMapConvertUint64(ctx *cli.Context) error { defer cleanup() client := litrpc.NewFirewallClient(clientConn) - id, err := hex.DecodeString(ctx.GlobalString("session_id")) - if err != nil { - return err + var groupID []byte + if ctx.GlobalIsSet("group_id") { + groupID, err = hex.DecodeString(ctx.GlobalString("group_id")) + if err != nil { + return err + } + } else if ctx.GlobalIsSet("session_id") { + groupID, err = hex.DecodeString(ctx.GlobalString("session_id")) + if err != nil { + return err + } + } else { + return fmt.Errorf("must set group_id") } input := firewalldb.Uint64ToStr(ctx.Uint64("input")) resp, err := client.PrivacyMapConversion( ctxb, &litrpc.PrivacyMapConversionRequest{ - SessionId: id, RealToPseudo: ctx.GlobalBool("realtopseudo"), Input: input, + GroupId: groupID, }, ) if err != nil { diff --git a/litrpc/firewall.pb.go b/litrpc/firewall.pb.go index 4d590c5e4..79e42dfb2 100644 --- a/litrpc/firewall.pb.go +++ b/litrpc/firewall.pb.go @@ -88,10 +88,15 @@ type PrivacyMapConversionRequest struct { // the response will the the pseudo value it if exists. Otherwise, the input // string will be assumed to be the pseudo value. RealToPseudo bool `protobuf:"varint,1,opt,name=real_to_pseudo,json=realToPseudo,proto3" json:"real_to_pseudo,omitempty"` + // Deprecated, use group_id. // The session ID under which to search for the real-pseudo pair. + // + // Deprecated: Marked as deprecated in firewall.proto. SessionId []byte `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // The input to be converted into the real or pseudo value. Input string `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` + // The group ID under which to search for the real-pseudo pair. + GroupId []byte `protobuf:"bytes,4,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` } func (x *PrivacyMapConversionRequest) Reset() { @@ -133,6 +138,7 @@ func (x *PrivacyMapConversionRequest) GetRealToPseudo() bool { return false } +// Deprecated: Marked as deprecated in firewall.proto. func (x *PrivacyMapConversionRequest) GetSessionId() []byte { if x != nil { return x.SessionId @@ -147,6 +153,13 @@ func (x *PrivacyMapConversionRequest) GetInput() string { return "" } +func (x *PrivacyMapConversionRequest) GetGroupId() []byte { + if x != nil { + return x.GroupId + } + return nil +} + type PrivacyMapConversionResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -566,15 +579,17 @@ var File_firewall_proto protoreflect.FileDescriptor var file_firewall_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x22, 0x78, 0x0a, 0x1b, 0x50, 0x72, 0x69, 0x76, - 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x6c, 0x5f, - 0x74, 0x6f, 0x5f, 0x70, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0c, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x6f, 0x50, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, 0x70, - 0x75, 0x74, 0x22, 0x36, 0x0a, 0x1c, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, + 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x22, 0x97, 0x01, 0x0a, 0x1b, 0x50, 0x72, 0x69, + 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x6c, + 0x5f, 0x74, 0x6f, 0x5f, 0x70, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x6f, 0x50, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x12, 0x21, + 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x49, 0x64, 0x22, 0x36, 0x0a, 0x1c, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0xba, 0x03, 0x0a, 0x12, 0x4c, diff --git a/litrpc/firewall.proto b/litrpc/firewall.proto index 60ad60370..1a4dff43a 100644 --- a/litrpc/firewall.proto +++ b/litrpc/firewall.proto @@ -36,14 +36,20 @@ message PrivacyMapConversionRequest { bool real_to_pseudo = 1; /* + Deprecated, use group_id. The session ID under which to search for the real-pseudo pair. */ - bytes session_id = 2; + bytes session_id = 2 [deprecated = true]; /* The input to be converted into the real or pseudo value. */ string input = 3; + + /* + The group ID under which to search for the real-pseudo pair. + */ + bytes group_id = 4; } message PrivacyMapConversionResponse { diff --git a/litrpc/firewall.swagger.json b/litrpc/firewall.swagger.json index 1eb03c2de..074a2593f 100644 --- a/litrpc/firewall.swagger.json +++ b/litrpc/firewall.swagger.json @@ -237,11 +237,16 @@ "session_id": { "type": "string", "format": "byte", - "description": "The session ID under which to search for the real-pseudo pair." + "description": "Deprecated, use group_id.\nThe session ID under which to search for the real-pseudo pair." }, "input": { "type": "string", "description": "The input to be converted into the real or pseudo value." + }, + "group_id": { + "type": "string", + "format": "byte", + "description": "The group ID under which to search for the real-pseudo pair." } } }, diff --git a/session_rpcserver.go b/session_rpcserver.go index 401c1d7bc..367387b81 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -592,13 +592,29 @@ func (s *sessionRpcServer) PrivacyMapConversion(_ context.Context, req *litrpc.PrivacyMapConversionRequest) ( *litrpc.PrivacyMapConversionResponse, error) { - sessionID, err := session.IDFromBytes(req.SessionId) - if err != nil { - return nil, err + var ( + groupID session.ID + err error + ) + if len(req.GroupId) != 0 { + groupID, err = session.IDFromBytes(req.GroupId) + if err != nil { + return nil, err + } + } else { + sessionID, err := session.IDFromBytes(req.SessionId) + if err != nil { + return nil, err + } + + groupID, err = s.cfg.db.GetGroupID(sessionID) + if err != nil { + return nil, err + } } var res string - privMap := s.cfg.privMap(sessionID) + privMap := s.cfg.privMap(groupID) err = privMap.View(func(tx firewalldb.PrivacyMapTx) error { var err error if req.RealToPseudo { From a14d7ae17bba4e91b6eefe8ca2d7617fdd49ac75 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 16:12:35 +0200 Subject: [PATCH 19/23] firewall: map session ID to group ID in privacy mapper interceptor --- firewall/privacy_mapper.go | 32 ++++++++++++++------- firewall/privacy_mapper_test.go | 49 +++++++++++++++++++++++++++++---- terminal.go | 1 + 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go index 8bf55b174..598e9746b 100644 --- a/firewall/privacy_mapper.go +++ b/firewall/privacy_mapper.go @@ -46,16 +46,22 @@ var _ mid.RequestInterceptor = (*PrivacyMapper)(nil) // PrivacyMapper is a RequestInterceptor that maps any pseudo names in certain // requests to their real values and vice versa for responses. type PrivacyMapper struct { - newDB firewalldb.NewPrivacyMapDB - randIntn func(int) (int, error) + newDB firewalldb.NewPrivacyMapDB + randIntn func(int) (int, error) + sessionIDIndexDB session.IDToGroupIndex } // NewPrivacyMapper returns a new instance of PrivacyMapper. The randIntn // function is used to draw randomness for request field obfuscation. func NewPrivacyMapper(newDB firewalldb.NewPrivacyMapDB, - randIntn func(int) (int, error)) *PrivacyMapper { + randIntn func(int) (int, error), + sessionIDIndexDB session.IDToGroupIndex) *PrivacyMapper { - return &PrivacyMapper{newDB: newDB, randIntn: randIntn} + return &PrivacyMapper{ + newDB: newDB, + randIntn: randIntn, + sessionIDIndexDB: sessionIDIndexDB, + } } // Name returns the name of the interceptor. @@ -91,6 +97,12 @@ func (p *PrivacyMapper) Intercept(ctx context.Context, return nil, fmt.Errorf("could not extract ID from macaroon") } + // Get group ID for session ID. + groupID, err := p.sessionIDIndexDB.GetGroupID(sessionID) + if err != nil { + return nil, err + } + log.Tracef("PrivacyMapper: Intercepting %v", ri) switch r := req.InterceptType.(type) { @@ -108,7 +120,7 @@ func (p *PrivacyMapper) Intercept(ctx context.Context, } replacement, err := p.checkAndReplaceIncomingRequest( - ctx, r.Request.MethodFullUri, msg, sessionID, + ctx, r.Request.MethodFullUri, msg, groupID, ) if err != nil { return mid.RPCErr(req, err) @@ -142,7 +154,7 @@ func (p *PrivacyMapper) Intercept(ctx context.Context, } replacement, err := p.replaceOutgoingResponse( - ctx, r.Response.MethodFullUri, msg, sessionID, + ctx, r.Response.MethodFullUri, msg, groupID, ) if err != nil { return mid.RPCErr(req, err) @@ -167,10 +179,10 @@ func (p *PrivacyMapper) Intercept(ctx context.Context, // checkAndReplaceIncomingRequest inspects an incoming request and optionally // modifies some of the request parameters. func (p *PrivacyMapper) checkAndReplaceIncomingRequest(ctx context.Context, - uri string, req proto.Message, sessionID session.ID) (proto.Message, + uri string, req proto.Message, groupID session.ID) (proto.Message, error) { - db := p.newDB(sessionID) + db := p.newDB(groupID) // If we don't have a handler for the URI, we don't allow the request // to go through. @@ -193,9 +205,9 @@ func (p *PrivacyMapper) checkAndReplaceIncomingRequest(ctx context.Context, // replaceOutgoingResponse inspects the responses before sending them out to the // client and replaces them if needed. func (p *PrivacyMapper) replaceOutgoingResponse(ctx context.Context, uri string, - resp proto.Message, sessionID session.ID) (proto.Message, error) { + resp proto.Message, groupID session.ID) (proto.Message, error) { - db := p.newDB(sessionID) + db := p.newDB(groupID) // If we don't have a handler for the URI, we don't allow the response // to go to avoid accidental leaks. diff --git a/firewall/privacy_mapper_test.go b/firewall/privacy_mapper_test.go index 3778363b6..23cc1494b 100644 --- a/firewall/privacy_mapper_test.go +++ b/firewall/privacy_mapper_test.go @@ -2,6 +2,7 @@ package firewall import ( "context" + "fmt" "testing" "time" @@ -292,9 +293,12 @@ func TestPrivacyMapper(t *testing.T) { db := newMockDB(t, mapPreloadRealToPseudo, sessionID) + err = db.AddSessionAndGroupIDPair(sessionID, sessionID) + require.NoError(t, err) + // randIntn is used for deterministic testing. randIntn := func(n int) (int, error) { return 100, nil } - p := NewPrivacyMapper(db.NewSessionDB, randIntn) + p := NewPrivacyMapper(db.NewSessionDB, randIntn, db) for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -355,7 +359,7 @@ func TestPrivacyMapper(t *testing.T) { rawMsg, err := proto.Marshal(msg) require.NoError(t, err) - p = NewPrivacyMapper(db.NewSessionDB, CryptoRandIntn) + p = NewPrivacyMapper(db.NewSessionDB, CryptoRandIntn, db) require.NoError(t, err) // We test the independent outgoing amount (incoming amount @@ -440,12 +444,21 @@ func TestPrivacyMapper(t *testing.T) { }) } -type mockDB map[string]*mockPrivacyMapDB +type mockDB struct { + privDB map[string]*mockPrivacyMapDB + + sessionIDIndex map[session.ID]session.ID + groupIDIndex map[session.ID][]session.ID +} func newMockDB(t *testing.T, preloadRealToPseudo map[string]string, sessID session.ID) mockDB { - db := make(mockDB) + db := mockDB{ + privDB: make(map[string]*mockPrivacyMapDB), + sessionIDIndex: make(map[session.ID]session.ID), + groupIDIndex: make(map[session.ID][]session.ID), + } sessDB := db.NewSessionDB(sessID) _ = sessDB.Update(func(tx firewalldb.PrivacyMapTx) error { @@ -459,17 +472,41 @@ func newMockDB(t *testing.T, preloadRealToPseudo map[string]string, } func (m mockDB) NewSessionDB(sessionID session.ID) firewalldb.PrivacyMapDB { - db, ok := m[string(sessionID[:])] + db, ok := m.privDB[string(sessionID[:])] if ok { return db } newDB := newMockPrivacyMapDB() - m[string(sessionID[:])] = newDB + m.privDB[string(sessionID[:])] = newDB return newDB } +func (m mockDB) AddSessionAndGroupIDPair(sessionID, groupID session.ID) error { + m.sessionIDIndex[sessionID] = groupID + m.groupIDIndex[groupID] = append(m.groupIDIndex[groupID], sessionID) + return nil +} + +func (m mockDB) GetGroupID(sessionID session.ID) (session.ID, error) { + groupID, ok := m.sessionIDIndex[sessionID] + if !ok { + return session.ID{}, fmt.Errorf("group ID not found") + } + + return groupID, nil +} + +func (m mockDB) GetSessionIDs(groupID session.ID) ([]session.ID, error) { + sessionIDs, ok := m.groupIDIndex[groupID] + if !ok { + return nil, fmt.Errorf("group ID not found") + } + + return sessionIDs, nil +} + func newMockPrivacyMapDB() *mockPrivacyMapDB { return &mockPrivacyMapDB{ r2p: make(map[string]string), diff --git a/terminal.go b/terminal.go index 8125ca1d8..ff4232028 100644 --- a/terminal.go +++ b/terminal.go @@ -814,6 +814,7 @@ func (g *LightningTerminal) startInternalSubServers( privacyMapper := firewall.NewPrivacyMapper( g.firewallDB.PrivacyDB, firewall.CryptoRandIntn, + g.sessionDB, ) mw := []mid.RequestInterceptor{ From 71cac8672fbc108106ee445bff576321af183d25 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 28 Aug 2023 08:53:15 +0200 Subject: [PATCH 20/23] session: add a CheckSessionGroupPredicate method This method can be used to check that each session in a group passes for a given predicate. --- session/interface.go | 6 +++ session/store.go | 116 ++++++++++++++++++++++++++++++++++-------- session/store_test.go | 89 ++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 20 deletions(-) diff --git a/session/interface.go b/session/interface.go index 9374fb5c9..1d080eb8e 100644 --- a/session/interface.go +++ b/session/interface.go @@ -171,5 +171,11 @@ type Store interface { // GetSessionByID fetches the session with the given ID. GetSessionByID(id ID) (*Session, error) + // CheckSessionGroupPredicate iterates over all the sessions in a group + // and checks if each one passes the given predicate function. True is + // returned if each session passes. + CheckSessionGroupPredicate(groupID ID, + fn func(s *Session) bool) (bool, error) + IDToGroupIndex } diff --git a/session/store.go b/session/store.go index 7a49572b8..782d92430 100644 --- a/session/store.go +++ b/session/store.go @@ -385,39 +385,115 @@ func (db *DB) GetGroupID(sessionID ID) (ID, error) { // // NOTE: this is part of the IDToGroupIndex interface. func (db *DB) GetSessionIDs(groupID ID) ([]ID, error) { - var sessionIDs []ID + var ( + sessionIDs []ID + err error + ) + err = db.View(func(tx *bbolt.Tx) error { + sessionIDs, err = getSessionIDs(tx, groupID) + + return err + }) + if err != nil { + return nil, err + } + + return sessionIDs, nil +} + +// CheckSessionGroupPredicate iterates over all the sessions in a group and +// checks if each one passes the given predicate function. True is returned if +// each session passes. +// +// NOTE: this is part of the Store interface. +func (db *DB) CheckSessionGroupPredicate(groupID ID, + fn func(s *Session) bool) (bool, error) { + + var ( + pass bool + errFailedPred = errors.New("session failed predicate") + ) err := db.View(func(tx *bbolt.Tx) error { sessionBkt, err := getBucket(tx, sessionBucketKey) if err != nil { return err } - groupIndexBkt := sessionBkt.Bucket(groupIDIndexKey) - if groupIndexBkt == nil { - return ErrDBInitErr + sessionIDs, err := getSessionIDs(tx, groupID) + if err != nil { + return err } - groupIDBkt := groupIndexBkt.Bucket(groupID[:]) - if groupIDBkt == nil { - return fmt.Errorf("no sessions for group ID %v", - groupID) - } + // Iterate over all the sessions. + for _, id := range sessionIDs { + key, err := getKeyForID(sessionBkt, id) + if err != nil { + return err + } + + v := sessionBkt.Get(key) + if len(v) == 0 { + return ErrSessionNotFound + } + + session, err := DeserializeSession(bytes.NewReader(v)) + if err != nil { + return err + } - sessionIDsBkt := groupIDBkt.Bucket(sessionIDKey) - if sessionIDsBkt == nil { - return fmt.Errorf("no sessions for group ID %v", - groupID) + if !fn(session) { + return errFailedPred + } } - return sessionIDsBkt.ForEach(func(_, - sessionIDBytes []byte) error { + pass = true - var sessionID ID - copy(sessionID[:], sessionIDBytes) - sessionIDs = append(sessionIDs, sessionID) + return nil + }) + if errors.Is(err, errFailedPred) { + return pass, nil + } + if err != nil { + return pass, err + } - return nil - }) + return pass, nil +} + +// getSessionIDs returns all the session IDs associated with the given group ID. +func getSessionIDs(tx *bbolt.Tx, groupID ID) ([]ID, error) { + var sessionIDs []ID + + sessionBkt, err := getBucket(tx, sessionBucketKey) + if err != nil { + return nil, err + } + + groupIndexBkt := sessionBkt.Bucket(groupIDIndexKey) + if groupIndexBkt == nil { + return nil, ErrDBInitErr + } + + groupIDBkt := groupIndexBkt.Bucket(groupID[:]) + if groupIDBkt == nil { + return nil, fmt.Errorf("no sessions for group ID %v", + groupID) + } + + sessionIDsBkt := groupIDBkt.Bucket(sessionIDKey) + if sessionIDsBkt == nil { + return nil, fmt.Errorf("no sessions for group ID %v", + groupID) + } + + err = sessionIDsBkt.ForEach(func(_, + sessionIDBytes []byte) error { + + var sessionID ID + copy(sessionID[:], sessionIDBytes) + sessionIDs = append(sessionIDs, sessionID) + + return nil }) if err != nil { return nil, err diff --git a/session/store_test.go b/session/store_test.go index 164709deb..949f7db43 100644 --- a/session/store_test.go +++ b/session/store_test.go @@ -1,6 +1,7 @@ package session import ( + "strings" "testing" "time" @@ -172,6 +173,94 @@ func TestLinkedSessions(t *testing.T) { require.EqualValues(t, []ID{s4.ID, s5.ID}, sIDs) } +// TestCheckSessionGroupPredicate asserts that the CheckSessionGroupPredicate +// method correctly checks if each session in a group passes a predicate. +func TestCheckSessionGroupPredicate(t *testing.T) { + // Set up a new DB. + db, err := NewDB(t.TempDir(), "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + // We will use the Label of the Session to test that the predicate + // function is checked correctly. + + // Add a new session to the DB. + s1 := newSession(t, db, "label 1", nil) + require.NoError(t, db.CreateSession(s1)) + + // Check that the group passes against an appropriate predicate. + ok, err := db.CheckSessionGroupPredicate( + s1.GroupID, func(s *Session) bool { + return strings.Contains(s.Label, "label 1") + }, + ) + require.NoError(t, err) + require.True(t, ok) + + // Check that the group fails against an appropriate predicate. + ok, err = db.CheckSessionGroupPredicate( + s1.GroupID, func(s *Session) bool { + return strings.Contains(s.Label, "label 2") + }, + ) + require.NoError(t, err) + require.False(t, ok) + + // Add a new session to the same group as the first one. + s2 := newSession(t, db, "label 2", &s1.GroupID) + require.NoError(t, db.CreateSession(s2)) + + // Check that the group passes against an appropriate predicate. + ok, err = db.CheckSessionGroupPredicate( + s1.GroupID, func(s *Session) bool { + return strings.Contains(s.Label, "label") + }, + ) + require.NoError(t, err) + require.True(t, ok) + + // Check that the group fails against an appropriate predicate. + ok, err = db.CheckSessionGroupPredicate( + s1.GroupID, func(s *Session) bool { + return strings.Contains(s.Label, "label 1") + }, + ) + require.NoError(t, err) + require.False(t, ok) + + // Add a new session that is not linked to the first one. + s3 := newSession(t, db, "completely different", nil) + require.NoError(t, db.CreateSession(s3)) + + // Ensure that the first group is unaffected. + ok, err = db.CheckSessionGroupPredicate( + s1.GroupID, func(s *Session) bool { + return strings.Contains(s.Label, "label") + }, + ) + require.NoError(t, err) + require.True(t, ok) + + // And that the new session is evaluated separately. + ok, err = db.CheckSessionGroupPredicate( + s3.GroupID, func(s *Session) bool { + return strings.Contains(s.Label, "label") + }, + ) + require.NoError(t, err) + require.False(t, ok) + + ok, err = db.CheckSessionGroupPredicate( + s3.GroupID, func(s *Session) bool { + return strings.Contains(s.Label, "different") + }, + ) + require.NoError(t, err) + require.True(t, ok) +} + func newSession(t *testing.T, db Store, label string, linkedGroupID *ID) *Session { From 5882a21df8207b390694561fa111cd58d6b5f00d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 19 Jun 2023 16:16:04 +0200 Subject: [PATCH 21/23] multi: allow client to link autopilot sessions --- autopilotserver/interface.go | 2 +- cmd/litcli/autopilot.go | 14 +++++++ session_rpcserver.go | 72 ++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/autopilotserver/interface.go b/autopilotserver/interface.go index e6bd8b210..feb763282 100644 --- a/autopilotserver/interface.go +++ b/autopilotserver/interface.go @@ -29,7 +29,7 @@ type Autopilot interface { // remains active. RegisterSession(ctx context.Context, pubKey *btcec.PublicKey, mailboxAddr string, devServer bool, - featureConf map[string][]byte, prevLocal *btcec.PublicKey, + featureConf map[string][]byte, linkedGroupKey *btcec.PublicKey, linkSig []byte) (*btcec.PublicKey, error) // ActivateSession attempts to inform the autopilot server that the diff --git a/cmd/litcli/autopilot.go b/cmd/litcli/autopilot.go index c4d643807..27595bfab 100644 --- a/cmd/litcli/autopilot.go +++ b/cmd/litcli/autopilot.go @@ -65,6 +65,11 @@ var addAutopilotSessionCmd = cli.Command{ "perform actions on. In the " + "form of: peerID1,peerID2,...", }, + cli.StringFlag{ + Name: "group_id", + Usage: "The hex encoded group ID of the session " + + "group to link this one to", + }, }, } @@ -224,6 +229,14 @@ func initAutopilotSession(ctx *cli.Context) error { } } + var groupID []byte + if ctx.IsSet("group_id") { + groupID, err = hex.DecodeString(ctx.String("group_id")) + if err != nil { + return err + } + } + resp, err := client.AddAutopilotSession( ctxb, &litrpc.AddAutopilotSessionRequest{ Label: ctx.String("label"), @@ -231,6 +244,7 @@ func initAutopilotSession(ctx *cli.Context) error { MailboxServerAddr: ctx.String("mailboxserveraddr"), DevServer: ctx.Bool("devserver"), Features: featureMap, + LinkedGroupId: groupID, }, ) if err != nil { diff --git a/session_rpcserver.go b/session_rpcserver.go index 367387b81..d5ff84d23 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -10,6 +10,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/accounts" "github.com/lightninglabs/lightning-terminal/autopilotserver" @@ -1010,6 +1012,52 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, caveats = append(caveats, firewall.MetaPrivacyCaveat) } + // If a previous session ID has been set to link this new one to, we + // first check if we have the referenced session, and we make sure it + // has been revoked. + var ( + linkedGroupID *session.ID + linkedGroupSession *session.Session + ) + if len(req.LinkedGroupId) != 0 { + var groupID session.ID + copy(groupID[:], req.LinkedGroupId) + + // Check that the group actually does exist. + groupSess, err := s.cfg.db.GetSessionByID(groupID) + if err != nil { + return nil, err + } + + // Ensure that the linked session is in fact the first session + // in its group. + if groupSess.ID != groupSess.GroupID { + return nil, fmt.Errorf("can not link to session "+ + "%x since it is not the first in the session "+ + "group %x", groupSess.ID, groupSess.GroupID) + } + + // Now we need to check that all the sessions in the group are + // no longer active. + ok, err := s.cfg.db.CheckSessionGroupPredicate( + groupID, func(s *session.Session) bool { + return s.State == session.StateRevoked || + s.State == session.StateExpired + }, + ) + if err != nil { + return nil, err + } + + if !ok { + return nil, fmt.Errorf("a linked session in group "+ + "%x is still active", groupID) + } + + linkedGroupID = &groupID + linkedGroupSession = groupSess + } + s.sessRegMu.Lock() defer s.sessRegMu.Unlock() @@ -1021,14 +1069,32 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, sess, err := session.NewSession( id, localPrivKey, req.Label, session.TypeAutopilot, expiry, req.MailboxServerAddr, req.DevServer, perms, caveats, - featureConfig, privacy, nil, + featureConfig, privacy, linkedGroupID, ) if err != nil { return nil, fmt.Errorf("error creating new session: %v", err) } + // If this session is being linked to a previous one, then we need to + // use the previous session's local private key to sign the new + // session's public key in order to prove to the Autopilot server that + // the two session's belong to the same owner. + var ( + linkSig []byte + prevSessionPub *btcec.PublicKey + ) + if linkedGroupID != nil { + privKey := linkedGroupSession.LocalPrivateKey + pubKey := sess.LocalPublicKey.SerializeCompressed() + + msg := chainhash.HashB(pubKey) + linkSig = ecdsa.Sign(privKey, msg).Serialize() + + prevSessionPub = linkedGroupSession.LocalPublicKey + } + // Register all the privacy map pairs for this session ID. - privDB := s.cfg.privMap(sess.ID) + privDB := s.cfg.privMap(sess.GroupID) err = privDB.Update(func(tx firewalldb.PrivacyMapTx) error { for r, p := range privacyMapPairs { err := tx.NewPair(r, p) @@ -1045,7 +1111,7 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, // Attempt to register the session with the Autopilot server. remoteKey, err := s.cfg.autopilot.RegisterSession( ctx, sess.LocalPublicKey, sess.ServerAddr, sess.DevServer, - featureConfig, nil, nil, + featureConfig, prevSessionPub, linkSig, ) if err != nil { return nil, fmt.Errorf("error registering session with "+ From c27f4ebceae1e25b2a6edf16ca822eb06a74eebb Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 25 Aug 2023 14:10:29 +0200 Subject: [PATCH 22/23] app+proto: run make protos --- app/src/types/generated/firewall_pb.d.ts | 12 +++ app/src/types/generated/firewall_pb.js | 106 ++++++++++++++++++++++- app/src/util/tests/sampleData.ts | 2 + proto/firewall.proto | 13 ++- 4 files changed, 130 insertions(+), 3 deletions(-) diff --git a/app/src/types/generated/firewall_pb.d.ts b/app/src/types/generated/firewall_pb.d.ts index 2d15559b0..f2739fb0a 100644 --- a/app/src/types/generated/firewall_pb.d.ts +++ b/app/src/types/generated/firewall_pb.d.ts @@ -15,6 +15,11 @@ export class PrivacyMapConversionRequest extends jspb.Message { getInput(): string; setInput(value: string): void; + getGroupId(): Uint8Array | string; + getGroupId_asU8(): Uint8Array; + getGroupId_asB64(): string; + setGroupId(value: Uint8Array | string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PrivacyMapConversionRequest.AsObject; static toObject(includeInstance: boolean, msg: PrivacyMapConversionRequest): PrivacyMapConversionRequest.AsObject; @@ -30,6 +35,7 @@ export namespace PrivacyMapConversionRequest { realToPseudo: boolean, sessionId: Uint8Array | string, input: string, + groupId: Uint8Array | string, } } @@ -89,6 +95,11 @@ export class ListActionsRequest extends jspb.Message { getEndTimestamp(): string; setEndTimestamp(value: string): void; + getGroupId(): Uint8Array | string; + getGroupId_asU8(): Uint8Array; + getGroupId_asB64(): string; + setGroupId(value: Uint8Array | string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ListActionsRequest.AsObject; static toObject(includeInstance: boolean, msg: ListActionsRequest): ListActionsRequest.AsObject; @@ -112,6 +123,7 @@ export namespace ListActionsRequest { sessionId: Uint8Array | string, startTimestamp: string, endTimestamp: string, + groupId: Uint8Array | string, } } diff --git a/app/src/types/generated/firewall_pb.js b/app/src/types/generated/firewall_pb.js index 76fdae118..e8cdc0ed9 100644 --- a/app/src/types/generated/firewall_pb.js +++ b/app/src/types/generated/firewall_pb.js @@ -69,7 +69,8 @@ proto.litrpc.PrivacyMapConversionRequest.toObject = function(includeInstance, ms var f, obj = { realToPseudo: jspb.Message.getFieldWithDefault(msg, 1, false), sessionId: msg.getSessionId_asB64(), - input: jspb.Message.getFieldWithDefault(msg, 3, "") + input: jspb.Message.getFieldWithDefault(msg, 3, ""), + groupId: msg.getGroupId_asB64() }; if (includeInstance) { @@ -118,6 +119,10 @@ proto.litrpc.PrivacyMapConversionRequest.deserializeBinaryFromReader = function( var value = /** @type {string} */ (reader.readString()); msg.setInput(value); break; + case 4: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setGroupId(value); + break; default: reader.skipField(); break; @@ -168,6 +173,13 @@ proto.litrpc.PrivacyMapConversionRequest.serializeBinaryToWriter = function(mess f ); } + f = message.getGroupId_asU8(); + if (f.length > 0) { + writer.writeBytes( + 4, + f + ); + } }; @@ -242,6 +254,45 @@ proto.litrpc.PrivacyMapConversionRequest.prototype.setInput = function(value) { }; +/** + * optional bytes group_id = 4; + * @return {!(string|Uint8Array)} + */ +proto.litrpc.PrivacyMapConversionRequest.prototype.getGroupId = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * optional bytes group_id = 4; + * This is a type-conversion wrapper around `getGroupId()` + * @return {string} + */ +proto.litrpc.PrivacyMapConversionRequest.prototype.getGroupId_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getGroupId())); +}; + + +/** + * optional bytes group_id = 4; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getGroupId()` + * @return {!Uint8Array} + */ +proto.litrpc.PrivacyMapConversionRequest.prototype.getGroupId_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getGroupId())); +}; + + +/** @param {!(string|Uint8Array)} value */ +proto.litrpc.PrivacyMapConversionRequest.prototype.setGroupId = function(value) { + jspb.Message.setProto3BytesField(this, 4, value); +}; + + /** * Generated by JsPbCodeGenerator. @@ -441,7 +492,8 @@ proto.litrpc.ListActionsRequest.toObject = function(includeInstance, msg) { countTotal: jspb.Message.getFieldWithDefault(msg, 8, false), sessionId: msg.getSessionId_asB64(), startTimestamp: jspb.Message.getFieldWithDefault(msg, 10, "0"), - endTimestamp: jspb.Message.getFieldWithDefault(msg, 11, "0") + endTimestamp: jspb.Message.getFieldWithDefault(msg, 11, "0"), + groupId: msg.getGroupId_asB64() }; if (includeInstance) { @@ -522,6 +574,10 @@ proto.litrpc.ListActionsRequest.deserializeBinaryFromReader = function(msg, read var value = /** @type {string} */ (reader.readUint64String()); msg.setEndTimestamp(value); break; + case 12: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setGroupId(value); + break; default: reader.skipField(); break; @@ -628,6 +684,13 @@ proto.litrpc.ListActionsRequest.serializeBinaryToWriter = function(message, writ f ); } + f = message.getGroupId_asU8(); + if (f.length > 0) { + writer.writeBytes( + 12, + f + ); + } }; @@ -824,6 +887,45 @@ proto.litrpc.ListActionsRequest.prototype.setEndTimestamp = function(value) { }; +/** + * optional bytes group_id = 12; + * @return {!(string|Uint8Array)} + */ +proto.litrpc.ListActionsRequest.prototype.getGroupId = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 12, "")); +}; + + +/** + * optional bytes group_id = 12; + * This is a type-conversion wrapper around `getGroupId()` + * @return {string} + */ +proto.litrpc.ListActionsRequest.prototype.getGroupId_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getGroupId())); +}; + + +/** + * optional bytes group_id = 12; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getGroupId()` + * @return {!Uint8Array} + */ +proto.litrpc.ListActionsRequest.prototype.getGroupId_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getGroupId())); +}; + + +/** @param {!(string|Uint8Array)} value */ +proto.litrpc.ListActionsRequest.prototype.setGroupId = function(value) { + jspb.Message.setProto3BytesField(this, 12, value); +}; + + /** * Generated by JsPbCodeGenerator. diff --git a/app/src/util/tests/sampleData.ts b/app/src/util/tests/sampleData.ts index e178a9d57..bc043b26f 100644 --- a/app/src/util/tests/sampleData.ts +++ b/app/src/util/tests/sampleData.ts @@ -906,6 +906,7 @@ export const litListSessions: LIT.ListSessionsResponse.AsObject = { sessionType: LIT.SessionType.TYPE_UI_PASSWORD, createdAt: '253300000000', accountId: '', + groupId: '', revokedAt: '453300000000', autopilotFeatureInfoMap: [ [ @@ -986,6 +987,7 @@ export const litListSessions: LIT.ListSessionsResponse.AsObject = { sessionType: LIT.SessionType.TYPE_UI_PASSWORD, createdAt: '253300000000', revokedAt: '453300000000', + groupId: '', accountId: '', autopilotFeatureInfoMap: [ [ diff --git a/proto/firewall.proto b/proto/firewall.proto index af2df19d2..9f429b78e 100644 --- a/proto/firewall.proto +++ b/proto/firewall.proto @@ -36,14 +36,20 @@ message PrivacyMapConversionRequest { bool real_to_pseudo = 1; /* + Deprecated, use group_id. The session ID under which to search for the real-pseudo pair. */ - bytes session_id = 2; + bytes session_id = 2 [deprecated = true]; /* The input to be converted into the real or pseudo value. */ string input = 3; + + /* + The group ID under which to search for the real-pseudo pair. + */ + bytes group_id = 4; } message PrivacyMapConversionResponse { @@ -120,6 +126,11 @@ message ListActionsRequest { considered. */ uint64 end_timestamp = 11 [jstype = JS_STRING]; + + /* + If specified, then only actions under the given group will be queried. + */ + bytes group_id = 12; } message ListActionsResponse { From 3b5007c3994ca151a502b3c5c5a2d84583a093bb Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 29 Aug 2023 15:15:40 +0200 Subject: [PATCH 23/23] session: ensure past linked sessions are not active This commit adds logic to the CreateSession method that checks that a all the past sessions in a linked set are no longer active. --- session/store.go | 63 ++++++++++++++++++++++++++++++++++--------- session/store_test.go | 18 +++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/session/store.go b/session/store.go index 782d92430..9de473927 100644 --- a/session/store.go +++ b/session/store.go @@ -85,15 +85,54 @@ func (db *DB) CreateSession(session *Session) error { // If this is a linked session (meaning the group ID is // different from the ID) the make sure that the Group ID of - // this session is an ID known by the store. We can do this by - // checking that an entry for this ID exists in the id-to-key - // index. + // this session is an ID known by the store. We also need to + // check that all older sessions in this group have been + // revoked. if session.ID != session.GroupID { _, err = getKeyForID(sessionBucket, session.GroupID) if err != nil { return fmt.Errorf("unknown linked session "+ "%x: %w", session.GroupID, err) } + + // Fetch all the session IDs for this group. This will + // through an error if this group does not exist. + sessionIDs, err := getSessionIDs( + sessionBucket, session.GroupID, + ) + if err != nil { + return err + } + + for _, id := range sessionIDs { + keyBytes, err := getKeyForID( + sessionBucket, id, + ) + if err != nil { + return err + } + + v := sessionBucket.Get(keyBytes) + if len(v) == 0 { + return ErrSessionNotFound + } + + sess, err := DeserializeSession( + bytes.NewReader(v), + ) + if err != nil { + return err + } + + // Ensure that the session is no longer active. + if sess.State == StateCreated || + sess.State == StateInUse { + + return fmt.Errorf("session (id=%x) "+ + "in group %x is still active", + sess.ID, sess.GroupID) + } + } } // Add the mapping from session ID to session key to the ID @@ -390,7 +429,12 @@ func (db *DB) GetSessionIDs(groupID ID) ([]ID, error) { err error ) err = db.View(func(tx *bbolt.Tx) error { - sessionIDs, err = getSessionIDs(tx, groupID) + sessionBkt, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + sessionIDs, err = getSessionIDs(sessionBkt, groupID) return err }) @@ -419,7 +463,7 @@ func (db *DB) CheckSessionGroupPredicate(groupID ID, return err } - sessionIDs, err := getSessionIDs(tx, groupID) + sessionIDs, err := getSessionIDs(sessionBkt, groupID) if err != nil { return err } @@ -461,14 +505,9 @@ func (db *DB) CheckSessionGroupPredicate(groupID ID, } // getSessionIDs returns all the session IDs associated with the given group ID. -func getSessionIDs(tx *bbolt.Tx, groupID ID) ([]ID, error) { +func getSessionIDs(sessionBkt *bbolt.Bucket, groupID ID) ([]ID, error) { var sessionIDs []ID - sessionBkt, err := getBucket(tx, sessionBucketKey) - if err != nil { - return nil, err - } - groupIndexBkt := sessionBkt.Bucket(groupIDIndexKey) if groupIndexBkt == nil { return nil, ErrDBInitErr @@ -486,7 +525,7 @@ func getSessionIDs(tx *bbolt.Tx, groupID ID) ([]ID, error) { groupID) } - err = sessionIDsBkt.ForEach(func(_, + err := sessionIDsBkt.ForEach(func(_, sessionIDBytes []byte) error { var sessionID ID diff --git a/session/store_test.go b/session/store_test.go index 949f7db43..a75a93d41 100644 --- a/session/store_test.go +++ b/session/store_test.go @@ -108,6 +108,15 @@ func TestLinkingSessions(t *testing.T) { // Now persist the first session and retry persisting the second one // and assert that this now works. require.NoError(t, db.CreateSession(s1)) + + // Persisting the second session immediately should fail due to the + // first session still being active. + require.ErrorContains(t, db.CreateSession(s2), "is still active") + + // Revoke the first session. + require.NoError(t, db.RevokeSession(s1.LocalPublicKey)) + + // Persisting the second linked session should now work. require.NoError(t, db.CreateSession(s2)) } @@ -132,7 +141,11 @@ func TestLinkedSessions(t *testing.T) { // Persist the sessions. require.NoError(t, db.CreateSession(s1)) + + require.NoError(t, db.RevokeSession(s1.LocalPublicKey)) require.NoError(t, db.CreateSession(s2)) + + require.NoError(t, db.RevokeSession(s2.LocalPublicKey)) require.NoError(t, db.CreateSession(s3)) // Assert that the session ID to group ID index works as expected. @@ -157,6 +170,8 @@ func TestLinkedSessions(t *testing.T) { // Persist the sessions. require.NoError(t, db.CreateSession(s4)) + require.NoError(t, db.RevokeSession(s4.LocalPublicKey)) + require.NoError(t, db.CreateSession(s5)) // Assert that the session ID to group ID index works as expected. @@ -208,6 +223,9 @@ func TestCheckSessionGroupPredicate(t *testing.T) { require.NoError(t, err) require.False(t, ok) + // Revoke the first session. + require.NoError(t, db.RevokeSession(s1.LocalPublicKey)) + // Add a new session to the same group as the first one. s2 := newSession(t, db, "label 2", &s1.GroupID) require.NoError(t, db.CreateSession(s2))