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.
This commit is contained in:
Elle Mouton 2025-02-09 10:37:40 +02:00
parent df0e86781d
commit 949c0bf7e2
No known key found for this signature in database
GPG key ID: D7D916376026F177
10 changed files with 131 additions and 102 deletions

View file

@ -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,

View file

@ -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"`

View file

@ -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
}

30
macaroons/macaroons.go Normal file
View file

@ -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)
}

70
macaroons/super_mac.go Normal file
View file

@ -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[:])
}

View file

@ -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))
}

View file

@ -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.

View file

@ -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.

View file

@ -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) {

View file

@ -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
}