Skip to content

Commit fb5d374

Browse files
authored
Merge pull request #286 from guggero/add-tls
[1/3] loopd: add TLS
2 parents d3ede8d + 458ba7d commit fb5d374

File tree

7 files changed

+304
-61
lines changed

7 files changed

+304
-61
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,18 @@ pending swaps after a restart.
333333
Information about pending swaps is stored persistently in the swap database.
334334
Its location is `~/.loopd/<network>/loop.db`.
335335

336+
## Transport security
337+
338+
The gRPC and REST connections of `loopd` are encrypted with TLS the same way
339+
`lnd` is.
340+
341+
If no custom loop directory is set then the TLS certificate is stored in
342+
`~/.loopd/<network>/tls.cert`.
343+
344+
The `loop` command will pick up the file automatically on mainnet if no custom
345+
loop directory is used. For other networks it should be sufficient to add the
346+
`--network` flag to tell the CLI in what sub directory to look for the files.
347+
336348
## Multiple Simultaneous Swaps
337349

338350
It is possible to execute multiple swaps simultaneously. Just keep loopd

cmd/loop/main.go

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ import (
66
"fmt"
77
"io/ioutil"
88
"os"
9+
"path/filepath"
910
"strconv"
11+
"strings"
1012
"time"
1113

14+
"github.com/lightninglabs/lndclient"
1215
"github.com/lightninglabs/loop"
16+
"github.com/lightninglabs/loop/loopd"
1317
"github.com/lightninglabs/loop/looprpc"
1418
"github.com/lightninglabs/loop/swap"
1519
"github.com/lightninglabs/protobuf-hex-display/json"
1620
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
1721
"github.com/lightninglabs/protobuf-hex-display/proto"
22+
"github.com/lightningnetwork/lnd/lncfg"
1823
"github.com/lightningnetwork/lnd/macaroons"
1924

2025
"github.com/btcsuite/btcutil"
@@ -43,10 +48,22 @@ var (
4348
// that we set when sending it over the line.
4449
defaultMacaroonTimeout int64 = 60
4550

51+
loopDirFlag = cli.StringFlag{
52+
Name: "loopdir",
53+
Value: loopd.LoopDirBase,
54+
Usage: "path to loop's base directory",
55+
}
56+
networkFlag = cli.StringFlag{
57+
Name: "network, n",
58+
Usage: "the network loop is running on e.g. mainnet, " +
59+
"testnet, etc.",
60+
Value: loopd.DefaultNetwork,
61+
}
62+
4663
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",
64+
Name: "tlscertpath",
65+
Usage: "path to loop's TLS certificate",
66+
Value: loopd.DefaultTLSCertPath,
5067
}
5168
macaroonPathFlag = cli.StringFlag{
5269
Name: "macaroonpath",
@@ -103,6 +120,8 @@ func main() {
103120
Value: "localhost:11010",
104121
Usage: "loopd daemon address host:port",
105122
},
123+
networkFlag,
124+
loopDirFlag,
106125
tlsCertFlag,
107126
macaroonPathFlag,
108127
}
@@ -121,8 +140,10 @@ func main() {
121140

122141
func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) {
123142
rpcServer := ctx.GlobalString("rpcserver")
124-
tlsCertPath := ctx.GlobalString(tlsCertFlag.Name)
125-
macaroonPath := ctx.GlobalString(macaroonPathFlag.Name)
143+
tlsCertPath, macaroonPath, err := extractPathArgs(ctx)
144+
if err != nil {
145+
return nil, nil, err
146+
}
126147
conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath)
127148
if err != nil {
128149
return nil, nil, err
@@ -137,6 +158,40 @@ func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
137158
return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
138159
}
139160

161+
// extractPathArgs parses the TLS certificate and macaroon paths from the
162+
// command.
163+
func extractPathArgs(ctx *cli.Context) (string, string, error) {
164+
// We'll start off by parsing the network. This is needed to determine
165+
// the correct path to the TLS certificate and macaroon when not
166+
// specified.
167+
networkStr := strings.ToLower(ctx.GlobalString("network"))
168+
_, err := lndclient.Network(networkStr).ChainParams()
169+
if err != nil {
170+
return "", "", err
171+
}
172+
173+
// We'll now fetch the loopdir so we can make a decision on how to
174+
// properly read the cert. This will either be the default, or will have
175+
// been overwritten by the end user.
176+
loopDir := lncfg.CleanAndExpandPath(ctx.GlobalString(loopDirFlag.Name))
177+
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
178+
tlsCertFlag.Name,
179+
))
180+
181+
// If a custom lnd directory was set, we'll also check if custom paths
182+
// for the TLS cert file were set as well. If not, we'll override their
183+
// paths so they can be found within the custom loop directory set. This
184+
// allows us to set a custom lnd directory, along with custom paths to
185+
// the TLS cert file.
186+
if loopDir != loopd.LoopDirBase || networkStr != loopd.DefaultNetwork {
187+
tlsCertPath = filepath.Join(
188+
loopDir, networkStr, loopd.DefaultTLSCertFilename,
189+
)
190+
}
191+
192+
return tlsCertPath, ctx.GlobalString(macaroonPathFlag.Name), nil
193+
}
194+
140195
type inLimits struct {
141196
maxMinerFee btcutil.Amount
142197
maxSwapFee btcutil.Amount
@@ -322,49 +377,44 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
322377
grpc.WithDefaultCallOptions(maxMsgRecvSize),
323378
}
324379

325-
switch {
326-
// If a TLS certificate file is specified, we need to load it and build
327-
// transport credentials with it.
328-
case tlsCertPath != "":
329-
creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "")
330-
if err != nil {
331-
fatal(err)
332-
}
380+
// TLS cannot be disabled, we'll always have a cert file to read.
381+
creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "")
382+
if err != nil {
383+
return nil, err
384+
}
333385

334-
// Macaroons are only allowed to be transmitted over a TLS
335-
// enabled connection.
336-
if macaroonPath != "" {
337-
opts = append(opts, readMacaroon(macaroonPath))
386+
// Macaroons are not yet enabled by default.
387+
if macaroonPath != "" {
388+
macOption, err := readMacaroon(macaroonPath)
389+
if err != nil {
390+
return nil, err
338391
}
339-
340-
opts = append(opts, grpc.WithTransportCredentials(creds))
341-
342-
// By default, if no certificate is supplied, we assume the RPC server
343-
// runs without TLS.
344-
default:
345-
opts = append(opts, grpc.WithInsecure())
392+
opts = append(opts, macOption)
346393
}
347394

395+
opts = append(opts, grpc.WithTransportCredentials(creds))
396+
348397
conn, err := grpc.Dial(address, opts...)
349398
if err != nil {
350-
return nil, fmt.Errorf("unable to connect to RPC server: %v", err)
399+
return nil, fmt.Errorf("unable to connect to RPC server: %v",
400+
err)
351401
}
352402

353403
return conn, nil
354404
}
355405

356406
// readMacaroon tries to read the macaroon file at the specified path and create
357407
// gRPC dial options from it.
358-
func readMacaroon(macPath string) grpc.DialOption {
408+
func readMacaroon(macPath string) (grpc.DialOption, error) {
359409
// Load the specified macaroon file.
360410
macBytes, err := ioutil.ReadFile(macPath)
361411
if err != nil {
362-
fatal(fmt.Errorf("unable to read macaroon path : %v", err))
412+
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
363413
}
364414

365415
mac := &macaroon.Macaroon{}
366416
if err = mac.UnmarshalBinary(macBytes); err != nil {
367-
fatal(fmt.Errorf("unable to decode macaroon: %v", err))
417+
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
368418
}
369419

370420
macConstraints := []macaroons.Constraint{
@@ -384,10 +434,10 @@ func readMacaroon(macPath string) grpc.DialOption {
384434
// Apply constraints to the macaroon.
385435
constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...)
386436
if err != nil {
387-
fatal(err)
437+
return nil, err
388438
}
389439

390440
// Now we append the macaroon credentials to the dial options.
391441
cred := macaroons.NewMacaroonCredential(constrainedMac)
392-
return grpc.WithPerRPCCredentials(cred)
442+
return grpc.WithPerRPCCredentials(cred), nil
393443
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/lightninglabs/lndclient v0.11.0-0
1414
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d
1515
github.com/lightningnetwork/lnd v0.11.0-beta
16+
github.com/lightningnetwork/lnd/cert v1.0.3
1617
github.com/lightningnetwork/lnd/queue v1.0.4
1718
github.com/stretchr/testify v1.5.1
1819
github.com/urfave/cli v1.20.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea
182182
github.com/lightningnetwork/lnd v0.11.0-beta h1:pUAT7FMHqS+iarNxyRtgj96XKCGAWwmb6ZdiUBy78ts=
183183
github.com/lightningnetwork/lnd v0.11.0-beta/go.mod h1:CzArvT7NFDLhVyW06+NJWSuWFmE6Ea+AjjA3txUBqTM=
184184
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
185+
github.com/lightningnetwork/lnd/cert v1.0.3 h1:/K2gjzLgVI8we2IIPKc0ztWTEa85uds5sWXi1K6mOT0=
186+
github.com/lightningnetwork/lnd/cert v1.0.3/go.mod h1:3MWXVLLPI0Mg0XETm9fT4N9Vyy/8qQLmaM5589bEggM=
185187
github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo=
186188
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
187189
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=

0 commit comments

Comments
 (0)