diff --git a/README.md b/README.md index 36f2377..1503fbb 100644 --- a/README.md +++ b/README.md @@ -435,14 +435,21 @@ following command (assuming you have a local testnet `lnd` running): The current server is reachable at `clm.testnet.lightningcluster.com:12010`, this may change as the alpha version progresses. -## Transport security +## Authentication and transport security -The gRPC and REST connections of `poold` are encrypted with TLS the same way -`lnd` is. +The gRPC and REST connections of `poold` are encrypted with TLS and secured with +macaroon authentication the same way `lnd` is. If no custom base directory is set then the TLS certificate is stored in -`~/.pool//tls.cert`. +`~/.pool//tls.cert` and the base macaroon in +`~/.pool//pool.macaroon`. -The `pool` command will pick up the file automatically on mainnet if no custom +The `pool` command will pick up these file automatically on mainnet if no custom base 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**: pool's macaroons are independent from `lnd`'s. The same macaroon +cannot be used for both `poold` and `lnd`. diff --git a/cmd/pool/main.go b/cmd/pool/main.go index 9b86589..5ae8635 100644 --- a/cmd/pool/main.go +++ b/cmd/pool/main.go @@ -52,9 +52,9 @@ var ( Value: pool.DefaultTLSCertPath, } macaroonPathFlag = cli.StringFlag{ - Name: "macaroonpath", - Usage: "path to macaroon file, only needed if pool runs " + - "in the same process as lnd", + Name: "macaroonpath", + Usage: "path to macaroon file", + Value: pool.DefaultMacaroonPath, } ) @@ -172,32 +172,45 @@ func extractPathArgs(ctx *cli.Context) (string, string, error) { } // We'll now fetch the basedir 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 cert and macaroon. This will either be the default, + // or will have been overwritten by the end user. baseDir := lncfg.CleanAndExpandPath(ctx.GlobalString(baseDirFlag.Name)) tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString( tlsCertFlag.Name, )) + macPath := lncfg.CleanAndExpandPath(ctx.GlobalString( + macaroonPathFlag.Name, + )) // If a custom base 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 base directory set. This - // allows us to set a custom base directory, along with custom paths to - // the TLS cert file. + // 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 base + // directory set. This allows us to set a custom base directory, along + // with custom paths to the TLS cert and macaroon file. if baseDir != pool.DefaultBaseDir || networkStr != pool.DefaultNetwork { tlsCertPath = filepath.Join( baseDir, networkStr, pool.DefaultTLSCertFilename, ) + macPath = filepath.Join( + baseDir, networkStr, pool.DefaultMacaroonFilename, + ) } - return tlsCertPath, ctx.GlobalString(macaroonPathFlag.Name), nil + return tlsCertPath, macPath, nil } 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. @@ -206,11 +219,6 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, fatal(err) } - // Macaroons are not yet enabled by default. - if macaroonPath != "" { - opts = append(opts, readMacaroon(macaroonPath)) - } - opts = append(opts, grpc.WithTransportCredentials(creds)) conn, err := grpc.Dial(address, opts...) @@ -253,16 +261,16 @@ func parseUint64(ctx *cli.Context, argIdx int, flag, cmd string) (uint64, error) // 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{ @@ -282,10 +290,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 } diff --git a/config.go b/config.go index 0e5e534..dd87757 100644 --- a/config.go +++ b/config.go @@ -62,6 +62,16 @@ var ( DefaultTLSKeyPath = filepath.Join( DefaultBaseDir, DefaultNetwork, DefaultTLSKeyFilename, ) + + // DefaultMacaroonFilename is the default file name for the + // autogenerated pool macaroon. + DefaultMacaroonFilename = "pool.macaroon" + + // DefaultMacaroonPath is the default full path of the base pool + // macaroon. + DefaultMacaroonPath = filepath.Join( + DefaultBaseDir, DefaultNetwork, DefaultMacaroonFilename, + ) ) type LndConfig struct { @@ -78,7 +88,7 @@ type Config struct { TLSPathAuctSrv string `long:"tlspathauctserver" description:"Path to auction server tls certificate"` RPCListen string `long:"rpclisten" description:"Address to listen on for gRPC clients"` RESTListen string `long:"restlisten" description:"Address to listen on for REST clients"` - BaseDir string `long:"basedir" description:"The base directory where pool stores all its data. If set, this option overwrites --logdir, --tlscertpath and --tlskeypath."` + BaseDir string `long:"basedir" description:"The base directory where pool stores all its data. If set, this option overwrites --logdir, --macaroonpath, --tlscertpath and --tlskeypath."` LogDir string `long:"logdir" description:"Directory to log output."` MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"` @@ -95,6 +105,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 pool's RPC and REST services if it doesn't exist."` + NewNodesOnly bool `long:"newnodesonly" description:"Only accept channels from nodes that the connected lnd node doesn't already have open or pending channels with."` Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65535"` @@ -140,6 +152,7 @@ func DefaultConfig() Config { DebugLevel: defaultLogLevel, TLSCertPath: DefaultTLSCertPath, TLSKeyPath: DefaultTLSKeyPath, + MacaroonPath: DefaultMacaroonPath, Lnd: &LndConfig{ Host: "localhost:10009", }, @@ -153,6 +166,7 @@ func Validate(cfg *Config) error { cfg.LogDir = lncfg.CleanAndExpandPath(cfg.LogDir) cfg.TLSCertPath = lncfg.CleanAndExpandPath(cfg.TLSCertPath) cfg.TLSKeyPath = lncfg.CleanAndExpandPath(cfg.TLSKeyPath) + cfg.MacaroonPath = lncfg.CleanAndExpandPath(cfg.MacaroonPath) // Since our pool directory overrides our log and TLS dir values, make // sure that they are not set when base dir is set. We hard here rather @@ -163,6 +177,7 @@ func Validate(cfg *Config) error { logDirSet := cfg.LogDir != defaultLogDir tlsCertPathSet := cfg.TLSCertPath != DefaultTLSCertPath tlsKeyPathSet := cfg.TLSKeyPath != DefaultTLSKeyPath + macaroonPathSet := cfg.MacaroonPath != DefaultMacaroonPath if logDirSet { return fmt.Errorf("basedir overwrites logdir, please " + @@ -179,6 +194,11 @@ func Validate(cfg *Config) error { "please only set one value") } + if macaroonPathSet { + return fmt.Errorf("basedir overwrites macaroonpath, " + + "please only set one value") + } + // Once we are satisfied that no other config value was set, we // replace them with our pool dir. cfg.LogDir = filepath.Join(cfg.BaseDir, defaultLogDirname) @@ -189,9 +209,9 @@ func Validate(cfg *Config) error { cfg.LogDir = filepath.Join(cfg.LogDir, cfg.Network) cfg.BaseDir = filepath.Join(cfg.BaseDir, 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 basedir. + // 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 basedir. if cfg.TLSCertPath == DefaultTLSCertPath { cfg.TLSCertPath = filepath.Join( cfg.BaseDir, DefaultTLSCertFilename, @@ -202,6 +222,11 @@ func Validate(cfg *Config) error { cfg.BaseDir, DefaultTLSKeyFilename, ) } + if cfg.MacaroonPath == DefaultMacaroonPath { + cfg.MacaroonPath = filepath.Join( + cfg.BaseDir, DefaultMacaroonFilename, + ) + } // If either of these directories do not exist, create them. if err := os.MkdirAll(cfg.BaseDir, os.ModePerm); err != nil { diff --git a/go.mod b/go.mod index c06571b..249256d 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,10 @@ require ( github.com/lightninglabs/aperture v0.1.1-beta.0.20200901205500-5237b07a6ef5 github.com/lightninglabs/lndclient v0.11.0-1 github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d - // TODO(guggero): Bump to v0.11.1-beta once released. - github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200907194312-751b02361e53 + + // 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/stretchr/testify v1.5.1 github.com/urfave/cli v1.20.0 @@ -24,5 +26,6 @@ require ( golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 google.golang.org/grpc v1.29.1 + gopkg.in/macaroon-bakery.v2 v2.0.1 gopkg.in/macaroon.v2 v2.1.0 ) diff --git a/go.sum b/go.sum index 99f23e8..7ef2f16 100644 --- a/go.sum +++ b/go.sum @@ -207,11 +207,9 @@ 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/go.mod h1:CzArvT7NFDLhVyW06+NJWSuWFmE6Ea+AjjA3txUBqTM= -github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200907194312-751b02361e53 h1:2q0mdQRgA8uEblmZO+i9pqkNWVb9S3dyV1evVRRe/k4= -github.com/lightningnetwork/lnd v0.11.0-beta.rc4.0.20200907194312-751b02361e53/go.mod h1:IvrqVCc5tN2on6E7IHhrwyiM7FCHZ92LphZD+v88LXY= +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 h1:g2rEu+sM2Uyz0bpfuvwri/ks6R/26H5iY1NcGbpDJ+c= github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= github.com/lightningnetwork/lnd/cert v1.0.3 h1:/K2gjzLgVI8we2IIPKc0ztWTEa85uds5sWXi1K6mOT0= diff --git a/macaroons.go b/macaroons.go new file mode 100644 index 0000000..02b7910 --- /dev/null +++ b/macaroons.go @@ -0,0 +1,188 @@ +package pool + +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 ( + // poolMacaroonLocation is the value we use for the pool macaroons' + // "Location" field when baking them. + poolMacaroonLocation = "pool" +) + +var ( + // RequiredPermissions is a map of all pool RPC methods and their + // required macaroon permissions to access poold. + RequiredPermissions = map[string][]bakery.Op{ + "/poolrpc.Trader/QuoteAccount": {{ + Entity: "account", + Action: "read", + }}, + "/poolrpc.Trader/InitAccount": {{ + Entity: "account", + Action: "write", + }}, + "/poolrpc.Trader/ListAccounts": {{ + Entity: "account", + Action: "read", + }}, + "/poolrpc.Trader/CloseAccount": {{ + Entity: "account", + Action: "write", + }}, + "/poolrpc.Trader/WithdrawAccount": {{ + Entity: "account", + Action: "write", + }}, + "/poolrpc.Trader/DepositAccount": {{ + Entity: "account", + Action: "write", + }}, + "/poolrpc.Trader/BumpAccountFee": {{ + Entity: "account", + Action: "write", + }}, + "/poolrpc.Trader/RecoverAccounts": {{ + Entity: "account", + Action: "write", + }}, + "/poolrpc.Trader/SubmitOrder": {{ + Entity: "order", + Action: "write", + }}, + "/poolrpc.Trader/ListOrders": {{ + Entity: "order", + Action: "read", + }}, + "/poolrpc.Trader/CancelOrder": {{ + Entity: "order", + Action: "write", + }}, + "/poolrpc.Trader/AuctionFee": {{ + Entity: "auction", + Action: "read", + }}, + "/poolrpc.Trader/BatchSnapshot": {{ + Entity: "auction", + Action: "read", + }}, + "/poolrpc.Trader/GetLsatTokens": {{ + Entity: "auth", + Action: "read", + }}, + } + + // allPermissions is the list of all existing permissions that exist + // for poold'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 pool. + allPermissions = []bakery.Op{{ + Entity: "account", + Action: "read", + }, { + Entity: "account", + Action: "write", + }, { + Entity: "order", + Action: "read", + }, { + Entity: "order", + Action: "write", + }, { + Entity: "auction", + Action: "read", + }, { + Entity: "auth", + Action: "read", + }} + + // macDbDefaultPw is the default encryption password used to encrypt the + // pool 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 (s *Server) startMacaroonService() error { + // Create the macaroon authentication/authorization service. + var err error + s.macaroonService, err = macaroons.NewService( + s.cfg.BaseDir, poolMacaroonLocation, 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 = s.macaroonService.CreateUnlock(&macDbDefaultPw) + if err != nil { + return fmt.Errorf("unable to unlock macaroon DB: %v", err) + } + + // Create macaroon files for pool CLI to use if they don't exist. + if !lnrpc.FileExists(s.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. + poolMac, err := s.macaroonService.NewMacaroon( + ctx, macaroons.DefaultRootKeyID, + allPermissions..., + ) + if err != nil { + return err + } + poolMacBytes, err := poolMac.M().MarshalBinary() + if err != nil { + return err + } + err = ioutil.WriteFile(s.cfg.MacaroonPath, poolMacBytes, 0644) + if err != nil { + if err := os.Remove(s.cfg.MacaroonPath); err != nil { + log.Errorf("Unable to remove %s: %v", + s.cfg.MacaroonPath, err) + } + return err + } + } + + return nil +} + +// stopMacaroonService closes the macaroon database. +func (s *Server) stopMacaroonService() error { + return s.macaroonService.Close() +} + +// macaroonInterceptor creates macaroon security interceptors. +func (s *Server) macaroonInterceptor() (grpc.UnaryServerInterceptor, + grpc.StreamServerInterceptor) { + + unaryInterceptor := s.macaroonService.UnaryServerInterceptor( + RequiredPermissions, + ) + streamInterceptor := s.macaroonService.StreamServerInterceptor( + RequiredPermissions, + ) + return unaryInterceptor, streamInterceptor +} diff --git a/server.go b/server.go index a635a51..2e863a1 100644 --- a/server.go +++ b/server.go @@ -22,9 +22,11 @@ import ( "github.com/lightninglabs/pool/poolrpc" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/verrpc" + "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/signal" "google.golang.org/grpc" "google.golang.org/grpc/metadata" + "gopkg.in/macaroon-bakery.v2/bakery" ) var ( @@ -63,16 +65,17 @@ type Server struct { // client or an error if none has been established yet. GetIdentity func() (*lsat.TokenID, error) - cfg *Config - db *clientdb.DB - lsatStore *lsat.FileStore - lndServices *lndclient.GrpcLndServices - lndClient lnrpc.LightningClient - grpcServer *grpc.Server - restProxy *http.Server - grpcListener net.Listener - restListener net.Listener - wg sync.WaitGroup + cfg *Config + db *clientdb.DB + lsatStore *lsat.FileStore + lndServices *lndclient.GrpcLndServices + lndClient lnrpc.LightningClient + grpcServer *grpc.Server + restProxy *http.Server + grpcListener net.Listener + restListener net.Listener + macaroonService *macaroons.Service + wg sync.WaitGroup } // NewServer creates a new trader server. @@ -92,11 +95,28 @@ func (s *Server) Start() error { // Print the version before executing either primary directive. log.Infof("Version: %v", Version()) + // Depending on how far we got in initializing the server, we might need + // to clean up certain services that were already started. Keep track of + // them with this map of service name to shutdown function. + shutdownFuncs := make(map[string]func() error) + defer func() { + for serviceName, shutdownFn := range shutdownFuncs { + if err := shutdownFn(); err != nil { + log.Errorf("Error shutting down %s service: %v", + serviceName, err) + } + } + }() + var err error s.lndServices, err = getLnd(s.cfg.Network, s.cfg.Lnd) if err != nil { return err } + shutdownFuncs["lnd"] = func() error { // nolint:unparam + s.lndServices.Close() + return nil + } // As there're some other lower-level operations we may need access to, // we'll also make a connection for a "basic client". @@ -111,11 +131,20 @@ func (s *Server) Start() error { return err } + // Start the macaroon service and let it create its default macaroon in + // case it doesn't exist yet. + if err := s.startMacaroonService(); err != nil { + return err + } + shutdownFuncs["macaroon"] = s.stopMacaroonService + // Setup the auctioneer client and interceptor. err = s.setupClient() if err != nil { return err } + shutdownFuncs["clientdb"] = s.db.Close + shutdownFuncs["auctioneer"] = s.AuctioneerClient.Stop // Instantiate the trader gRPC server and start it. s.rpcServer = newRPCServer(s) @@ -123,13 +152,19 @@ func (s *Server) Start() error { if err != nil { return err } + shutdownFuncs["rpcServer"] = s.rpcServer.Stop + // Let's create our interceptor chain, starting with the security + // interceptors that will check macaroons for their validity. + unaryMacIntercept, streamMacIntercept := s.macaroonInterceptor() serverOpts := []grpc.ServerOption{ - grpc.StreamInterceptor( + grpc.ChainStreamInterceptor( errorLogStreamServerInterceptor(rpcLog), + streamMacIntercept, ), - grpc.UnaryInterceptor( + grpc.ChainUnaryInterceptor( errorLogUnaryServerInterceptor(rpcLog), + unaryMacIntercept, ), } s.grpcServer = grpc.NewServer(serverOpts...) @@ -155,7 +190,6 @@ func (s *Server) Start() error { if err != nil { return fmt.Errorf("RPC server unable to listen on %s", s.cfg.RPCListen) - } // We'll also create and start an accompanying proxy to serve @@ -196,6 +230,8 @@ func (s *Server) Start() error { s.cfg.RESTListen) } s.restListener = tls.NewListener(s.restListener, serverTLSCfg) + shutdownFuncs["restListener"] = s.restListener.Close + s.restProxy = &http.Server{Handler: mux} s.wg.Add(1) go func() { @@ -209,6 +245,7 @@ func (s *Server) Start() error { }() } s.grpcListener = tls.NewListener(s.grpcListener, serverTLSCfg) + shutdownFuncs["rpcListener"] = s.grpcListener.Close // Start the grpc server. s.wg.Add(1) @@ -232,7 +269,16 @@ func (s *Server) Start() error { // The final thing we'll do on start up is sync the order state of the // auctioneer with what we have on disk. - return s.syncLocalOrderState() + err = s.syncLocalOrderState() + if err != nil { + return err + } + + // If we got here successfully, there's no need to shutdown anything + // anymore. + shutdownFuncs = nil + + return nil } // StartAsSubserver is an alternative start method where the RPC server does not @@ -250,11 +296,33 @@ func (s *Server) StartAsSubserver(lndClient lnrpc.LightningClient, // Print the version before executing either primary directive. log.Infof("Version: %v", Version()) + // Depending on how far we got in initializing the server, we might need + // to clean up certain services that were already started. Keep track of + // them with this map of service name to shutdown function. + shutdownFuncs := make(map[string]func() error) + defer func() { + for serviceName, shutdownFn := range shutdownFuncs { + if err := shutdownFn(); err != nil { + log.Errorf("Error shutting down %s service: %v", + serviceName, err) + } + } + }() + + // Start the macaroon service and let it create its default macaroon in + // case it doesn't exist yet. + if err := s.startMacaroonService(); err != nil { + return err + } + shutdownFuncs["macaroon"] = s.stopMacaroonService + // Setup the auctioneer client and interceptor. err := s.setupClient() if err != nil { return err } + shutdownFuncs["clientdb"] = s.db.Close + shutdownFuncs["auctioneer"] = s.AuctioneerClient.Stop // Instantiate the trader gRPC server and start it. s.rpcServer = newRPCServer(s) @@ -262,10 +330,35 @@ func (s *Server) StartAsSubserver(lndClient lnrpc.LightningClient, if err != nil { return err } + shutdownFuncs["rpcServer"] = s.rpcServer.Stop // The final thing we'll do on start up is sync the order state of the // auctioneer with what we have on disk. - return s.syncLocalOrderState() + err = s.syncLocalOrderState() + if err != nil { + return err + } + + // If we got here successfully, there's no need to shutdown anything + // anymore. + shutdownFuncs = nil + + return nil +} + +// 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 poold running as an external subserver in the same process +// as lnd but still validate its own macaroons. +func (s *Server) ValidateMacaroon(ctx context.Context, + requiredPermissions []bakery.Op, fullMethod string) error { + + // Delegate the call to pool's own macaroon validator service. + return s.macaroonService.ValidateMacaroon( + ctx, requiredPermissions, fullMethod, + ) } // setupClient initializes the auctioneer client and its interceptors. @@ -395,6 +488,9 @@ func (s *Server) Stop() error { if err := s.db.Close(); err != nil { log.Errorf("Error closing DB: %v", err) } + if err := s.macaroonService.Close(); err != nil { + log.Errorf("Error stopping macaroon service: %v", err) + } s.lndServices.Close() s.wg.Wait()