44 "bytes"
55 "errors"
66 "fmt"
7+ "io/ioutil"
78 "os"
89 "strconv"
910 "time"
@@ -14,11 +15,14 @@ import (
1415 "github.com/lightninglabs/protobuf-hex-display/json"
1516 "github.com/lightninglabs/protobuf-hex-display/jsonpb"
1617 "github.com/lightninglabs/protobuf-hex-display/proto"
18+ "github.com/lightningnetwork/lnd/macaroons"
1719
1820 "github.com/btcsuite/btcutil"
1921
2022 "github.com/urfave/cli"
2123 "google.golang.org/grpc"
24+ "google.golang.org/grpc/credentials"
25+ "gopkg.in/macaroon.v2"
2226)
2327
2428var (
3438 // maxMsgRecvSize is the largest message our client will receive. We
3539 // set this to 200MiB atm.
3640 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+ }
3756)
3857
3958func printJSON (resp interface {}) {
@@ -84,6 +103,8 @@ func main() {
84103 Value : "localhost:11010" ,
85104 Usage : "loopd daemon address host:port" ,
86105 },
106+ tlsCertFlag ,
107+ macaroonPathFlag ,
87108 }
88109 app .Commands = []cli.Command {
89110 loopOutCommand , loopInCommand , termsCommand ,
@@ -99,7 +120,9 @@ func main() {
99120
100121func getClient (ctx * cli.Context ) (looprpc.SwapClientClient , func (), error ) {
101122 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 )
103126 if err != nil {
104127 return nil , nil , err
105128 }
@@ -256,16 +279,79 @@ func logSwap(swap *looprpc.SwapStatus) {
256279 fmt .Println ()
257280}
258281
259- func getClientConn (address string ) (* grpc.ClientConn , error ) {
282+ func getClientConn (address , tlsCertPath , macaroonPath string ) (* grpc.ClientConn ,
283+ error ) {
284+
260285 opts := []grpc.DialOption {
261- grpc .WithInsecure (),
262286 grpc .WithDefaultCallOptions (maxMsgRecvSize ),
263287 }
264288
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+
265312 conn , err := grpc .Dial (address , opts ... )
266313 if err != nil {
267314 return nil , fmt .Errorf ("unable to connect to RPC server: %v" , err )
268315 }
269316
270317 return conn , nil
271318}
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