@@ -2,10 +2,125 @@ package itest
2
2
3
3
import (
4
4
"context"
5
+ "crypto/tls"
6
+ "crypto/x509"
7
+ "encoding/hex"
8
+ "fmt"
9
+ "io/ioutil"
10
+ "testing"
5
11
6
12
"github.com/btcsuite/btcutil"
13
+ "github.com/lightninglabs/faraday/frdrpc"
14
+ "github.com/lightninglabs/loop/looprpc"
15
+ "github.com/lightninglabs/pool/poolrpc"
7
16
"github.com/lightningnetwork/lnd/lnrpc"
8
17
"github.com/stretchr/testify/require"
18
+ "golang.org/x/net/http2"
19
+ "google.golang.org/grpc"
20
+ "google.golang.org/grpc/credentials"
21
+ "google.golang.org/grpc/metadata"
22
+ "google.golang.org/protobuf/encoding/protojson"
23
+ "google.golang.org/protobuf/proto"
24
+ "gopkg.in/macaroon.v2"
25
+ )
26
+
27
+ // requestFn is a function type for a helper function that makes a daemon
28
+ // specific request and returns the response and error for it. This is used to
29
+ // abstract away the lnd/faraday/loop/pool specific gRPC code from the actual
30
+ // test code.
31
+ type requestFn func (ctx context.Context ,
32
+ c grpc.ClientConnInterface ) (proto.Message , error )
33
+
34
+ // macaroonFn is a function that returns the correct macaroon path for each of
35
+ // the integrated daemons.
36
+ type macaroonFn func (cfg * LitNodeConfig ) string
37
+
38
+ var (
39
+ dummyMac = makeMac ()
40
+ dummyMacBytes = serializeMac (dummyMac )
41
+
42
+ marshalOptions = & protojson.MarshalOptions {
43
+ UseProtoNames : true ,
44
+ EmitUnpopulated : true ,
45
+ }
46
+
47
+ transport = & http2.Transport {
48
+ TLSClientConfig : & tls.Config {
49
+ InsecureSkipVerify : true ,
50
+ },
51
+ }
52
+
53
+ lndRequestFn = func (ctx context.Context ,
54
+ c grpc.ClientConnInterface ) (proto.Message , error ) {
55
+
56
+ lndConn := lnrpc .NewLightningClient (c )
57
+ return lndConn .GetInfo (
58
+ ctx , & lnrpc.GetInfoRequest {},
59
+ )
60
+ }
61
+ lndMacaroonFn = func (cfg * LitNodeConfig ) string {
62
+ return cfg .AdminMacPath
63
+ }
64
+ faradayRequestFn = func (ctx context.Context ,
65
+ c grpc.ClientConnInterface ) (proto.Message , error ) {
66
+
67
+ frdConn := frdrpc .NewFaradayServerClient (c )
68
+ return frdConn .RevenueReport (
69
+ ctx , & frdrpc.RevenueReportRequest {},
70
+ )
71
+ }
72
+ faradayMacaroonFn = func (cfg * LitNodeConfig ) string {
73
+ return cfg .FaradayMacPath
74
+ }
75
+ loopRequestFn = func (ctx context.Context ,
76
+ c grpc.ClientConnInterface ) (proto.Message , error ) {
77
+
78
+ loopConn := looprpc .NewSwapClientClient (c )
79
+ return loopConn .ListSwaps (
80
+ ctx , & looprpc.ListSwapsRequest {},
81
+ )
82
+ }
83
+ loopMacaroonFn = func (cfg * LitNodeConfig ) string {
84
+ return cfg .LoopMacPath
85
+ }
86
+ poolRequestFn = func (ctx context.Context ,
87
+ c grpc.ClientConnInterface ) (proto.Message , error ) {
88
+
89
+ poolConn := poolrpc .NewTraderClient (c )
90
+ return poolConn .GetInfo (
91
+ ctx , & poolrpc.GetInfoRequest {},
92
+ )
93
+ }
94
+ poolMacaroonFn = func (cfg * LitNodeConfig ) string {
95
+ return cfg .PoolMacPath
96
+ }
97
+
98
+ endpoints = []struct {
99
+ name string
100
+ macaroonFn macaroonFn
101
+ requestFn requestFn
102
+ successPattern string
103
+ }{{
104
+ name : "lnrpc" ,
105
+ macaroonFn : lndMacaroonFn ,
106
+ requestFn : lndRequestFn ,
107
+ successPattern : "\" identity_pubkey\" :\" 0" ,
108
+ }, {
109
+ name : "frdrpc" ,
110
+ macaroonFn : faradayMacaroonFn ,
111
+ requestFn : faradayRequestFn ,
112
+ successPattern : "\" reports\" :[]" ,
113
+ }, {
114
+ name : "looprpc" ,
115
+ macaroonFn : loopMacaroonFn ,
116
+ requestFn : loopRequestFn ,
117
+ successPattern : "\" swaps\" :[]" ,
118
+ }, {
119
+ name : "poolrpc" ,
120
+ macaroonFn : poolMacaroonFn ,
121
+ requestFn : poolRequestFn ,
122
+ successPattern : "\" accounts_active\" :0" ,
123
+ }}
9
124
)
10
125
11
126
// testModeIntegrated makes sure that in integrated mode all daemons work
@@ -21,4 +136,157 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) {
21
136
resp , err := net .Alice .GetInfo (ctx , & lnrpc.GetInfoRequest {})
22
137
require .NoError (t .t , err )
23
138
require .NotEmpty (t .t , resp .Alias )
139
+ require .Contains (t .t , resp .Alias , "0" )
140
+
141
+ t .t .Run ("certificate check" , func (tt * testing.T ) {
142
+ runCertificateCheck (tt , net .Alice )
143
+ })
144
+ t .t .Run ("gRPC macaroon auth check" , func (tt * testing.T ) {
145
+ cfg := net .Alice .Cfg
146
+
147
+ for _ , endpoint := range endpoints {
148
+ endpoint := endpoint
149
+ tt .Run (endpoint .name + " lnd port" , func (ttt * testing.T ) {
150
+ runGRPCAuthTest (
151
+ ttt , cfg .RPCAddr (), cfg .TLSCertPath ,
152
+ endpoint .macaroonFn (cfg ),
153
+ endpoint .requestFn ,
154
+ endpoint .successPattern ,
155
+ )
156
+ })
157
+
158
+ tt .Run (endpoint .name + " lit port" , func (ttt * testing.T ) {
159
+ runGRPCAuthTest (
160
+ ttt , cfg .LitAddr (), cfg .TLSCertPath ,
161
+ endpoint .macaroonFn (cfg ),
162
+ endpoint .requestFn ,
163
+ endpoint .successPattern ,
164
+ )
165
+ })
166
+ }
167
+ })
168
+ }
169
+
170
+ // runCertificateCheck checks that the TLS certificates presented to clients are
171
+ // what we expect them to be.
172
+ func runCertificateCheck (t * testing.T , node * HarnessNode ) {
173
+ // In integrated mode we expect the LiT HTTPS port (8443 by default) and
174
+ // lnd's RPC port to present the same certificate, namely lnd's TLS
175
+ // cert.
176
+ litCerts , err := getServerCertificates (node .Cfg .LitAddr ())
177
+ require .NoError (t , err )
178
+ require .Len (t , litCerts , 1 )
179
+ require .Equal (
180
+ t , "lnd autogenerated cert" , litCerts [0 ].Issuer .Organization [0 ],
181
+ )
182
+
183
+ lndCerts , err := getServerCertificates (node .Cfg .RPCAddr ())
184
+ require .NoError (t , err )
185
+ require .Len (t , lndCerts , 1 )
186
+ require .Equal (
187
+ t , "lnd autogenerated cert" , lndCerts [0 ].Issuer .Organization [0 ],
188
+ )
189
+
190
+ require .Equal (t , litCerts [0 ].Raw , lndCerts [0 ].Raw )
191
+ }
192
+
193
+ // runGRPCAuthTest tests authentication of the given gRPC interface.
194
+ func runGRPCAuthTest (t * testing.T , hostPort , tlsCertPath , macPath string ,
195
+ makeRequest requestFn , successContent string ) {
196
+
197
+ ctxb := context .Background ()
198
+ ctxt , cancel := context .WithTimeout (ctxb , defaultTimeout )
199
+ defer cancel ()
200
+
201
+ rawConn , err := connectRPC (ctxt , hostPort , tlsCertPath )
202
+ require .NoError (t , err )
203
+
204
+ // We have a connection without any macaroon. A call should fail.
205
+ _ , err = makeRequest (ctxt , rawConn )
206
+ require .Error (t , err )
207
+ require .Contains (t , err .Error (), "expected 1 macaroon, got 0" )
208
+
209
+ // Add dummy data as the macaroon, that should fail as well.
210
+ ctxm := macaroonContext (ctxt , []byte ("dummy" ))
211
+ _ , err = makeRequest (ctxm , rawConn )
212
+ require .Error (t , err )
213
+ require .Contains (t , err .Error (), "packet too short" )
214
+
215
+ // Add a macaroon that can be parsed but that's not issued by lnd, which
216
+ // should also fail.
217
+ ctxm = macaroonContext (ctxt , dummyMacBytes )
218
+ _ , err = makeRequest (ctxm , rawConn )
219
+ require .Error (t , err )
220
+ require .Contains (t , err .Error (), "cannot get macaroon: root key with" )
221
+
222
+ // Then finally we try with the correct macaroon which should now
223
+ // succeed.
224
+ macBytes , err := ioutil .ReadFile (macPath )
225
+ require .NoError (t , err )
226
+ ctxm = macaroonContext (ctxt , macBytes )
227
+ resp , err := makeRequest (ctxm , rawConn )
228
+ require .NoError (t , err )
229
+
230
+ json , err := marshalOptions .Marshal (resp )
231
+ require .NoError (t , err )
232
+ require .Contains (t , string (json ), successContent )
233
+ }
234
+
235
+ // getServerCertificates returns the TLS certificates that a server presents to
236
+ // clients.
237
+ func getServerCertificates (hostPort string ) ([]* x509.Certificate , error ) {
238
+ // We don't care about the validity of the certificate, we just want to
239
+ // download it.
240
+ conn , err := tls .Dial ("tcp" , hostPort , transport .TLSClientConfig )
241
+ if err != nil {
242
+ return nil , fmt .Errorf ("error dialing %s: %v" , hostPort , err )
243
+ }
244
+ defer func () {
245
+ _ = conn .Close ()
246
+ }()
247
+
248
+ return conn .ConnectionState ().PeerCertificates , nil
249
+ }
250
+
251
+ func macaroonContext (ctx context.Context , macBytes []byte ) context.Context {
252
+ md := metadata.MD {}
253
+ if len (macBytes ) > 0 {
254
+ md ["macaroon" ] = []string {hex .EncodeToString (macBytes )}
255
+ }
256
+ return metadata .NewOutgoingContext (ctx , md )
257
+ }
258
+
259
+ func makeMac () * macaroon.Macaroon {
260
+ dummyMac , err := macaroon .New (
261
+ []byte ("aabbccddeeff00112233445566778899" ), []byte ("AA==" ),
262
+ "LSAT" , macaroon .LatestVersion ,
263
+ )
264
+ if err != nil {
265
+ panic (fmt .Errorf ("unable to create macaroon: %v" , err ))
266
+ }
267
+ return dummyMac
268
+ }
269
+
270
+ func serializeMac (mac * macaroon.Macaroon ) []byte {
271
+ macBytes , err := mac .MarshalBinary ()
272
+ if err != nil {
273
+ panic (fmt .Errorf ("unable to serialize macaroon: %v" , err ))
274
+ }
275
+ return macBytes
276
+ }
277
+
278
+ func connectRPC (ctx context.Context , hostPort ,
279
+ tlsCertPath string ) (* grpc.ClientConn , error ) {
280
+
281
+ tlsCreds , err := credentials .NewClientTLSFromFile (tlsCertPath , "" )
282
+ if err != nil {
283
+ return nil , err
284
+ }
285
+
286
+ opts := []grpc.DialOption {
287
+ grpc .WithBlock (),
288
+ grpc .WithTransportCredentials (tlsCreds ),
289
+ }
290
+
291
+ return grpc .DialContext (ctx , hostPort , opts ... )
24
292
}
0 commit comments