terminal: direct calls to remote daemons

If any of the daemons is configured to be running in remote mode, the
RPC proxy only acts as a gRPC web reverse proxy and just forwards any
requests to the correct backend.
For non-remote daemons the requests shouldn't get to the director in the
first place but instead be handled by the main gRPC server.
This commit is contained in:
Oliver Gugger 2021-02-16 13:39:39 +01:00
parent 71fb8e5e8f
commit e52141e100
No known key found for this signature in database
GPG key ID: 8E4256593F177720
2 changed files with 169 additions and 23 deletions

View file

@ -11,6 +11,7 @@ import (
"time"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/macaroons"
grpcProxy "github.com/mwitkow/grpc-proxy/proxy"
"google.golang.org/grpc"
@ -111,6 +112,9 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator,
// v authenticated call | - pool |
// +---+----------------------+ +--------------------+
// | lnd (remote or local) |
// | faraday remote |
// | loop remote |
// | pool remote |
// +--------------------------+
//
type rpcProxy struct {
@ -119,7 +123,11 @@ type rpcProxy struct {
macValidator macaroons.MacaroonValidator
lndConn *grpc.ClientConn
lndConn *grpc.ClientConn
faradayConn *grpc.ClientConn
loopConn *grpc.ClientConn
poolConn *grpc.ClientConn
grpcServer *grpc.Server
grpcWebProxy *grpcweb.WrappedGrpcServer
}
@ -135,6 +143,41 @@ func (p *rpcProxy) Start() error {
return fmt.Errorf("could not dial lnd: %v", err)
}
// Make sure we can connect to all the daemons that are configured to be
// running in remote mode.
if p.cfg.faradayRemote {
p.faradayConn, err = dialBackend(
"faraday", p.cfg.Remote.Faraday.RPCServer,
lncfg.CleanAndExpandPath(
p.cfg.Remote.Faraday.TLSCertPath,
),
)
if err != nil {
return fmt.Errorf("could not dial remote faraday: %v",
err)
}
}
if p.cfg.loopRemote {
p.loopConn, err = dialBackend(
"loop", p.cfg.Remote.Loop.RPCServer,
lncfg.CleanAndExpandPath(p.cfg.Remote.Loop.TLSCertPath),
)
if err != nil {
return fmt.Errorf("could not dial remote loop: %v", err)
}
}
if p.cfg.poolRemote {
p.poolConn, err = dialBackend(
"pool", p.cfg.Remote.Pool.RPCServer,
lncfg.CleanAndExpandPath(p.cfg.Remote.Pool.TLSCertPath),
)
if err != nil {
return fmt.Errorf("could not dial remote pool: %v", err)
}
}
return nil
}
@ -149,6 +192,27 @@ func (p *rpcProxy) Stop() error {
}
}
if p.faradayConn != nil {
if err := p.faradayConn.Close(); err != nil {
log.Errorf("Error closing faraday connection: %v", err)
return err
}
}
if p.loopConn != nil {
if err := p.loopConn.Close(); err != nil {
log.Errorf("Error closing loop connection: %v", err)
return err
}
}
if p.poolConn != nil {
if err := p.poolConn.Close(); err != nil {
log.Errorf("Error closing pool connection: %v", err)
return err
}
}
return nil
}
@ -188,7 +252,7 @@ func (p *rpcProxy) isHandling(resp http.ResponseWriter,
// backend, depending on what kind of authentication information is attached to
// the request.
func (p *rpcProxy) director(ctx context.Context,
_ string) (context.Context, *grpc.ClientConn, error) {
requestURI string) (context.Context, *grpc.ClientConn, error) {
// If this header is present in the request from the web client,
// the actual connection to the backend will not be established.
@ -199,7 +263,29 @@ func (p *rpcProxy) director(ctx context.Context,
outCtx := metadata.NewOutgoingContext(ctx, mdCopy)
return outCtx, p.lndConn, nil
// Direct the call to the correct backend. For lnd we _always_ have a
// client connection, no matter if it's running in integrated or remote
// mode. For all other daemons the request shouldn't get here in
// integrated mode (after all, the director only picks up calls that the
// gRPC server itself would throw a 404 for) so we throw an error
// message for them if they're not in remote mode.
switch {
case isLndURI(requestURI):
return outCtx, p.lndConn, nil
case isFaradayURI(requestURI) && p.cfg.faradayRemote:
return outCtx, p.faradayConn, nil
case isLoopURI(requestURI) && p.cfg.loopRemote:
return outCtx, p.loopConn, nil
case isPoolURI(requestURI) && p.cfg.poolRemote:
return outCtx, p.poolConn, nil
default:
return ctx, nil, fmt.Errorf("unknown gRPC web request: %v",
requestURI)
}
}
// UnaryServerInterceptor is a gRPC interceptor that checks whether the
@ -313,13 +399,25 @@ func (p *rpcProxy) basicAuthToMacaroon(ctx context.Context,
_, _, _, macPath = p.cfg.lndConnectParams()
case isLoopURI(requestURI):
macPath = p.cfg.Loop.MacaroonPath
if p.cfg.loopRemote {
macPath = p.cfg.Remote.Loop.MacaroonPath
} else {
macPath = p.cfg.Loop.MacaroonPath
}
case isFaradayURI(requestURI):
macPath = p.cfg.Faraday.MacaroonPath
if p.cfg.faradayRemote {
macPath = p.cfg.Remote.Faraday.MacaroonPath
} else {
macPath = p.cfg.Faraday.MacaroonPath
}
case isPoolURI(requestURI):
macPath = p.cfg.Pool.MacaroonPath
if p.cfg.poolRemote {
macPath = p.cfg.Remote.Pool.MacaroonPath
} else {
macPath = p.cfg.Pool.MacaroonPath
}
default:
return ctx, fmt.Errorf("unknown gRPC web request: %v",
@ -328,7 +426,7 @@ func (p *rpcProxy) basicAuthToMacaroon(ctx context.Context,
// Now that we know which macaroon to load, do it and attach it to the
// request context.
macBytes, err := readMacaroon(macPath)
macBytes, err := readMacaroon(lncfg.CleanAndExpandPath(macPath))
if err != nil {
return ctx, fmt.Errorf("error reading macaroon: %v", err)
}

View file

@ -310,24 +310,31 @@ func (g *LightningTerminal) startSubservers() error {
return err
}
// Both connection types are ready now, let's start our subservers.
err = g.faradayServer.StartAsSubserver(g.lndClient.LndServices)
if err != nil {
return err
// Both connection types are ready now, let's start our subservers if
// they should be started locally as an integrated service.
if !g.cfg.faradayRemote {
err = g.faradayServer.StartAsSubserver(g.lndClient.LndServices)
if err != nil {
return err
}
g.faradayStarted = true
}
g.faradayStarted = true
err = g.loopServer.StartAsSubserver(g.lndClient)
if err != nil {
return err
if !g.cfg.loopRemote {
err = g.loopServer.StartAsSubserver(g.lndClient)
if err != nil {
return err
}
g.loopStarted = true
}
g.loopStarted = true
err = g.poolServer.StartAsSubserver(basicClient, g.lndClient)
if err != nil {
return err
if !g.cfg.poolRemote {
err = g.poolServer.StartAsSubserver(basicClient, g.lndClient)
if err != nil {
return err
}
g.poolStarted = true
}
g.poolStarted = true
return nil
}
@ -338,9 +345,23 @@ func (g *LightningTerminal) startSubservers() error {
// the same server instance.
func (g *LightningTerminal) RegisterGrpcSubserver(grpcServer *grpc.Server) error {
g.lndGrpcServer = grpcServer
frdrpc.RegisterFaradayServerServer(grpcServer, g.faradayServer)
looprpc.RegisterSwapClientServer(grpcServer, g.loopServer)
poolrpc.RegisterTraderServer(grpcServer, g.poolServer)
// In remote mode the "director" of the RPC proxy will act as a catch-
// all for any gRPC request that isn't known because we didn't register
// any server for it. The director will then forward the request to the
// remote service.
if !g.cfg.faradayRemote {
frdrpc.RegisterFaradayServerServer(grpcServer, g.faradayServer)
}
if !g.cfg.loopRemote {
looprpc.RegisterSwapClientServer(grpcServer, g.loopServer)
}
if !g.cfg.poolRemote {
poolrpc.RegisterTraderServer(grpcServer, g.poolServer)
}
return nil
}
@ -383,6 +404,12 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context,
// checked as they'll have their own interceptor.
switch {
case isLoopURI(fullMethod):
// In remote mode we just pass through the request, the remote
// daemon will check the macaroon.
if g.cfg.loopRemote {
return nil
}
if !g.loopStarted {
return fmt.Errorf("loop is not yet ready for " +
"requests, lnd possibly still starting or " +
@ -394,6 +421,12 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context,
)
case isFaradayURI(fullMethod):
// In remote mode we just pass through the request, the remote
// daemon will check the macaroon.
if g.cfg.faradayRemote {
return nil
}
if !g.faradayStarted {
return fmt.Errorf("faraday is not yet ready for " +
"requests, lnd possibly still starting or " +
@ -405,6 +438,18 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context,
)
case isPoolURI(fullMethod):
// In remote mode we just pass through the request, the remote
// daemon will check the macaroon.
if g.cfg.poolRemote {
return nil
}
if !g.poolStarted {
return fmt.Errorf("pool is not yet ready for " +
"requests, lnd possibly still starting or " +
"syncing")
}
return g.poolServer.ValidateMacaroon(
ctx, requiredPermissions, fullMethod,
)
@ -514,6 +559,9 @@ func (g *LightningTerminal) shutdown() error {
// v authenticated call | - pool |
// +---+----------------------+ +--------------------+
// | lnd (remote or local) |
// | faraday remote |
// | loop remote |
// | pool remote |
// +--------------------------+
//
func (g *LightningTerminal) startMainWebServer() error {