@@ -3,6 +3,7 @@ package itest
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ "crypto/sha512"
6
7
"crypto/tls"
7
8
"crypto/x509"
8
9
"encoding/base64"
@@ -15,13 +16,16 @@ import (
15
16
"testing"
16
17
"time"
17
18
19
+ "github.com/btcsuite/btcd/btcec"
18
20
"github.com/btcsuite/btcutil"
19
21
"github.com/lightninglabs/faraday/frdrpc"
22
+ "github.com/lightninglabs/lightning-node-connect/mailbox"
20
23
terminal "github.com/lightninglabs/lightning-terminal"
21
24
"github.com/lightninglabs/lightning-terminal/litrpc"
22
25
"github.com/lightninglabs/lightning-terminal/session"
23
26
"github.com/lightninglabs/loop/looprpc"
24
27
"github.com/lightninglabs/pool/poolrpc"
28
+ "github.com/lightningnetwork/lnd/keychain"
25
29
"github.com/lightningnetwork/lnd/lnrpc"
26
30
"github.com/stretchr/testify/require"
27
31
"golang.org/x/net/http2"
@@ -39,6 +43,10 @@ const (
39
43
// file of the main UI. This is created by Webpack and should be fairly
40
44
// stable.
41
45
indexHtmlMarker = "webpackJsonplightning-terminal"
46
+
47
+ // mailboxServerAddr is the address of the mailbox server to use during
48
+ // integration tests.
49
+ mailboxServerAddr = "mailbox.testnet.lightningcluster.com:443"
42
50
)
43
51
44
52
// requestFn is a function type for a helper function that makes a daemon
@@ -137,6 +145,7 @@ var (
137
145
supportsMacAuthOnLitPort bool
138
146
supportsUIPasswordOnLndPort bool
139
147
supportsUIPasswordOnLitPort bool
148
+ allowedThroughLNC bool
140
149
grpcWebURI string
141
150
restWebURI string
142
151
}{{
@@ -148,6 +157,7 @@ var (
148
157
supportsMacAuthOnLitPort : true ,
149
158
supportsUIPasswordOnLndPort : false ,
150
159
supportsUIPasswordOnLitPort : true ,
160
+ allowedThroughLNC : true ,
151
161
grpcWebURI : "/lnrpc.Lightning/GetInfo" ,
152
162
restWebURI : "/v1/getinfo" ,
153
163
}, {
@@ -159,6 +169,7 @@ var (
159
169
supportsMacAuthOnLitPort : true ,
160
170
supportsUIPasswordOnLndPort : false ,
161
171
supportsUIPasswordOnLitPort : true ,
172
+ allowedThroughLNC : true ,
162
173
grpcWebURI : "/frdrpc.FaradayServer/RevenueReport" ,
163
174
restWebURI : "/v1/faraday/revenue" ,
164
175
}, {
@@ -170,6 +181,7 @@ var (
170
181
supportsMacAuthOnLitPort : true ,
171
182
supportsUIPasswordOnLndPort : false ,
172
183
supportsUIPasswordOnLitPort : true ,
184
+ allowedThroughLNC : true ,
173
185
grpcWebURI : "/looprpc.SwapClient/ListSwaps" ,
174
186
restWebURI : "/v1/loop/swaps" ,
175
187
}, {
@@ -181,17 +193,22 @@ var (
181
193
supportsMacAuthOnLitPort : true ,
182
194
supportsUIPasswordOnLndPort : false ,
183
195
supportsUIPasswordOnLitPort : true ,
196
+ allowedThroughLNC : true ,
184
197
grpcWebURI : "/poolrpc.Trader/GetInfo" ,
185
198
restWebURI : "/v1/pool/info" ,
186
199
}, {
187
- name : "litrpc" ,
188
- macaroonFn : nil ,
189
- requestFn : litRequestFn ,
190
- successPattern : "\" sessions\" :[]" ,
200
+ name : "litrpc" ,
201
+ macaroonFn : nil ,
202
+ requestFn : litRequestFn ,
203
+ // In some test cases we actually expect some sessions, so we
204
+ // don't explicitly check for an empty array but just the
205
+ // existence of the array in the response.
206
+ successPattern : "\" sessions\" :[" ,
191
207
supportsMacAuthOnLndPort : false ,
192
208
supportsMacAuthOnLitPort : false ,
193
209
supportsUIPasswordOnLndPort : true ,
194
210
supportsUIPasswordOnLitPort : true ,
211
+ allowedThroughLNC : false ,
195
212
grpcWebURI : "/litrpc.Sessions/ListSessions" ,
196
213
}}
197
214
)
@@ -352,6 +369,23 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) {
352
369
})
353
370
}
354
371
})
372
+
373
+ t .t .Run ("lnc auth" , func (tt * testing.T ) {
374
+ cfg := net .Alice .Cfg
375
+
376
+ for _ , endpoint := range endpoints {
377
+ endpoint := endpoint
378
+ tt .Run (endpoint .name + " lit port" , func (ttt * testing.T ) {
379
+ runLNCAuthTest (
380
+ ttt , cfg .LitAddr (), cfg .UIPassword ,
381
+ cfg .TLSCertPath ,
382
+ endpoint .requestFn ,
383
+ endpoint .successPattern ,
384
+ endpoint .allowedThroughLNC ,
385
+ )
386
+ })
387
+ }
388
+ })
355
389
}
356
390
357
391
// runCertificateCheck checks that the TLS certificates presented to clients are
@@ -591,6 +625,61 @@ func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI,
591
625
require .Contains (t , body , successPattern )
592
626
}
593
627
628
+ // runLNCAuthTest tests authentication of the given interface when connecting
629
+ // through Lightning Node Connect.
630
+ func runLNCAuthTest (t * testing.T , hostPort , uiPassword , tlsCertPath string ,
631
+ makeRequest requestFn , successContent string , callAllowed bool ) {
632
+
633
+ ctxb := context .Background ()
634
+ ctxt , cancel := context .WithTimeout (ctxb , defaultTimeout )
635
+ defer cancel ()
636
+
637
+ rawConn , err := connectRPC (ctxt , hostPort , tlsCertPath )
638
+ require .NoError (t , err )
639
+
640
+ // We first need to create an LNC session that we can use to connect.
641
+ // We use the UI password to create the session.
642
+ ctxm := uiPasswordContext (ctxt , uiPassword , true )
643
+ litClient := litrpc .NewSessionsClient (rawConn )
644
+ sessResp , err := litClient .AddSession (ctxm , & litrpc.AddSessionRequest {
645
+ Label : "integration-test" ,
646
+ SessionType : litrpc .SessionType_TYPE_MACAROON_READONLY ,
647
+ ExpiryTimestampSeconds : uint64 (
648
+ time .Now ().Add (5 * time .Minute ).Unix (),
649
+ ),
650
+ MailboxServerAddr : mailboxServerAddr ,
651
+ })
652
+ require .NoError (t , err )
653
+
654
+ // Try the LNC connection now.
655
+ connectPhrase := strings .Split (
656
+ sessResp .Session .PairingSecretMnemonic , " " ,
657
+ )
658
+ rawLNCConn , err := connectMailbox (ctxt , connectPhrase )
659
+ require .NoError (t , err )
660
+
661
+ // We should be able to make a request via LNC to the given RPC
662
+ // endpoint, unless it is explicitly disallowed (we currently don't want
663
+ // to support creating more sessions through LNC until we have all
664
+ // macaroon permissions properly set up).
665
+ resp , err := makeRequest (ctxm , rawLNCConn )
666
+
667
+ // Is this a disallowed call?
668
+ if ! callAllowed {
669
+ require .Error (t , err )
670
+ require .Contains (t , err .Error (), "unknown service" )
671
+
672
+ return
673
+ }
674
+
675
+ // The call should be allowed, so we expect no error.
676
+ require .NoError (t , err )
677
+
678
+ json , err := marshalOptions .Marshal (resp )
679
+ require .NoError (t , err )
680
+ require .Contains (t , string (json ), successContent )
681
+ }
682
+
594
683
// getURL retrieves the body of a given URL, ignoring any TLS certificate the
595
684
// server might present.
596
685
func getURL (url string ) (string , error ) {
@@ -671,6 +760,39 @@ func getServerCertificates(hostPort string) ([]*x509.Certificate, error) {
671
760
return conn .ConnectionState ().PeerCertificates , nil
672
761
}
673
762
763
+ // connectMailbox tries to establish a connection through LNC using the given
764
+ // connect phrase and the test mailbox server.
765
+ func connectMailbox (ctx context.Context ,
766
+ connectPhrase []string ) (grpc.ClientConnInterface , error ) {
767
+
768
+ var mnemonicWords [mailbox .NumPasswordWords ]string
769
+ copy (mnemonicWords [:], connectPhrase )
770
+ password := mailbox .PasswordMnemonicToEntropy (mnemonicWords )
771
+
772
+ sid := sha512 .Sum512 (password [:])
773
+
774
+ privKey , err := btcec .NewPrivateKey (btcec .S256 ())
775
+ if err != nil {
776
+ return nil , err
777
+ }
778
+ ecdh := & keychain.PrivKeyECDH {PrivKey : privKey }
779
+
780
+ transportConn , err := mailbox .NewClient (ctx , sid )
781
+ if err != nil {
782
+ return nil , err
783
+ }
784
+
785
+ noiseConn := mailbox .NewNoiseGrpcConn (ecdh , nil , password [:])
786
+
787
+ dialOpts := []grpc.DialOption {
788
+ grpc .WithContextDialer (transportConn .Dial ),
789
+ grpc .WithTransportCredentials (noiseConn ),
790
+ grpc .WithPerRPCCredentials (noiseConn ),
791
+ }
792
+
793
+ return grpc .DialContext (ctx , mailboxServerAddr , dialOpts ... )
794
+ }
795
+
674
796
func macaroonContext (ctx context.Context , macBytes []byte ) context.Context {
675
797
md := metadata.MD {}
676
798
if len (macBytes ) > 0 {
0 commit comments