diff --git a/firewall/request_logger.go b/firewall/request_logger.go index b28b1092a..2af278311 100644 --- a/firewall/request_logger.go +++ b/firewall/request_logger.go @@ -7,8 +7,8 @@ import ( "sync" "github.com/lightninglabs/lightning-terminal/firewalldb" + litmac "github.com/lightninglabs/lightning-terminal/macaroons" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" - "github.com/lightninglabs/lightning-terminal/session" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" @@ -182,22 +182,28 @@ func (r *RequestLogger) Intercept(ctx context.Context, func (r *RequestLogger) addNewAction(ctx context.Context, ri *RequestInfo, withPayloadData bool) error { - var macaroonID fn.Option[[4]byte] + var ( + rootKeyID fn.Option[uint64] + ) + if ri.Macaroon != nil { var err error - macID, err := session.IDFromMacaroon(ri.Macaroon) + + fullRootKeyID, err := litmac.RootKeyIDFromMacaroon( + ri.Macaroon, + ) if err != nil { - return fmt.Errorf("could not extract ID from macaroon") + return fmt.Errorf("could not extract root key ID from "+ + "macaroon: %w", err) } - - macaroonID = fn.Some([4]byte(macID)) + rootKeyID = fn.Some(fullRootKeyID) } actionReq := &firewalldb.AddActionReq{ - SessionID: ri.SessionID, - AccountID: ri.AccountID, - MacaroonIdentifier: macaroonID, - RPCMethod: ri.URI, + SessionID: ri.SessionID, + AccountID: ri.AccountID, + MacaroonRootKeyID: rootKeyID, + RPCMethod: ri.URI, } if withPayloadData { diff --git a/firewalldb/actions.go b/firewalldb/actions.go index 1d0c8c36f..9a4e350be 100644 --- a/firewalldb/actions.go +++ b/firewalldb/actions.go @@ -34,17 +34,21 @@ const ( // It contains all the information that is needed to create a new Action in the // ActionStateInit State. type AddActionReq struct { - // MacaroonIdentifier is a 4 byte identifier created from the last 4 - // bytes of the root key ID of the macaroon used to perform the action. + // MacaroonRootKeyID is the uint64 / full 8 bytes of the root key ID of + // the macaroon used to perform the action. // If no macaroon was used for the action, then this will not be set. - MacaroonIdentifier fn.Option[[4]byte] + // + // NOTE: for our BoltDB impl, only the lower 32 bits / last 4 bytes of + // this uint64 are stored. When read back, the upper 32 bits / first 4 + // bytes are zeroed. + MacaroonRootKeyID fn.Option[uint64] // SessionID holds the optional session ID of the session that this // action was performed with. // // NOTE: for our BoltDB impl, this is not persisted in any way, and we - // populate it by casting the macaroon ID to a session.ID and so is not - // guaranteed to be linked to an existing session. + // populate it by casting the MacaroonRootKeyID to a session.ID and so + // is not guaranteed to be linked to an existing session. SessionID fn.Option[session.ID] // AccountID holds the optional account ID of the account that this @@ -80,6 +84,18 @@ type AddActionReq struct { RPCParamsJson []byte } +// MacaroonId returns the 4 byte macaroon ID that is derived from the +// MacaroonRootKeyID. If the MacaroonRootKeyID is not set, then this will return +// an empty 4 byte array. +func (a *AddActionReq) MacaroonId() [4]byte { + var macID [4]byte + a.MacaroonRootKeyID.WhenSome(func(rootID uint64) { + macID = session.IDFromMacRootKeyID(rootID) + }) + + return macID +} + // Action represents an RPC call made through the firewall. type Action struct { AddActionReq diff --git a/firewalldb/actions_kvdb.go b/firewalldb/actions_kvdb.go index b2dddd36c..adf58eb02 100644 --- a/firewalldb/actions_kvdb.go +++ b/firewalldb/actions_kvdb.go @@ -59,11 +59,9 @@ func (db *BoltDB) AddAction(ctx context.Context, req *AddActionReq) (ActionLocator, error) { // If no macaroon is provided, then an empty 4-byte array is used as the - // macaroon ID. - var macaroonID [4]byte - req.MacaroonIdentifier.WhenSome(func(id [4]byte) { - macaroonID = id - }) + // macaroon ID. Note that the kvdb implementation only stores the last + // 4 bytes of the macaroon root key ID. + macaroonID := req.MacaroonId() // If the new action links to a session, the session must exist. // For the bbolt impl of the store, this is our best effort attempt @@ -596,7 +594,12 @@ func DeserializeAction(r io.Reader, sessionID session.ID) (*Action, error) { return nil, err } - action.MacaroonIdentifier = fn.Some([4]byte(sessionID)) + // Since the kvdb only persists 4 bytes for the macaroon root key ID, we + // first cast it to a uint32, and then to a uint64, effectively padding + // the first 4 bytes with zeroes. + rootKeyID := uint64(binary.BigEndian.Uint32(sessionID[:])) + + action.MacaroonRootKeyID = fn.Some(rootKeyID) action.SessionID = fn.Some(sessionID) action.ActorName = string(actor) action.FeatureName = string(featureName) diff --git a/firewalldb/actions_sql.go b/firewalldb/actions_sql.go index 75c9d0a6d..9e2fa63bd 100644 --- a/firewalldb/actions_sql.go +++ b/firewalldb/actions_sql.go @@ -3,6 +3,7 @@ package firewalldb import ( "context" "database/sql" + "encoding/binary" "errors" "fmt" "math" @@ -140,8 +141,11 @@ func (s *SQLDB) AddAction(ctx context.Context, } var macID []byte - req.MacaroonIdentifier.WhenSome(func(id [4]byte) { - macID = id[:] + req.MacaroonRootKeyID.WhenSome(func(rootKeyID uint64) { + rootKeyBytes := make([]byte, 8) + binary.BigEndian.PutUint64(rootKeyBytes[:], rootKeyID) + + macID = rootKeyBytes }) id, err := db.InsertAction(ctx, sqlc.InsertActionParams{ @@ -393,14 +397,19 @@ func unmarshalAction(ctx context.Context, db SQLActionQueries, legacyAcctID = fn.Some(acctID) } - var macID fn.Option[[4]byte] - if len(dbAction.MacaroonIdentifier) > 0 { - macID = fn.Some([4]byte(dbAction.MacaroonIdentifier)) + // Note that we export the full 8 byte macaroon root key ID in the sql + // actions DB, while the kvdb version persists and exports stored the + // last 4 bytes only. + var macRootKeyID fn.Option[uint64] + if len(dbAction.MacaroonIdentifier) >= 8 { + macRootKeyID = fn.Some( + binary.BigEndian.Uint64(dbAction.MacaroonIdentifier), + ) } return &Action{ AddActionReq: AddActionReq{ - MacaroonIdentifier: macID, + MacaroonRootKeyID: macRootKeyID, AccountID: legacyAcctID, SessionID: legacySessID, ActorName: dbAction.ActorName.String, diff --git a/firewalldb/actions_test.go b/firewalldb/actions_test.go index 69990c1da..8aa70509f 100644 --- a/firewalldb/actions_test.go +++ b/firewalldb/actions_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/lightninglabs/lightning-terminal/accounts" + litmac "github.com/lightninglabs/lightning-terminal/macaroons" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/fn" @@ -60,10 +61,12 @@ func TestActionStorage(t *testing.T) { acct1, err := accountsDB.NewAccount(ctx, 0, time.Time{}, "foo") require.NoError(t, err) + sess1RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess1.ID) + action1Req := &AddActionReq{ SessionID: fn.Some(sess1.ID), AccountID: fn.Some(acct1.ID), - MacaroonIdentifier: fn.Some([4]byte(sess1.ID)), + MacaroonRootKeyID: fn.Some(sess1RootKeyID), ActorName: "Autopilot", FeatureName: "auto-fees", Trigger: "fee too low", @@ -79,15 +82,17 @@ func TestActionStorage(t *testing.T) { State: ActionStateDone, } + sess2RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess2.ID) + action2Req := &AddActionReq{ - SessionID: fn.Some(sess2.ID), - MacaroonIdentifier: fn.Some([4]byte(sess2.ID)), - ActorName: "Autopilot", - FeatureName: "rebalancer", - Trigger: "channels not balanced", - Intent: "balance", - RPCMethod: "SendToRoute", - RPCParamsJson: []byte("hops, amount"), + SessionID: fn.Some(sess2.ID), + MacaroonRootKeyID: fn.Some(sess2RootKeyID), + ActorName: "Autopilot", + FeatureName: "rebalancer", + Trigger: "channels not balanced", + Intent: "balance", + RPCMethod: "SendToRoute", + RPCParamsJson: []byte("hops, amount"), } action2 := &Action{ @@ -213,8 +218,10 @@ func TestListActions(t *testing.T) { addAction := func(sessionID [4]byte) { actionIds++ + sessRootKeyID := litmac.NewSuperMacaroonRootKeyID(sessionID) + actionReq := &AddActionReq{ - MacaroonIdentifier: fn.Some(sessionID), + MacaroonRootKeyID: fn.Some(sessRootKeyID), ActorName: "Autopilot", FeatureName: fmt.Sprintf("%d", actionIds), Trigger: "fee too low", @@ -236,11 +243,9 @@ func TestListActions(t *testing.T) { assertActions := func(dbActions []*Action, al []*action) { require.Len(t, dbActions, len(al)) for i, a := range al { - mID, err := dbActions[i].MacaroonIdentifier.UnwrapOrErr( - fmt.Errorf("macaroon identifier is none"), + require.EqualValues( + t, a.sessionID, dbActions[i].MacaroonId(), ) - require.NoError(t, err) - require.EqualValues(t, a.sessionID, mID) require.Equal(t, a.actionID, dbActions[i].FeatureName) } } @@ -424,9 +429,11 @@ func TestListGroupActions(t *testing.T) { ) require.NoError(t, err) + sess1RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess1.ID) + action1Req := &AddActionReq{ SessionID: fn.Some(sess1.ID), - MacaroonIdentifier: fn.Some([4]byte(sess1.ID)), + MacaroonRootKeyID: fn.Some(sess1RootKeyID), ActorName: "Autopilot", FeatureName: "auto-fees", Trigger: "fee too low", @@ -442,15 +449,17 @@ func TestListGroupActions(t *testing.T) { State: ActionStateDone, } + sess2RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess2.ID) + action2Req := &AddActionReq{ - SessionID: fn.Some(sess2.ID), - MacaroonIdentifier: fn.Some([4]byte(sess2.ID)), - ActorName: "Autopilot", - FeatureName: "rebalancer", - Trigger: "channels not balanced", - Intent: "balance", - RPCMethod: "SendToRoute", - RPCParamsJson: []byte("hops, amount"), + SessionID: fn.Some(sess2.ID), + MacaroonRootKeyID: fn.Some(sess2RootKeyID), + ActorName: "Autopilot", + FeatureName: "rebalancer", + Trigger: "channels not balanced", + Intent: "balance", + RPCMethod: "SendToRoute", + RPCParamsJson: []byte("hops, amount"), } action2 := &Action{ diff --git a/firewalldb/test_kvdb.go b/firewalldb/test_kvdb.go index c3cd4533a..6b2f05c95 100644 --- a/firewalldb/test_kvdb.go +++ b/firewalldb/test_kvdb.go @@ -3,9 +3,9 @@ package firewalldb import ( + "encoding/binary" "testing" - "github.com/lightninglabs/lightning-terminal/accounts" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/fn" @@ -59,8 +59,25 @@ func newDBFromPathWithSessions(t *testing.T, dbPath string, func assertEqualActions(t *testing.T, expected, got *Action) { // Accounts are not explicitly linked in our bbolt DB implementation. + actualAccountID := got.AccountID got.AccountID = expected.AccountID - require.Equal(t, expected, got) - got.AccountID = fn.None[accounts.AccountID]() + // As the kvdb implementation only stores the last 4 bytes Macaroon Root + // Key ID, we pad it with 4 zero bytes when comparing. + expectedMacRootKey := expected.MacaroonRootKeyID + + expectedMacRootKey.WhenSome(func(rootID uint64) { + // Remove the 4 byte prefix of the actual Macaroon Root Key ID. + sessID := session.IDFromMacRootKeyID(rootID) + + // Recreate the full 8 byte Macaroon Root Key ID (represented as + // a uint64) by padding the first 4 bytes with zeroes. + expected.MacaroonRootKeyID = fn.Some( + uint64(binary.BigEndian.Uint32(sessID[:])), + ) + }) + + require.Equal(t, expected, got) + got.AccountID = actualAccountID + expected.MacaroonRootKeyID = expectedMacRootKey } diff --git a/session_rpcserver.go b/session_rpcserver.go index b88cea053..0d56bd79b 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -817,10 +817,7 @@ func (s *sessionRpcServer) ListActions(ctx context.Context, sessionID = id }) - var macID [4]byte - a.MacaroonIdentifier.WhenSome(func(id [4]byte) { - macID = id - }) + macID := a.MacaroonId() resp[i] = &litrpc.Action{ SessionId: sessionID[:],