Merge pull request #453 from ellemouton/macaroonService

multi: use lndclient MacaroonService
pull/456/head
Carla Kirk-Cohen 2 years ago committed by GitHub
commit 44e734c575
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,13 +10,12 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0
github.com/jessevdk/go-flags v1.4.0
github.com/lightninglabs/aperture v0.1.6-beta
github.com/lightninglabs/lndclient v0.14.0-5
github.com/lightninglabs/lndclient v0.14.0-8
github.com/lightninglabs/loop/swapserverrpc v1.0.0
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display
github.com/lightningnetwork/lnd v0.14.1-beta
github.com/lightningnetwork/lnd/cert v1.1.0
github.com/lightningnetwork/lnd/clock v1.1.0
github.com/lightningnetwork/lnd/kvdb v1.2.1
github.com/lightningnetwork/lnd/queue v1.1.0
github.com/lightningnetwork/lnd/ticker v1.1.0
github.com/stretchr/testify v1.7.0

@ -461,8 +461,8 @@ github.com/lightninglabs/aperture v0.1.6-beta/go.mod h1:9xl4mx778ZAzrB87nLHMqk+X
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/lndclient v0.11.0-4/go.mod h1:8/cTKNwgL87NX123gmlv3Xh6p1a7pvzu+40Un3PhHiI=
github.com/lightninglabs/lndclient v0.14.0-5 h1:dI2/Y2fn9m5VuwMTd/DcF6y0DYdMy3pk0MPu4xNjj54=
github.com/lightninglabs/lndclient v0.14.0-5/go.mod h1:2kH9vNoc29ghIkfMjxwSeK8yCxsYfR80XAJ9PU/QWWk=
github.com/lightninglabs/lndclient v0.14.0-8 h1:vdwV6yFU4A7BjG2V8cpI8Kqdl2M0NSfsA+RWR+JGTko=
github.com/lightninglabs/lndclient v0.14.0-8/go.mod h1:YIE/Yac69hIMiq9cm/ZC2sP4F0Llv3tC4hZGfgOhdeY=
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
github.com/lightninglabs/neutrino v0.12.1/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E=

@ -15,8 +15,8 @@ import (
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc"
@ -85,8 +85,7 @@ type Daemon struct {
restListener net.Listener
restCtxCancel func()
macaroonService *macaroons.Service
macaroonDB kvdb.Backend
macaroonService *lndclient.MacaroonService
}
// New creates a new instance of the loop client daemon.
@ -164,7 +163,7 @@ func (d *Daemon) Start() error {
// for REST (if enabled), instead of creating an own mux and HTTP server, we
// register to an existing one.
func (d *Daemon) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices,
createDefaultMacaroonFile bool) error {
withMacaroonService bool) error {
// There should be no reason to start the daemon twice. Therefore return
// an error if that's tried. This is mostly to guard against Start and
@ -181,7 +180,7 @@ func (d *Daemon) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices,
// the swap server client, the RPC server instance and our main swap
// handlers. If this fails, then nothing has been started yet and we can
// just return the error.
err := d.initialize(createDefaultMacaroonFile)
err := d.initialize(withMacaroonService)
if errors.Is(err, bbolt.ErrTimeout) {
// We're trying to be started inside LiT so there most likely is
// another standalone Loop process blocking the DB.
@ -200,6 +199,10 @@ func (d *Daemon) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices,
func (d *Daemon) ValidateMacaroon(ctx context.Context,
requiredPermissions []bakery.Op, fullMethod string) error {
if d.macaroonService == nil {
return fmt.Errorf("macaroon service has not been initialised")
}
// Delegate the call to loop's own macaroon validator service.
return d.macaroonService.ValidateMacaroon(
ctx, requiredPermissions, fullMethod,
@ -213,11 +216,14 @@ func (d *Daemon) startWebServers() error {
// With our client created, let's now finish setting up and start our
// RPC server. First we add the security interceptor to our gRPC server
// options that checks the macaroons for validity.
serverOpts, err := d.macaroonInterceptor()
unaryInterceptor, streamInterceptor, err := d.macaroonService.Interceptors()
if err != nil {
return fmt.Errorf("error with macaroon interceptor: %v", err)
}
d.grpcServer = grpc.NewServer(serverOpts...)
d.grpcServer = grpc.NewServer(
grpc.UnaryInterceptor(unaryInterceptor),
grpc.StreamInterceptor(streamInterceptor),
)
looprpc.RegisterSwapClientServer(d.grpcServer, d)
// Register our debug server if it is compiled in.
@ -341,7 +347,7 @@ func (d *Daemon) startWebServers() error {
// the swap client RPC server instance and our main swap and error handlers. If
// this method fails with an error then no goroutine was started yet and no
// cleanup is necessary. If it succeeds, then goroutines have been spawned.
func (d *Daemon) initialize(createDefaultMacaroonFile bool) error {
func (d *Daemon) initialize(withMacaroonService bool) error {
// If no swap server is specified, use the default addresses for mainnet
// and testnet.
if d.cfg.Server.Host == "" {
@ -370,15 +376,43 @@ func (d *Daemon) initialize(createDefaultMacaroonFile bool) 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(createDefaultMacaroonFile)
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
// Add our debug permissions to our main set of required permissions
// if compiled in.
for endpoint, perm := range debugRequiredPermissions {
RequiredPermissions[endpoint] = perm
}
if withMacaroonService {
// Start the macaroon service and let it create its default
// macaroon in case it doesn't exist yet.
d.macaroonService, err = lndclient.NewMacaroonService(
&lndclient.MacaroonServiceConfig{
DBPath: d.cfg.DataDir,
DBFileName: "macaroons.db",
DBTimeout: loopdb.DefaultLoopDBTimeout,
MacaroonLocation: loopMacaroonLocation,
MacaroonPath: d.cfg.MacaroonPath,
Checkers: []macaroons.Checker{
macaroons.IPLockChecker,
},
RequiredPerms: RequiredPermissions,
DBPassword: macDbDefaultPw,
LndClient: &d.lnd.LndServices,
EphemeralKey: lndclient.SharedKeyNUMS,
KeyLocator: lndclient.SharedKeyLocator,
},
)
if err != nil {
return err
}
if err = d.macaroonService.Start(); 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.
@ -396,10 +430,15 @@ func (d *Daemon) initialize(createDefaultMacaroonFile bool) error {
// Retrieve all currently existing swaps from the database.
swapsList, err := d.impl.FetchSwaps()
if err != nil {
if d.macaroonService == nil {
clientCleanup()
return err
}
// 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 {
if err := d.macaroonService.Stop(); err != nil {
log.Errorf("Error shutting down macaroon service: %v",
err)
}
@ -520,9 +559,11 @@ func (d *Daemon) stop() {
d.restCtxCancel()
}
err := d.StopMacaroonService()
if err != nil {
log.Errorf("Error stopping macaroon service: %v", err)
if d.macaroonService != nil {
err := d.macaroonService.Stop()
if err != nil {
log.Errorf("Error stopping macaroon service: %v", err)
}
}
// Next, shut down the connections to lnd and the swap server.

@ -1,18 +1,6 @@
package loopd
import (
"context"
"fmt"
"io/ioutil"
"os"
"github.com/coreos/bbolt"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/rpcperms"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
@ -105,36 +93,6 @@ var (
}},
}
// 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",
}, {
Entity: "suggestions",
Action: "read",
}, {
Entity: "suggestions",
Action: "write",
}}
// 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
@ -146,129 +104,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. If macaroons are disabled in general in the configuration, none of
// these actions are taken.
func (d *Daemon) startMacaroonService(createDefaultMacaroonFile bool) error {
var err error
d.macaroonDB, err = kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: d.cfg.DataDir,
DBFileName: "macaroons.db",
DBTimeout: loopdb.DefaultLoopDBTimeout,
})
if err != nil {
return fmt.Errorf("unable to load macaroon db: %v", err)
}
if err == bbolt.ErrTimeout {
return fmt.Errorf("%w: couldn't obtain exclusive lock on "+
"%s/%s, timed out after %v", bbolt.ErrTimeout,
d.cfg.DataDir, "macaroons.db",
loopdb.DefaultLoopDBTimeout)
}
// Create the macaroon authentication/authorization service.
d.macaroonService, err = macaroons.NewService(
d.macaroonDB, loopMacaroonLocation, 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 = d.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 loop CLI
// in the default directory.
if createDefaultMacaroonFile && !lnrpc.FileExists(d.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. Add our debug permissions if required.
allPermissions = append(allPermissions, debugPermissions...)
loopMac, err := d.macaroonService.Oven.NewMacaroon(
idCtx, bakery.LatestVersion, nil, 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 {
var shutdownErr error
if err := d.macaroonService.Close(); err != nil {
log.Errorf("Error closing macaroon service: %v", err)
shutdownErr = err
}
if err := d.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 (d *Daemon) macaroonInterceptor() ([]grpc.ServerOption, error) {
// Add our debug permissions to our main set of required permissions
// if compiled in.
for endpoint, perm := range debugRequiredPermissions {
RequiredPermissions[endpoint] = perm
}
interceptor := rpcperms.NewInterceptorChain(log, false, nil)
err := interceptor.Start()
if err != nil {
return nil, err
}
interceptor.SetWalletUnlocked()
interceptor.AddMacaroonService(d.macaroonService)
for method, permissions := range 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
}

@ -1,13 +1,11 @@
//go:build !dev
// +build !dev
package loopd
import "gopkg.in/macaroon-bakery.v2/bakery"
var (
debugRequiredPermissions = map[string][]bakery.Op{}
debugPermissions []bakery.Op
)
var debugRequiredPermissions = map[string][]bakery.Op{}
// registerDebugServer is our default debug server registration function, which
// excludes debug functionality.

@ -1,3 +1,4 @@
//go:build dev
// +build dev
package loopd
@ -19,13 +20,6 @@ var (
Action: "write",
}},
}
debugPermissions = []bakery.Op{
{
Entity: "debug",
Action: "write",
},
}
)
// registerDebugServer registers the debug server.

Loading…
Cancel
Save