diff --git a/go.mod b/go.mod index e34f187..c43f75f 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index f9c9308..e100bf2 100644 --- a/go.sum +++ b/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= diff --git a/loopd/daemon.go b/loopd/daemon.go index d2eb263..f846bdb 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -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. @@ -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. @@ -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 createDefaultMacaroonFile { + // 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. diff --git a/loopd/macaroons.go b/loopd/macaroons.go index 5ea4155..c312939 100644 --- a/loopd/macaroons.go +++ b/loopd/macaroons.go @@ -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 -} diff --git a/loopd/register_default.go b/loopd/register_default.go index 08f3572..27a0974 100644 --- a/loopd/register_default.go +++ b/loopd/register_default.go @@ -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. diff --git a/loopd/swapclient_server_debug.go b/loopd/swapclient_server_debug.go index 81182b3..d1f9465 100644 --- a/loopd/swapclient_server_debug.go +++ b/loopd/swapclient_server_debug.go @@ -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.