From 949c0bf7e213db5554f70e9665a66a817ba29de0 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 9 Feb 2025 10:37:40 +0200 Subject: [PATCH] multi: move macaroon helpers to macaroons dir ... and out of the `sessions` directory. This is to avoid import cycles when we start explicitly linking accounts to sessions. --- accounts/rpcserver.go | 3 +- cmd/litcli/helpers.go | 6 +- itest/litd_firewall_test.go | 3 +- macaroons/macaroons.go | 30 +++++++ macaroons/super_mac.go | 70 +++++++++++++++ .../super_mac_test.go | 10 ++- rpc_proxy.go | 10 ++- session/interface.go | 3 +- session/macaroon.go | 89 +------------------ terminal.go | 9 +- 10 files changed, 131 insertions(+), 102 deletions(-) create mode 100644 macaroons/macaroons.go create mode 100644 macaroons/super_mac.go rename session/macaroon_test.go => macaroons/super_mac_test.go (83%) diff --git a/accounts/rpcserver.go b/accounts/rpcserver.go index 79e3e674..12e02b5c 100644 --- a/accounts/rpcserver.go +++ b/accounts/rpcserver.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightninglabs/lightning-terminal/litrpc" + litmac "github.com/lightninglabs/lightning-terminal/macaroons" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -79,7 +80,7 @@ func (s *RPCServer) CreateAccount(ctx context.Context, var rootKeyIdSuffix [4]byte copy(rootKeyIdSuffix[:], account.ID[0:4]) - macRootKey := session.NewSuperMacaroonRootKeyID(rootKeyIdSuffix) + macRootKey := litmac.NewSuperMacaroonRootKeyID(rootKeyIdSuffix) accountCaveat := checkers.Condition( macaroons.CondLndCustom, diff --git a/cmd/litcli/helpers.go b/cmd/litcli/helpers.go index 1d3170ea..a06818e5 100644 --- a/cmd/litcli/helpers.go +++ b/cmd/litcli/helpers.go @@ -7,7 +7,7 @@ import ( "encoding/json" "os" - "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightninglabs/lightning-terminal/macaroons" "github.com/urfave/cli" ) @@ -66,7 +66,7 @@ func superMacRootKey(ctx *cli.Context) error { } } - id := session.NewSuperMacaroonRootKeyID(suffix) + id := macaroons.NewSuperMacaroonRootKeyID(suffix) printJSON(struct { RootKeyID uint64 `json:"root_key_id"` @@ -97,7 +97,7 @@ var isSuperMacaroonCmd = cli.Command{ // isSuperMacaroon checks if the users given macaroon is considered a super // macaroon. func isSuperMacaroon(ctx *cli.Context) error { - isSuperMac := session.IsSuperMacaroon(ctx.String("mac")) + isSuperMac := macaroons.IsSuperMacaroon(ctx.String("mac")) printJSON(struct { IsSuperMacaroon bool `json:"is_super_macaroon"` diff --git a/itest/litd_firewall_test.go b/itest/litd_firewall_test.go index 8037bdeb..ae35107c 100644 --- a/itest/litd_firewall_test.go +++ b/itest/litd_firewall_test.go @@ -22,6 +22,7 @@ import ( "github.com/lightninglabs/lightning-terminal/firewall" "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/litrpc" + "github.com/lightninglabs/lightning-terminal/macaroons" "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightningnetwork/lnd" @@ -2566,7 +2567,7 @@ func (c *caveatCredentials) GetRequestMetadata(ctx context.Context, return metadata, nil } - mac, err := session.ParseMacaroon(macHex) + mac, err := macaroons.ParseMacaroon(macHex) if err != nil { return nil, err } diff --git a/macaroons/macaroons.go b/macaroons/macaroons.go new file mode 100644 index 00000000..243c979c --- /dev/null +++ b/macaroons/macaroons.go @@ -0,0 +1,30 @@ +package macaroons + +import ( + "fmt" + "strconv" + + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/protobuf/proto" + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" +) + +// RootKeyIDFromMacaroon extracts the root key ID of the passed macaroon. +func RootKeyIDFromMacaroon(mac *macaroon.Macaroon) (uint64, error) { + rawID := mac.Id() + if rawID[0] != byte(bakery.LatestVersion) { + return 0, fmt.Errorf("mac id is not on the latest version") + } + + decodedID := &lnrpc.MacaroonId{} + idProto := rawID[1:] + err := proto.Unmarshal(idProto, decodedID) + if err != nil { + return 0, err + } + + // The storage ID is a string representation of a 64-bit unsigned + // number. + return strconv.ParseUint(string(decodedID.StorageId), 10, 64) +} diff --git a/macaroons/super_mac.go b/macaroons/super_mac.go new file mode 100644 index 00000000..03d5eed5 --- /dev/null +++ b/macaroons/super_mac.go @@ -0,0 +1,70 @@ +package macaroons + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" +) + +// SuperMacaroonRootKeyPrefix is the prefix we set on a super macaroon's root +// key to clearly mark it as such. +var SuperMacaroonRootKeyPrefix = [4]byte{0xFF, 0xEE, 0xDD, 0xCC} + +// SuperMacaroonValidator is a function type for validating a super macaroon. +type SuperMacaroonValidator func(ctx context.Context, + superMacaroon []byte, requiredPermissions []bakery.Op, + fullMethod string) error + +// 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) +} + +// ParseMacaroon parses a hex encoded macaroon into its native struct. +func ParseMacaroon(macHex string) (*macaroon.Macaroon, error) { + macBytes, err := hex.DecodeString(macHex) + if err != nil { + return nil, err + } + + mac := &macaroon.Macaroon{} + if err := mac.UnmarshalBinary(macBytes); err != nil { + return nil, err + } + + return mac, nil +} + +// IsSuperMacaroon returns true if the given hex encoded macaroon is a super +// macaroon baked by LiT which can be identified by its root key ID. +func IsSuperMacaroon(macHex string) bool { + mac, err := ParseMacaroon(macHex) + if err != nil { + return false + } + + rootKeyID, err := RootKeyIDFromMacaroon(mac) + if err != nil { + return false + } + + return isSuperMacaroonRootKeyID(rootKeyID) +} + +// isSuperMacaroonRootKeyID returns true if the given macaroon root key ID (also +// known as storage ID) is a super macaroon, which can be identified by its +// first 4 bytes. +func isSuperMacaroonRootKeyID(rootKeyID uint64) bool { + rootKeyBytes := make([]byte, 8) + binary.BigEndian.PutUint64(rootKeyBytes, rootKeyID) + return bytes.HasPrefix(rootKeyBytes, SuperMacaroonRootKeyPrefix[:]) +} diff --git a/session/macaroon_test.go b/macaroons/super_mac_test.go similarity index 83% rename from session/macaroon_test.go rename to macaroons/super_mac_test.go index 7b48a57c..d4b91aea 100644 --- a/session/macaroon_test.go +++ b/macaroons/super_mac_test.go @@ -1,4 +1,4 @@ -package session +package macaroons import ( "testing" @@ -25,13 +25,21 @@ var ( "60a6caf" ) +// TestSuperMacaroonRootKeyID tests that adding the super macaroon prefix to +// a root key ID results in a valid super macaroon root key ID. func TestSuperMacaroonRootKeyID(t *testing.T) { + t.Parallel() + someBytes := [4]byte{02, 03, 44, 88} rootKeyID := NewSuperMacaroonRootKeyID(someBytes) require.True(t, isSuperMacaroonRootKeyID(rootKeyID)) require.False(t, isSuperMacaroonRootKeyID(123)) } +// TestIsSuperMacaroon tests that we can correctly identify an example super +// macaroon. func TestIsSuperMacaroon(t *testing.T) { + t.Parallel() + require.True(t, IsSuperMacaroon(testMacHex)) } diff --git a/rpc_proxy.go b/rpc_proxy.go index be03683b..f9011eb7 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -13,8 +13,8 @@ import ( "github.com/improbable-eng/grpc-web/go/grpcweb" "github.com/lightninglabs/lightning-terminal/litrpc" + litmac "github.com/lightninglabs/lightning-terminal/macaroons" "github.com/lightninglabs/lightning-terminal/perms" - "github.com/lightninglabs/lightning-terminal/session" litstatus "github.com/lightninglabs/lightning-terminal/status" "github.com/lightninglabs/lightning-terminal/subservers" "github.com/lightningnetwork/lnd/lncfg" @@ -71,7 +71,7 @@ func (e *proxyErr) Unwrap() error { // or REST request and delegate (and convert if necessary) it to the correct // component. func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, - superMacValidator session.SuperMacaroonValidator, + superMacValidator litmac.SuperMacaroonValidator, permsMgr *perms.Manager, subServerMgr *subservers.Manager, statusMgr *litstatus.Manager, getLNDClient lndBasicClientFn) *rpcProxy { @@ -176,7 +176,7 @@ type rpcProxy struct { bakeSuperMac bakeSuperMac macValidator macaroons.MacaroonValidator - superMacValidator session.SuperMacaroonValidator + superMacValidator litmac.SuperMacaroonValidator superMacaroon string @@ -331,7 +331,9 @@ func (p *rpcProxy) makeDirector(allowLitRPC bool) func(ctx context.Context, )) } - case len(macHeader) == 1 && session.IsSuperMacaroon(macHeader[0]): + case len(macHeader) == 1 && + litmac.IsSuperMacaroon(macHeader[0]): + // If we have a macaroon, and it's a super macaroon, // then we need to convert it into the actual daemon // macaroon if they're running in remote mode. diff --git a/session/interface.go b/session/interface.go index 00f109d5..b614b9bc 100644 --- a/session/interface.go +++ b/session/interface.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/lightning-node-connect/mailbox" + "github.com/lightninglabs/lightning-terminal/macaroons" "gopkg.in/macaroon-bakery.v2/bakery" "gopkg.in/macaroon.v2" ) @@ -86,7 +87,7 @@ func NewSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type, return nil, fmt.Errorf("error deriving pairing secret: %v", err) } - macRootKey := NewSuperMacaroonRootKeyID(id) + macRootKey := macaroons.NewSuperMacaroonRootKeyID(id) // The group ID will by default be the same as the Session ID // unless this session links to a previous session. diff --git a/session/macaroon.go b/session/macaroon.go index 72dad881..90bb57a0 100644 --- a/session/macaroon.go +++ b/session/macaroon.go @@ -1,87 +1,21 @@ package session import ( - "bytes" - "context" "encoding/binary" - "encoding/hex" "fmt" - "strconv" "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lnrpc" - "google.golang.org/protobuf/proto" - "gopkg.in/macaroon-bakery.v2/bakery" + "github.com/lightninglabs/lightning-terminal/macaroons" "gopkg.in/macaroon.v2" ) -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 -// SuperMacaroonValidator is a function type for validating a super macaroon. -type SuperMacaroonValidator func(ctx context.Context, - superMacaroon []byte, requiredPermissions []bakery.Op, - fullMethod string) error - -// 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) -} - -// ParseMacaroon parses a hex encoded macaroon into its native struct. -func ParseMacaroon(macHex string) (*macaroon.Macaroon, error) { - macBytes, err := hex.DecodeString(macHex) - if err != nil { - return nil, err - } - - mac := &macaroon.Macaroon{} - if err := mac.UnmarshalBinary(macBytes); err != nil { - return nil, err - } - - return mac, nil -} - -// IsSuperMacaroon returns true if the given hex encoded macaroon is a super -// macaroon baked by LiT which can be identified by its root key ID. -func IsSuperMacaroon(macHex string) bool { - mac, err := ParseMacaroon(macHex) - if err != nil { - return false - } - - rootKeyID, err := RootKeyIDFromMacaroon(mac) - if err != nil { - return false - } - - return isSuperMacaroonRootKeyID(rootKeyID) -} - -// isSuperMacaroonRootKeyID returns true if the given macaroon root key ID (also -// known as storage ID) is a super macaroon, which can be identified by its -// first 4 bytes. -func isSuperMacaroonRootKeyID(rootKeyID uint64) bool { - rootKeyBytes := make([]byte, 8) - binary.BigEndian.PutUint64(rootKeyBytes, rootKeyID) - return bytes.HasPrefix(rootKeyBytes, SuperMacaroonRootKeyPrefix[:]) -} - // IDFromMacaroon is a helper function that creates a session ID from // a macaroon ID. func IDFromMacaroon(mac *macaroon.Macaroon) (ID, error) { - rootKeyID, err := RootKeyIDFromMacaroon(mac) + rootKeyID, err := macaroons.RootKeyIDFromMacaroon(mac) if err != nil { return ID{}, err } @@ -110,25 +44,6 @@ func IDFromBytes(b []byte) (ID, error) { return id, nil } -// RootKeyIDFromMacaroon extracts the root key ID of the passed macaroon. -func RootKeyIDFromMacaroon(mac *macaroon.Macaroon) (uint64, error) { - rawID := mac.Id() - if rawID[0] != byte(bakery.LatestVersion) { - return 0, fmt.Errorf("mac id is not on the latest version") - } - - decodedID := &lnrpc.MacaroonId{} - idProto := rawID[1:] - err := proto.Unmarshal(idProto, decodedID) - if err != nil { - return 0, err - } - - // The storage ID is a string representation of a 64-bit unsigned - // 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) { diff --git a/terminal.go b/terminal.go index c38b414d..e398a52c 100644 --- a/terminal.go +++ b/terminal.go @@ -26,6 +26,7 @@ import ( "github.com/lightninglabs/lightning-terminal/firewall" "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/litrpc" + litmac "github.com/lightninglabs/lightning-terminal/macaroons" "github.com/lightninglabs/lightning-terminal/perms" "github.com/lightninglabs/lightning-terminal/queue" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" @@ -662,7 +663,7 @@ func (g *LightningTerminal) start(ctx context.Context) error { var suffixBytes [4]byte binary.BigEndian.PutUint32(suffixBytes[:], rootKeyIDSuffix) - rootKeyID := session.NewSuperMacaroonRootKeyID(suffixBytes) + rootKeyID := litmac.NewSuperMacaroonRootKeyID(suffixBytes) return BakeSuperMacaroon( ctx, g.basicClient, rootKeyID, @@ -953,7 +954,7 @@ func (g *LightningTerminal) setUpLNDClients(ctx context.Context, // faraday, loop, and pool, all at the same time. log.Infof("Baking internal super macaroon") superMacaroon, err := BakeSuperMacaroon( - ctx, g.basicClient, session.NewSuperMacaroonRootKeyID( + ctx, g.basicClient, litmac.NewSuperMacaroonRootKeyID( [4]byte{}, ), g.permsMgr.ActivePermissions(false), nil, @@ -1265,7 +1266,7 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context, // the proxy and its director and any super macaroon will be converted // to a daemon specific macaroon before directing the call to the remote // daemon. Those calls don't land here. - if session.IsSuperMacaroon(macHex) { + if litmac.IsSuperMacaroon(macHex) { macBytes, err := hex.DecodeString(macHex) if err != nil { return err @@ -1873,7 +1874,7 @@ func BakeSuperMacaroon(ctx context.Context, lnd lnrpc.LightningClient, return "", err } - mac, err := session.ParseMacaroon(res.Macaroon) + mac, err := litmac.ParseMacaroon(res.Macaroon) if err != nil { return "", err }