Skip to content

Commit bec0d0b

Browse files
committed
itest: add testOutboundRSMacaroonEnforcement itest
testOutboundRSMacaroonEnforcement tests that a valid macaroon including the `remotesigner` entity is required to connect to a watch-only node that uses an outbound remote signer, while the watch-only node is in the state (WalletState_ALLOW_REMOTE_SIGNER) where it waits for the signer to connect.
1 parent 0c8d455 commit bec0d0b

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,10 @@ var allTestCases = []*lntest.TestCase{
522522
Name: "outbound remote signer",
523523
TestFunc: testOutboundRemoteSigner,
524524
},
525+
{
526+
Name: "outbound remote signer macaroon enforcement",
527+
TestFunc: testOutboundRSMacaroonEnforcement,
528+
},
525529
{
526530
Name: "taproot coop close",
527531
TestFunc: testTaprootCoopClose,

itest/lnd_remote_signer_test.go

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ package itest
33
import (
44
"fmt"
55
"testing"
6+
"time"
67

78
"github.com/btcsuite/btcd/btcutil"
89
"github.com/btcsuite/btcd/btcutil/hdkeychain"
910
"github.com/btcsuite/btcwallet/waddrmgr"
1011
"github.com/lightningnetwork/lnd/keychain"
12+
"github.com/lightningnetwork/lnd/lncfg"
1113
"github.com/lightningnetwork/lnd/lnrpc"
1214
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
1315
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
1416
"github.com/lightningnetwork/lnd/lntest"
1517
"github.com/lightningnetwork/lnd/lntest/node"
18+
"github.com/lightningnetwork/lnd/lntest/wait"
19+
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
1620
"github.com/stretchr/testify/require"
1721
)
1822

@@ -324,7 +328,7 @@ func testOutboundRemoteSigner(ht *lntest.HarnessTest) {
324328
"--remotesigner.timeout=30s",
325329
"--remotesigner.requesttimeout=30s",
326330
}, commitArgs...),
327-
password,
331+
password, true,
328332
)
329333

330334
// As the signer node will make an outbound connection to the
@@ -412,6 +416,107 @@ func testOutboundRemoteSigner(ht *lntest.HarnessTest) {
412416
}
413417
}
414418

419+
// testOutboundRSMacaroonEnforcement tests that a valid macaroon including
420+
// the `remotesigner` entity is required to connect to a watch-only node that
421+
// uses an outbound remote signer, while the watch-only node is in the state
422+
// where it waits for the signer to connect.
423+
func testOutboundRSMacaroonEnforcement(ht *lntest.HarnessTest) {
424+
// Ensure that the watch-only node uses a configuration that requires an
425+
// outbound remote signer during startup.
426+
watchOnlyArgs := []string{
427+
"--remotesigner.enable",
428+
"--remotesigner.signertype=outbound",
429+
"--remotesigner.timeout=15s",
430+
"--remotesigner.requesttimeout=15s",
431+
}
432+
433+
// Create the watch-only node. Note that we require authentication for
434+
// the watch-only node, as we want to test that the macaroon enforcement
435+
// works as expected.
436+
watchOnly := ht.CreateNewNode("WatchOnly", watchOnlyArgs, nil, false)
437+
438+
startChan := make(chan error)
439+
440+
// Start the watch-only node in a goroutine as it requires a remote
441+
// signer to connect before it can fully start.
442+
go func() {
443+
startChan <- watchOnly.Start(ht.Context())
444+
}()
445+
446+
// Wait and ensure that the watch-only node reaches the state where
447+
// it waits for the remote signer to connect, as this is the state where
448+
// we want to test the macaroon enforcement.
449+
err := wait.Predicate(func() bool {
450+
if watchOnly.RPC == nil {
451+
return false
452+
}
453+
454+
state, err := watchOnly.RPC.State.GetState(
455+
ht.Context(), &lnrpc.GetStateRequest{},
456+
)
457+
if err != nil {
458+
return false
459+
}
460+
461+
return state.State == lnrpc.WalletState_ALLOW_REMOTE_SIGNER
462+
}, 5*time.Second)
463+
require.NoError(ht, err)
464+
465+
// Set up a connection to the watch-only node. However, instead of using
466+
// the watch-only node's admin macaroon, we'll use the invoice macaroon.
467+
// The connection should not be allowed using this macaroon because it
468+
// lacks the `remotesigner` entity required when the signer node
469+
// connects to the watch-only node.
470+
cfg := &lncfg.RemoteSigner{
471+
SignerType: lncfg.SignerClientType,
472+
RPCHost: watchOnly.Cfg.RPCAddr(),
473+
MacaroonPath: watchOnly.Cfg.InvoiceMacPath,
474+
TLSCertPath: watchOnly.Cfg.TLSCertPath,
475+
Timeout: 10 * time.Second,
476+
}
477+
streamFeeder := rpcwallet.NewStreamFeeder(
478+
cfg.RPCHost, cfg.MacaroonPath, cfg.TLSCertPath,
479+
cfg.RequestTimeout,
480+
)
481+
482+
stream, cleanup, err := streamFeeder.GetStream(ht.Context())
483+
require.NoError(ht, err)
484+
485+
defer cleanup()
486+
487+
// Since we're using an unauthorized macaroon, we should expect to be
488+
// denied access to the watch-only node.
489+
_, err = stream.Recv()
490+
require.ErrorContains(ht, err, "permission denied")
491+
492+
// Finally, connect a real signer to the watch-only node so that
493+
// it can start up properly.
494+
signerArgs := []string{
495+
"--remotesigner.signertype=signer",
496+
"--remotesigner.timeout=30s",
497+
"--remotesigner.requesttimeout=10s",
498+
fmt.Sprintf(
499+
"--remotesigner.rpchost=localhost:%d",
500+
watchOnly.Cfg.RPCPort,
501+
),
502+
fmt.Sprintf(
503+
"--remotesigner.tlscertpath=%s",
504+
watchOnly.Cfg.TLSCertPath,
505+
),
506+
fmt.Sprintf(
507+
"--remotesigner.macaroonpath=%s",
508+
watchOnly.Cfg.AdminMacPath, // An authorized macaroon.
509+
),
510+
}
511+
512+
_ = ht.NewNode("Signer", signerArgs)
513+
514+
// Finally, wait and ensure that the watch-only node is able to start
515+
// up properly.
516+
err = <-startChan
517+
require.NoError(ht, err, "Shouldn't error on watch-only node startup")
518+
}
519+
415520
// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd
416521
// internal key scope.
417522
func deriveCustomScopeAccounts(t *testing.T) []*lnrpc.WatchOnlyAccount {

lntest/harness.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ func (h *HarnessTest) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config,
817817
func (h *HarnessTest) NewNodeWatchOnly(name string, extraArgs []string,
818818
password []byte, watchOnly *lnrpc.WatchOnly) *node.HarnessNode {
819819

820-
hn := h.CreateNewNode(name, extraArgs, password)
820+
hn := h.CreateNewNode(name, extraArgs, password, true)
821821

822822
h.StartWatchOnly(hn, name, password, watchOnly)
823823

@@ -827,9 +827,9 @@ func (h *HarnessTest) NewNodeWatchOnly(name string, extraArgs []string,
827827
// CreateNodeWatchOnly creates a new node and asserts its creation. The function
828828
// will only create the node and will not start it.
829829
func (h *HarnessTest) CreateNewNode(name string, extraArgs []string,
830-
password []byte) *node.HarnessNode {
830+
password []byte, noAuth bool) *node.HarnessNode {
831831

832-
hn, err := h.manager.newNode(h.T, name, extraArgs, password, true)
832+
hn, err := h.manager.newNode(h.T, name, extraArgs, password, noAuth)
833833
require.NoErrorf(h, err, "unable to create new node for %s", name)
834834

835835
return hn

0 commit comments

Comments
 (0)