From c22c0db128ec8dcb9a811a3ae465eedeb50016f2 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 27 Sep 2021 13:21:52 +0200 Subject: [PATCH 1/6] aperture: parse command line flags --- aperture.go | 27 +++++++++++++++++++++++---- go.mod | 1 + 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/aperture.go b/aperture.go index 8434d9a..fb40ae5 100644 --- a/aperture.go +++ b/aperture.go @@ -2,6 +2,7 @@ package aperture import ( "crypto/tls" + "errors" "fmt" "io" "io/ioutil" @@ -11,6 +12,7 @@ import ( "sync" "time" + flags "github.com/jessevdk/go-flags" "github.com/lightninglabs/aperture/auth" "github.com/lightninglabs/aperture/mint" "github.com/lightninglabs/aperture/proxy" @@ -71,10 +73,21 @@ var ( func Main() { // TODO: Prevent from running twice. err := run() - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, err) - os.Exit(1) + + // Unwrap our error and check whether help was requested from our flag + // library. If the error is not wrapped, Unwrap returns nil. It is + // still safe to check the type of this nil error. + flagErr, isFlagErr := errors.Unwrap(err).(*flags.Error) + isHelpErr := isFlagErr && flagErr.Type == flags.ErrHelp + + // If we got a nil error, or help was requested, just exit. + if err == nil || isHelpErr { + os.Exit(0) } + + // Print any other non-help related errors. + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) } // run sets up the proxy server and runs it. This function blocks until a @@ -91,7 +104,7 @@ func run() error { configFile := filepath.Join(apertureDataDir, defaultConfigFilename) cfg, err := getConfig(configFile) if err != nil { - return fmt.Errorf("unable to parse config file: %v", err) + return fmt.Errorf("unable to parse config file: %w", err) } err = setupLogging(cfg, interceptor) if err != nil { @@ -316,6 +329,12 @@ func getConfig(configFile string) (*Config, error) { return nil, err } + // Finally, parse the remaining command line options again to ensure + // they take precedence. + if _, err := flags.Parse(cfg); err != nil { + return nil, err + } + // Then check the configuration that we got from the config file, all // required values need to be set at this point. if cfg.ListenAddr == "" { diff --git a/go.mod b/go.mod index 09586e1..a5972fc 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210706234807-aaf03fee735a github.com/fortytw2/leaktest v1.3.0 github.com/golang/protobuf v1.5.2 + github.com/jessevdk/go-flags v1.4.0 github.com/lightninglabs/lndclient v0.12.0-9 github.com/lightningnetwork/lnd v0.13.0-beta.rc5.0.20210728112744-ebabda671786 github.com/lightningnetwork/lnd/cert v1.0.3 From 264fc2f99832018851dec68d4bddfc651b3c2f72 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 27 Sep 2021 14:11:18 +0200 Subject: [PATCH 2/6] aperture: allow relative macaroon/tls paths --- aperture.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aperture.go b/aperture.go index fb40ae5..4ef4326 100644 --- a/aperture.go +++ b/aperture.go @@ -16,6 +16,7 @@ import ( "github.com/lightninglabs/aperture/auth" "github.com/lightninglabs/aperture/mint" "github.com/lightninglabs/aperture/proxy" + "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/cert" "github.com/lightningnetwork/lnd/lnrpc" @@ -335,6 +336,14 @@ func getConfig(configFile string) (*Config, error) { return nil, err } + // Clean and expand our cert and macaroon paths. + cfg.Authenticator.TLSPath = lnd.CleanAndExpandPath( + cfg.Authenticator.TLSPath, + ) + cfg.Authenticator.MacDir = lnd.CleanAndExpandPath( + cfg.Authenticator.MacDir, + ) + // Then check the configuration that we got from the config file, all // required values need to be set at this point. if cfg.ListenAddr == "" { From 9bddd769937ba852783b01b19ca488e2575ea14f Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 27 Sep 2021 13:25:41 +0200 Subject: [PATCH 3/6] aperture: add descriptions to lnd config --- config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config.go b/config.go index 42a51f6..6569677 100644 --- a/config.go +++ b/config.go @@ -26,13 +26,13 @@ type AuthConfig struct { // LndHost is the hostname of the LND instance to connect to. LndHost string `long:"lndhost" description:"Hostname of the LND instance to connect to"` - TLSPath string `long:"tlspath"` + TLSPath string `long:"tlspath" description:"Path to LND instance's tls certificate"` - MacDir string `long:"macdir"` + MacDir string `long:"macdir" description:"Directory containing LND instance's macaroons"` - Network string `long:"network"` + Network string `long:"network" description:"The network LND is connected to." choice:"regtest" choice:"simnet" choice:"testnet" choice:"mainnet"` - Disable bool `long:"disable"` + Disable bool `long:"disable" description:"Whether to disable LND auth."` } type TorConfig struct { From a0cf13ba00f230fb0adfe9e6ea1914d29dfd1cd3 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 27 Sep 2021 13:43:47 +0200 Subject: [PATCH 4/6] config: make etcd, tor and auth groups so they can be specified inline Our yaml parsing is unaffected, but this change allows us to specify these groups one param at a time. We do not update services because they require special grouping, so we leave that as a json parameter. --- config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index 6569677..0739f6f 100644 --- a/config.go +++ b/config.go @@ -67,11 +67,11 @@ type Config struct { // directory defined by StaticRoot. ServeStatic bool `long:"servestatic" description:"Flag to enable or disable static content serving."` - Etcd *EtcdConfig `long:"etcd" description:"Configuration for the etcd instance backing the proxy."` + Etcd *EtcdConfig `group:"etcd" namespace:"etcd"` - Authenticator *AuthConfig `long:"authenticator" description:"Configuration for the authenticator."` + Authenticator *AuthConfig `group:"authenticator" namespace:"authenticator"` - Tor *TorConfig `long:"tor" description:"Configuration for the Tor instance backing the proxy."` + Tor *TorConfig `group:"tor" namespace:"tor"` // Services is a list of JSON objects in string format, which specify // each backend service to Aperture. From e6fae0f007e2d969bc27a74f0560a938b98f3288 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 27 Sep 2021 13:49:03 +0200 Subject: [PATCH 5/6] aperture: add validation to config --- aperture.go | 5 +++-- config.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/aperture.go b/aperture.go index 4ef4326..24aa3bf 100644 --- a/aperture.go +++ b/aperture.go @@ -346,9 +346,10 @@ func getConfig(configFile string) (*Config, error) { // Then check the configuration that we got from the config file, all // required values need to be set at this point. - if cfg.ListenAddr == "" { - return nil, fmt.Errorf("missing listen address for server") + if err := cfg.validate(); err != nil { + return nil, err } + return cfg, nil } diff --git a/config.go b/config.go index 0739f6f..ed35d02 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,9 @@ package aperture import ( + "errors" + "fmt" + "github.com/btcsuite/btcutil" "github.com/lightninglabs/aperture/proxy" ) @@ -35,6 +38,27 @@ type AuthConfig struct { Disable bool `long:"disable" description:"Whether to disable LND auth."` } +func (a *AuthConfig) validate() error { + // If we're disabled, we don't mind what these values are. + if a.Disable { + return nil + } + + if a.LndHost == "" { + return errors.New("lnd host required") + } + + if a.TLSPath == "" { + return errors.New("lnd tls required") + } + + if a.MacDir == "" { + return errors.New("lnd mac dir required") + } + + return nil +} + type TorConfig struct { Control string `long:"control" description:"The host:port of the Tor instance."` ListenPort uint16 `long:"listenport" description:"The port we should listen on for client requests over Tor. Note that this port should not be exposed to the outside world, it is only intended to be reached by clients through the onion service."` @@ -81,3 +105,17 @@ type Config struct { // for all subsystems the same or individual level by subsystem. DebugLevel string `long:"debuglevel" description:"Debug level for the Aperture application and its subsystems."` } + +func (c *Config) validate() error { + if c.Authenticator != nil { + if err := c.Authenticator.validate(); err != nil { + return err + } + } + + if c.ListenAddr == "" { + return fmt.Errorf("missing listen address for server") + } + + return nil +} From 0b8128232d54e91962a8c8d42fa019452d5fdfd6 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 27 Sep 2021 13:39:24 +0200 Subject: [PATCH 6/6] aperture: add config file flag and parse command line flags --- aperture.go | 42 +++++++++++++++++++++++++++++++++++------- config.go | 3 +++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/aperture.go b/aperture.go index 24aa3bf..b3d4ef1 100644 --- a/aperture.go +++ b/aperture.go @@ -102,8 +102,7 @@ func run() error { } // Next, parse configuration file and set up logging. - configFile := filepath.Join(apertureDataDir, defaultConfigFilename) - cfg, err := getConfig(configFile) + cfg, err := getConfig() if err != nil { return fmt.Errorf("unable to parse config file: %w", err) } @@ -319,15 +318,44 @@ func fileExists(name string) bool { // getConfig loads and parses the configuration file then checks it for valid // content. -func getConfig(configFile string) (*Config, error) { +func getConfig() (*Config, error) { + // Pre-parse command line flags to determine whether we've been pointed + // to a custom config file. cfg := &Config{} - b, err := ioutil.ReadFile(configFile) - if err != nil { + if _, err := flags.Parse(cfg); err != nil { return nil, err } - err = yaml.Unmarshal(b, cfg) - if err != nil { + + // If a custom config file is provided, we require that it exists. + var mustExist bool + + configFile := filepath.Join(apertureDataDir, defaultConfigFilename) + if cfg.ConfigFile != "" { + configFile = lnd.CleanAndExpandPath(cfg.ConfigFile) + mustExist = true + } + + // Read our config file, either from the custom path provided or our + // default location. + b, err := ioutil.ReadFile(configFile) + switch { + // If the file was found, unmarshal it. + case err == nil: + err = yaml.Unmarshal(b, cfg) + if err != nil { + return nil, err + } + + // If the error is unrelated to the existence of the file, we must + // always return it. + case !os.IsNotExist(err): return nil, err + + // If we require that the config file exists and we got an error + // related to file existence, we must fail. + case mustExist && os.IsNotExist(err): + return nil, fmt.Errorf("config file: %v must exist: %w", + configFile, err) } // Finally, parse the remaining command line options again to ensure diff --git a/config.go b/config.go index ed35d02..cfd9b69 100644 --- a/config.go +++ b/config.go @@ -104,6 +104,9 @@ type Config struct { // DebugLevel is a string defining the log level for the service either // for all subsystems the same or individual level by subsystem. DebugLevel string `long:"debuglevel" description:"Debug level for the Aperture application and its subsystems."` + + // ConfigFile points aperture to an alternative config file. + ConfigFile string `long:"configfile" description:"Custom path to a config file."` } func (c *Config) validate() error {