From 067b1842fe7f65413e1c74365ac3a29cc4a9b681 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Fri, 4 Feb 2022 12:40:10 +0100 Subject: [PATCH] itest: add REST integration test --- itest/litd_mode_integrated_test.go | 90 +++++++++++++++++++++++++++++- itest/litd_mode_remote_test.go | 21 +++++++ itest/litd_node.go | 3 +- 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index 0b9ab1a8a..4de0fc28a 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -138,6 +138,7 @@ var ( supportsUIPasswordOnLndPort bool supportsUIPasswordOnLitPort bool grpcWebURI string + restWebURI string }{{ name: "lnrpc", macaroonFn: lndMacaroonFn, @@ -148,6 +149,7 @@ var ( supportsUIPasswordOnLndPort: false, supportsUIPasswordOnLitPort: true, grpcWebURI: "/lnrpc.Lightning/GetInfo", + restWebURI: "/v1/getinfo", }, { name: "frdrpc", macaroonFn: faradayMacaroonFn, @@ -158,6 +160,7 @@ var ( supportsUIPasswordOnLndPort: false, supportsUIPasswordOnLitPort: true, grpcWebURI: "/frdrpc.FaradayServer/RevenueReport", + restWebURI: "/v1/faraday/revenue", }, { name: "looprpc", macaroonFn: loopMacaroonFn, @@ -168,6 +171,7 @@ var ( supportsUIPasswordOnLndPort: false, supportsUIPasswordOnLitPort: true, grpcWebURI: "/looprpc.SwapClient/ListSwaps", + restWebURI: "/v1/loop/swaps", }, { name: "poolrpc", macaroonFn: poolMacaroonFn, @@ -178,6 +182,7 @@ var ( supportsUIPasswordOnLndPort: false, supportsUIPasswordOnLitPort: true, grpcWebURI: "/poolrpc.Trader/GetInfo", + restWebURI: "/v1/pool/info", }, { name: "litrpc", macaroonFn: nil, @@ -326,6 +331,27 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) { }) } }) + + t.t.Run("REST auth", func(tt *testing.T) { + cfg := net.Alice.Cfg + + for _, endpoint := range endpoints { + endpoint := endpoint + + if endpoint.restWebURI == "" { + continue + } + + tt.Run(endpoint.name+" lit port", func(ttt *testing.T) { + runRESTAuthTest( + ttt, cfg.LitAddr(), cfg.UIPassword, + endpoint.macaroonFn(cfg), + endpoint.restWebURI, + endpoint.successPattern, + ) + }) + } + }) } // runCertificateCheck checks that the TLS certificates presented to clients are @@ -513,6 +539,58 @@ func runGRPCWebAuthTest(t *testing.T, hostPort, uiPassword, grpcWebURI string) { require.Contains(t, body, "grpc-status: 0") } +// runRESTAuthTest tests authentication of the given REST interface. +func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI, + successPattern string) { + + basicAuth := base64.StdEncoding.EncodeToString( + []byte(fmt.Sprintf("%s:%s", uiPassword, uiPassword)), + ) + basicAuthHeader := http.Header{ + "authorization": []string{fmt.Sprintf("Basic %s", basicAuth)}, + } + url := fmt.Sprintf("https://%s%s", hostPort, restURI) + + // First test a REST call without authorization, which should fail. + body, responseHeader, err := callURL(url, "GET", nil, nil, false) + require.NoError(t, err) + + require.Equal( + t, "application/grpc", + responseHeader.Get("grpc-metadata-content-type"), + ) + require.Equal( + t, "application/json", + responseHeader.Get("content-type"), + ) + require.Contains( + t, body, + "{\"code\":2, \"message\":\"expected 1 macaroon, got 0\"", + ) + + // Now add the UI password which should make the request succeed. + body, responseHeader, err = callURL( + url, "GET", nil, basicAuthHeader, false, + ) + require.NoError(t, err) + require.Contains(t, body, successPattern) + + // And finally, try with the given macaroon. + macBytes, err := ioutil.ReadFile(macaroonPath) + require.NoError(t, err) + + macaroonHeader := http.Header{ + "grpc-metadata-macaroon": []string{ + hex.EncodeToString(macBytes), + }, + } + body, responseHeader, err = callURL( + url, "GET", nil, macaroonHeader, false, + ) + require.NoError(t, err) + require.Contains(t, body, successPattern) +} + // getURL retrieves the body of a given URL, ignoring any TLS certificate the // server might present. func getURL(url string) (string, error) { @@ -538,7 +616,15 @@ func getURL(url string) (string, error) { func postURL(url string, postBody []byte, header http.Header) (string, http.Header, error) { - req, err := http.NewRequest("POST", url, bytes.NewReader(postBody)) + return callURL(url, "POST", postBody, header, true) +} + +// callURL does a HTTP call to the given URL, ignoring any TLS certificate the +// server might present. +func callURL(url, method string, postBody []byte, header http.Header, + expectOk bool) (string, http.Header, error) { + + req, err := http.NewRequest(method, url, bytes.NewReader(postBody)) if err != nil { return "", nil, err } @@ -552,7 +638,7 @@ func postURL(url string, postBody []byte, header http.Header) (string, return "", nil, err } - if resp.StatusCode != 200 { + if expectOk && resp.StatusCode != 200 { return "", nil, fmt.Errorf("request failed, got status code "+ "%d (%s)", resp.StatusCode, resp.Status) } diff --git a/itest/litd_mode_remote_test.go b/itest/litd_mode_remote_test.go index a80088f30..793907aa6 100644 --- a/itest/litd_mode_remote_test.go +++ b/itest/litd_mode_remote_test.go @@ -118,4 +118,25 @@ func testModeRemote(net *NetworkHarness, t *harnessTest) { }) } }) + + t.t.Run("REST auth", func(tt *testing.T) { + cfg := net.Bob.Cfg + + for _, endpoint := range endpoints { + endpoint := endpoint + + if endpoint.restWebURI == "" { + continue + } + + tt.Run(endpoint.name+" lit port", func(ttt *testing.T) { + runRESTAuthTest( + ttt, cfg.LitAddr(), cfg.UIPassword, + endpoint.macaroonFn(cfg), + endpoint.restWebURI, + endpoint.successPattern, + ) + }) + } + }) } diff --git a/itest/litd_node.go b/itest/litd_node.go index f5cdd2cb7..44cb03a82 100644 --- a/itest/litd_node.go +++ b/itest/litd_node.go @@ -109,6 +109,7 @@ func (cfg *LitNodeConfig) GenArgs() []string { fmt.Sprintf("--loop.loopdir=%s", cfg.LoopDir), fmt.Sprintf("--pool.basedir=%s", cfg.PoolDir), fmt.Sprintf("--uipassword=%s", cfg.UIPassword), + "--enablerest", "--restcors=*", } ) @@ -139,7 +140,7 @@ func (cfg *LitNodeConfig) GenArgs() []string { return litArgs } - + // All arguments so far were for lnd. Let's namespace them now so we can // add args for the other daemons and LiT itself afterwards. litArgs = append(litArgs, cfg.LitArgs...)