mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Merge pull request #281 from guggero/macaroons
[2/3] loopd: add macaroon authentication to the loop RPC server
This commit is contained in:
commit
d638d07fe3
8 changed files with 307 additions and 40 deletions
17
README.md
17
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/<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
|
||||
|
|
|
|||
|
|
@ -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...)
|
||||
|
|
|
|||
6
go.mod
6
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
|
||||
)
|
||||
|
||||
|
|
|
|||
8
go.sum
8
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=
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
192
loopd/macaroons.go
Normal file
192
loopd/macaroons.go
Normal file
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue