From 502892f81d77c93df200d10e014e20650ab612b9 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 10 Sep 2020 14:03:51 +0200 Subject: [PATCH 1/5] mod: bump lnd to v0.11.1-beta --- go.mod | 6 ++++-- go.sum | 6 ++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c06571b..829d76e 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 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= From 1dc624a24afab109c70e31f88e933e64ba3ff540 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 9 Sep 2020 17:13:50 +0200 Subject: [PATCH 2/5] config+server: add macaroon authentication to pool's server connn To secure access to pool's RPC server, we add a macaroon authentication service and its gRPC interceptors to the daemon's server connection. --- config.go | 33 +++++++-- go.mod | 1 + macaroons.go | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ server.go | 110 ++++++++++++++++++++++++++---- 4 files changed, 313 insertions(+), 19 deletions(-) create mode 100644 macaroons.go 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 829d76e..249256d 100644 --- a/go.mod +++ b/go.mod @@ -26,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/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..ff9ddc6 100644 --- a/server.go +++ b/server.go @@ -22,6 +22,7 @@ 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" @@ -63,16 +64,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 +94,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 +130,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 +151,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 +189,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 +229,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 +244,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 +268,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 +295,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 +329,20 @@ 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 } // setupClient initializes the auctioneer client and its interceptors. @@ -395,6 +472,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() From b100fd3d6a2b89350165f920cfe52c7a5ad99697 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 9 Sep 2020 17:33:34 +0200 Subject: [PATCH 3/5] cmd/pool: add macaroon params to CLI --- cmd/pool/main.go | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) 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 } From b9358dec8f1b315cac11a069f4ee1ce2c58639a4 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 9 Sep 2020 17:34:55 +0200 Subject: [PATCH 4/5] server: allow poold to be used as external subserver When poold runs in the same process as lnd (in LiT), it hooks itself into lnd's RPC server as an external subserver. But because the user should still be able to use the default llm macaroon, the pool daemon must be able to validate its own macaroons as lnd's macaroon service doesn't know the root key for it. --- server.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/server.go b/server.go index ff9ddc6..2e863a1 100644 --- a/server.go +++ b/server.go @@ -26,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/signal" "google.golang.org/grpc" "google.golang.org/grpc/metadata" + "gopkg.in/macaroon-bakery.v2/bakery" ) var ( @@ -345,6 +346,21 @@ func (s *Server) StartAsSubserver(lndClient lnrpc.LightningClient, 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. func (s *Server) setupClient() error { // If no auction server is specified, use the default addresses for From dc948a84c437b2a5f3cbbb49f56c8e5af0202f87 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 9 Sep 2020 17:37:38 +0200 Subject: [PATCH 5/5] README: mention macaroons --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) 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`.