mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Merge pull request #453 from ellemouton/macaroonService
multi: use lndclient MacaroonService
This commit is contained in:
commit
44e734c575
6 changed files with 68 additions and 204 deletions
3
go.mod
3
go.mod
|
|
@ -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
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -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…
Add table
Add a link
Reference in a new issue