@@ -10,13 +10,16 @@ import (
10
10
"fmt"
11
11
"io/ioutil"
12
12
"net/http"
13
+ "os"
13
14
"strings"
14
15
"testing"
15
16
"time"
16
17
17
18
"github.com/btcsuite/btcutil"
18
19
"github.com/lightninglabs/faraday/frdrpc"
20
+ terminal "github.com/lightninglabs/lightning-terminal"
19
21
"github.com/lightninglabs/lightning-terminal/litrpc"
22
+ "github.com/lightninglabs/lightning-terminal/session"
20
23
"github.com/lightninglabs/loop/looprpc"
21
24
"github.com/lightninglabs/pool/poolrpc"
22
25
"github.com/lightningnetwork/lnd/lnrpc"
@@ -135,6 +138,7 @@ var (
135
138
supportsUIPasswordOnLndPort bool
136
139
supportsUIPasswordOnLitPort bool
137
140
grpcWebURI string
141
+ restWebURI string
138
142
}{{
139
143
name : "lnrpc" ,
140
144
macaroonFn : lndMacaroonFn ,
@@ -145,6 +149,7 @@ var (
145
149
supportsUIPasswordOnLndPort : false ,
146
150
supportsUIPasswordOnLitPort : true ,
147
151
grpcWebURI : "/lnrpc.Lightning/GetInfo" ,
152
+ restWebURI : "/v1/getinfo" ,
148
153
}, {
149
154
name : "frdrpc" ,
150
155
macaroonFn : faradayMacaroonFn ,
@@ -155,6 +160,7 @@ var (
155
160
supportsUIPasswordOnLndPort : false ,
156
161
supportsUIPasswordOnLitPort : true ,
157
162
grpcWebURI : "/frdrpc.FaradayServer/RevenueReport" ,
163
+ restWebURI : "/v1/faraday/revenue" ,
158
164
}, {
159
165
name : "looprpc" ,
160
166
macaroonFn : loopMacaroonFn ,
@@ -165,6 +171,7 @@ var (
165
171
supportsUIPasswordOnLndPort : false ,
166
172
supportsUIPasswordOnLitPort : true ,
167
173
grpcWebURI : "/looprpc.SwapClient/ListSwaps" ,
174
+ restWebURI : "/v1/loop/swaps" ,
168
175
}, {
169
176
name : "poolrpc" ,
170
177
macaroonFn : poolMacaroonFn ,
@@ -175,6 +182,7 @@ var (
175
182
supportsUIPasswordOnLndPort : false ,
176
183
supportsUIPasswordOnLitPort : true ,
177
184
grpcWebURI : "/poolrpc.Trader/GetInfo" ,
185
+ restWebURI : "/v1/pool/info" ,
178
186
}, {
179
187
name : "litrpc" ,
180
188
macaroonFn : nil ,
@@ -283,6 +291,67 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) {
283
291
})
284
292
}
285
293
})
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
+ })
286
355
}
287
356
288
357
// runCertificateCheck checks that the TLS certificates presented to clients are
@@ -470,6 +539,58 @@ func runGRPCWebAuthTest(t *testing.T, hostPort, uiPassword, grpcWebURI string) {
470
539
require .Contains (t , body , "grpc-status: 0" )
471
540
}
472
541
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
+
473
594
// getURL retrieves the body of a given URL, ignoring any TLS certificate the
474
595
// server might present.
475
596
func getURL (url string ) (string , error ) {
@@ -495,7 +616,15 @@ func getURL(url string) (string, error) {
495
616
func postURL (url string , postBody []byte , header http.Header ) (string ,
496
617
http.Header , error ) {
497
618
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 ))
499
628
if err != nil {
500
629
return "" , nil , err
501
630
}
@@ -509,7 +638,7 @@ func postURL(url string, postBody []byte, header http.Header) (string,
509
638
return "" , nil , err
510
639
}
511
640
512
- if resp .StatusCode != 200 {
641
+ if expectOk && resp .StatusCode != 200 {
513
642
return "" , nil , fmt .Errorf ("request failed, got status code " +
514
643
"%d (%s)" , resp .StatusCode , resp .Status )
515
644
}
@@ -601,3 +730,51 @@ func connectRPC(ctx context.Context, hostPort,
601
730
602
731
return grpc .DialContext (ctx , hostPort , opts ... )
603
732
}
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