Skip to content

Commit d638d07

Browse files
authored
Merge pull request #281 from guggero/macaroons
[2/3] loopd: add macaroon authentication to the loop RPC server
2 parents ccb9e07 + 9adbd59 commit d638d07

File tree

8 files changed

+307
-40
lines changed

8 files changed

+307
-40
lines changed

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,18 +333,25 @@ 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
336+
## Authentication and transport security
337337

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

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

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

349+
For more information on macaroons,
350+
[see the macaroon documentation of lnd.](https://github.com/lightningnetwork/lnd/blob/master/docs/macaroons.md)
351+
352+
**NOTE**: Loop's macaroons are independent from `lnd`'s. The same macaroon
353+
cannot be used for both `loopd` and `lnd`.
354+
348355
## Multiple Simultaneous Swaps
349356

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

cmd/loop/main.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ var (
6666
Value: loopd.DefaultTLSCertPath,
6767
}
6868
macaroonPathFlag = cli.StringFlag{
69-
Name: "macaroonpath",
70-
Usage: "path to macaroon file, only needed if loop runs " +
71-
"in the same process as lnd",
69+
Name: "macaroonpath",
70+
Usage: "path to macaroon file",
71+
Value: loopd.DefaultMacaroonPath,
7272
}
7373
)
7474

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

173173
// 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.
174+
// properly read the macaroons and also the cert. This will either be
175+
// the default, or will have been overwritten by the end user.
176176
loopDir := lncfg.CleanAndExpandPath(ctx.GlobalString(loopDirFlag.Name))
177177
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
178178
tlsCertFlag.Name,
179179
))
180+
macPath := lncfg.CleanAndExpandPath(ctx.GlobalString(
181+
macaroonPathFlag.Name,
182+
))
180183

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.
184+
// If a custom loop directory was set, we'll also check if custom paths
185+
// for the TLS cert and macaroon file were set as well. If not, we'll
186+
// override their paths so they can be found within the custom loop
187+
// directory set. This allows us to set a custom loop directory, along
188+
// with custom paths to the TLS cert and macaroon file.
186189
if loopDir != loopd.LoopDirBase || networkStr != loopd.DefaultNetwork {
187190
tlsCertPath = filepath.Join(
188191
loopDir, networkStr, loopd.DefaultTLSCertFilename,
189192
)
193+
macPath = filepath.Join(
194+
loopDir, networkStr, loopd.DefaultMacaroonFilename,
195+
)
190196
}
191197

192-
return tlsCertPath, ctx.GlobalString(macaroonPathFlag.Name), nil
198+
return tlsCertPath, macPath, nil
193199
}
194200

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

382+
// We always need to send a macaroon.
383+
macOption, err := readMacaroon(macaroonPath)
384+
if err != nil {
385+
return nil, err
386+
}
387+
376388
opts := []grpc.DialOption{
377389
grpc.WithDefaultCallOptions(maxMsgRecvSize),
390+
macOption,
378391
}
379392

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

386-
// Macaroons are not yet enabled by default.
387-
if macaroonPath != "" {
388-
macOption, err := readMacaroon(macaroonPath)
389-
if err != nil {
390-
return nil, err
391-
}
392-
opts = append(opts, macOption)
393-
}
394-
395399
opts = append(opts, grpc.WithTransportCredentials(creds))
396400

397401
conn, err := grpc.Dial(address, opts...)

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@ require (
1212
github.com/jessevdk/go-flags v1.4.0
1313
github.com/lightninglabs/lndclient v0.11.0-0
1414
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d
15-
github.com/lightningnetwork/lnd v0.11.0-beta
15+
16+
// TODO(guggero): Bump lnd to the final v0.11.1-beta version once it's
17+
// released.
18+
github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200911014924-bc6e52888763
1619
github.com/lightningnetwork/lnd/cert v1.0.3
1720
github.com/lightningnetwork/lnd/queue v1.0.4
1821
github.com/stretchr/testify v1.5.1
1922
github.com/urfave/cli v1.20.0
2023
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0
2124
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c
2225
google.golang.org/grpc v1.24.0
26+
gopkg.in/macaroon-bakery.v2 v2.0.1
2327
gopkg.in/macaroon.v2 v2.1.0
2428
)
2529

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ github.com/btcsuite/btcwallet/walletdb v1.3.3 h1:u6e7vRIKBF++cJy+hOHaMGg+88ZTwvp
4545
github.com/btcsuite/btcwallet/walletdb v1.3.3/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
4646
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
4747
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgrCfFWqPd9NA=
48+
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgrCfFWqPd9NA=
49+
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
4850
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
4951
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
5052
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
@@ -179,14 +181,14 @@ github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce7
179181
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
180182
github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea h1:oCj48NQ8u7Vz+MmzHqt0db6mxcFZo3Ho7M5gCJauY/k=
181183
github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
182-
github.com/lightningnetwork/lnd v0.11.0-beta h1:pUAT7FMHqS+iarNxyRtgj96XKCGAWwmb6ZdiUBy78ts=
183184
github.com/lightningnetwork/lnd v0.11.0-beta/go.mod h1:CzArvT7NFDLhVyW06+NJWSuWFmE6Ea+AjjA3txUBqTM=
185+
github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200911014924-bc6e52888763 h1:OUWOTo2BAcsnEaMQIf4gLktU3zGytx6pXrmjUNpZpdg=
186+
github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200911014924-bc6e52888763/go.mod h1:IvrqVCc5tN2on6E7IHhrwyiM7FCHZ92LphZD+v88LXY=
184187
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
185188
github.com/lightningnetwork/lnd/cert v1.0.3 h1:/K2gjzLgVI8we2IIPKc0ztWTEa85uds5sWXi1K6mOT0=
186189
github.com/lightningnetwork/lnd/cert v1.0.3/go.mod h1:3MWXVLLPI0Mg0XETm9fT4N9Vyy/8qQLmaM5589bEggM=
187190
github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo=
188191
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
189-
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
190192
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
191193
github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAAChPBwa4BJxH4k=
192194
github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
@@ -246,7 +248,6 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
246248
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
247249
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
248250
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
249-
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
250251
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
251252
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
252253
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -259,7 +260,6 @@ github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
259260
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
260261
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
261262
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
262-
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
263263
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
264264
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs=
265265
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=

loopd/config.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ var (
5757
DefaultTLSKeyPath = filepath.Join(
5858
LoopDirBase, DefaultNetwork, DefaultTLSKeyFilename,
5959
)
60+
61+
// DefaultMacaroonFilename is the default file name for the
62+
// autogenerated loop macaroon.
63+
DefaultMacaroonFilename = "loop.macaroon"
64+
65+
// DefaultMacaroonPath is the default full path of the base loop
66+
// macaroon.
67+
DefaultMacaroonPath = filepath.Join(
68+
LoopDirBase, DefaultNetwork, DefaultMacaroonFilename,
69+
)
6070
)
6171

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

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

@@ -93,6 +103,8 @@ type Config struct {
93103
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed."`
94104
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."`
95105

106+
MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for loop's RPC and REST services if it doesn't exist."`
107+
96108
LogDir string `long:"logdir" description:"Directory to log output."`
97109
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)."`
98110
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB."`
@@ -133,6 +145,7 @@ func DefaultConfig() Config {
133145
DebugLevel: defaultLogLevel,
134146
TLSCertPath: DefaultTLSCertPath,
135147
TLSKeyPath: DefaultTLSKeyPath,
148+
MacaroonPath: DefaultMacaroonPath,
136149
MaxLSATCost: lsat.DefaultMaxCostSats,
137150
MaxLSATFee: lsat.DefaultMaxRoutingFeeSats,
138151
LoopOutMaxParts: defaultLoopOutMaxParts,
@@ -193,9 +206,9 @@ func Validate(cfg *Config) error {
193206
cfg.DataDir = filepath.Join(cfg.DataDir, cfg.Network)
194207
cfg.LogDir = filepath.Join(cfg.LogDir, cfg.Network)
195208

196-
// We want the TLS files to also be in the "namespaced" sub directory.
197-
// Replace the default values with actual values in case the user
198-
// specified either loopdir or datadir.
209+
// We want the TLS and macaroon files to also be in the "namespaced" sub
210+
// directory. Replace the default values with actual values in case the
211+
// user specified either loopdir or datadir.
199212
if cfg.TLSCertPath == DefaultTLSCertPath {
200213
cfg.TLSCertPath = filepath.Join(
201214
cfg.DataDir, DefaultTLSCertFilename,
@@ -206,6 +219,11 @@ func Validate(cfg *Config) error {
206219
cfg.DataDir, DefaultTLSKeyFilename,
207220
)
208221
}
222+
if cfg.MacaroonPath == DefaultMacaroonPath {
223+
cfg.MacaroonPath = filepath.Join(
224+
cfg.DataDir, DefaultMacaroonFilename,
225+
)
226+
}
209227

210228
// If either of these directories do not exist, create them.
211229
if err := os.MkdirAll(cfg.DataDir, os.ModePerm); err != nil {

loopd/daemon.go

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import (
1616
"github.com/lightninglabs/loop"
1717
"github.com/lightninglabs/loop/looprpc"
1818
"github.com/lightningnetwork/lnd/lntypes"
19+
"github.com/lightningnetwork/lnd/macaroons"
1920
"google.golang.org/grpc"
21+
"gopkg.in/macaroon-bakery.v2/bakery"
2022
)
2123

2224
var (
@@ -79,6 +81,8 @@ type Daemon struct {
7981
restServer *http.Server
8082
restListener net.Listener
8183
restCtxCancel func()
84+
85+
macaroonService *macaroons.Service
8286
}
8387

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

174+
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
175+
// checks its signature, makes sure all specified permissions for the called
176+
// method are contained within and finally ensures all caveat conditions are
177+
// met. A non-nil error is returned if any of the checks fail. This method is
178+
// needed to enable loopd running as an external subserver in the same process
179+
// as lnd but still validate its own macaroons.
180+
func (d *Daemon) ValidateMacaroon(ctx context.Context,
181+
requiredPermissions []bakery.Op, fullMethod string) error {
182+
183+
// Delegate the call to loop's own macaroon validator service.
184+
return d.macaroonService.ValidateMacaroon(
185+
ctx, requiredPermissions, fullMethod,
186+
)
187+
}
188+
170189
// startWebServers starts the gRPC and REST servers in goroutines.
171190
func (d *Daemon) startWebServers() error {
172191
var err error
173192

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

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

345+
// Start the macaroon service and let it create its default macaroon in
346+
// case it doesn't exist yet.
347+
err = d.startMacaroonService()
348+
if err != nil {
349+
// The client is the only thing we started yet, so if we clean
350+
// up its connection now, nothing else needs to be shut down at
351+
// this point.
352+
clientCleanup()
353+
return err
354+
}
355+
325356
// Now finally fully initialize the swap client RPC server instance.
326357
d.swapClientServer = swapClientServer{
327358
impl: swapclient,
@@ -336,9 +367,13 @@ func (d *Daemon) initialize() error {
336367
// Retrieve all currently existing swaps from the database.
337368
swapsList, err := d.impl.FetchSwaps()
338369
if err != nil {
339-
// The client is the only thing we started yet, so if we clean
340-
// up its connection now, nothing else needs to be shut down at
341-
// this point.
370+
// The client and the macaroon service are the only things we
371+
// started yet, so if we clean that up now, nothing else needs
372+
// to be shut down at this point.
373+
if err := d.stopMacaroonService(); err != nil {
374+
log.Errorf("Error shutting down macaroon service: %v",
375+
err)
376+
}
342377
clientCleanup()
343378
return err
344379
}
@@ -443,6 +478,11 @@ func (d *Daemon) stop() {
443478
d.restCtxCancel()
444479
}
445480

481+
err := d.macaroonService.Close()
482+
if err != nil {
483+
log.Errorf("Error stopping macaroon service: %v", err)
484+
}
485+
446486
// Next, shut down the connections to lnd and the swap server.
447487
if d.lnd != nil {
448488
d.lnd.Close()

0 commit comments

Comments
 (0)