diff --git a/README.md b/README.md index 05853ec..5a1e0ea 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,18 @@ pending swaps after a restart. Information about pending swaps is stored persistently in the swap database. Its location is `~/.loopd//loop.db`. +## Transport security + +The gRPC and REST connections of `loopd` are encrypted with TLS the same way +`lnd` is. + +If no custom loop directory is set then the TLS certificate is stored in +`~/.loopd//tls.cert`. + +The `loop` command will pick up the file automatically on mainnet if no custom +loop directory is used. For other networks it should be sufficient to add the +`--network` flag to tell the CLI in what sub directory to look for the files. + ## Multiple Simultaneous Swaps It is possible to execute multiple swaps simultaneously. Just keep loopd diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 1e7e7ff..6847975 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -6,15 +6,20 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "strconv" + "strings" "time" + "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/loopd" "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/swap" "github.com/lightninglabs/protobuf-hex-display/json" "github.com/lightninglabs/protobuf-hex-display/jsonpb" "github.com/lightninglabs/protobuf-hex-display/proto" + "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/macaroons" "github.com/btcsuite/btcutil" @@ -43,10 +48,22 @@ var ( // that we set when sending it over the line. defaultMacaroonTimeout int64 = 60 + loopDirFlag = cli.StringFlag{ + Name: "loopdir", + Value: loopd.LoopDirBase, + Usage: "path to loop's base directory", + } + networkFlag = cli.StringFlag{ + Name: "network, n", + Usage: "the network loop is running on e.g. mainnet, " + + "testnet, etc.", + Value: loopd.DefaultNetwork, + } + tlsCertFlag = cli.StringFlag{ - Name: "tlscertpath", - Usage: "path to loop's TLS certificate, only needed if loop " + - "runs in the same process as lnd", + Name: "tlscertpath", + Usage: "path to loop's TLS certificate", + Value: loopd.DefaultTLSCertPath, } macaroonPathFlag = cli.StringFlag{ Name: "macaroonpath", @@ -103,6 +120,8 @@ func main() { Value: "localhost:11010", Usage: "loopd daemon address host:port", }, + networkFlag, + loopDirFlag, tlsCertFlag, macaroonPathFlag, } @@ -121,8 +140,10 @@ func main() { func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) { rpcServer := ctx.GlobalString("rpcserver") - tlsCertPath := ctx.GlobalString(tlsCertFlag.Name) - macaroonPath := ctx.GlobalString(macaroonPathFlag.Name) + tlsCertPath, macaroonPath, err := extractPathArgs(ctx) + if err != nil { + return nil, nil, err + } conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) if err != nil { return nil, nil, err @@ -137,6 +158,40 @@ func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount { return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate) } +// extractPathArgs parses the TLS certificate and macaroon paths from the +// command. +func extractPathArgs(ctx *cli.Context) (string, string, error) { + // We'll start off by parsing the network. This is needed to determine + // the correct path to the TLS certificate and macaroon when not + // specified. + networkStr := strings.ToLower(ctx.GlobalString("network")) + _, err := lndclient.Network(networkStr).ChainParams() + if err != nil { + return "", "", err + } + + // We'll now fetch the loopdir so we can make a decision on how to + // properly read the cert. This will either be the default, or will have + // been overwritten by the end user. + loopDir := lncfg.CleanAndExpandPath(ctx.GlobalString(loopDirFlag.Name)) + tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString( + tlsCertFlag.Name, + )) + + // If a custom lnd directory was set, we'll also check if custom paths + // for the TLS cert file were set as well. If not, we'll override their + // paths so they can be found within the custom loop directory set. This + // allows us to set a custom lnd directory, along with custom paths to + // the TLS cert file. + if loopDir != loopd.LoopDirBase || networkStr != loopd.DefaultNetwork { + tlsCertPath = filepath.Join( + loopDir, networkStr, loopd.DefaultTLSCertFilename, + ) + } + + return tlsCertPath, ctx.GlobalString(macaroonPathFlag.Name), nil +} + type inLimits struct { maxMinerFee btcutil.Amount maxSwapFee btcutil.Amount @@ -322,32 +377,27 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, grpc.WithDefaultCallOptions(maxMsgRecvSize), } - switch { - // If a TLS certificate file is specified, we need to load it and build - // transport credentials with it. - case tlsCertPath != "": - creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "") - if err != nil { - fatal(err) - } + // TLS cannot be disabled, we'll always have a cert file to read. + creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "") + if err != nil { + return nil, err + } - // Macaroons are only allowed to be transmitted over a TLS - // enabled connection. - if macaroonPath != "" { - opts = append(opts, readMacaroon(macaroonPath)) + // Macaroons are not yet enabled by default. + if macaroonPath != "" { + macOption, err := readMacaroon(macaroonPath) + if err != nil { + return nil, err } - - opts = append(opts, grpc.WithTransportCredentials(creds)) - - // By default, if no certificate is supplied, we assume the RPC server - // runs without TLS. - default: - opts = append(opts, grpc.WithInsecure()) + opts = append(opts, macOption) } + opts = append(opts, grpc.WithTransportCredentials(creds)) + conn, err := grpc.Dial(address, opts...) if err != nil { - return nil, fmt.Errorf("unable to connect to RPC server: %v", err) + return nil, fmt.Errorf("unable to connect to RPC server: %v", + err) } return conn, nil @@ -355,16 +405,16 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, // readMacaroon tries to read the macaroon file at the specified path and create // gRPC dial options from it. -func readMacaroon(macPath string) grpc.DialOption { +func readMacaroon(macPath string) (grpc.DialOption, error) { // Load the specified macaroon file. macBytes, err := ioutil.ReadFile(macPath) if err != nil { - fatal(fmt.Errorf("unable to read macaroon path : %v", err)) + return nil, fmt.Errorf("unable to read macaroon path : %v", err) } mac := &macaroon.Macaroon{} if err = mac.UnmarshalBinary(macBytes); err != nil { - fatal(fmt.Errorf("unable to decode macaroon: %v", err)) + return nil, fmt.Errorf("unable to decode macaroon: %v", err) } macConstraints := []macaroons.Constraint{ @@ -384,10 +434,10 @@ func readMacaroon(macPath string) grpc.DialOption { // Apply constraints to the macaroon. constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...) if err != nil { - fatal(err) + return nil, err } // Now we append the macaroon credentials to the dial options. cred := macaroons.NewMacaroonCredential(constrainedMac) - return grpc.WithPerRPCCredentials(cred) + return grpc.WithPerRPCCredentials(cred), nil } diff --git a/go.mod b/go.mod index cf4b839..7102d55 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/lightninglabs/lndclient v0.11.0-0 github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d github.com/lightningnetwork/lnd v0.11.0-beta + github.com/lightningnetwork/lnd/cert v1.0.3 github.com/lightningnetwork/lnd/queue v1.0.4 github.com/stretchr/testify v1.5.1 github.com/urfave/cli v1.20.0 diff --git a/go.sum b/go.sum index cd663f1..5381885 100644 --- a/go.sum +++ b/go.sum @@ -182,6 +182,8 @@ github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea github.com/lightningnetwork/lnd v0.11.0-beta h1:pUAT7FMHqS+iarNxyRtgj96XKCGAWwmb6ZdiUBy78ts= github.com/lightningnetwork/lnd v0.11.0-beta/go.mod h1:CzArvT7NFDLhVyW06+NJWSuWFmE6Ea+AjjA3txUBqTM= github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= +github.com/lightningnetwork/lnd/cert v1.0.3 h1:/K2gjzLgVI8we2IIPKc0ztWTEa85uds5sWXi1K6mOT0= +github.com/lightningnetwork/lnd/cert v1.0.3/go.mod h1:3MWXVLLPI0Mg0XETm9fT4N9Vyy/8qQLmaM5589bEggM= github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= diff --git a/loopd/config.go b/loopd/config.go index feb40dd..ae062be 100644 --- a/loopd/config.go +++ b/loopd/config.go @@ -1,30 +1,62 @@ package loopd import ( + "crypto/tls" + "crypto/x509" "fmt" "os" "path/filepath" + "time" "github.com/btcsuite/btcutil" "github.com/lightninglabs/loop/lsat" + "github.com/lightningnetwork/lnd/cert" "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc/credentials" ) var ( - loopDirBase = btcutil.AppDataDir("loop", false) + // LoopDirBase is the default main directory where loop stores its data. + LoopDirBase = btcutil.AppDataDir("loop", false) + + // DefaultNetwork is the default bitcoin network loop runs on. + DefaultNetwork = "mainnet" - defaultNetwork = "mainnet" defaultLogLevel = "info" defaultLogDirname = "logs" defaultLogFilename = "loopd.log" - defaultLogDir = filepath.Join(loopDirBase, defaultLogDirname) - defaultConfigFile = filepath.Join( - loopDirBase, defaultNetwork, defaultConfigFilename, + + defaultLogDir = filepath.Join(LoopDirBase, defaultLogDirname) + defaultConfigFile = filepath.Join( + LoopDirBase, DefaultNetwork, defaultConfigFilename, ) defaultMaxLogFiles = 3 defaultMaxLogFileSize = 10 defaultLoopOutMaxParts = uint32(5) + + // DefaultTLSCertFilename is the default file name for the autogenerated + // TLS certificate. + DefaultTLSCertFilename = "tls.cert" + + // DefaultTLSKeyFilename is the default file name for the autogenerated + // TLS key. + DefaultTLSKeyFilename = "tls.key" + + defaultSelfSignedOrganization = "loop autogenerated cert" + + // DefaultTLSCertPath is the default full path of the autogenerated TLS + // certificate. + DefaultTLSCertPath = filepath.Join( + LoopDirBase, DefaultNetwork, DefaultTLSCertFilename, + ) + + // DefaultTLSKeyPath is the default full path of the autogenerated TLS + // key. + DefaultTLSKeyPath = filepath.Join( + LoopDirBase, DefaultNetwork, DefaultTLSKeyFilename, + ) ) type lndConfig struct { @@ -50,12 +82,20 @@ type Config struct { RESTListen string `long:"restlisten" description:"Address to listen on for REST clients"` CORSOrigin string `long:"corsorigin" description:"The value to send in the Access-Control-Allow-Origin header. Header will be omitted if empty."` - LoopDir string `long:"loopdir" description:"The directory for all of loop's data."` - ConfigFile string `long:"configfile" description:"Path to configuration file."` - DataDir string `long:"datadir" description:"Directory for loopdb."` + LoopDir string `long:"loopdir" description:"The directory for all of loop's data. If set, this option overwrites --datadir, --logdir, --tlscertpath and --tlskeypath."` + ConfigFile string `long:"configfile" description:"Path to configuration file."` + DataDir string `long:"datadir" description:"Directory for loopdb."` + + TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for loop's RPC and REST services."` + TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for loop's RPC and REST services."` + TLSExtraIPs []string `long:"tlsextraip" description:"Adds an extra IP to the generated certificate."` + TLSExtraDomains []string `long:"tlsextradomain" description:"Adds an extra domain to the generated certificate."` + TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed."` + TLSDisableAutofill bool `long:"tlsdisableautofill" description:"Do not include the interface IPs or the system hostname in TLS certificate, use first --tlsextradomain as Common Name instead, if set."` + LogDir string `long:"logdir" description:"Directory to log output."` - MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"` - MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"` + MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)."` + MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB."` DebugLevel string `long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` MaxLSATCost uint32 `long:"maxlsatcost" description:"Maximum cost in satoshis that loopd is going to pay for an LSAT token automatically. Does not include routing fees."` @@ -78,19 +118,21 @@ const ( // DefaultConfig returns all default values for the Config struct. func DefaultConfig() Config { return Config{ - Network: defaultNetwork, + Network: DefaultNetwork, RPCListen: "localhost:11010", RESTListen: "localhost:8081", Server: &loopServerConfig{ NoTLS: false, }, - LoopDir: loopDirBase, + LoopDir: LoopDirBase, ConfigFile: defaultConfigFile, - DataDir: loopDirBase, + DataDir: LoopDirBase, LogDir: defaultLogDir, MaxLogFiles: defaultMaxLogFiles, MaxLogFileSize: defaultMaxLogFileSize, DebugLevel: defaultLogLevel, + TLSCertPath: DefaultTLSCertPath, + TLSKeyPath: DefaultTLSKeyPath, MaxLSATCost: lsat.DefaultMaxCostSats, MaxLSATFee: lsat.DefaultMaxRoutingFeeSats, LoopOutMaxParts: defaultLoopOutMaxParts, @@ -106,15 +148,20 @@ func Validate(cfg *Config) error { cfg.LoopDir = lncfg.CleanAndExpandPath(cfg.LoopDir) cfg.DataDir = lncfg.CleanAndExpandPath(cfg.DataDir) cfg.LogDir = lncfg.CleanAndExpandPath(cfg.LogDir) + cfg.TLSCertPath = lncfg.CleanAndExpandPath(cfg.TLSCertPath) + cfg.TLSKeyPath = lncfg.CleanAndExpandPath(cfg.TLSKeyPath) // Since our loop directory overrides our log/data dir values, make sure // that they are not set when loop dir is set. We hard here rather than // overwriting and potentially confusing the user. - logDirSet := cfg.LogDir != defaultLogDir - dataDirSet := cfg.DataDir != loopDirBase - loopDirSet := cfg.LoopDir != loopDirBase + loopDirSet := cfg.LoopDir != LoopDirBase if loopDirSet { + logDirSet := cfg.LogDir != defaultLogDir + dataDirSet := cfg.DataDir != LoopDirBase + tlsCertPathSet := cfg.TLSCertPath != DefaultTLSCertPath + tlsKeyPathSet := cfg.TLSKeyPath != DefaultTLSKeyPath + if logDirSet { return fmt.Errorf("loopdir overwrites logdir, please " + "only set one value") @@ -125,7 +172,17 @@ func Validate(cfg *Config) error { "only set one value") } - // Once we are satisfied that neither config value was set, we + if tlsCertPathSet { + return fmt.Errorf("loopdir overwrites tlscertpath, " + + "please only set one value") + } + + if tlsKeyPathSet { + return fmt.Errorf("loopdir overwrites tlskeypath, " + + "please only set one value") + } + + // Once we are satisfied that no other config value was set, we // replace them with our loop dir. cfg.DataDir = cfg.LoopDir cfg.LogDir = filepath.Join(cfg.LoopDir, defaultLogDirname) @@ -136,6 +193,20 @@ func Validate(cfg *Config) error { cfg.DataDir = filepath.Join(cfg.DataDir, cfg.Network) cfg.LogDir = filepath.Join(cfg.LogDir, cfg.Network) + // We want the TLS files to also be in the "namespaced" sub directory. + // Replace the default values with actual values in case the user + // specified either loopdir or datadir. + if cfg.TLSCertPath == DefaultTLSCertPath { + cfg.TLSCertPath = filepath.Join( + cfg.DataDir, DefaultTLSCertFilename, + ) + } + if cfg.TLSKeyPath == DefaultTLSKeyPath { + cfg.TLSKeyPath = filepath.Join( + cfg.DataDir, DefaultTLSKeyFilename, + ) + } + // If either of these directories do not exist, create them. if err := os.MkdirAll(cfg.DataDir, os.ModePerm); err != nil { return err @@ -147,3 +218,75 @@ func Validate(cfg *Config) error { return nil } + +// getTLSConfig generates a new self signed certificate or refreshes an existing +// one if necessary, then returns the full TLS configuration for initializing +// a secure server interface. +func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials, + error) { + + // Let's load our certificate first or create then load if it doesn't + // yet exist. + certData, parsedCert, err := loadCertWithCreate(cfg) + if err != nil { + return nil, nil, err + } + + // If the certificate expired or it was outdated, delete it and the TLS + // key and generate a new pair. + if time.Now().After(parsedCert.NotAfter) { + log.Info("TLS certificate is expired or outdated, " + + "removing old file then generating a new one") + + err := os.Remove(cfg.TLSCertPath) + if err != nil { + return nil, nil, err + } + + err = os.Remove(cfg.TLSKeyPath) + if err != nil { + return nil, nil, err + } + + certData, _, err = loadCertWithCreate(cfg) + if err != nil { + return nil, nil, err + } + } + + tlsCfg := cert.TLSConfFromCert(certData) + restCreds, err := credentials.NewClientTLSFromFile( + cfg.TLSCertPath, "", + ) + if err != nil { + return nil, nil, err + } + + return tlsCfg, &restCreds, nil +} + +// loadCertWithCreate tries to load the TLS certificate from disk. If the +// specified cert and key files don't exist, the certificate/key pair is created +// first. +func loadCertWithCreate(cfg *Config) (tls.Certificate, *x509.Certificate, + error) { + + // Ensure we create TLS key and certificate if they don't exist. + if !lnrpc.FileExists(cfg.TLSCertPath) && + !lnrpc.FileExists(cfg.TLSKeyPath) { + + log.Infof("Generating TLS certificates...") + err := cert.GenCertPair( + defaultSelfSignedOrganization, cfg.TLSCertPath, + cfg.TLSKeyPath, cfg.TLSExtraIPs, + cfg.TLSExtraDomains, cfg.TLSDisableAutofill, + cert.DefaultAutogenValidity, + ) + if err != nil { + return tls.Certificate{}, nil, err + } + log.Infof("Done generating TLS certificates") + } + + return cert.LoadCert(cfg.TLSCertPath, cfg.TLSKeyPath) +} diff --git a/loopd/daemon.go b/loopd/daemon.go index f171d7f..63794e7 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -2,10 +2,12 @@ package loopd import ( "context" + "crypto/tls" "errors" "fmt" "net" "net/http" + "strings" "sync" "sync/atomic" @@ -29,11 +31,13 @@ var ( // listenerCfg holds closures used to retrieve listeners for the gRPC services. type listenerCfg struct { - // grpcListener returns a listener to use for the gRPC server. - grpcListener func() (net.Listener, error) + // grpcListener returns a TLS listener to use for the gRPC server, based + // on the passed TLS configuration. + grpcListener func(*tls.Config) (net.Listener, error) - // restListener returns a listener to use for the REST proxy. - restListener func() (net.Listener, error) + // restListener returns a TLS listener to use for the REST proxy, based + // on the passed TLS configuration. + restListener func(*tls.Config) (net.Listener, error) // getLnd returns a grpc connection to an lnd instance. getLnd func(lndclient.Network, *lndConfig) (*lndclient.GrpcLndServices, @@ -175,11 +179,15 @@ func (d *Daemon) startWebServers() error { // Next, start the gRPC server listening for HTTP/2 connections. log.Infof("Starting gRPC listener") - d.grpcListener, err = d.listenerCfg.grpcListener() + serverTLSCfg, restClientCreds, err := getTLSConfig(d.cfg) + if err != nil { + return fmt.Errorf("could not create gRPC server options: %v", + err) + } + d.grpcListener, err = d.listenerCfg.grpcListener(serverTLSCfg) if err != nil { return fmt.Errorf("RPC server unable to listen on %s: %v", d.cfg.RPCListen, err) - } // The default JSON marshaler of the REST proxy only sets OrigName to @@ -203,17 +211,33 @@ func (d *Daemon) startWebServers() error { restHandler = allowCORS(restHandler, d.cfg.CORSOrigin) } proxyOpts := []grpc.DialOption{ - grpc.WithInsecure(), + grpc.WithTransportCredentials(*restClientCreds), grpc.WithDefaultCallOptions(maxMsgRecvSize), } + + // With TLS enabled by default, we cannot call 0.0.0.0 internally from + // the REST proxy as that IP address isn't in the cert. We need to + // rewrite it to the loopback address. + restProxyDest := d.cfg.RPCListen + switch { + case strings.Contains(restProxyDest, "0.0.0.0"): + restProxyDest = strings.Replace( + restProxyDest, "0.0.0.0", "127.0.0.1", 1, + ) + + case strings.Contains(restProxyDest, "[::]"): + restProxyDest = strings.Replace( + restProxyDest, "[::]", "[::1]", 1, + ) + } err = looprpc.RegisterSwapClientHandlerFromEndpoint( - ctx, mux, d.cfg.RPCListen, proxyOpts, + ctx, mux, restProxyDest, proxyOpts, ) if err != nil { return err } - d.restListener, err = d.listenerCfg.restListener() + d.restListener, err = d.listenerCfg.restListener(serverTLSCfg) if err != nil { return fmt.Errorf("REST proxy unable to listen on %s: %v", d.cfg.RESTListen, err) diff --git a/loopd/run.go b/loopd/run.go index 6317a11..199a5ca 100644 --- a/loopd/run.go +++ b/loopd/run.go @@ -2,6 +2,7 @@ package loopd import ( "context" + "crypto/tls" "fmt" "net" "os" @@ -51,22 +52,32 @@ type RPCConfig struct { // and RPCConfig. func newListenerCfg(config *Config, rpcCfg RPCConfig) *listenerCfg { return &listenerCfg{ - grpcListener: func() (net.Listener, error) { + grpcListener: func(tlsCfg *tls.Config) (net.Listener, error) { // If a custom RPC listener is set, we will listen on // it instead of the regular tcp socket. if rpcCfg.RPCListener != nil { return rpcCfg.RPCListener, nil } - return net.Listen("tcp", config.RPCListen) + listener, err := net.Listen("tcp", config.RPCListen) + if err != nil { + return nil, err + } + + return tls.NewListener(listener, tlsCfg), nil }, - restListener: func() (net.Listener, error) { + restListener: func(tlsCfg *tls.Config) (net.Listener, error) { // If a custom RPC listener is set, we disable REST. if rpcCfg.RPCListener != nil { return nil, nil } - return net.Listen("tcp", config.RESTListen) + listener, err := net.Listen("tcp", config.RESTListen) + if err != nil { + return nil, err + } + + return tls.NewListener(listener, tlsCfg), nil }, getLnd: func(network lndclient.Network, cfg *lndConfig) ( *lndclient.GrpcLndServices, error) { @@ -240,7 +251,7 @@ func getConfigPath(cfg Config, loopDir string) string { // If the user has set a loop directory that is different to the default // we will use this loop directory as the location of our config file. // We do not namespace by network, because this is a custom loop dir. - if loopDir != loopDirBase { + if loopDir != LoopDirBase { return filepath.Join(loopDir, defaultConfigFilename) }