mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
Merge pull request #1324 from Cyberguru1/bake-super-macaroon-startup
terminal: auto-bake super macaroon on startup
This commit is contained in:
commit
0bd5af814c
7 changed files with 600 additions and 0 deletions
54
config.go
54
config.go
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/lightninglabs/lightning-terminal/db/sqlc"
|
||||
"github.com/lightninglabs/lightning-terminal/firewall"
|
||||
"github.com/lightninglabs/lightning-terminal/firewalldb"
|
||||
"github.com/lightninglabs/lightning-terminal/macaroons"
|
||||
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
|
||||
"github.com/lightninglabs/lightning-terminal/session"
|
||||
"github.com/lightninglabs/lightning-terminal/subservers"
|
||||
|
|
@ -96,6 +97,22 @@ const (
|
|||
// autogenerated lit macaroon.
|
||||
DefaultMacaroonFilename = "lit.macaroon"
|
||||
|
||||
// DefaultSuperMacaroonFilename is the default file name for the
|
||||
// autogenerated super macaroon.
|
||||
DefaultSuperMacaroonFilename = "super.macaroon"
|
||||
|
||||
// noneChoice is the none choice for the bake-super-macaroon
|
||||
// configuration option.
|
||||
noneChoice = "none"
|
||||
|
||||
// readOnlyChoice is the read-only choice for the bake-super-macaroon
|
||||
// configuration option.
|
||||
readOnlyChoice = "read-only"
|
||||
|
||||
// defaultBakeSuperMacaroon is the default value for the
|
||||
// bake-super-macaroon configuration option.
|
||||
defaultBakeSuperMacaroon = noneChoice
|
||||
|
||||
defaultFirstLNCConnTimeout = 10 * time.Minute
|
||||
|
||||
// DatabaseBackendSqlite is the name of the SQLite database backend.
|
||||
|
|
@ -169,6 +186,12 @@ var (
|
|||
defaultSqliteDatabasePath = filepath.Join(
|
||||
DefaultLitDir, DefaultNetwork, defaultSqliteDatabaseFileName,
|
||||
)
|
||||
|
||||
// DefaultSuperMacaroonPath is the default full path of the super
|
||||
// macaroon.
|
||||
DefaultSuperMacaroonPath = filepath.Join(
|
||||
DefaultLitDir, DefaultNetwork, DefaultSuperMacaroonFilename,
|
||||
)
|
||||
)
|
||||
|
||||
// Config is the main configuration struct of lightning-terminal. It contains
|
||||
|
|
@ -203,6 +226,9 @@ type Config struct {
|
|||
|
||||
MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for litd's RPC and REST services if it doesn't exist."`
|
||||
|
||||
BakeSuperMacaroon string `long:"bake-super-macaroon" description:"Bake a super macaroon on startup if it doesn't exist." choice:"none" choice:"read-only" choice:"read-write"`
|
||||
SuperMacaroonPath string `long:"super-macaroon-path" description:"Path to write the auto-baked super macaroon. This must include both the directory and the name of the macaroon file itself (which must end with the .macaroon suffix)."`
|
||||
|
||||
FirstLNCConnDeadline time.Duration `long:"firstlncconndeadline" description:"The duration after a new LNC session will be revoked if no connection is made with it. This only applies for the first connection which is made using the pairing phrase. "`
|
||||
|
||||
// DatabaseBackend is the database backend we will use for storing all
|
||||
|
|
@ -543,6 +569,8 @@ func defaultConfig() *Config {
|
|||
LetsEncryptListen: defaultLetsEncryptListen,
|
||||
LetsEncryptDir: defaultLetsEncryptDir,
|
||||
MacaroonPath: DefaultMacaroonPath,
|
||||
SuperMacaroonPath: DefaultSuperMacaroonPath,
|
||||
BakeSuperMacaroon: defaultBakeSuperMacaroon,
|
||||
DatabaseBackend: DatabaseBackendSqlite,
|
||||
Sqlite: &db.SqliteConfig{
|
||||
DatabaseFileName: defaultSqliteDatabasePath,
|
||||
|
|
@ -732,6 +760,32 @@ func loadAndValidateConfig(ctx context.Context,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.BakeSuperMacaroon != defaultBakeSuperMacaroon {
|
||||
if cfg.SuperMacaroonPath == DefaultSuperMacaroonPath {
|
||||
cfg.SuperMacaroonPath = filepath.Join(
|
||||
litDir, cfg.Network,
|
||||
DefaultSuperMacaroonFilename,
|
||||
)
|
||||
}
|
||||
|
||||
err = macaroons.HasMacaroonSuffix(
|
||||
cfg.SuperMacaroonPath,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clean and expand the super macaroon path
|
||||
cfg.SuperMacaroonPath = lncfg.CleanAndExpandPath(
|
||||
cfg.SuperMacaroonPath,
|
||||
)
|
||||
dir := filepath.Dir(cfg.SuperMacaroonPath)
|
||||
if err := makeDirectories(dir); err != nil {
|
||||
return nil, fmt.Errorf("unable to create super "+
|
||||
"macaroon directory %v: %w", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = cfg.DevConfig.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -39,6 +39,15 @@
|
|||
an account, supporting pagination (sorted in ascending lexicographical
|
||||
order of their payment hash) and counting of total payments.
|
||||
|
||||
* [Auto-bake super macaroon on startup](https://github.com/lightninglabs/lightning-terminal/pull/1324):
|
||||
Added config options `--bake-super-macaroon` (choice: `none`, `read-only`,
|
||||
`read-write`) and `--super-macaroon-path` to automatically bake a super
|
||||
macaroon on startup and keep its permissions in sync. When set to `read-only`
|
||||
or `read-write`, the daemon will automatically bake a super macaroon
|
||||
containing read-only or read-write permissions, respectively, for all active
|
||||
sub-servers on startup. If the macaroon already exists but has different
|
||||
permissions, it will be automatically regenerated.
|
||||
|
||||
### Technical and Architectural Updates
|
||||
|
||||
* [Report litd's own version for `litd
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -1530,3 +1531,217 @@ func bakeSuperMacaroon(t *testing.T, cfg *LitNodeConfig,
|
|||
|
||||
return tempFile.Name()
|
||||
}
|
||||
|
||||
// testSuperMacaroonOnStartup tests that the super macaroon is successfully
|
||||
// baked on startup if configured.
|
||||
func testSuperMacaroonOnStartup(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
superMacPath := filepath.Join(
|
||||
net.Alice.Cfg.LitDir, "startup-super.macaroon",
|
||||
)
|
||||
|
||||
verifyMacaroonPermissions := func(path string, expectWrite bool) {
|
||||
ctxTimeout, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
rawConn, err := connectRPC(
|
||||
ctxTimeout, net.Alice.Cfg.LitAddr(),
|
||||
net.Alice.Cfg.LitTLSCertPath,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
defer rawConn.Close()
|
||||
|
||||
macBytes, err := os.ReadFile(path)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
ctxm := macaroonContext(ctxTimeout, macBytes)
|
||||
lnrpcConn := lnrpc.NewLightningClient(rawConn)
|
||||
|
||||
_, err = lnrpcConn.GetInfo(ctxm, &lnrpc.GetInfoRequest{})
|
||||
require.NoError(t.t, err)
|
||||
|
||||
_, err = lnrpcConn.NewAddress(ctxm, &lnrpc.NewAddressRequest{
|
||||
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||
})
|
||||
if expectWrite {
|
||||
require.NoError(t.t, err)
|
||||
} else {
|
||||
require.Error(t.t, err)
|
||||
require.Contains(t.t, err.Error(), "permission denied")
|
||||
}
|
||||
}
|
||||
|
||||
verifyMacaroonSubserverPermissions := func(path string,
|
||||
expectSubservers bool) {
|
||||
|
||||
macBytes, err := os.ReadFile(path)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
mac := &macaroon.Macaroon{}
|
||||
err = mac.UnmarshalBinary(macBytes)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
rawID := mac.Id()
|
||||
require.NotEmpty(t.t, rawID)
|
||||
|
||||
decodedID := &lnrpc.MacaroonId{}
|
||||
err = proto.Unmarshal(rawID[1:], decodedID)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
hasSubserver := false
|
||||
for _, op := range decodedID.Ops {
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
if op.Entity == "loop" || op.Entity == "pool" ||
|
||||
op.Entity == "faraday" {
|
||||
|
||||
hasSubserver = true
|
||||
}
|
||||
}
|
||||
|
||||
if expectSubservers {
|
||||
require.True(
|
||||
t.t, hasSubserver,
|
||||
"expected macaroon to contain "+
|
||||
"subserver permissions",
|
||||
)
|
||||
} else {
|
||||
require.False(
|
||||
t.t, hasSubserver,
|
||||
"expected macaroon NOT to contain "+
|
||||
"subserver permissions",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure any old file is removed first.
|
||||
_ = os.Remove(superMacPath)
|
||||
|
||||
// Test that starting Alice with an invalid super-macaroon-path (not
|
||||
// ending with .macaroon) fails.
|
||||
invalidMacPath := filepath.Join(
|
||||
net.Alice.Cfg.LitDir, "invalid-path.mac",
|
||||
)
|
||||
err := net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithLitArg("bake-super-macaroon", "read-only"),
|
||||
WithLitArg("super-macaroon-path", invalidMacPath),
|
||||
},
|
||||
)
|
||||
require.Error(t.t, err)
|
||||
|
||||
// Drain the expected process exit error from the error channel to
|
||||
// prevent it from failing the test runner at the end of the test.
|
||||
select {
|
||||
case <-net.lndErrorChan:
|
||||
case <-time.After(defaultTimeout):
|
||||
t.t.Fatalf("expected process exit error in lndErrorChan")
|
||||
}
|
||||
|
||||
// Restart Alice with read-only super macaroon baking enabled.
|
||||
err = net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithLitArg("bake-super-macaroon", "read-only"),
|
||||
WithLitArg("super-macaroon-path", superMacPath),
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// Verify that the super macaroon was created on startup.
|
||||
require.FileExists(t.t, superMacPath)
|
||||
|
||||
// Verify permissions: write should be blocked.
|
||||
verifyMacaroonPermissions(superMacPath, false)
|
||||
|
||||
// Restart Alice with a read-write super macaroon, WITHOUT
|
||||
// deleting the file. This will test the overwrite behavior when
|
||||
// permissions differ.
|
||||
err = net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithLitArg("bake-super-macaroon", "read-write"),
|
||||
WithLitArg("super-macaroon-path", superMacPath),
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
require.FileExists(t.t, superMacPath)
|
||||
|
||||
// Verify permissions: write should succeed.
|
||||
verifyMacaroonPermissions(superMacPath, true)
|
||||
|
||||
// Restart Alice back with a read-only super macaroon, WITHOUT
|
||||
// deleting the file. This will test the overwrite behavior when
|
||||
// switching back to read-only.
|
||||
err = net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithLitArg("bake-super-macaroon", "read-only"),
|
||||
WithLitArg("super-macaroon-path", superMacPath),
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
require.FileExists(t.t, superMacPath)
|
||||
|
||||
// Verify permissions: write should be blocked again.
|
||||
verifyMacaroonPermissions(superMacPath, false)
|
||||
|
||||
// Restart Alice with a sub-server disabled to bake a super macaroon
|
||||
// with a subset of permissions.
|
||||
err = net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithLitArg("bake-super-macaroon", "read-write"),
|
||||
WithLitArg("super-macaroon-path", superMacPath),
|
||||
WithLitArg("loop-mode", "disable"),
|
||||
WithLitArg("pool-mode", "disable"),
|
||||
WithLitArg("faraday-mode", "disable"),
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
require.FileExists(t.t, superMacPath)
|
||||
verifyMacaroonSubserverPermissions(superMacPath, false)
|
||||
|
||||
// Restart Alice with the sub-servers re-enabled. This will add
|
||||
// permissions and trigger macaroon regeneration on startup.
|
||||
err = net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithLitArg("bake-super-macaroon", "read-write"),
|
||||
WithLitArg("super-macaroon-path", superMacPath),
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
require.FileExists(t.t, superMacPath)
|
||||
verifyMacaroonSubserverPermissions(superMacPath, true)
|
||||
|
||||
// Verify permissions: write should succeed.
|
||||
verifyMacaroonPermissions(superMacPath, true)
|
||||
|
||||
// Clean up the super macaroon file.
|
||||
_ = os.Remove(superMacPath)
|
||||
|
||||
// Restart Alice with super macaroon baking disabled and verify that
|
||||
// no super macaroon is created.
|
||||
err = net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithLitArg("bake-super-macaroon", "none"),
|
||||
WithLitArg("super-macaroon-path", superMacPath),
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
|
||||
_, err = os.Stat(superMacPath)
|
||||
require.True(t.t, os.IsNotExist(err))
|
||||
|
||||
// Clean up after ourselves.
|
||||
err = net.RestartNode(
|
||||
net.Alice, nil, []LitArgOption{
|
||||
WithoutLitArg("bake-super-macaroon"),
|
||||
WithoutLitArg("super-macaroon-path"),
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
net.ConnectNodes(t.t, net.Alice, net.Bob)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,4 +31,8 @@ var allTestCases = []*testCase{
|
|||
name: "kvdb to sql migration",
|
||||
test: testKvdbSQLMigration,
|
||||
},
|
||||
{
|
||||
name: "terminal super macaroon on startup",
|
||||
test: testSuperMacaroonOnStartup,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,12 @@ import (
|
|||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
|
@ -118,3 +122,127 @@ func BakeSuperMacaroon(ctx context.Context, lnd lnrpc.LightningClient,
|
|||
|
||||
return hex.EncodeToString(macBytes), err
|
||||
}
|
||||
|
||||
// SuperMacaroonExists determines whether a macaroon file exists at the given
|
||||
// path.
|
||||
func SuperMacaroonExists(path string) bool {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MacaroonMatchesPermissions checks if the macaroon at the given path contains
|
||||
// exactly the expected permissions (no more and no less).
|
||||
func MacaroonMatchesPermissions(path string,
|
||||
expectedPerms []bakery.Op) (bool, error) {
|
||||
|
||||
macBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
mac := &macaroon.Macaroon{}
|
||||
if err := mac.UnmarshalBinary(macBytes); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rawID := mac.Id()
|
||||
if len(rawID) == 0 || rawID[0] != byte(bakery.LatestVersion) {
|
||||
return false, errors.New("invalid macaroon version")
|
||||
}
|
||||
|
||||
decodedID := &lnrpc.MacaroonId{}
|
||||
if err := proto.Unmarshal(rawID[1:], decodedID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Map expected permissions for easy lookup: entity -> action -> true.
|
||||
expectedMap := make(map[string]map[string]bool)
|
||||
for _, op := range expectedPerms {
|
||||
if expectedMap[op.Entity] == nil {
|
||||
expectedMap[op.Entity] = make(map[string]bool)
|
||||
}
|
||||
|
||||
expectedMap[op.Entity][op.Action] = true
|
||||
}
|
||||
|
||||
// Map actual permissions from decoded macaroon ID:
|
||||
// entity -> action -> true.
|
||||
actualMap := make(map[string]map[string]bool)
|
||||
for _, op := range decodedID.Ops {
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
if actualMap[op.Entity] == nil {
|
||||
actualMap[op.Entity] = make(map[string]bool)
|
||||
}
|
||||
for _, action := range op.Actions {
|
||||
actualMap[op.Entity][action] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the mapped sets for exact equality.
|
||||
if len(expectedMap) != len(actualMap) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for entity, actions := range expectedMap {
|
||||
actualActions, ok := actualMap[entity]
|
||||
|
||||
if !ok || len(actions) != len(actualActions) {
|
||||
return false, nil
|
||||
}
|
||||
for action := range actions {
|
||||
if !actualActions[action] {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// BakeAndWriteSuperMacaroon bakes a super macaroon and writes it to disk.
|
||||
func BakeAndWriteSuperMacaroon(ctx context.Context, lnd lnrpc.LightningClient,
|
||||
path string, perms []bakery.Op) error {
|
||||
|
||||
var suffixBytes [4]byte
|
||||
rootKeyID := NewSuperMacaroonRootKeyID(suffixBytes)
|
||||
|
||||
superMacHex, err := BakeSuperMacaroon(
|
||||
ctx, lnd, rootKeyID, perms, nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to bake super macaroon: %w", err)
|
||||
}
|
||||
|
||||
superMacBytes, err := hex.DecodeString(superMacHex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode baked "+
|
||||
"super macaroon: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, superMacBytes, 0600); err != nil {
|
||||
return fmt.Errorf("unable to write super macaroon to %v: %w",
|
||||
path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasMacaroonSuffix checks that the super macaroon path is not empty and ends
|
||||
// with the expected suffix.
|
||||
func HasMacaroonSuffix(path string) error {
|
||||
if path == "" {
|
||||
return fmt.Errorf("super-macaroon-path cannot be empty")
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(path, ".macaroon") {
|
||||
return fmt.Errorf("super-macaroon-path must end with the " +
|
||||
".macaroon suffix")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
package macaroons
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -43,3 +47,121 @@ func TestIsSuperMacaroon(t *testing.T) {
|
|||
|
||||
require.True(t, IsSuperMacaroon(testMacHex))
|
||||
}
|
||||
|
||||
// TestSuperMacaroonHelpers tests that SuperMacaroonExists and
|
||||
// MacaroonMatchesPermissions behave correctly.
|
||||
func TestSuperMacaroonHelpers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
path := filepath.Join(tempDir, "test.macaroon")
|
||||
|
||||
// Verify that it doesn't exist yet.
|
||||
require.False(t, SuperMacaroonExists(path))
|
||||
|
||||
// Write the test macaroon.
|
||||
macBytes, err := hex.DecodeString(testMacHex)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(path, macBytes, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now it should exist.
|
||||
require.True(t, SuperMacaroonExists(path))
|
||||
|
||||
// The macaroon matches this expected list of permissions.
|
||||
expectedPerms := []bakery.Op{
|
||||
{Entity: "account", Action: "read"},
|
||||
{Entity: "auction", Action: "read"},
|
||||
{Entity: "audit", Action: "read"},
|
||||
{Entity: "auth", Action: "read"},
|
||||
{Entity: "info", Action: "read"},
|
||||
{Entity: "insights", Action: "read"},
|
||||
{Entity: "invoices", Action: "read"},
|
||||
{Entity: "loop", Action: "in"},
|
||||
{Entity: "loop", Action: "out"},
|
||||
{Entity: "macaroon", Action: "read"},
|
||||
{Entity: "message", Action: "read"},
|
||||
{Entity: "offchain", Action: "read"},
|
||||
{Entity: "onchain", Action: "read"},
|
||||
{Entity: "order", Action: "read"},
|
||||
{Entity: "peers", Action: "read"},
|
||||
{Entity: "rates", Action: "read"},
|
||||
{Entity: "recommendation", Action: "read"},
|
||||
{Entity: "report", Action: "read"},
|
||||
{Entity: "suggestions", Action: "read"},
|
||||
{Entity: "swap", Action: "read"},
|
||||
{Entity: "terms", Action: "read"},
|
||||
}
|
||||
|
||||
matches, err := MacaroonMatchesPermissions(path, expectedPerms)
|
||||
require.NoError(t, err)
|
||||
require.True(t, matches)
|
||||
|
||||
// A subset of permissions should NOT match exactly.
|
||||
matches, err = MacaroonMatchesPermissions(path, expectedPerms[:5])
|
||||
require.NoError(t, err)
|
||||
require.False(t, matches)
|
||||
|
||||
// Extra/different permissions should NOT match exactly.
|
||||
differentPerms := append(
|
||||
expectedPerms,
|
||||
bakery.Op{Entity: "invalid", Action: "write"},
|
||||
)
|
||||
matches, err = MacaroonMatchesPermissions(path, differentPerms)
|
||||
require.NoError(t, err)
|
||||
require.False(t, matches)
|
||||
}
|
||||
|
||||
// TestHasMacaroonSuffix tests that HasMacaroonSuffix correctly
|
||||
// checks the suffix of the super macaroon path.
|
||||
func TestHasMacaroonSuffix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
wantErr bool
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
name: "empty path",
|
||||
path: "",
|
||||
wantErr: true,
|
||||
errStr: "super-macaroon-path cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "valid path",
|
||||
path: "/tmp/test.macaroon",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid path - missing suffix",
|
||||
path: "/tmp/test.mac",
|
||||
wantErr: true,
|
||||
errStr: "super-macaroon-path must end " +
|
||||
"with the .macaroon suffix",
|
||||
},
|
||||
{
|
||||
name: "invalid path - wrong suffix",
|
||||
path: "/tmp/test.macaroon.tmp",
|
||||
wantErr: true,
|
||||
errStr: "super-macaroon-path must end " +
|
||||
"with the .macaroon suffix",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := HasMacaroonSuffix(tt.path)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.errStr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
terminal.go
68
terminal.go
|
|
@ -822,6 +822,13 @@ func (g *LightningTerminal) start(ctx context.Context) error {
|
|||
return fmt.Errorf("could not start litd sub-servers: %v", err)
|
||||
}
|
||||
|
||||
// Bake the super macaroon on startup if configured, now that all local
|
||||
// and remote sub-servers have been started and active permissions are
|
||||
// fully known.
|
||||
if err := g.setupSuperMacaroon(ctx); err != nil {
|
||||
return fmt.Errorf("could not setup super macaroon: %w", err)
|
||||
}
|
||||
|
||||
// We can now set the status of LiT as running.
|
||||
g.statusMgr.SetRunning(subservers.LIT)
|
||||
|
||||
|
|
@ -2192,3 +2199,64 @@ func randId(n int) string {
|
|||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// setupSuperMacaroon bakes a super macaroon and writes it to disk if needed.
|
||||
func (g *LightningTerminal) setupSuperMacaroon(ctx context.Context) error {
|
||||
// If the bake-super-macaroon option is set to none, we don't bake a
|
||||
// macaroon.
|
||||
if g.cfg.BakeSuperMacaroon == noneChoice {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the super macaroon baking option is enabled, we cannot run in
|
||||
// stateless initialization mode as it won't write any macaroons to the
|
||||
// filesystem.
|
||||
if g.cfg.statelessInitMode {
|
||||
return fmt.Errorf("cannot use bake-super-macaroon " +
|
||||
"with stateless-init mode")
|
||||
}
|
||||
|
||||
path := g.cfg.SuperMacaroonPath
|
||||
|
||||
readOnly := g.cfg.BakeSuperMacaroon == readOnlyChoice
|
||||
activePerms := g.permsMgr.ActivePermissions(readOnly)
|
||||
|
||||
if litmac.SuperMacaroonExists(path) {
|
||||
matches, err := litmac.MacaroonMatchesPermissions(
|
||||
path, activePerms,
|
||||
)
|
||||
|
||||
if err == nil && matches {
|
||||
log.Debugf("Super macaroon already exists at "+
|
||||
"%v and matches configuration, "+
|
||||
"skipping bake", path)
|
||||
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"unable to verify super macaroon "+
|
||||
"permissions at %v, please delete "+
|
||||
"it if the issue persists: %w",
|
||||
path, err,
|
||||
)
|
||||
}
|
||||
|
||||
log.Infof("Super macaroon permissions " +
|
||||
"differ from configuration, " +
|
||||
"regenerating...")
|
||||
}
|
||||
|
||||
log.Infof("Baking super macaroon on startup...")
|
||||
|
||||
// Bake the super macaroon and write it to disk.
|
||||
if err := litmac.BakeAndWriteSuperMacaroon(
|
||||
ctx, g.basicClient, path, activePerms,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Successfully baked and wrote super macaroon to %v", path)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue