Skip to content

Commit

Permalink
Merge pull request #968 from ellemouton/sql10Sessions2
Browse files Browse the repository at this point in the history
[sql-10] sessions: decouple super macaroon helpers from `sessions` package
  • Loading branch information
ellemouton authored Feb 11, 2025
2 parents 290344f + 402373c commit c0e85e0
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 175 deletions.
18 changes: 8 additions & 10 deletions accounts/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightninglabs/lightning-terminal/session"
litmac "github.com/lightninglabs/lightning-terminal/macaroons"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
Expand All @@ -22,12 +22,12 @@ type RPCServer struct {

service *InterceptorService

superMacBaker session.MacaroonBaker
superMacBaker litmac.Baker
}

// NewRPCServer returns a new RPC server for the given service.
func NewRPCServer(service *InterceptorService,
superMacBaker session.MacaroonBaker) *RPCServer {
superMacBaker litmac.Baker) *RPCServer {

return &RPCServer{
service: service,
Expand Down Expand Up @@ -79,19 +79,17 @@ 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,
fmt.Sprintf("%s %x", CondAccount, account.ID[:]),
)

macHex, err := s.superMacBaker(ctx, macRootKey, &session.MacaroonRecipe{
Permissions: MacaroonPermissions,
Caveats: []macaroon.Caveat{{
Id: []byte(accountCaveat),
}},
})
macHex, err := s.superMacBaker(
ctx, macRootKey, MacaroonPermissions,
[]macaroon.Caveat{{Id: []byte(accountCaveat)}},
)
if err != nil {
return nil, fmt.Errorf("error baking account macaroon: %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/litcli/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"encoding/json"
"os"

"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightninglabs/lightning-terminal/macaroons"
"github.com/urfave/cli"
)

Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down
3 changes: 2 additions & 1 deletion itest/litd_firewall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
35 changes: 35 additions & 0 deletions macaroons/macaroons.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package macaroons

import (
"context"
"fmt"
"strconv"

"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/protobuf/proto"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)

// Baker is a function type for baking a super macaroon.
type Baker func(ctx context.Context, rootKeyID uint64,
perms []bakery.Op, caveats []macaroon.Caveat) (string, error)

// 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)
}
120 changes: 120 additions & 0 deletions macaroons/super_mac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package macaroons

import (
"bytes"
"context"
"encoding/binary"
"encoding/hex"
"errors"

"github.com/lightningnetwork/lnd/lnrpc"
"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[:])
}

// BakeSuperMacaroon uses the lnd client to bake a macaroon that can include
// permissions for multiple daemons.
func BakeSuperMacaroon(ctx context.Context, lnd lnrpc.LightningClient,
rootKeyID uint64, perms []bakery.Op, caveats []macaroon.Caveat) (string,
error) {

if lnd == nil {
return "", errors.New("lnd not yet connected")
}

req := &lnrpc.BakeMacaroonRequest{
Permissions: make(
[]*lnrpc.MacaroonPermission, len(perms),
),
AllowExternalPermissions: true,
RootKeyId: rootKeyID,
}
for idx, perm := range perms {
req.Permissions[idx] = &lnrpc.MacaroonPermission{
Entity: perm.Entity,
Action: perm.Action,
}
}

res, err := lnd.BakeMacaroon(ctx, req)
if err != nil {
return "", err
}

mac, err := ParseMacaroon(res.Macaroon)
if err != nil {
return "", err
}

for _, caveat := range caveats {
if err := mac.AddFirstPartyCaveat(caveat.Id); err != nil {
return "", err
}
}

macBytes, err := mac.MarshalBinary()
if err != nil {
return "", err
}

return hex.EncodeToString(macBytes), err
}
10 changes: 9 additions & 1 deletion session/macaroon_test.go → macaroons/super_mac_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package session
package macaroons

import (
"testing"
Expand All @@ -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))
}
10 changes: 6 additions & 4 deletions rpc_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -176,7 +176,7 @@ type rpcProxy struct {
bakeSuperMac bakeSuperMac

macValidator macaroons.MacaroonValidator
superMacValidator session.SuperMacaroonValidator
superMacValidator litmac.SuperMacaroonValidator

superMacaroon string

Expand Down Expand Up @@ -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.
Expand Down
8 changes: 2 additions & 6 deletions session/interface.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package session

import (
"context"
"fmt"
"time"

"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"
)
Expand Down Expand Up @@ -71,10 +71,6 @@ type Session struct {
GroupID ID
}

// MacaroonBaker is a function type for baking a super macaroon.
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(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
expiry time.Time, serverAddr string, devServer bool, perms []bakery.Op,
Expand All @@ -86,7 +82,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.
Expand Down
Loading

0 comments on commit c0e85e0

Please sign in to comment.