diff --git a/README.md b/README.md index 5a1e0ea..05e7eca 100644 --- a/README.md +++ b/README.md @@ -333,18 +333,25 @@ pending swaps after a restart. Information about pending swaps is stored persistently in the swap database. Its location is `~/.loopd//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//tls.cert`. +`~/.loop//tls.cert` and the base macaroon in +`~/.loop//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 diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 6847975..97d33cd 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -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, } ) @@ -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 { @@ -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. @@ -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...) diff --git a/go.mod b/go.mod index 7102d55..2b43d44 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,10 @@ 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 @@ -20,6 +23,7 @@ require ( 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 ) diff --git a/go.sum b/go.sum index 5381885..117478a 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= @@ -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= @@ -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= diff --git a/loopd/config.go b/loopd/config.go index ae062be..2e711af 100644 --- a/loopd/config.go +++ b/loopd/config.go @@ -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 { @@ -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."` @@ -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."` @@ -133,6 +145,7 @@ func DefaultConfig() Config { DebugLevel: defaultLogLevel, TLSCertPath: DefaultTLSCertPath, TLSKeyPath: DefaultTLSKeyPath, + MacaroonPath: DefaultMacaroonPath, MaxLSATCost: lsat.DefaultMaxCostSats, MaxLSATFee: lsat.DefaultMaxRoutingFeeSats, LoopOutMaxParts: defaultLoopOutMaxParts, @@ -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, @@ -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 { diff --git a/loopd/daemon.go b/loopd/daemon.go index 63794e7..f806788 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -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 ( @@ -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. @@ -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) @@ -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, @@ -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 } @@ -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() diff --git a/loopd/macaroons.go b/loopd/macaroons.go new file mode 100644 index 0000000..07c4e93 --- /dev/null +++ b/loopd/macaroons.go @@ -0,0 +1,192 @@ +package loopd + +import ( + "context" + "fmt" + "io/ioutil" + "os" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/macaroons" + "google.golang.org/grpc" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +const ( + // loopMacaroonLocation is the value we use for the loopd macaroons' + // "Location" field when baking them. + loopMacaroonLocation = "loop" +) + +var ( + // RequiredPermissions is a map of all loop RPC methods and their + // required macaroon permissions to access loopd. + RequiredPermissions = map[string][]bakery.Op{ + "/looprpc.SwapClient/LoopOut": {{ + Entity: "swap", + Action: "execute", + }, { + Entity: "loop", + Action: "out", + }}, + "/looprpc.SwapClient/LoopIn": {{ + Entity: "swap", + Action: "execute", + }, { + Entity: "loop", + Action: "in", + }}, + "/looprpc.SwapClient/Monitor": {{ + Entity: "swap", + Action: "read", + }}, + "/looprpc.SwapClient/ListSwaps": {{ + Entity: "swap", + Action: "read", + }}, + "/looprpc.SwapClient/SwapInfo": {{ + Entity: "swap", + Action: "read", + }}, + "/looprpc.SwapClient/LoopOutTerms": {{ + Entity: "terms", + Action: "read", + }, { + Entity: "loop", + Action: "out", + }}, + "/looprpc.SwapClient/LoopOutQuote": {{ + Entity: "swap", + Action: "read", + }, { + Entity: "loop", + Action: "out", + }}, + "/looprpc.SwapClient/GetLoopInTerms": {{ + Entity: "terms", + Action: "read", + }, { + Entity: "loop", + Action: "in", + }}, + "/looprpc.SwapClient/GetLoopInQuote": {{ + Entity: "swap", + Action: "read", + }, { + Entity: "loop", + Action: "in", + }}, + "/looprpc.SwapClient/GetLsatTokens": {{ + Entity: "auth", + Action: "read", + }}, + } + + // allPermissions is the list of all existing permissions that exist + // for loopd's RPC. The default macaroon that is created on startup + // contains all these permissions and is therefore equivalent to lnd's + // admin.macaroon but for loop. + allPermissions = []bakery.Op{{ + Entity: "loop", + Action: "out", + }, { + Entity: "loop", + Action: "in", + }, { + Entity: "swap", + Action: "execute", + }, { + Entity: "swap", + Action: "read", + }, { + Entity: "terms", + Action: "read", + }, { + Entity: "auth", + Action: "read", + }} + + // macDbDefaultPw is the default encryption password used to encrypt the + // loop macaroon database. The macaroon service requires us to set a + // non-nil password so we set it to an empty string. This will cause the + // keys to be encrypted on disk but won't provide any security at all as + // the password is known to anyone. + // + // TODO(guggero): Allow the password to be specified by the user. Needs + // create/unlock calls in the RPC. Using a password should be optional + // though. + macDbDefaultPw = []byte("") +) + +// startMacaroonService starts the macaroon validation service, creates or +// unlocks the macaroon database and creates the default macaroon if it doesn't +// exist yet. If macaroons are disabled in general in the configuration, none of +// these actions are taken. +func (d *Daemon) startMacaroonService() error { + // Create the macaroon authentication/authorization service. + var err error + d.macaroonService, err = macaroons.NewService( + d.cfg.DataDir, loopMacaroonLocation, macaroons.IPLockChecker, + ) + if err != nil { + return fmt.Errorf("unable to set up macaroon authentication: "+ + "%v", err) + } + + // Try to unlock the macaroon store with the private password. + err = d.macaroonService.CreateUnlock(&macDbDefaultPw) + if err != nil { + return fmt.Errorf("unable to unlock macaroon DB: %v", err) + } + + // Create macaroon files for loop CLI to use if they don't exist. + if !lnrpc.FileExists(d.cfg.MacaroonPath) { + ctx := context.Background() + + // We only generate one default macaroon that contains all + // existing permissions (equivalent to the admin.macaroon in + // lnd). Custom macaroons can be created through the bakery + // RPC. + loopMac, err := d.macaroonService.NewMacaroon( + ctx, macaroons.DefaultRootKeyID, + allPermissions..., + ) + if err != nil { + return err + } + loopMacBytes, err := loopMac.M().MarshalBinary() + if err != nil { + return err + } + err = ioutil.WriteFile(d.cfg.MacaroonPath, loopMacBytes, 0644) + if err != nil { + if err := os.Remove(d.cfg.MacaroonPath); err != nil { + log.Errorf("Unable to remove %s: %v", + d.cfg.MacaroonPath, err) + } + return err + } + } + + return nil +} + +// stopMacaroonService closes the macaroon database. +func (d *Daemon) stopMacaroonService() error { + return d.macaroonService.Close() +} + +// macaroonInterceptor creates gRPC server options with the macaroon security +// interceptors. +func (d *Daemon) macaroonInterceptor() []grpc.ServerOption { + unaryInterceptor := d.macaroonService.UnaryServerInterceptor( + RequiredPermissions, + ) + streamInterceptor := d.macaroonService.StreamServerInterceptor( + RequiredPermissions, + ) + return []grpc.ServerOption{ + grpc.UnaryInterceptor(unaryInterceptor), + grpc.StreamInterceptor(streamInterceptor), + } +} diff --git a/loopd/run.go b/loopd/run.go index 199a5ca..434e19d 100644 --- a/loopd/run.go +++ b/loopd/run.go @@ -208,7 +208,9 @@ func Run(rpcCfg RPCConfig) error { // we are running. When our command tries to get a lnd connection, it // blocks until lnd is synced. We listen for interrupts so that we can // shutdown the daemon while waiting for sync to complete. - signal.Intercept() + if err := signal.Intercept(); err != nil { + return err + } // Execute command. if parser.Active == nil {