mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-19 13:17:50 +02:00
commit
fb5d374a1d
7 changed files with 306 additions and 63 deletions
12
README.md
12
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/<network>/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/<network>/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
|
||||
|
|
|
|||
114
cmd/loop/main.go
114
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)
|
||||
}
|
||||
|
||||
// Macaroons are only allowed to be transmitted over a TLS
|
||||
// enabled connection.
|
||||
if macaroonPath != "" {
|
||||
opts = append(opts, readMacaroon(macaroonPath))
|
||||
}
|
||||
|
||||
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())
|
||||
// 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 not yet enabled by default.
|
||||
if macaroonPath != "" {
|
||||
macOption, err := readMacaroon(macaroonPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
1
go.mod
1
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
|
||||
|
|
|
|||
2
go.sum
2
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=
|
||||
|
|
|
|||
177
loopd/config.go
177
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 <subsystem>=<level>,<subsystem2>=<level>,... 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
21
loopd/run.go
21
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue