Skip to content

Commit 3a38f8f

Browse files
committed
cmd/loopd: add tlsCertPath and macaroonPath flags
1 parent 74b3580 commit 3a38f8f

File tree

1 file changed

+89
-3
lines changed

1 file changed

+89
-3
lines changed

cmd/loop/main.go

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
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

2428
var (
@@ -34,6 +38,21 @@ var (
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

3958
func 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

100121
func 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

Comments
 (0)