From b76832a92ede47a07fac63fc3b0ea662b23e37c6 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 14 Oct 2024 15:21:40 +0200 Subject: [PATCH] itest: add a stateless_init test Extract some of the tests used for the integrated and remote mode tests and re-use them in a new test that runs them against a node stareted in state-less init mode. --- itest/litd_mode_integrated_test.go | 227 +++++++++++++++++++---------- itest/litd_mode_remote_test.go | 13 +- itest/litd_stateless_init_test.go | 62 ++++++++ itest/litd_test_list_on_test.go | 4 + 4 files changed, 223 insertions(+), 83 deletions(-) create mode 100644 itest/litd_stateless_init_test.go diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index 8533e5a5..50fda931 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -225,6 +225,7 @@ var ( restWebURI string restPOST bool canDisable bool + isSubServer bool // noAuth is true if the call does not require a macaroon. noAuth bool @@ -277,6 +278,7 @@ var ( grpcWebURI: "/frdrpc.FaradayServer/RevenueReport", restWebURI: "/v1/faraday/revenue", canDisable: true, + isSubServer: true, }, { name: "looprpc", macaroonFn: loopMacaroonFn, @@ -287,6 +289,7 @@ var ( grpcWebURI: "/looprpc.SwapClient/ListSwaps", restWebURI: "/v1/loop/swaps", canDisable: true, + isSubServer: true, }, { name: "poolrpc", macaroonFn: poolMacaroonFn, @@ -297,6 +300,7 @@ var ( grpcWebURI: "/poolrpc.Trader/GetInfo", restWebURI: "/v1/pool/info", canDisable: true, + isSubServer: true, }, { name: "taprpc", macaroonFn: tapMacaroonFn, @@ -307,6 +311,7 @@ var ( grpcWebURI: "/taprpc.TaprootAssets/ListAssets", restWebURI: "/v1/taproot-assets/assets", canDisable: true, + isSubServer: true, }, { name: "taprpc-whitelist", macaroonFn: emptyMacaroonFn, @@ -318,6 +323,7 @@ var ( restWebURI: "/v1/taproot-assets/universe/info", canDisable: true, noAuth: true, + isSubServer: true, }, { name: "litrpc-sessions", macaroonFn: litMacaroonFn, @@ -494,6 +500,7 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpoint.successPattern, endpointDisabled || endpoint.litOnly, "Unimplemented desc = unknown service", + endpoint.isSubServer, false, ) }) @@ -506,47 +513,18 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpoint.successPattern, endpointDisabled, endpoint.disabledPattern, + endpoint.isSubServer, + false, ) }) } }) t.Run("UI password auth check", func(tt *testing.T) { - cfg := net.Alice.Cfg - - for _, endpoint := range endpoints { - endpoint := endpoint - endpointDisabled := subServersDisabled && - endpoint.canDisable - - tt.Run(endpoint.name+" lnd port", func(ttt *testing.T) { - runUIPasswordCheck( - ttt, cfg.RPCAddr(), cfg.TLSCertPath, - cfg.UIPassword, endpoint.requestFn, - endpoint.noAuth, - true, endpoint.successPattern, - endpointDisabled || endpoint.litOnly, - "Unimplemented desc = unknown service", - ) - }) - - tt.Run(endpoint.name+" lit port", func(ttt *testing.T) { - shouldFailWithoutMacaroon := false - if withoutUIPassword { - shouldFailWithoutMacaroon = true - } - - runUIPasswordCheck( - ttt, cfg.LitAddr(), cfg.LitTLSCertPath, - cfg.UIPassword, endpoint.requestFn, - endpoint.noAuth, - shouldFailWithoutMacaroon, - endpoint.successPattern, - endpointDisabled, - endpoint.disabledPattern, - ) - }) - } + uiPasswordAuthCheck( + tt, net.Alice.Cfg, subServersDisabled, + withoutUIPassword, false, + ) }) t.Run("UI index page fallback", func(tt *testing.T) { @@ -576,37 +554,10 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, }) t.Run("gRPC super macaroon auth check", func(tt *testing.T) { - cfg := net.Alice.Cfg - - superMacFile := bakeSuperMacaroon(tt, cfg, true) - - for _, endpoint := range endpoints { - endpoint := endpoint - endpointDisabled := subServersDisabled && - endpoint.canDisable - - tt.Run(endpoint.name+" lnd port", func(ttt *testing.T) { - runGRPCAuthTest( - ttt, cfg.RPCAddr(), cfg.TLSCertPath, - superMacFile, endpoint.noAuth, - endpoint.requestFn, - endpoint.successPattern, - endpointDisabled || endpoint.litOnly, - "Unimplemented desc = unknown service", - ) - }) - - tt.Run(endpoint.name+" lit port", func(ttt *testing.T) { - runGRPCAuthTest( - ttt, cfg.LitAddr(), cfg.LitTLSCertPath, - superMacFile, endpoint.noAuth, - endpoint.requestFn, - endpoint.successPattern, - endpointDisabled, - endpoint.disabledPattern, - ) - }) - } + superMacaroonAuth( + tt, net.Alice.Cfg, subServersDisabled, false, + getLiTMacFromFile, + ) }) t.Run("REST auth", func(tt *testing.T) { @@ -673,7 +624,9 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, return } - superMacFile := bakeSuperMacaroon(tt, cfg, false) + superMacFile := bakeSuperMacaroon( + tt, cfg, getLiTMacFromFile, false, + ) ht := newHarnessTest(tt, net) runAccountSystemTest( @@ -740,6 +693,48 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, }) } +func uiPasswordAuthCheck(t *testing.T, cfg *LitNodeConfig, subServersDisabled, + withoutUIPassword, statelessInit bool) { + + for _, endpoint := range endpoints { + endpoint := endpoint + endpointDisabled := subServersDisabled && + endpoint.canDisable + + t.Run(endpoint.name+" lnd port", func(ttt *testing.T) { + runUIPasswordCheck( + ttt, cfg.RPCAddr(), cfg.TLSCertPath, + cfg.UIPassword, endpoint.requestFn, + endpoint.noAuth, + true, + endpoint.successPattern, + endpointDisabled || endpoint.litOnly, + "Unimplemented desc = unknown service", + endpoint.isSubServer, + statelessInit, + ) + }) + + t.Run(endpoint.name+" lit port", func(ttt *testing.T) { + shouldFailWithoutMacaroon := false + if withoutUIPassword { + shouldFailWithoutMacaroon = true + } + + runUIPasswordCheck( + ttt, cfg.LitAddr(), cfg.LitTLSCertPath, + cfg.UIPassword, endpoint.requestFn, + endpoint.noAuth, + shouldFailWithoutMacaroon, + endpoint.successPattern, + endpointDisabled, + endpoint.disabledPattern, + endpoint.isSubServer, statelessInit, + ) + }) + } +} + // setUpLNCConn creates a new LNC session and then creates a connection to that // session via the mailbox that the session was created with. func setUpLNCConn(ctx context.Context, t *testing.T, hostPort, tlsCertPath, @@ -778,6 +773,43 @@ func setUpLNCConn(ctx context.Context, t *testing.T, hostPort, tlsCertPath, return rawLNCConn } +func superMacaroonAuth(t *testing.T, cfg *LitNodeConfig, + subServersDisabled, statelessInit bool, + getMac func(*testing.T, *LitNodeConfig) []byte) { + + superMacFile := bakeSuperMacaroon(t, cfg, getMac, true) + + for _, endpoint := range endpoints { + endpoint := endpoint + endpointDisabled := subServersDisabled && + endpoint.canDisable + + t.Run(endpoint.name+" lnd port", func(ttt *testing.T) { + runGRPCAuthTest( + ttt, cfg.RPCAddr(), cfg.TLSCertPath, + superMacFile, endpoint.noAuth, + endpoint.requestFn, + endpoint.successPattern, + endpointDisabled || endpoint.litOnly, + "Unimplemented desc = unknown service", + endpoint.isSubServer, statelessInit, + ) + }) + + t.Run(endpoint.name+" lit port", func(ttt *testing.T) { + runGRPCAuthTest( + ttt, cfg.LitAddr(), cfg.LitTLSCertPath, + superMacFile, endpoint.noAuth, + endpoint.requestFn, + endpoint.successPattern, + endpointDisabled, + endpoint.disabledPattern, + endpoint.isSubServer, statelessInit, + ) + }) + } +} + // runCertificateCheck checks that the TLS certificates presented to clients are // what we expect them to be. func runCertificateCheck(t *testing.T, node *HarnessNode) { @@ -799,7 +831,7 @@ func runCertificateCheck(t *testing.T, node *HarnessNode) { // runGRPCAuthTest tests authentication of the given gRPC interface. func runGRPCAuthTest(t *testing.T, hostPort, tlsCertPath, macPath string, noMac bool, makeRequest requestFn, successContent string, disabled bool, - disabledErr string) { + disabledErr string, isSubServer, statelessInit bool) { ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -833,13 +865,27 @@ func runGRPCAuthTest(t *testing.T, hostPort, tlsCertPath, macPath string, // Add dummy data as the macaroon, that should fail as well. ctxm := macaroonContext(ctxt, []byte("dummy")) _, err = makeRequest(ctxm, rawConn) - require.ErrorContains(t, err, "packet too short") + if statelessInit && isSubServer { + // Some sub-servers have slightly different structured + // errors but all of them contain these two words/phrases. + require.ErrorContains(t, err, "macaroon service") + require.ErrorContains(t, err, "initialised") + } else { + require.ErrorContains(t, err, "packet too short") + } // Add a macaroon that can be parsed but that's not issued by lnd, which // should also fail. ctxm = macaroonContext(ctxt, dummyMacBytes) _, err = makeRequest(ctxm, rawConn) - require.ErrorContains(t, err, "invalid ID") + if statelessInit && isSubServer { + // Some sub-servers have slightly different structured + // errors but all of them contain these two words/phrases. + require.ErrorContains(t, err, "macaroon service") + require.ErrorContains(t, err, "initialised") + } else { + require.ErrorContains(t, err, "invalid ID") + } // Then finally we try with the correct macaroon which should now // succeed, as long as it is not for a disabled sub-server. @@ -861,7 +907,8 @@ func runGRPCAuthTest(t *testing.T, hostPort, tlsCertPath, macPath string, // runUIPasswordCheck tests UI password authentication. func runUIPasswordCheck(t *testing.T, hostPort, tlsCertPath, uiPassword string, makeRequest requestFn, noAuth, shouldFailWithoutMacaroon bool, - successContent string, disabled bool, disabledErr string) { + successContent string, disabled bool, disabledErr string, + isSubServer, statelessInit bool) { ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -903,9 +950,15 @@ func runUIPasswordCheck(t *testing.T, hostPort, tlsCertPath, uiPassword string, // shouldn't be allowed and result in an error. ctxm = uiPasswordContext(ctxt, "foobar", true) _, err = makeRequest(ctxm, rawConn) - if disabled { + switch { + case disabled: require.ErrorContains(t, err, disabledErr) - } else { + case statelessInit && isSubServer: + // Some sub-servers have slightly different structured + // errors but all of them contain these two words/phrases. + require.ErrorContains(t, err, "macaroon service") + require.ErrorContains(t, err, "initialised") + default: require.ErrorContains(t, err, "invalid ID") } @@ -931,9 +984,16 @@ func runUIPasswordCheck(t *testing.T, hostPort, tlsCertPath, uiPassword string, ctxm = uiPasswordContext(ctxt, uiPassword, true) _, err = makeRequest(ctxm, rawConn) - if disabled { + switch { + case disabled: require.ErrorContains(t, err, disabledErr) - } else { + case statelessInit && isSubServer: + // Some sub-servers have slightly different structured + // errors but all of them contain these two + // words/phrases. + require.ErrorContains(t, err, "macaroon service") + require.ErrorContains(t, err, "initialised") + default: require.ErrorContains(t, err, "invalid ID") } @@ -1373,8 +1433,16 @@ func connectRPC(ctx context.Context, hostPort, return grpc.DialContext(ctx, hostPort, opts...) } -func bakeSuperMacaroon(t *testing.T, cfg *LitNodeConfig, readOnly bool) string { +func getLiTMacFromFile(t *testing.T, cfg *LitNodeConfig) []byte { litMac := litMacaroonFn(cfg) + litMacBytes, err := os.ReadFile(litMac) + require.NoError(t, err) + + return litMacBytes +} + +func bakeSuperMacaroon(t *testing.T, cfg *LitNodeConfig, + getMac func(*testing.T, *LitNodeConfig) []byte, readOnly bool) string { ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -1385,14 +1453,11 @@ func bakeSuperMacaroon(t *testing.T, cfg *LitNodeConfig, readOnly bool) string { defer rawConn.Close() - litMacBytes, err := os.ReadFile(litMac) - require.NoError(t, err) - - litMacCtx := macaroonContext(ctxt, litMacBytes) + macCtx := macaroonContext(ctxt, getMac(t, cfg)) litConn := litrpc.NewProxyClient(rawConn) bakeMacResp, err := litConn.BakeSuperMacaroon( - litMacCtx, &litrpc.BakeSuperMacaroonRequest{ + macCtx, &litrpc.BakeSuperMacaroonRequest{ RootKeyIdSuffix: 0, ReadOnly: readOnly, }, diff --git a/itest/litd_mode_remote_test.go b/itest/litd_mode_remote_test.go index 6d02e1d9..35df9048 100644 --- a/itest/litd_mode_remote_test.go +++ b/itest/litd_mode_remote_test.go @@ -67,6 +67,8 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpoint.successPattern, endpointEnabled, endpoint.disabledPattern, + endpoint.isSubServer, + false, ) }) } @@ -94,6 +96,7 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpoint.successPattern, endpointEnabled, endpoint.disabledPattern, + endpoint.isSubServer, false, ) }) } @@ -126,7 +129,9 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, t.Run("gRPC super macaroon auth check", func(tt *testing.T) { cfg := net.Bob.Cfg - superMacFile := bakeSuperMacaroon(tt, cfg, true) + superMacFile := bakeSuperMacaroon( + tt, cfg, getLiTMacFromFile, true, + ) for _, endpoint := range endpoints { endpoint := endpoint @@ -141,6 +146,8 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpoint.successPattern, endpointEnabled, endpoint.disabledPattern, + endpoint.isSubServer, + false, ) }) } @@ -261,7 +268,9 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, return } - superMacFile := bakeSuperMacaroon(tt, cfg, false) + superMacFile := bakeSuperMacaroon( + tt, cfg, getLiTMacFromFile, false, + ) ht := newHarnessTest(tt, net) runAccountSystemTest( diff --git a/itest/litd_stateless_init_test.go b/itest/litd_stateless_init_test.go new file mode 100644 index 00000000..c9224b4b --- /dev/null +++ b/itest/litd_stateless_init_test.go @@ -0,0 +1,62 @@ +package itest + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +// testStatelessInitMode runs various authentication tests against a node that +// is running in stateless-init mode. +func testStatelessInitMode(_ context.Context, net *NetworkHarness, + t *harnessTest) { + + // Set up a new node (charlie) in stateless init mode (create and + // unlock). + walletPassword := []byte("stateless") + charlie, adminMac := net.NewNodeWithSeed( + t.t, "Charlie", nil, walletPassword, false, true, + ) + defer shutdownAndAssert(net, t, charlie) + + // assertNoFiles is a helper that can be used to assert that a set of + // files does not exist. + assertNoFiles := func(paths ...string) { + for _, path := range paths { + _, err := os.Stat(path) + require.Error(t.t, err) + } + } + + // Assert that there are no macaroon on the file system. + assertNoFiles( + // LND macaroons. + charlie.Cfg.AdminMacPath, + charlie.Cfg.ReadMacPath, + charlie.Cfg.InvoiceMacPath, + // LiT macaroon. + charlie.Cfg.LitMacPath, + // Sub-server macaroons + charlie.Cfg.LoopMacPath, charlie.Cfg.PoolMacPath, + charlie.Cfg.FaradayMacPath, charlie.Cfg.TapMacPath, + ) + + // Show that UI functions work as expected. + t.t.Run("UI password auth check", func(tt *testing.T) { + uiPasswordAuthCheck(tt, charlie.Cfg, false, false, true) + }) + + // Baking a super macaroon using the admin macaroon we received on + // wallet creation and performing the calls with that macaroon should + // work. + t.t.Run("gRPC super macaroon auth check", func(tt *testing.T) { + superMacaroonAuth( + tt, charlie.Cfg, false, true, + func(t *testing.T, config *LitNodeConfig) []byte { + return adminMac + }, + ) + }) +} diff --git a/itest/litd_test_list_on_test.go b/itest/litd_test_list_on_test.go index 282f5b57..1ddc5a2d 100644 --- a/itest/litd_test_list_on_test.go +++ b/itest/litd_test_list_on_test.go @@ -12,6 +12,10 @@ var allTestCases = []*testCase{ name: "test mode remote", test: testModeRemote, }, + { + name: "stateless init mode", + test: testStatelessInitMode, + }, { name: "test firewall rules", test: testFirewallRules,