From 8a59081473db8b151cc516bdfd441bf1b69d997e Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 14 Jan 2022 14:58:30 +0200 Subject: [PATCH 1/2] multi: use lndclient MacaroonService Since the code for creating and using a macaroon service is the same for multiple projects (pool, loop, faraday & litd), the code has been unified in lndclient. So this commit removes the macaroon service code and instead uses the lndclient code. Since the default key for the macaroon db is now a shared secret with LND, faraday requires LND's admin macaroon so that this secret can be derived. So this commit also updates all references of LND's `readme` macaroon to the `admin` macaroon. --- README.md | 4 +- config.go | 4 +- frdrpcserver/macaroons.go | 156 -------------------------------------- frdrpcserver/rpcserver.go | 60 +++++++++++---- go.mod | 2 - itest/test_context.go | 2 +- 6 files changed, 51 insertions(+), 177 deletions(-) diff --git a/README.md b/README.md index 6388e51..c940e2f 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ make && make install ``` ## Usage -Faraday connects to a single instance of lnd. It requires access to `lnd`'s `readonly.macaroon` and a valid TLS certificate. It will attempt to use the default `lnd` values if no command line flags are specified. +Faraday connects to a single instance of lnd. It requires access to `lnd`'s `admin.macaroon` and a valid TLS certificate. It will attempt to use the default `lnd` values if no command line flags are specified. ``` ./faraday \ ---lnd.macaroonpath={full path to lnd's readonly.macaroon} \ +--lnd.macaroonpath={full path to lnd's admin.macaroon} \ --lnd.tlscertpath={path to lnd cert} \ --lnd.rpcserver={host:port of lnd's rpcserver} ``` diff --git a/config.go b/config.go index afa0b92..f27787d 100644 --- a/config.go +++ b/config.go @@ -76,7 +76,7 @@ var ( // defaultLndMacaroon is the default macaroon file we use to connect to // lnd. - defaultLndMacaroon = "readonly.macaroon" + defaultLndMacaroon = "admin.macaroon" // DefaultLndDir is the default location where we look for lnd's tls and // macaroon files. @@ -103,7 +103,7 @@ type LndConfig struct { // all of lnd's macaroons. The specified macaroon MUST have all // permissions that all the subservers use, otherwise permission errors // will occur. - MacaroonPath string `long:"macaroonpath" description:"The full path to the single macaroon to use, either the readonly.macaroon or a custom baked one. Cannot be specified at the same time as macaroondir. A custom macaroon must contain ALL permissions required for all subservers to work, otherwise permission errors will occur."` + MacaroonPath string `long:"macaroonpath" description:"The full path to the single macaroon to use, either the admin.macaroon or a custom baked one. Cannot be specified at the same time as macaroondir. A custom macaroon must contain ALL permissions required for all subservers to work, otherwise permission errors will occur."` // TLSCertPath is the path to the tls cert that faraday should use. TLSCertPath string `long:"tlscertpath" description:"Path to TLS cert"` diff --git a/frdrpcserver/macaroons.go b/frdrpcserver/macaroons.go index b44b397..71627b1 100644 --- a/frdrpcserver/macaroons.go +++ b/frdrpcserver/macaroons.go @@ -1,21 +1,7 @@ package frdrpcserver import ( - "context" - "fmt" - "io/ioutil" - "os" "time" - - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/macaroons" - "github.com/lightningnetwork/lnd/rpcperms" - "go.etcd.io/bbolt" - "google.golang.org/grpc" - "gopkg.in/macaroon-bakery.v2/bakery" - - "github.com/lightninglabs/faraday/frdrpcserver/perms" ) const ( @@ -30,27 +16,6 @@ const ( var ( - // allPermissions is the list of all existing permissions that exist - // for faraday'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 faraday. - allPermissions = []bakery.Op{{ - Entity: "recommendation", - Action: "read", - }, { - Entity: "report", - Action: "read", - }, { - Entity: "audit", - Action: "read", - }, { - Entity: "insights", - Action: "read", - }, { - Entity: "rates", - Action: "read", - }} - // macDbDefaultPw is the default encryption password used to encrypt the // faraday 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 @@ -62,124 +27,3 @@ var ( // 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. -func (s *RPCServer) startMacaroonService(createDefaultMacaroonFile bool) error { - var err error - s.macaroonDB, err = kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{ - DBPath: s.cfg.FaradayDir, - DBFileName: "macaroons.db", - DBTimeout: macDatabaseOpenTimeout, - }) - if err == bbolt.ErrTimeout { - return fmt.Errorf("error while trying to open %s/%s: "+ - "timed out after %v when trying to obtain exclusive "+ - "lock - make sure no other faraday daemon process "+ - "(standalone or embedded in lightning-terminal) is "+ - "running", s.cfg.FaradayDir, "macaroons.db", - macDatabaseOpenTimeout) - } - if err != nil { - return fmt.Errorf("unable to load macaroon db: %v", err) - } - - // Create the macaroon authentication/authorization service. - s.macaroonService, err = macaroons.NewService( - s.macaroonDB, faradayMacaroonLocation, false, - 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) - } - - // There are situations in which we don't want a macaroon to be created - // on disk (for example when running inside LiT stateless integrated - // mode). For any other cases, we create macaroon files for the faraday - // CLI in the default directory. - if createDefaultMacaroonFile && !lnrpc.FileExists(s.cfg.MacaroonPath) { - // We don't offer the ability to rotate macaroon root keys yet, - // so just use the default one since the service expects some - // value to be set. - idCtx := macaroons.ContextWithRootKeyID( - context.Background(), macaroons.DefaultRootKeyID, - ) - - // 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. - faradayMac, err := s.macaroonService.Oven.NewMacaroon( - idCtx, bakery.LatestVersion, nil, allPermissions..., - ) - if err != nil { - return err - } - frdMacBytes, err := faradayMac.M().MarshalBinary() - if err != nil { - return err - } - err = ioutil.WriteFile(s.cfg.MacaroonPath, frdMacBytes, 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 *RPCServer) stopMacaroonService() error { - var shutdownErr error - if err := s.macaroonService.Close(); err != nil { - log.Errorf("Error closing macaroon service: %v", err) - shutdownErr = err - } - - if err := s.macaroonDB.Close(); err != nil { - log.Errorf("Error closing macaroon DB: %v", err) - shutdownErr = err - } - - return shutdownErr -} - -// macaroonInterceptor creates gRPC server options with the macaroon security -// interceptors. -func (s *RPCServer) macaroonInterceptor() ([]grpc.ServerOption, error) { - interceptor := rpcperms.NewInterceptorChain(log, false, nil) - - err := interceptor.Start() - if err != nil { - return nil, err - } - - interceptor.SetWalletUnlocked() - interceptor.AddMacaroonService(s.macaroonService) - - for method, permissions := range perms.RequiredPermissions { - err := interceptor.AddPermission(method, permissions) - if err != nil { - return nil, err - } - } - - unaryInterceptor := interceptor.MacaroonUnaryServerInterceptor() - streamInterceptor := interceptor.MacaroonStreamServerInterceptor() - return []grpc.ServerOption{ - grpc.UnaryInterceptor(unaryInterceptor), - grpc.StreamInterceptor(streamInterceptor), - }, nil -} diff --git a/frdrpcserver/rpcserver.go b/frdrpcserver/rpcserver.go index 1d7dace..08dfa2c 100644 --- a/frdrpcserver/rpcserver.go +++ b/frdrpcserver/rpcserver.go @@ -22,7 +22,6 @@ import ( proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightninglabs/lndclient" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -33,6 +32,7 @@ import ( "github.com/lightninglabs/faraday/chain" "github.com/lightninglabs/faraday/fiat" "github.com/lightninglabs/faraday/frdrpc" + "github.com/lightninglabs/faraday/frdrpcserver/perms" "github.com/lightninglabs/faraday/recommend" "github.com/lightninglabs/faraday/resolutions" "github.com/lightninglabs/faraday/revenue" @@ -107,8 +107,7 @@ type RPCServer struct { // restServer is the REST proxy server. restServer *http.Server - macaroonService *macaroons.Service - macaroonDB kvdb.Backend + macaroonService *lndclient.MacaroonService restCancel func() wg sync.WaitGroup @@ -181,16 +180,38 @@ func (s *RPCServer) Start() error { } }() + // Set up the macaroon service. + var err error + s.macaroonService, err = lndclient.NewMacaroonService( + &lndclient.MacaroonServiceConfig{ + DBPath: s.cfg.FaradayDir, + DBFileName: "macaroons.db", + DBTimeout: macDatabaseOpenTimeout, + MacaroonLocation: faradayMacaroonLocation, + MacaroonPath: s.cfg.MacaroonPath, + Checkers: []macaroons.Checker{ + macaroons.IPLockChecker, + }, + RequiredPerms: perms.RequiredPermissions, + DBPassword: macDbDefaultPw, + LndClient: &s.cfg.Lnd, + EphemeralKey: lndclient.SharedKeyNUMS, + KeyLocator: lndclient.SharedKeyLocator, + }) + if err != nil { + return fmt.Errorf("error creating macroon service: %v", err) + } + // Start the macaroon service and let it create its default macaroon in // case it doesn't exist yet. - if err := s.startMacaroonService(true); err != nil { + if err := s.macaroonService.Start(); err != nil { return fmt.Errorf("error starting macaroon service: %v", err) } - shutdownFuncs["macaroon"] = s.stopMacaroonService + shutdownFuncs["macaroon"] = s.macaroonService.Stop // First we add the security interceptor to our gRPC server options that // checks the macaroons for validity. - serverOpts, err := s.macaroonInterceptor() + unaryInterceptor, streamInterceptor, err := s.macaroonService.Interceptors() if err != nil { return fmt.Errorf("error with macaroon interceptor: %v", err) } @@ -200,8 +221,11 @@ func (s *RPCServer) Start() error { // use tls.NewListener(). Otherwise we run into the ALPN error with non- // golang clients. tlsCredentials := credentials.NewTLS(s.cfg.TLSServerConfig) - serverOpts = append(serverOpts, grpc.Creds(tlsCredentials)) - s.grpcServer = grpc.NewServer(serverOpts...) + s.grpcServer = grpc.NewServer( + grpc.UnaryInterceptor(unaryInterceptor), + grpc.StreamInterceptor(streamInterceptor), + grpc.Creds(tlsCredentials), + ) // Start the gRPC RPCServer listening for HTTP/2 connections. log.Info("Starting gRPC listener") @@ -309,10 +333,12 @@ func (s *RPCServer) StartAsSubserver(lndClient lndclient.LndServices, return errServerAlreadyStarted } - // Start the macaroon service and let it create its default macaroon in - // case it doesn't exist yet. - if err := s.startMacaroonService(createDefaultMacaroonFile); err != nil { - return fmt.Errorf("error starting macaroon service: %v", err) + if createDefaultMacaroonFile { + // Start the macaroon service and let it create its default + // macaroon in case it doesn't exist yet. + if err := s.macaroonService.Start(); err != nil { + return fmt.Errorf("error starting macaroon service: %v", err) + } } s.cfg.Lnd = lndClient @@ -328,6 +354,10 @@ func (s *RPCServer) StartAsSubserver(lndClient lndclient.LndServices, func (s *RPCServer) ValidateMacaroon(ctx context.Context, requiredPermissions []bakery.Op, fullMethod string) error { + if s.macaroonService == nil { + return fmt.Errorf("macaroon service not yet initialised") + } + // Delegate the call to faraday's own macaroon validator service. return s.macaroonService.ValidateMacaroon( ctx, requiredPermissions, fullMethod, @@ -348,8 +378,10 @@ func (s *RPCServer) Stop() error { } } - if err := s.stopMacaroonService(); err != nil { - log.Errorf("Error stopping macaroon service: %v", err) + if s.macaroonService != nil { + if err := s.macaroonService.Stop(); err != nil { + log.Errorf("Error stopping macaroon service: %v", err) + } } // Stop the grpc server and wait for all go routines to terminate. diff --git a/go.mod b/go.mod index b2c026c..16c5f29 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,9 @@ require ( github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display github.com/lightningnetwork/lnd v0.14.1-beta.0.20220324135938-0dcaa511a249 github.com/lightningnetwork/lnd/cert v1.1.1 - github.com/lightningnetwork/lnd/kvdb v1.3.1 github.com/shopspring/decimal v1.2.0 github.com/stretchr/testify v1.7.0 github.com/urfave/cli v1.22.4 - go.etcd.io/bbolt v1.3.6 google.golang.org/grpc v1.38.0 google.golang.org/protobuf v1.26.0 gopkg.in/macaroon-bakery.v2 v2.0.1 diff --git a/itest/test_context.go b/itest/test_context.go index 11762d7..98b690d 100644 --- a/itest/test_context.go +++ b/itest/test_context.go @@ -39,7 +39,7 @@ var ( faradayArgs = []string{ "--rpclisten=localhost:8465", "--network=regtest", - "--lnd.macaroonpath=lnd-alice/data/chain/bitcoin/regtest/readonly.macaroon", + "--lnd.macaroonpath=lnd-alice/data/chain/bitcoin/regtest/admin.macaroon", "--lnd.tlscertpath=lnd-alice/tls.cert", "--debuglevel=debug", "--connect_bitcoin", From 00a8a39aa8a830f4138cd1e1a650f57b810be54e Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 17 Jan 2022 16:32:42 +0200 Subject: [PATCH 2/2] frdrpc/rpcserver: rename createDefaultMacaroonFile Rename createDefaultMacaroonFile to withMacaroonService since this is now a more appropriate name. --- frdrpcserver/rpcserver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frdrpcserver/rpcserver.go b/frdrpcserver/rpcserver.go index 08dfa2c..3072e9b 100644 --- a/frdrpcserver/rpcserver.go +++ b/frdrpcserver/rpcserver.go @@ -327,13 +327,13 @@ func (s *RPCServer) Start() error { // for REST (if enabled), instead of creating an own mux and HTTP server, we // register to an existing one. func (s *RPCServer) StartAsSubserver(lndClient lndclient.LndServices, - createDefaultMacaroonFile bool) error { + withMacaroonService bool) error { if atomic.AddInt32(&s.started, 1) != 1 { return errServerAlreadyStarted } - if createDefaultMacaroonFile { + if withMacaroonService { // Start the macaroon service and let it create its default // macaroon in case it doesn't exist yet. if err := s.macaroonService.Start(); err != nil {