Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,18 @@ pending swaps after a restart.
Information about pending swaps is stored persistently in the swap database.
Its location is `~/.loopd/<network>/loop.db`.

## Transport security

The gRPC and REST connections of `loopd` are encrypted with TLS the same way
`lnd` is.

If no custom loop directory is set then the TLS certificate is stored in
`~/.loopd/<network>/tls.cert`.

The `loop` command will pick up the file automatically on mainnet if no custom
loop directory is used. For other networks it should be sufficient to add the
`--network` flag to tell the CLI in what sub directory to look for the files.

## Multiple Simultaneous Swaps

It is possible to execute multiple swaps simultaneously. Just keep loopd
Expand Down
110 changes: 80 additions & 30 deletions cmd/loop/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/loopd"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/protobuf-hex-display/json"
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
"github.com/lightninglabs/protobuf-hex-display/proto"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/macaroons"

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

loopDirFlag = cli.StringFlag{
Name: "loopdir",
Value: loopd.LoopDirBase,
Usage: "path to loop's base directory",
}
networkFlag = cli.StringFlag{
Name: "network, n",
Usage: "the network loop is running on e.g. mainnet, " +
"testnet, etc.",
Value: loopd.DefaultNetwork,
}

tlsCertFlag = cli.StringFlag{
Name: "tlscertpath",
Usage: "path to loop's TLS certificate, only needed if loop " +
"runs in the same process as lnd",
Name: "tlscertpath",
Usage: "path to loop's TLS certificate",
Value: loopd.DefaultTLSCertPath,
}
macaroonPathFlag = cli.StringFlag{
Name: "macaroonpath",
Expand Down Expand Up @@ -103,6 +120,8 @@ func main() {
Value: "localhost:11010",
Usage: "loopd daemon address host:port",
},
networkFlag,
loopDirFlag,
tlsCertFlag,
macaroonPathFlag,
}
Expand All @@ -121,8 +140,10 @@ func main() {

func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) {
rpcServer := ctx.GlobalString("rpcserver")
tlsCertPath := ctx.GlobalString(tlsCertFlag.Name)
macaroonPath := ctx.GlobalString(macaroonPathFlag.Name)
tlsCertPath, macaroonPath, err := extractPathArgs(ctx)
if err != nil {
return nil, nil, err
}
conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath)
if err != nil {
return nil, nil, err
Expand All @@ -137,6 +158,40 @@ func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
}

// extractPathArgs parses the TLS certificate and macaroon paths from the
// command.
func extractPathArgs(ctx *cli.Context) (string, string, error) {
// We'll start off by parsing the network. This is needed to determine
// the correct path to the TLS certificate and macaroon when not
// specified.
networkStr := strings.ToLower(ctx.GlobalString("network"))
_, err := lndclient.Network(networkStr).ChainParams()
if err != nil {
return "", "", err
}

// We'll now fetch the loopdir so we can make a decision on how to
// properly read the cert. This will either be the default, or will have
// been overwritten by the end user.
loopDir := lncfg.CleanAndExpandPath(ctx.GlobalString(loopDirFlag.Name))
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
tlsCertFlag.Name,
))

// If a custom lnd directory was set, we'll also check if custom paths
// for the TLS cert file were set as well. If not, we'll override their
// paths so they can be found within the custom loop directory set. This
// allows us to set a custom lnd directory, along with custom paths to
// the TLS cert file.
if loopDir != loopd.LoopDirBase || networkStr != loopd.DefaultNetwork {
tlsCertPath = filepath.Join(
loopDir, networkStr, loopd.DefaultTLSCertFilename,
)
}

return tlsCertPath, ctx.GlobalString(macaroonPathFlag.Name), nil
}

type inLimits struct {
maxMinerFee btcutil.Amount
maxSwapFee btcutil.Amount
Expand Down Expand Up @@ -322,49 +377,44 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
grpc.WithDefaultCallOptions(maxMsgRecvSize),
}

switch {
// If a TLS certificate file is specified, we need to load it and build
// transport credentials with it.
case tlsCertPath != "":
creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "")
if err != nil {
fatal(err)
}
// TLS cannot be disabled, we'll always have a cert file to read.
creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "")
if err != nil {
return nil, err
}

// Macaroons are only allowed to be transmitted over a TLS
// enabled connection.
if macaroonPath != "" {
opts = append(opts, readMacaroon(macaroonPath))
// Macaroons are not yet enabled by default.
if macaroonPath != "" {
macOption, err := readMacaroon(macaroonPath)
if err != nil {
return nil, err
}

opts = append(opts, grpc.WithTransportCredentials(creds))

// By default, if no certificate is supplied, we assume the RPC server
// runs without TLS.
default:
opts = append(opts, grpc.WithInsecure())
opts = append(opts, macOption)
}

opts = append(opts, grpc.WithTransportCredentials(creds))

conn, err := grpc.Dial(address, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v", err)
return nil, fmt.Errorf("unable to connect to RPC server: %v",
err)
}

return conn, nil
}

// readMacaroon tries to read the macaroon file at the specified path and create
// gRPC dial options from it.
func readMacaroon(macPath string) grpc.DialOption {
func readMacaroon(macPath string) (grpc.DialOption, error) {
// Load the specified macaroon file.
macBytes, err := ioutil.ReadFile(macPath)
if err != nil {
fatal(fmt.Errorf("unable to read macaroon path : %v", err))
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
}

mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macBytes); err != nil {
fatal(fmt.Errorf("unable to decode macaroon: %v", err))
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
}

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

// Now we append the macaroon credentials to the dial options.
cred := macaroons.NewMacaroonCredential(constrainedMac)
return grpc.WithPerRPCCredentials(cred)
return grpc.WithPerRPCCredentials(cred), nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/lightninglabs/lndclient v0.11.0-0
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d
github.com/lightningnetwork/lnd v0.11.0-beta
github.com/lightningnetwork/lnd/cert v1.0.3
github.com/lightningnetwork/lnd/queue v1.0.4
github.com/stretchr/testify v1.5.1
github.com/urfave/cli v1.20.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea
github.com/lightningnetwork/lnd v0.11.0-beta h1:pUAT7FMHqS+iarNxyRtgj96XKCGAWwmb6ZdiUBy78ts=
github.com/lightningnetwork/lnd v0.11.0-beta/go.mod h1:CzArvT7NFDLhVyW06+NJWSuWFmE6Ea+AjjA3txUBqTM=
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
github.com/lightningnetwork/lnd/cert v1.0.3 h1:/K2gjzLgVI8we2IIPKc0ztWTEa85uds5sWXi1K6mOT0=
github.com/lightningnetwork/lnd/cert v1.0.3/go.mod h1:3MWXVLLPI0Mg0XETm9fT4N9Vyy/8qQLmaM5589bEggM=
github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo=
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
Expand Down
Loading