Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions firewall/request_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
26 changes: 21 additions & 5 deletions firewalldb/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions firewalldb/actions_kvdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 15 additions & 6 deletions firewalldb/actions_sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package firewalldb
import (
"context"
"database/sql"
"encoding/binary"
"errors"
"fmt"
"math"
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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),
)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not populating the new field below? (see comment about only needing 1 field potentially anyways)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idid not set the field previously to currently only have one field set when reading a persisted action, to make it as little confusing as possible at callsites, and ensure no callsites used the MacaroonRootKeyID field yet. But I agree that this potentially even makes it more confusing. So therefore changed so that we set the MacaroonRootKeyID field when reading persisted actions on a an sql databasebackend :)


return &Action{
AddActionReq: AddActionReq{
MacaroonIdentifier: macID,
MacaroonRootKeyID: macRootKeyID,
AccountID: legacyAcctID,
SessionID: legacySessID,
ActorName: dbAction.ActorName.String,
Expand Down
55 changes: 32 additions & 23 deletions firewalldb/actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand All @@ -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{
Expand Down Expand Up @@ -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",
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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",
Expand All @@ -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{
Expand Down
23 changes: 20 additions & 3 deletions firewalldb/test_kvdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
5 changes: 1 addition & 4 deletions session_rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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[:],
Expand Down
Loading