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 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" defaultLogLevel = "info" defaultLogDirname = "logs" defaultLogFilename = "loopd.log" 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, ) // DefaultMacaroonFilename is the default file name for the // autogenerated loop macaroon. DefaultMacaroonFilename = "loop.macaroon" // DefaultMacaroonPath is the default full path of the base loop // macaroon. DefaultMacaroonPath = filepath.Join( LoopDirBase, DefaultNetwork, DefaultMacaroonFilename, ) ) type lndConfig struct { Host string `long:"host" description:"lnd instance rpc address"` MacaroonDir string `long:"macaroondir" description:"Path to the directory containing all the required lnd macaroons"` TLSPath string `long:"tlspath" description:"Path to lnd tls certificate"` } type loopServerConfig struct { Host string `long:"host" description:"Loop server address host:port"` Proxy string `long:"proxy" description:"The host:port of a SOCKS proxy through which all connections to the loop server will be established over"` NoTLS bool `long:"notls" description:"Disable tls for communication to the loop server [testing only]"` TLSPath string `long:"tlspath" description:"Path to loop server tls certificate [testing only]"` } type viewParameters struct{} type Config struct { ShowVersion bool `long:"version" description:"Display version information and exit"` Network string `long:"network" description:"network to run on" choice:"regtest" choice:"testnet" choice:"mainnet" choice:"simnet"` RPCListen string `long:"rpclisten" description:"Address to listen on for gRPC clients"` 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. If set, this option overwrites --datadir, --logdir, --tlscertpath, --tlskeypath and --macaroonpath."` 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."` MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for loop's RPC and REST services if it doesn't exist."` 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."` 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."` MaxLSATFee uint32 `long:"maxlsatfee" description:"Maximum routing fee in satoshis that we are willing to pay while paying for an LSAT token."` LoopOutMaxParts uint32 `long:"loopoutmaxparts" description:"The maximum number of payment parts that may be used for a loop out swap."` Lnd *lndConfig `group:"lnd" namespace:"lnd"` Server *loopServerConfig `group:"server" namespace:"server"` View viewParameters `command:"view" alias:"v" description:"View all swaps in the database. This command can only be executed when loopd is not running."` } const ( mainnetServer = "swap.lightning.today:11010" testnetServer = "test.swap.lightning.today:11010" ) // DefaultConfig returns all default values for the Config struct. func DefaultConfig() Config { return Config{ Network: DefaultNetwork, RPCListen: "localhost:11010", RESTListen: "localhost:8081", Server: &loopServerConfig{ NoTLS: false, }, LoopDir: LoopDirBase, ConfigFile: defaultConfigFile, DataDir: LoopDirBase, LogDir: defaultLogDir, MaxLogFiles: defaultMaxLogFiles, MaxLogFileSize: defaultMaxLogFileSize, DebugLevel: defaultLogLevel, TLSCertPath: DefaultTLSCertPath, TLSKeyPath: DefaultTLSKeyPath, MacaroonPath: DefaultMacaroonPath, MaxLSATCost: lsat.DefaultMaxCostSats, MaxLSATFee: lsat.DefaultMaxRoutingFeeSats, LoopOutMaxParts: defaultLoopOutMaxParts, Lnd: &lndConfig{ Host: "localhost:10009", }, } } // Validate cleans up paths in the config provided and validates it. func Validate(cfg *Config) error { // Cleanup any paths before we use them. 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) cfg.MacaroonPath = lncfg.CleanAndExpandPath(cfg.MacaroonPath) // 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. 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") } if dataDirSet { return fmt.Errorf("loopdir overwrites datadir, please " + "only set one value") } 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) } // Append the network type to the data and log directory so they are // "namespaced" per network. cfg.DataDir = filepath.Join(cfg.DataDir, cfg.Network) cfg.LogDir = filepath.Join(cfg.LogDir, cfg.Network) // We want the TLS and macaroon 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 cfg.MacaroonPath == DefaultMacaroonPath { cfg.MacaroonPath = filepath.Join( cfg.DataDir, DefaultMacaroonFilename, ) } // If either of these directories do not exist, create them. if err := os.MkdirAll(cfg.DataDir, os.ModePerm); err != nil { return err } if err := os.MkdirAll(cfg.LogDir, os.ModePerm); err != nil { return err } 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) }