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
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,25 @@ 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
## Authentication and transport security

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

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

The `loop` command will pick up the file automatically on mainnet if no custom
The `loop` command will pick up these 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.

For more information on macaroons,
[see the macaroon documentation of lnd.](https://github.com/lightningnetwork/lnd/blob/master/docs/macaroons.md)

**NOTE**: Loop's macaroons are independent from `lnd`'s. The same macaroon
cannot be used for both `loopd` and `lnd`.

## Multiple Simultaneous Swaps

It is possible to execute multiple swaps simultaneously. Just keep loopd
Expand Down
44 changes: 24 additions & 20 deletions cmd/loop/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ var (
Value: loopd.DefaultTLSCertPath,
}
macaroonPathFlag = cli.StringFlag{
Name: "macaroonpath",
Usage: "path to macaroon file, only needed if loop runs " +
"in the same process as lnd",
Name: "macaroonpath",
Usage: "path to macaroon file",
Value: loopd.DefaultMacaroonPath,
}
)

Expand Down Expand Up @@ -171,25 +171,31 @@ func extractPathArgs(ctx *cli.Context) (string, string, error) {
}

// 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.
// properly read the macaroons and also 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,
))
macPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
macaroonPathFlag.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 a custom loop directory was set, we'll also check if custom paths
// for the TLS cert and macaroon 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 loop directory, along
// with custom paths to the TLS cert and macaroon file.
if loopDir != loopd.LoopDirBase || networkStr != loopd.DefaultNetwork {
tlsCertPath = filepath.Join(
loopDir, networkStr, loopd.DefaultTLSCertFilename,
)
macPath = filepath.Join(
loopDir, networkStr, loopd.DefaultMacaroonFilename,
)
}

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

type inLimits struct {
Expand Down Expand Up @@ -373,8 +379,15 @@ func logSwap(swap *looprpc.SwapStatus) {
func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
error) {

// We always need to send a macaroon.
macOption, err := readMacaroon(macaroonPath)
if err != nil {
return nil, err
}

opts := []grpc.DialOption{
grpc.WithDefaultCallOptions(maxMsgRecvSize),
macOption,
}

// TLS cannot be disabled, we'll always have a cert file to read.
Expand All @@ -383,15 +396,6 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
return nil, err
}

// Macaroons are not yet enabled by default.
if macaroonPath != "" {
macOption, err := readMacaroon(macaroonPath)
if err != nil {
return nil, err
}
opts = append(opts, macOption)
}

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

conn, err := grpc.Dial(address, opts...)
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ require (
github.com/jessevdk/go-flags v1.4.0
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

// TODO(guggero): Bump lnd to the final v0.11.1-beta version once it's
// released.
github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200911014924-bc6e52888763
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
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c
google.golang.org/grpc v1.24.0
gopkg.in/macaroon-bakery.v2 v2.0.1
gopkg.in/macaroon.v2 v2.1.0
)

Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ github.com/btcsuite/btcwallet/walletdb v1.3.3 h1:u6e7vRIKBF++cJy+hOHaMGg+88ZTwvp
github.com/btcsuite/btcwallet/walletdb v1.3.3/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgrCfFWqPd9NA=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgrCfFWqPd9NA=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
Expand Down Expand Up @@ -179,14 +181,14 @@ github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce7
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea h1:oCj48NQ8u7Vz+MmzHqt0db6mxcFZo3Ho7M5gCJauY/k=
github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
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 v0.11.0-beta.rc4.0.20200911014924-bc6e52888763 h1:OUWOTo2BAcsnEaMQIf4gLktU3zGytx6pXrmjUNpZpdg=
github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200911014924-bc6e52888763/go.mod h1:IvrqVCc5tN2on6E7IHhrwyiM7FCHZ92LphZD+v88LXY=
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=
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAAChPBwa4BJxH4k=
github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
Expand Down Expand Up @@ -246,7 +248,6 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
Expand All @@ -259,7 +260,6 @@ github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs=
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
Expand Down
26 changes: 22 additions & 4 deletions loopd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ var (
DefaultTLSKeyPath = filepath.Join(
LoopDirBase, DefaultNetwork, DefaultTLSKeyFilename,
)

// DefaultMacaroonFilename is the default file name for the
// autogenerated loop macaroon.
DefaultMacaroonFilename = "loop.macaroon"

// DefaultMacaroonPath is the default full path of the base loop
// macaroon.
DefaultMacaroonPath = filepath.Join(
LoopDirBase, DefaultNetwork, DefaultMacaroonFilename,
)
)

type lndConfig struct {
Expand All @@ -82,7 +92,7 @@ type Config struct {
RESTListen string `long:"restlisten" description:"Address to listen on for REST clients"`
CORSOrigin string `long:"corsorigin" description:"The value to send in the Access-Control-Allow-Origin header. Header will be omitted if empty."`

LoopDir string `long:"loopdir" description:"The directory for all of loop's data. If set, this option overwrites --datadir, --logdir, --tlscertpath and --tlskeypath."`
LoopDir string `long:"loopdir" description:"The directory for all of loop's data. If set, this option overwrites --datadir, --logdir, --tlscertpath, --tlskeypath and --macaroonpath."`
ConfigFile string `long:"configfile" description:"Path to configuration file."`
DataDir string `long:"datadir" description:"Directory for loopdb."`

Expand All @@ -93,6 +103,8 @@ type Config struct {
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed."`
TLSDisableAutofill bool `long:"tlsdisableautofill" description:"Do not include the interface IPs or the system hostname in TLS certificate, use first --tlsextradomain as Common Name instead, if set."`

MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for loop's RPC and REST services if it doesn't exist."`

LogDir string `long:"logdir" description:"Directory to log output."`
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)."`
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB."`
Expand Down Expand Up @@ -133,6 +145,7 @@ func DefaultConfig() Config {
DebugLevel: defaultLogLevel,
TLSCertPath: DefaultTLSCertPath,
TLSKeyPath: DefaultTLSKeyPath,
MacaroonPath: DefaultMacaroonPath,
MaxLSATCost: lsat.DefaultMaxCostSats,
MaxLSATFee: lsat.DefaultMaxRoutingFeeSats,
LoopOutMaxParts: defaultLoopOutMaxParts,
Expand Down Expand Up @@ -193,9 +206,9 @@ func Validate(cfg *Config) error {
cfg.DataDir = filepath.Join(cfg.DataDir, cfg.Network)
cfg.LogDir = filepath.Join(cfg.LogDir, cfg.Network)

// We want the TLS files to also be in the "namespaced" sub directory.
// Replace the default values with actual values in case the user
// specified either loopdir or datadir.
// We want the TLS and macaroon files to also be in the "namespaced" sub
// directory. Replace the default values with actual values in case the
// user specified either loopdir or datadir.
if cfg.TLSCertPath == DefaultTLSCertPath {
cfg.TLSCertPath = filepath.Join(
cfg.DataDir, DefaultTLSCertFilename,
Expand All @@ -206,6 +219,11 @@ func Validate(cfg *Config) error {
cfg.DataDir, DefaultTLSKeyFilename,
)
}
if cfg.MacaroonPath == DefaultMacaroonPath {
cfg.MacaroonPath = filepath.Join(
cfg.DataDir, DefaultMacaroonFilename,
)
}

// If either of these directories do not exist, create them.
if err := os.MkdirAll(cfg.DataDir, os.ModePerm); err != nil {
Expand Down
50 changes: 45 additions & 5 deletions loopd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)

var (
Expand Down Expand Up @@ -79,6 +81,8 @@ type Daemon struct {
restServer *http.Server
restListener net.Listener
restCtxCancel func()

macaroonService *macaroons.Service
}

// New creates a new instance of the loop client daemon.
Expand Down Expand Up @@ -167,13 +171,29 @@ func (d *Daemon) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices) error {
return d.initialize()
}

// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
// checks its signature, makes sure all specified permissions for the called
// method are contained within and finally ensures all caveat conditions are
// met. A non-nil error is returned if any of the checks fail. This method is
// needed to enable loopd running as an external subserver in the same process
// as lnd but still validate its own macaroons.
func (d *Daemon) ValidateMacaroon(ctx context.Context,
requiredPermissions []bakery.Op, fullMethod string) error {

// Delegate the call to loop's own macaroon validator service.
return d.macaroonService.ValidateMacaroon(
ctx, requiredPermissions, fullMethod,
)
}

// startWebServers starts the gRPC and REST servers in goroutines.
func (d *Daemon) startWebServers() error {
var err error

// With our client created, let's now finish setting up and start our
// RPC server.
serverOpts := []grpc.ServerOption{}
// RPC server. First we add the security interceptor to our gRPC server
// options that checks the macaroons for validity.
serverOpts := d.macaroonInterceptor()
d.grpcServer = grpc.NewServer(serverOpts...)
looprpc.RegisterSwapClientServer(d.grpcServer, d)

Expand Down Expand Up @@ -322,6 +342,17 @@ func (d *Daemon) initialize() error {
// stop on main context cancel. So we create it early and pass it down.
d.mainCtx, d.mainCtxCancel = context.WithCancel(context.Background())

// Start the macaroon service and let it create its default macaroon in
// case it doesn't exist yet.
err = d.startMacaroonService()
if err != nil {
// The client is the only thing we started yet, so if we clean
// up its connection now, nothing else needs to be shut down at
// this point.
clientCleanup()
return err
}

// Now finally fully initialize the swap client RPC server instance.
d.swapClientServer = swapClientServer{
impl: swapclient,
Expand All @@ -336,9 +367,13 @@ func (d *Daemon) initialize() error {
// Retrieve all currently existing swaps from the database.
swapsList, err := d.impl.FetchSwaps()
if err != nil {
// The client is the only thing we started yet, so if we clean
// up its connection now, nothing else needs to be shut down at
// this point.
// The client and the macaroon service are the only things we
// started yet, so if we clean that up now, nothing else needs
// to be shut down at this point.
if err := d.stopMacaroonService(); err != nil {
log.Errorf("Error shutting down macaroon service: %v",
err)
}
clientCleanup()
return err
}
Expand Down Expand Up @@ -443,6 +478,11 @@ func (d *Daemon) stop() {
d.restCtxCancel()
}

err := d.macaroonService.Close()
if err != nil {
log.Errorf("Error stopping macaroon service: %v", err)
}

// Next, shut down the connections to lnd and the swap server.
if d.lnd != nil {
d.lnd.Close()
Expand Down
Loading