diff --git a/rpc_proxy.go b/rpc_proxy.go index 309cb861..ed8c369c 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -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) } diff --git a/terminal.go b/terminal.go index a51dae82..b049c4d9 100644 --- a/terminal.go +++ b/terminal.go @@ -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 {