@@ -10,13 +10,16 @@ import (
1010 "fmt"
1111 "io/ioutil"
1212 "net/http"
13+ "os"
1314 "strings"
1415 "testing"
1516 "time"
1617
1718 "github.com/btcsuite/btcutil"
1819 "github.com/lightninglabs/faraday/frdrpc"
20+ terminal "github.com/lightninglabs/lightning-terminal"
1921 "github.com/lightninglabs/lightning-terminal/litrpc"
22+ "github.com/lightninglabs/lightning-terminal/session"
2023 "github.com/lightninglabs/loop/looprpc"
2124 "github.com/lightninglabs/pool/poolrpc"
2225 "github.com/lightningnetwork/lnd/lnrpc"
@@ -135,6 +138,7 @@ var (
135138 supportsUIPasswordOnLndPort bool
136139 supportsUIPasswordOnLitPort bool
137140 grpcWebURI string
141+ restWebURI string
138142 }{{
139143 name : "lnrpc" ,
140144 macaroonFn : lndMacaroonFn ,
@@ -145,6 +149,7 @@ var (
145149 supportsUIPasswordOnLndPort : false ,
146150 supportsUIPasswordOnLitPort : true ,
147151 grpcWebURI : "/lnrpc.Lightning/GetInfo" ,
152+ restWebURI : "/v1/getinfo" ,
148153 }, {
149154 name : "frdrpc" ,
150155 macaroonFn : faradayMacaroonFn ,
@@ -155,6 +160,7 @@ var (
155160 supportsUIPasswordOnLndPort : false ,
156161 supportsUIPasswordOnLitPort : true ,
157162 grpcWebURI : "/frdrpc.FaradayServer/RevenueReport" ,
163+ restWebURI : "/v1/faraday/revenue" ,
158164 }, {
159165 name : "looprpc" ,
160166 macaroonFn : loopMacaroonFn ,
@@ -165,6 +171,7 @@ var (
165171 supportsUIPasswordOnLndPort : false ,
166172 supportsUIPasswordOnLitPort : true ,
167173 grpcWebURI : "/looprpc.SwapClient/ListSwaps" ,
174+ restWebURI : "/v1/loop/swaps" ,
168175 }, {
169176 name : "poolrpc" ,
170177 macaroonFn : poolMacaroonFn ,
@@ -175,6 +182,7 @@ var (
175182 supportsUIPasswordOnLndPort : false ,
176183 supportsUIPasswordOnLitPort : true ,
177184 grpcWebURI : "/poolrpc.Trader/GetInfo" ,
185+ restWebURI : "/v1/pool/info" ,
178186 }, {
179187 name : "litrpc" ,
180188 macaroonFn : nil ,
@@ -283,6 +291,67 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) {
283291 })
284292 }
285293 })
294+
295+ t .t .Run ("gRPC super macaroon auth check" , func (tt * testing.T ) {
296+ cfg := net .Alice .Cfg
297+
298+ superMacFile , err := bakeSuperMacaroon (cfg , true )
299+ require .NoError (tt , err )
300+
301+ defer func () {
302+ _ = os .Remove (superMacFile )
303+ }()
304+
305+ for _ , endpoint := range endpoints {
306+ endpoint := endpoint
307+ tt .Run (endpoint .name + " lnd port" , func (ttt * testing.T ) {
308+ if ! endpoint .supportsMacAuthOnLndPort {
309+ return
310+ }
311+
312+ runGRPCAuthTest (
313+ ttt , cfg .RPCAddr (), cfg .TLSCertPath ,
314+ superMacFile ,
315+ endpoint .requestFn ,
316+ endpoint .successPattern ,
317+ )
318+ })
319+
320+ tt .Run (endpoint .name + " lit port" , func (ttt * testing.T ) {
321+ if ! endpoint .supportsMacAuthOnLitPort {
322+ return
323+ }
324+
325+ runGRPCAuthTest (
326+ ttt , cfg .LitAddr (), cfg .TLSCertPath ,
327+ superMacFile ,
328+ endpoint .requestFn ,
329+ endpoint .successPattern ,
330+ )
331+ })
332+ }
333+ })
334+
335+ t .t .Run ("REST auth" , func (tt * testing.T ) {
336+ cfg := net .Alice .Cfg
337+
338+ for _ , endpoint := range endpoints {
339+ endpoint := endpoint
340+
341+ if endpoint .restWebURI == "" {
342+ continue
343+ }
344+
345+ tt .Run (endpoint .name + " lit port" , func (ttt * testing.T ) {
346+ runRESTAuthTest (
347+ ttt , cfg .LitAddr (), cfg .UIPassword ,
348+ endpoint .macaroonFn (cfg ),
349+ endpoint .restWebURI ,
350+ endpoint .successPattern ,
351+ )
352+ })
353+ }
354+ })
286355}
287356
288357// runCertificateCheck checks that the TLS certificates presented to clients are
@@ -470,6 +539,58 @@ func runGRPCWebAuthTest(t *testing.T, hostPort, uiPassword, grpcWebURI string) {
470539 require .Contains (t , body , "grpc-status: 0" )
471540}
472541
542+ // runRESTAuthTest tests authentication of the given REST interface.
543+ func runRESTAuthTest (t * testing.T , hostPort , uiPassword , macaroonPath , restURI ,
544+ successPattern string ) {
545+
546+ basicAuth := base64 .StdEncoding .EncodeToString (
547+ []byte (fmt .Sprintf ("%s:%s" , uiPassword , uiPassword )),
548+ )
549+ basicAuthHeader := http.Header {
550+ "authorization" : []string {fmt .Sprintf ("Basic %s" , basicAuth )},
551+ }
552+ url := fmt .Sprintf ("https://%s%s" , hostPort , restURI )
553+
554+ // First test a REST call without authorization, which should fail.
555+ body , responseHeader , err := callURL (url , "GET" , nil , nil , false )
556+ require .NoError (t , err )
557+
558+ require .Equal (
559+ t , "application/grpc" ,
560+ responseHeader .Get ("grpc-metadata-content-type" ),
561+ )
562+ require .Equal (
563+ t , "application/json" ,
564+ responseHeader .Get ("content-type" ),
565+ )
566+ require .Contains (
567+ t , body ,
568+ "expected 1 macaroon, got 0" ,
569+ )
570+
571+ // Now add the UI password which should make the request succeed.
572+ body , responseHeader , err = callURL (
573+ url , "GET" , nil , basicAuthHeader , false ,
574+ )
575+ require .NoError (t , err )
576+ require .Contains (t , body , successPattern )
577+
578+ // And finally, try with the given macaroon.
579+ macBytes , err := ioutil .ReadFile (macaroonPath )
580+ require .NoError (t , err )
581+
582+ macaroonHeader := http.Header {
583+ "grpc-metadata-macaroon" : []string {
584+ hex .EncodeToString (macBytes ),
585+ },
586+ }
587+ body , responseHeader , err = callURL (
588+ url , "GET" , nil , macaroonHeader , false ,
589+ )
590+ require .NoError (t , err )
591+ require .Contains (t , body , successPattern )
592+ }
593+
473594// getURL retrieves the body of a given URL, ignoring any TLS certificate the
474595// server might present.
475596func getURL (url string ) (string , error ) {
@@ -495,7 +616,15 @@ func getURL(url string) (string, error) {
495616func postURL (url string , postBody []byte , header http.Header ) (string ,
496617 http.Header , error ) {
497618
498- req , err := http .NewRequest ("POST" , url , bytes .NewReader (postBody ))
619+ return callURL (url , "POST" , postBody , header , true )
620+ }
621+
622+ // callURL does a HTTP call to the given URL, ignoring any TLS certificate the
623+ // server might present.
624+ func callURL (url , method string , postBody []byte , header http.Header ,
625+ expectOk bool ) (string , http.Header , error ) {
626+
627+ req , err := http .NewRequest (method , url , bytes .NewReader (postBody ))
499628 if err != nil {
500629 return "" , nil , err
501630 }
@@ -509,7 +638,7 @@ func postURL(url string, postBody []byte, header http.Header) (string,
509638 return "" , nil , err
510639 }
511640
512- if resp .StatusCode != 200 {
641+ if expectOk && resp .StatusCode != 200 {
513642 return "" , nil , fmt .Errorf ("request failed, got status code " +
514643 "%d (%s)" , resp .StatusCode , resp .Status )
515644 }
@@ -601,3 +730,51 @@ func connectRPC(ctx context.Context, hostPort,
601730
602731 return grpc .DialContext (ctx , hostPort , opts ... )
603732}
733+
734+ func bakeSuperMacaroon (cfg * LitNodeConfig , readOnly bool ) (string , error ) {
735+ lndAdminMac := lndMacaroonFn (cfg )
736+
737+ ctxb := context .Background ()
738+ ctxt , cancel := context .WithTimeout (ctxb , defaultTimeout )
739+ defer cancel ()
740+
741+ rawConn , err := connectRPC (ctxt , cfg .RPCAddr (), cfg .TLSCertPath )
742+ if err != nil {
743+ return "" , err
744+ }
745+
746+ lndAdminMacBytes , err := ioutil .ReadFile (lndAdminMac )
747+ if err != nil {
748+ return "" , err
749+ }
750+ lndAdminCtx := macaroonContext (ctxt , lndAdminMacBytes )
751+ lndConn := lnrpc .NewLightningClient (rawConn )
752+
753+ superMacPermissions := terminal .GetAllPermissions (readOnly )
754+ nullID := [4 ]byte {}
755+ superMacHex , err := terminal .BakeSuperMacaroon (
756+ lndAdminCtx , lndConn , session .NewSuperMacaroonRootKeyID (nullID ),
757+ superMacPermissions , nil ,
758+ )
759+ if err != nil {
760+ return "" , err
761+ }
762+
763+ // The BakeSuperMacaroon function just hex encoded the macaroon, we know
764+ // it's valid.
765+ superMacBytes , _ := hex .DecodeString (superMacHex )
766+
767+ tempFile , err := ioutil .TempFile ("" , "lit-super-macaroon" )
768+ if err != nil {
769+ _ = os .Remove (tempFile .Name ())
770+ return "" , err
771+ }
772+
773+ err = ioutil .WriteFile (tempFile .Name (), superMacBytes , 0644 )
774+ if err != nil {
775+ _ = os .Remove (tempFile .Name ())
776+ return "" , err
777+ }
778+
779+ return tempFile .Name (), nil
780+ }
0 commit comments