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.
This commit is contained in:
Elle Mouton 2022-01-14 14:58:30 +02:00
parent dd4329d258
commit 8a59081473
No known key found for this signature in database
GPG key ID: D7D916376026F177
6 changed files with 51 additions and 177 deletions

View file

@ -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}
```

View file

@ -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"`

View file

@ -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
}

View file

@ -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.

2
go.mod
View file

@ -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

View file

@ -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",