diff --git a/config.go b/config.go index 0971324f9..4474786f0 100644 --- a/config.go +++ b/config.go @@ -35,8 +35,9 @@ const ( uiPasswordMinLength = 8 - ModeIntegrated = "integrated" - ModeRemote = "remote" + ModeIntegrated = "integrated" + ModeRemote = "remote" + ModeStatelessRemote = "stateless-remote" defaultLndMode = ModeRemote defaultFaradayMode = ModeIntegrated @@ -149,7 +150,7 @@ type Config struct { // friendly. Because then we can reference the explicit modes in the // help descriptions of the section headers. We'll parse the mode into // a bool for internal use for better code readability. - LndMode string `long:"lnd-mode" description:"The mode to run lnd in, either 'remote' (default) or 'integrated'. 'integrated' means lnd is started alongside the UI and everything is stored in lnd's main data directory, configure everything by using the --lnd.* flags. 'remote' means the UI connects to an existing lnd node and acts as a proxy for gRPC calls to it. In the remote node LiT creates its own directory for log and configuration files, configure everything using the --remote.* flags." choice:"integrated" choice:"remote"` + LndMode string `long:"lnd-mode" description:"The mode to run lnd in, either 'remote' (default) or 'integrated'. 'integrated' means lnd is started alongside the UI and everything is stored in lnd's main data directory, configure everything by using the --lnd.* flags. 'remote' means the UI connects to an existing lnd node and acts as a proxy for gRPC calls to it. In the remote node LiT creates its own directory for log and configuration files, configure everything using the --remote.* flags. 'stateless-remote' means that sensitive information related to LND and the lightning daemons is passed by the user via the UI before connecting to them." choice:"integrated" choice:"remote" choice:"stateless-remote"` Lnd *lnd.Config `group:"Integrated lnd (use when lnd-mode=integrated)" namespace:"lnd"` FaradayMode string `long:"faraday-mode" description:"The mode to run faraday in, either 'integrated' (default) or 'remote'. 'integrated' means faraday is started alongside the UI and everything is stored in faraday's main data directory, configure everything by using the --faraday.* flags. 'remote' means the UI connects to an existing faraday node and acts as a proxy for gRPC calls to it." choice:"integrated" choice:"remote"` @@ -208,6 +209,12 @@ type RemoteDaemonConfig struct { // TLSCertPath is the path to the tls cert of the remote daemon that // should be used to verify the TLS identity of the remote RPC server. TLSCertPath string `long:"tlscertpath" description:"The full path to the remote daemon's TLS cert to use for RPC connection verification."` + + // In stateless-remote mode, we grab the macaroon directly instead of getting it from a path. + Macaroon string `long:"macaroon" description:"In stateless-remote mode, we grab the macaroon directly instead of getting it from a path."` + + // In stateless-remote mode, we grab the tls certificate directly instead of getting it from a path. + TLSCert string `long:"tlscert" description:"In stateless-remote mode, we grab the tls certificate directly instead of getting it from a path."` } // lndConnectParams returns the connection parameters to connect to the local @@ -215,8 +222,15 @@ type RemoteDaemonConfig struct { func (c *Config) lndConnectParams() (string, lndclient.Network, string, string) { - // In remote lnd mode, we just pass along what was configured in the + // In either remote mode, we just pass along what was configured in the // remote section of the lnd config. + if c.LndMode == ModeStatelessRemote { + return c.Remote.Lnd.RPCServer, + lndclient.Network(c.Network), + c.Remote.Lnd.TLSCert, + c.Remote.Lnd.Macaroon + } + if c.LndMode == ModeRemote { return c.Remote.Lnd.RPCServer, lndclient.Network(c.Network), @@ -533,6 +547,11 @@ func loadConfigFile(preCfg *Config, usageMessage string, return nil, err } + case ModeStatelessRemote: + if err := validateRemoteModeConfig(cfg); err != nil { + return nil, err + } + default: return nil, fmt.Errorf("invalid lnd mode %v", cfg.LndMode) } @@ -720,7 +739,7 @@ func buildTLSConfigForHttp2(config *Config) (*tls.Config, error) { GetCertificate: manager.GetCertificate, } - case config.LndMode == ModeRemote: + case config.LndMode == ModeRemote || config.LndMode == ModeStatelessRemote: tlsCertPath := config.Remote.LitTLSCertPath tlsKeyPath := config.Remote.LitTLSKeyPath diff --git a/doc/config-lnd-stateless-remote.md b/doc/config-lnd-stateless-remote.md new file mode 100644 index 000000000..c593fcd8f --- /dev/null +++ b/doc/config-lnd-stateless-remote.md @@ -0,0 +1,44 @@ +# Configuring LiT in stateless-remote mode + +Stateless remote is a mode where LiT can be run without any sensitive data +stored on disk. Instead, the macaroon and TLS certificate data are passed in. + +## Configuring stateless-remote mode + +The lit.conf (which is by default located in the .lit folder) config should +look like this: + +``` +httpslisten=[::]:8443 +uipassword= +lnd-mode=stateless-remote +``` + +## Generating a "super" macaroon + +In order to use this new stateless-remote mode. We'll need to bake a new +"super" macaroon that is able to access LND, Pool, Loop, and Faraday. Using +LND BakeMacaroon to create a macaroon via the command line like: + +`lncli bakemacaroon --allow_external_permissions onchain:read offchain:read address:read message:read peers:read info:read invoices:read signer:read macaroon:read onchain:write offchain:write address:write message:write peers:write info:write invoices:write signer:generate macaroon:generate macaroon:write recommendation:read report:read audit:read insights:read rates:read loop:out loop:in swap:execute swap:read terms:read auth:read suggestions:read suggestions:write account:read account:write order:read order:write auction:read auth:read` + +## Pass in an lndconnect string + +Now, create a lndconnect string containing the host info, macaroon, and tls +certificate, like so: https://github.com/LN-Zap/lndconnect/blob/master/lnd_connect_uri.md + +In full, an example lndconnect string might look like: + + +`lndconnect://localhost:10009?cert=MIICbzCCAhWgAwIBAgIRAL3ybHGc4hSVHoSO49QTHzIwCgYIKoZIzj0EAwIwMzEfMB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEQMA4GA1UEAxMHb3JiaXRhbDAeFw0yMTA3MTEwNTM5MjVaFw0yMjA5MDUwNTM5MjVaMDMxHzAdBgNVBAoTFmxuZCBhdXRvZ2VuZXJhdGVkIGNlcnQxEDAOBgNVBAMTB29yYml0YWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARy136neRMl40N2KRC%2BQXZUCbpFvYgwe1vEDmksNHBDggqY4zDDMg8OorWeUCCb%2BGaAi0z0M1sirB%2FNKu9xm4Yco4IBCDCCAQQwDgYDVR0PAQH%2FBAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB%2FwQFMAMBAf8wHQYDVR0OBBYEFESdIcJVWCF4aUqHVlqv%2BmtMORO%2FMIGsBgNVHREEgaQwgaGCB29yYml0YWyCCWxvY2FsaG9zdIIEdW5peIIKdW5peHBhY2tldIIHYnVmY29ubocEfwAAAYcQAAAAAAAAAAAAAAAAAAAAAYcEwKgABIcErBEAAYcErBIAAYcQJgEEQU0Ao5A2CXYVJBFsb4cQJgEEQU0Ao5AwGun%2F3cB0g4cQ%2FoAAAAAAAADv29UMGwZVEYcQ%2FoAAAAAAAAAAQtT%2F%2Foii0DAKBggqhkjOPQQDAgNIADBFAiEAt3%2FeNBX09qjejnZykwSls95ppasWjGHmkpqaM7T6%2BQgCIGyjfzJHx8RRy2uSbCfHctaR5%2F5v4sNUSelJIpT0o4c1&macaroon=0201047465737402067788991234560000062052d26ed139ea5af83e675500c4ccb2471f62191b745bab820f129e5588a255d2` + +When making the lndconnect string, make sure to do the following: + +* Remove linebreaks (\n) from the certificate, as grpc will reject a metadata +string value with linebreaks. +* Make sure the query string is valid, by encoding the url. `+` is not a valid + character to be passed in by way of a query string, for instance. + +Once litd is running, open localhost:8443 and enter the lndconnect string. + + diff --git a/rpc_proxy.go b/rpc_proxy.go index 407fcdfb1..4b4727638 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -1,22 +1,24 @@ package terminal import ( + "bytes" "context" "encoding/base64" "encoding/hex" "fmt" "io/ioutil" "net/http" + "net/url" "strings" "time" "github.com/improbable-eng/grpc-web/go/grpcweb" + "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/macaroons" grpcProxy "github.com/mwitkow/grpc-proxy/proxy" "google.golang.org/grpc" "google.golang.org/grpc/backoff" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "gopkg.in/macaroon-bakery.v2/bakery" "gopkg.in/macaroon.v2" @@ -49,9 +51,10 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, // need to be addressed with a custom director that just takes care of a // few HTTP header fields. p := &rpcProxy{ - cfg: cfg, - basicAuth: basicAuth, - macValidator: validator, + cfg: cfg, + basicAuth: basicAuth, + macValidator: validator, + lndConnectChan: make(chan struct{}), } p.grpcServer = grpc.NewServer( // From the grpxProxy doc: This codec is *crucial* to the @@ -131,19 +134,26 @@ type rpcProxy struct { loopConn *grpc.ClientConn poolConn *grpc.ClientConn - grpcServer *grpc.Server - grpcWebProxy *grpcweb.WrappedGrpcServer + grpcServer *grpc.Server + grpcWebProxy *grpcweb.WrappedGrpcServer + lndConnectChan chan struct{} + lndConnectSent bool } // Start creates initial connection to lnd. func (p *rpcProxy) Start() error { var err error - // Setup the connection to lnd. - host, _, tlsPath, _ := p.cfg.lndConnectParams() - p.lndConn, err = dialBackend("lnd", host, tlsPath) - if err != nil { - return fmt.Errorf("could not dial lnd: %v", err) + // If we're in stateless remote mode, we need to wait until the user + // passes in the LND connection info into the UI before connecting to + // LND. + if p.cfg.LndMode != ModeStatelessRemote { + // Setup the connection to lnd. + host, _, tlsPath, _ := p.cfg.lndConnectParams() + p.lndConn, err = dialBackend("lnd", host, tlsPath, "") + if err != nil { + return fmt.Errorf("could not dial lnd: %v", err) + } } // Make sure we can connect to all the daemons that are configured to be @@ -153,7 +163,7 @@ func (p *rpcProxy) Start() error { "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", @@ -165,6 +175,7 @@ func (p *rpcProxy) Start() error { 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) @@ -175,6 +186,7 @@ func (p *rpcProxy) Start() error { 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) @@ -300,14 +312,32 @@ func (p *rpcProxy) UnaryServerInterceptor( "required for method", info.FullMethod) } - // For now, basic authentication is just a quick fix until we - // have proper macaroon support implemented in the UI. We allow - // gRPC web requests to have it and "convert" the auth into a - // proper macaroon now. - newCtx, err := p.basicAuthToMacaroon(ctx, info.FullMethod) - if err != nil { - return nil, fmt.Errorf("error upgrading basic auth: %v", - err) + var ( + err error + newCtx context.Context + ) + // If we're in stateless remote mode, we already have the + // macaroon to parse. + if p.cfg.LndMode == ModeStatelessRemote { + newCtx, err = p.getLndConnectStr(ctx) + if err != nil { + return nil, fmt.Errorf("error retrieving "+ + "macaroon from lndconnect string: %v", + err) + } + } else { + // For now, basic authentication is just a quick fix + // until we have proper macaroon support implemented in + // the UI. We allow gRPC web requests to have it and + // "convert" the auth into a proper macaroon now. + newCtx, err = p.basicAuthToMacaroon( + ctx, + info.FullMethod, + ) + if err != nil { + return nil, fmt.Errorf("error upgrading "+ + "basic auth: %v", err) + } } // With the basic auth converted to a macaroon if necessary, @@ -337,19 +367,38 @@ func (p *rpcProxy) StreamServerInterceptor( "for method", info.FullMethod) } - // For now, basic authentication is just a quick fix until we - // have proper macaroon support implemented in the UI. We allow - // gRPC web requests to have it and "convert" the auth into a - // proper macaroon now. - ctx, err := p.basicAuthToMacaroon(ss.Context(), info.FullMethod) - if err != nil { - return fmt.Errorf("error upgrading basic auth: %v", err) + var ( + err error + newCtx context.Context + ) + // If we're in stateless remote mode, we already have the + // macaroon to parse. + if p.cfg.LndMode == ModeStatelessRemote { + newCtx, err = p.getLndConnectStr(ss.Context()) + if err != nil { + return fmt.Errorf("error retrieving "+ + "data from lndconnect string: %v", + err) + } + } else { + // For now, basic authentication is just a quick fix + // until we have proper macaroon support implemented in + // the UI. We allow gRPC web requests to have it and + // "convert" the auth into a proper macaroon now. + newCtx, err = p.basicAuthToMacaroon( + ss.Context(), + info.FullMethod, + ) + if err != nil { + return fmt.Errorf("error upgrading "+ + "basic auth: %v", err) + } } // With the basic auth converted to a macaroon if necessary, // let's now validate the macaroon. err = p.macValidator.ValidateMacaroon( - ctx, uriPermissions, info.FullMethod, + newCtx, uriPermissions, info.FullMethod, ) if err != nil { return err @@ -431,16 +480,103 @@ func (p *rpcProxy) basicAuthToMacaroon(ctx context.Context, return metadata.NewIncomingContext(ctx, md), nil } +// getLndConnectStr is used in stateless remote mode to extract the data from +// the lndconnect string the user should have passed in. +func (p *rpcProxy) getLndConnectStr(ctx context.Context) (context.Context, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return ctx, nil + } + + authHeaders := md.Get("authorization") + if len(authHeaders) == 0 { + // No lndconnect string provided, we let the gRPC security + // interceptor reject the request. + return ctx, nil + } + + lndConnectParts := strings.Split(authHeaders[0], " ") + lndConnectStr := lndConnectParts[1] + + // Since we're in stateless remote mode, we need to extract the + // macaroon parameter from the lndconnect url that was passed in, + // then attatch it to the request context. + u, err := url.Parse(lndConnectStr) + if err != nil { + return ctx, nil + } + + queryMap, _ := url.ParseQuery(u.RawQuery) + macaroon := queryMap["macaroon"][0] + + // If channel isn't closed, it means we haven't sent the info from the + // lndconnect string, and need to do so, so we can connect to + // LND. + if !p.lndConnectSent { + tlsCert := queryMap["cert"][0] + + tlsCert = strings.Replace(tlsCert, " ", "+", 1) + + // Since the cert was passed in the lndConnect string without + // linebreaks or a prefix/suffix, we need to turn the + // certificate back into something the PEM library can parse. + tlsCert = insertNth(tlsCert, 64, '\n') + certPrefix := "-----BEGIN CERTIFICATE-----\n" + certSuffix := "\n-----END CERTIFICATE-----" + + fullCert := certPrefix + tlsCert + certSuffix + + p.cfg.Remote.Lnd = &RemoteDaemonConfig{ + RPCServer: u.Host, + Macaroon: macaroon, + TLSCert: fullCert, + } + + // We also need to establish an LND connection for the proxy. + p.lndConn, err = dialBackend("lnd", u.Host, "", fullCert) + if err != nil { + log.Errorf("could not dial lnd: %v", err) + + } + + // Close the channel when we're done to signal to + // startSubserver that we're ready to connect to LND. + close(p.lndConnectChan) + p.lndConnectSent = true + } + + md.Set(HeaderMacaroon, macaroon) + return metadata.NewIncomingContext(ctx, md), nil +} + +// Add a rune every "n" characters to a string. +func insertNth(s string, n int, insert rune) string { + var buffer bytes.Buffer + var n_1 = n - 1 + var l_1 = len(s) - 1 + + for i, char := range s { + buffer.WriteRune(char) + if i%n == n_1 && i != l_1 { + buffer.WriteRune(insert) + } + } + + return buffer.String() +} + // dialBackend connects to a gRPC backend through the given address and uses the // given TLS certificate to authenticate the connection. -func dialBackend(name, dialAddr, tlsCertPath string) (*grpc.ClientConn, error) { - var opts []grpc.DialOption - tlsConfig, err := credentials.NewClientTLSFromFile(tlsCertPath, "") +func dialBackend(name, dialAddr, tlsCertPath, tlsCertData string) ( + *grpc.ClientConn, error) { + + tlsConfig, err := lndclient.GetTLSCredentials(tlsCertData, tlsCertPath) if err != nil { - return nil, fmt.Errorf("could not read %s TLS cert %s: %v", - name, tlsCertPath, err) + return nil, fmt.Errorf("unable to get tls creds: %v", err) } + var opts []grpc.DialOption + opts = append( opts, diff --git a/rpc_proxy_test.go b/rpc_proxy_test.go new file mode 100644 index 000000000..67f0cff0d --- /dev/null +++ b/rpc_proxy_test.go @@ -0,0 +1,63 @@ +package terminal + +import ( + "context" + "fmt" + "testing" + + "google.golang.org/grpc/metadata" +) + +// Test that getLndConnectStr successfully retrieves the host, macaroon, and +// tls certificate from an lndconnect string. +func TestGetLndConnectStr(t *testing.T) { + // Set up a terminal and rpcProxy for testing purposes. + terminal := &LightningTerminal{} + cfg := &Config{ + Remote: &RemoteConfig{ + Lnd: &RemoteDaemonConfig{}, + }, + LndMode: ModeStatelessRemote, + } + terminal.cfg = cfg + + rpcProxy := newRpcProxy(terminal.cfg, terminal, getAllPermissions()) + terminal.rpcProxy = rpcProxy + + host := "localhost" + port := "10000" + dummyMacStr := "0201047465737402067788991234560000062052d26ed139ea5af8" + + "3e675500c4ccb2471f62191b745bab820f129e5588a255d2" + testCert := "testCert" + metadataMap := make(map[string]string) + metadataMap["authorization"] = fmt.Sprintf("Macaroon lndconnect://%s"+ + ":%s?cert=%s&macaroon=%s", host, port, testCert, dummyMacStr) + md := metadata.New(metadataMap) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctx = metadata.NewIncomingContext(ctx, md) + + // Call rpcProxy's getMacaroon method to see if it works as expected. + newCtx, err := terminal.rpcProxy.getLndConnectStr(ctx) + if err != nil { + t.Fatalf("Could not retrieve macaroon from lndconnect "+ + "string: %v", err) + } + + // Check that the output context carries the macaroon as it should. + md, ok := metadata.FromIncomingContext(newCtx) + if !ok { + t.Fatalf("Could not retrieve metadata from context.") + } + + mac := md.Get("Macaroon") + + if mac[0] != dummyMacStr { + t.Fatalf("Macaroon header metadata not set correctly.") + } + + if cfg.Remote.Lnd.Macaroon != dummyMacStr && cfg.Remote.Lnd.TLSCert != testCert { + t.Fatal("lnd info was not set correctly") + } +} diff --git a/terminal.go b/terminal.go index 443dcafbf..0f3a78d52 100644 --- a/terminal.go +++ b/terminal.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "embed" + "encoding/hex" "errors" "fmt" "io/fs" @@ -300,8 +301,25 @@ func (g *LightningTerminal) Run() error { // embedded daemons as external subservers that hook into the same gRPC and REST // servers that lnd started. func (g *LightningTerminal) startSubservers() error { - var basicClient lnrpc.LightningClient - host, network, tlsPath, macPath := g.cfg.lndConnectParams() + var ( + basicClient lnrpc.LightningClient + host string + network lndclient.Network + tlsPath string + macPath string + tlsData string + macData string + ) + + // If we're in stateless-remote mode, we need to wait until the + // lnd connect string is provided by the user from the UI. + if g.cfg.LndMode == ModeStatelessRemote { + <-g.rpcProxy.lndConnectChan + + host, network, tlsData, macData = g.cfg.lndConnectParams() + } else { + host, network, tlsPath, macPath = g.cfg.lndConnectParams() + } // The main RPC listener of lnd might need some time to start, it could // be that we run into a connection refused a few times. We use the @@ -314,7 +332,7 @@ func (g *LightningTerminal) startSubservers() error { // subservers have the same requirements. var err error basicClient, err = lndclient.NewBasicClient( - host, tlsPath, path.Dir(macPath), string(network), + host, tlsPath, path.Dir(macPath), tlsData, macData, string(network), lndclient.MacFilename(path.Base(macPath)), ) return err @@ -353,6 +371,8 @@ func (g *LightningTerminal) startSubservers() error { Network: network, TLSPath: tlsPath, CustomMacaroonPath: macPath, + TLSData: tlsData, + CustomMacaroonHex: macData, BlockUntilChainSynced: true, BlockUntilUnlocked: true, CallerCtx: ctxc, @@ -365,7 +385,9 @@ func (g *LightningTerminal) startSubservers() error { // 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) + err = g.faradayServer.StartAsSubserver( + g.lndClient.LndServices, macData, + ) if err != nil { return err } @@ -373,7 +395,7 @@ func (g *LightningTerminal) startSubservers() error { } if !g.cfg.loopRemote { - err = g.loopServer.StartAsSubserver(g.lndClient) + err = g.loopServer.StartAsSubserver(g.lndClient, macData) if err != nil { return err } @@ -381,7 +403,7 @@ func (g *LightningTerminal) startSubservers() error { } if !g.cfg.poolRemote { - err = g.poolServer.StartAsSubserver(basicClient, g.lndClient) + err = g.poolServer.StartAsSubserver(basicClient, g.lndClient, macData) if err != nil { return err } @@ -449,6 +471,37 @@ func (g *LightningTerminal) RegisterRestSubserver(ctx context.Context, func (g *LightningTerminal) ValidateMacaroon(ctx context.Context, requiredPermissions []bakery.Op, fullMethod string) error { + if g.cfg.LndMode == ModeStatelessRemote && g.lndClient != nil { + // Check with LND that the request follows all caveats built + // into the macaroon. + ctx := context.Background() + + macBytes, err := hex.DecodeString(g.cfg.Remote.Lnd.Macaroon) + if err != nil { + return err + } + + // Convert permissions to the form that lndClient will accept. + permissions := make([]lndclient.MacaroonPermission, 0) + for _, perm := range requiredPermissions { + newPerm := lndclient.MacaroonPermission{ + Entity: perm.Entity, + Action: perm.Action, + } + permissions = append(permissions, newPerm) + } + + res, err := g.lndClient.Client.CheckMacaroonPermissions( + ctx, macBytes, permissions, fullMethod, + ) + if !res { + return fmt.Errorf("macaroon is not valid, returned %v", + res) + } + + return err + } + // Validate all macaroons for services that are running in the local // process. Calls that we proxy to a remote host don't need to be // checked as they'll have their own interceptor. @@ -902,7 +955,7 @@ func (g *LightningTerminal) showStartupInfo() error { host, network, tlsPath, macPath := g.cfg.lndConnectParams() basicClient, err := lndclient.NewBasicClient( host, tlsPath, path.Dir(macPath), string(network), - lndclient.MacFilename(path.Base(macPath)), + "", "", lndclient.MacFilename(path.Base(macPath)), ) if err != nil { return fmt.Errorf("error querying remote node: %v", err)