Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
83 changes: 62 additions & 21 deletions loopd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -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.
Expand All @@ -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)
}
Expand Down Expand Up @@ -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.
Expand Down
168 changes: 0 additions & 168 deletions loopd/macaroons.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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
Expand All @@ -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
}
6 changes: 2 additions & 4 deletions loopd/register_default.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Loading