4
4
"bytes"
5
5
"errors"
6
6
"fmt"
7
+ "io/ioutil"
7
8
"os"
8
9
"strconv"
9
10
"time"
@@ -14,11 +15,14 @@ import (
14
15
"github.com/lightninglabs/protobuf-hex-display/json"
15
16
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
16
17
"github.com/lightninglabs/protobuf-hex-display/proto"
18
+ "github.com/lightningnetwork/lnd/macaroons"
17
19
18
20
"github.com/btcsuite/btcutil"
19
21
20
22
"github.com/urfave/cli"
21
23
"google.golang.org/grpc"
24
+ "google.golang.org/grpc/credentials"
25
+ "gopkg.in/macaroon.v2"
22
26
)
23
27
24
28
var (
34
38
// maxMsgRecvSize is the largest message our client will receive. We
35
39
// set this to 200MiB atm.
36
40
maxMsgRecvSize = grpc .MaxCallRecvMsgSize (1 * 1024 * 1024 * 200 )
41
+
42
+ // defaultMacaroonTimeout is the default macaroon timeout in seconds
43
+ // that we set when sending it over the line.
44
+ defaultMacaroonTimeout int64 = 60
45
+
46
+ tlsCertFlag = cli.StringFlag {
47
+ Name : "tlscertpath" ,
48
+ Usage : "path to loop's TLS certificate, only needed if loop " +
49
+ "runs in the same process as lnd" ,
50
+ }
51
+ macaroonPathFlag = cli.StringFlag {
52
+ Name : "macaroonpath" ,
53
+ Usage : "path to macaroon file, only needed if loop runs " +
54
+ "in the same process as lnd" ,
55
+ }
37
56
)
38
57
39
58
func printJSON (resp interface {}) {
@@ -84,6 +103,8 @@ func main() {
84
103
Value : "localhost:11010" ,
85
104
Usage : "loopd daemon address host:port" ,
86
105
},
106
+ tlsCertFlag ,
107
+ macaroonPathFlag ,
87
108
}
88
109
app .Commands = []cli.Command {
89
110
loopOutCommand , loopInCommand , termsCommand ,
@@ -99,7 +120,9 @@ func main() {
99
120
100
121
func getClient (ctx * cli.Context ) (looprpc.SwapClientClient , func (), error ) {
101
122
rpcServer := ctx .GlobalString ("rpcserver" )
102
- conn , err := getClientConn (rpcServer )
123
+ tlsCertPath := ctx .GlobalString (tlsCertFlag .Name )
124
+ macaroonPath := ctx .GlobalString (macaroonPathFlag .Name )
125
+ conn , err := getClientConn (rpcServer , tlsCertPath , macaroonPath )
103
126
if err != nil {
104
127
return nil , nil , err
105
128
}
@@ -256,16 +279,79 @@ func logSwap(swap *looprpc.SwapStatus) {
256
279
fmt .Println ()
257
280
}
258
281
259
- func getClientConn (address string ) (* grpc.ClientConn , error ) {
282
+ func getClientConn (address , tlsCertPath , macaroonPath string ) (* grpc.ClientConn ,
283
+ error ) {
284
+
260
285
opts := []grpc.DialOption {
261
- grpc .WithInsecure (),
262
286
grpc .WithDefaultCallOptions (maxMsgRecvSize ),
263
287
}
264
288
289
+ switch {
290
+ // If a TLS certificate file is specified, we need to load it and build
291
+ // transport credentials with it.
292
+ case tlsCertPath != "" :
293
+ creds , err := credentials .NewClientTLSFromFile (tlsCertPath , "" )
294
+ if err != nil {
295
+ fatal (err )
296
+ }
297
+
298
+ // Macaroons are only allowed to be transmitted over a TLS
299
+ // enabled connection.
300
+ if macaroonPath != "" {
301
+ opts = append (opts , readMacaroon (macaroonPath ))
302
+ }
303
+
304
+ opts = append (opts , grpc .WithTransportCredentials (creds ))
305
+
306
+ // By default, if no certificate is supplied, we assume the RPC server
307
+ // runs without TLS.
308
+ default :
309
+ opts = append (opts , grpc .WithInsecure ())
310
+ }
311
+
265
312
conn , err := grpc .Dial (address , opts ... )
266
313
if err != nil {
267
314
return nil , fmt .Errorf ("unable to connect to RPC server: %v" , err )
268
315
}
269
316
270
317
return conn , nil
271
318
}
319
+
320
+ // readMacaroon tries to read the macaroon file at the specified path and create
321
+ // gRPC dial options from it.
322
+ func readMacaroon (macPath string ) grpc.DialOption {
323
+ // Load the specified macaroon file.
324
+ macBytes , err := ioutil .ReadFile (macPath )
325
+ if err != nil {
326
+ fatal (fmt .Errorf ("unable to read macaroon path : %v" , err ))
327
+ }
328
+
329
+ mac := & macaroon.Macaroon {}
330
+ if err = mac .UnmarshalBinary (macBytes ); err != nil {
331
+ fatal (fmt .Errorf ("unable to decode macaroon: %v" , err ))
332
+ }
333
+
334
+ macConstraints := []macaroons.Constraint {
335
+ // We add a time-based constraint to prevent replay of the
336
+ // macaroon. It's good for 60 seconds by default to make up for
337
+ // any discrepancy between client and server clocks, but leaking
338
+ // the macaroon before it becomes invalid makes it possible for
339
+ // an attacker to reuse the macaroon. In addition, the validity
340
+ // time of the macaroon is extended by the time the server clock
341
+ // is behind the client clock, or shortened by the time the
342
+ // server clock is ahead of the client clock (or invalid
343
+ // altogether if, in the latter case, this time is more than 60
344
+ // seconds).
345
+ macaroons .TimeoutConstraint (defaultMacaroonTimeout ),
346
+ }
347
+
348
+ // Apply constraints to the macaroon.
349
+ constrainedMac , err := macaroons .AddConstraints (mac , macConstraints ... )
350
+ if err != nil {
351
+ fatal (err )
352
+ }
353
+
354
+ // Now we append the macaroon credentials to the dial options.
355
+ cred := macaroons .NewMacaroonCredential (constrainedMac )
356
+ return grpc .WithPerRPCCredentials (cred )
357
+ }
0 commit comments