From 6dd55170fe2d065c09bffbc3060f3c666a191540 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Thu, 19 Jan 2023 20:46:27 +0100 Subject: [PATCH] Config: Add option to set a proxy for outgoing connections #3132 Signed-off-by: Michael Mayer --- internal/commands/show_flags.go | 12 ++++++---- internal/commands/show_options.go | 2 +- internal/config/config.go | 27 ++++++++++++++------- internal/config/config_proxy.go | 25 +++++++++++++++++++ internal/config/config_proxy_test.go | 36 ++++++++++++++++++++++++++++ internal/config/flags.go | 10 ++++++++ internal/config/options.go | 2 ++ internal/config/report.go | 10 ++++---- internal/hub/config.go | 4 ++-- 9 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 internal/config/config_proxy.go create mode 100644 internal/config/config_proxy_test.go diff --git a/internal/commands/show_flags.go b/internal/commands/show_flags.go index dcef9e387..43297b075 100644 --- a/internal/commands/show_flags.go +++ b/internal/commands/show_flags.go @@ -12,10 +12,11 @@ import ( // ShowFlagsCommand configures the command name, flags, and action. var ShowFlagsCommand = cli.Command{ - Name: "flags", - Usage: "Displays supported environment variables and CLI flags", - Flags: report.CliFlags, - Action: showFlagsAction, + Name: "flags", + Aliases: []string{"env", "vars"}, + Usage: "Displays supported environment variables and CLI flags", + Flags: report.CliFlags, + Action: showFlagsAction, } var faceFlagsInfo = `!!! info "" @@ -23,7 +24,7 @@ var faceFlagsInfo = `!!! info "" We recommend that only advanced users change these parameters:` -// showFlagsAction shows environment variable command-line parameter names. +// showFlagsAction displays supported environment variables and CLI flags. func showFlagsAction(ctx *cli.Context) error { conf := config.NewConfig(ctx) conf.SetLogLevel(logrus.FatalLevel) @@ -53,6 +54,7 @@ func showFlagsAction(ctx *cli.Context) error { {Start: "PHOTOPRISM_READONLY", Title: "Feature Flags"}, {Start: "PHOTOPRISM_DEFAULT_LOCALE", Title: "Customization"}, {Start: "PHOTOPRISM_CDN_URL", Title: "Site Information"}, + {Start: "PHOTOPRISM_HTTPS_PROXY", Title: "HTTPS Proxy"}, {Start: "PHOTOPRISM_TRUSTED_PROXY", Title: "Web Server"}, {Start: "PHOTOPRISM_DATABASE_DRIVER", Title: "Database Connection"}, {Start: "PHOTOPRISM_DARKTABLE_BIN", Title: "File Converters"}, diff --git a/internal/commands/show_options.go b/internal/commands/show_options.go index 677eacc18..41b3a86af 100644 --- a/internal/commands/show_options.go +++ b/internal/commands/show_options.go @@ -18,7 +18,7 @@ var ShowOptionsCommand = cli.Command{ Action: showOptionsAction, } -// showOptionsAction shows supported YAML config file options. +// showOptionsAction displays supported YAML config options and CLI flag. func showOptionsAction(ctx *cli.Context) error { conf := config.NewConfig(ctx) conf.SetLogLevel(logrus.TraceLevel) diff --git a/internal/config/config.go b/internal/config/config.go index 0a10cdcc9..912707fc3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,9 +1,11 @@ package config import ( + "crypto/tls" "encoding/hex" "fmt" "hash/crc32" + "net/http" "net/url" "os" "path/filepath" @@ -232,6 +234,15 @@ func (c *Config) Init() error { log.Warnf("config: the wakeup interval is %s, but must be 1h or less for face recognition to work", c.WakeupInterval().String()) } + // Set HTTPS proxy for outgoing connections. + if httpsProxy := c.HttpsProxy(); httpsProxy != "" { + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ + InsecureSkipVerify: c.HttpsProxyInsecure(), + } + + _ = os.Setenv("HTTPS_PROXY", httpsProxy) + } + // Set HTTP user agent. places.UserAgent = c.UserAgent() @@ -720,20 +731,20 @@ func (c *Config) ResolutionLimit() int { return result } -// UpdateHub renews backend api credentials for maps & places without a token. +// UpdateHub renews backend api credentials for maps and places without a token. func (c *Config) UpdateHub() { _ = c.ResyncHub("") } -// ResyncHub renews backend api credentials for maps & places with an optional token. +// ResyncHub renews backend api credentials for maps and places with an optional token. func (c *Config) ResyncHub(token string) error { if err := c.hub.ReSync(token); err != nil { - log.Debugf("config: %s (refresh backend api tokens)", err) + log.Debugf("config: %s, see https://docs.photoprism.app/getting-started/troubleshooting/firewall/", err) if token != "" { return i18n.Error(i18n.ErrAccountConnect) } } else if err = c.hub.Save(); err != nil { - log.Debugf("config: %s (save backend api tokens)", err) + log.Debugf("config: %s while saving api keys for maps and places", err) } else { c.hub.Propagate() } @@ -751,10 +762,10 @@ func (c *Config) initHub() { if err := c.hub.Load(); err == nil { // Do nothing. - } else if err := c.hub.Update(); err != nil { - log.Debugf("config: %s (init backend api tokens)", err) - } else if err := c.hub.Save(); err != nil { - log.Debugf("config: %s (save backend api tokens)", err) + } else if err = c.hub.Update(); err != nil { + log.Debugf("config: %s, see https://docs.photoprism.app/getting-started/troubleshooting/firewall/", err) + } else if err = c.hub.Save(); err != nil { + log.Debugf("config: %s while saving api keys for maps and places", err) } c.hub.Propagate() diff --git a/internal/config/config_proxy.go b/internal/config/config_proxy.go new file mode 100644 index 000000000..913408e51 --- /dev/null +++ b/internal/config/config_proxy.go @@ -0,0 +1,25 @@ +package config + +import ( + "os" +) + +// HttpsProxy returns the HTTPS proxy to use for outgoing connections. +func (c *Config) HttpsProxy() string { + if c.options.HttpsProxy != "" { + return c.options.HttpsProxy + } else if httpsProxy := os.Getenv("HTTPS_PROXY"); httpsProxy != "" { + return httpsProxy + } + + return "" +} + +// HttpsProxyInsecure checks if invalid TLS certificates should be ignored when using the configured HTTPS proxy. +func (c *Config) HttpsProxyInsecure() bool { + if c.HttpsProxy() == "" { + return false + } + + return c.options.HttpsProxyInsecure +} diff --git a/internal/config/config_proxy_test.go b/internal/config/config_proxy_test.go new file mode 100644 index 000000000..ed63dd0f3 --- /dev/null +++ b/internal/config/config_proxy_test.go @@ -0,0 +1,36 @@ +package config + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig_HttpsProxy(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, "", c.HttpsProxy()) + + _ = os.Setenv("HTTPS_PROXY", "https://foo.bar:8081") + + assert.Equal(t, "https://foo.bar:8081", c.HttpsProxy()) + + _ = os.Setenv("HTTPS_PROXY", "") + + assert.Equal(t, "", c.HttpsProxy()) +} + +func TestConfig_HttpsProxyInsecure(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.False(t, c.HttpsProxyInsecure()) + + _ = os.Setenv("HTTPS_PROXY", "https://foo.bar:8081") + + assert.False(t, c.HttpsProxyInsecure()) + + _ = os.Setenv("HTTPS_PROXY", "") + + assert.False(t, c.HttpsProxyInsecure()) +} diff --git a/internal/config/flags.go b/internal/config/flags.go index 0f8df566c..aadbd33c1 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -407,6 +407,16 @@ var Flags = CliFlags{ Usage: "sharing preview image `URL`", EnvVar: "PHOTOPRISM_SITE_PREVIEW", }, Tags: []string{EnvSponsor}}, { + Flag: cli.StringFlag{ + Name: "https-proxy", + Usage: "trusted HTTPS proxy to use for outgoing connections", + EnvVar: "PHOTOPRISM_HTTPS_PROXY", + }}, { + Flag: cli.BoolFlag{ + Name: "https-proxy-insecure", + Usage: "ignore invalid certificates when using an HTTPS proxy", + EnvVar: "PHOTOPRISM_HTTPS_PROXY_INSECURE", + }}, { Flag: cli.StringSliceFlag{ Name: "trusted-proxy", Usage: "`CIDR` range from which reverse proxy headers can be trusted", diff --git a/internal/config/options.go b/internal/config/options.go index e329a2033..b2df02b95 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -94,6 +94,8 @@ type Options struct { SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"` SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"` SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"` + HttpsProxy string `yaml:"HttpsProxy" json:"HttpsProxy" flag:"https-proxy"` + HttpsProxyInsecure bool `yaml:"HttpsProxyInsecure" json:"HttpsProxyInsecure" flag:"https-proxy-insecure"` TrustedProxies []string `yaml:"TrustedProxies" json:"-" flag:"trusted-proxy"` ProxyProtoHeaders []string `yaml:"ProxyProtoHeaders" json:"-" flag:"proxy-proto-header"` ProxyProtoHttps []string `yaml:"ProxyProtoHttps" json:"-" flag:"proxy-proto-https"` diff --git a/internal/config/report.go b/internal/config/report.go index 3fd220ed5..4ddaea398 100644 --- a/internal/config/report.go +++ b/internal/config/report.go @@ -107,6 +107,8 @@ func (c *Config) Report() (rows [][]string, cols []string) { {"app-icon", c.AppIcon()}, {"app-name", c.AppName()}, {"app-mode", c.AppMode()}, + {"legal-info", c.LegalInfo()}, + {"legal-url", c.LegalUrl()}, {"wallpaper-uri", c.WallpaperUri()}, // Site Infos. @@ -120,16 +122,16 @@ func (c *Config) Report() (rows [][]string, cols []string) { {"site-description", c.SiteDescription()}, {"site-preview", c.SitePreview()}, - // Legal info. - {"legal-info", c.LegalInfo()}, - {"legal-url", c.LegalUrl()}, - // URIs. {"content-uri", c.ContentUri()}, {"static-uri", c.StaticUri()}, {"api-uri", c.ApiUri()}, {"base-uri", c.BaseUri("/")}, + // HTTPS Proxy. + {"https-proxy", c.HttpsProxy()}, + {"https-proxy-insecure", fmt.Sprintf("%t", c.HttpsProxyInsecure())}, + // HTTP(S) Proxy. {"trusted-proxy", c.TrustedProxy()}, {"proxy-proto-header", strings.Join(c.ProxyProtoHeader(), ", ")}, diff --git a/internal/hub/config.go b/internal/hub/config.go index 2f14ecf9f..a12a324d9 100644 --- a/internal/hub/config.go +++ b/internal/hub/config.go @@ -208,9 +208,9 @@ func (c *Config) ReSync(token string) (err error) { if c.Key != "" { url = fmt.Sprintf(ServiceURL+"/%s", c.Key) method = http.MethodPut - log.Debugf("config: requesting updated api key for maps and places") + log.Tracef("config: requesting updated keys for maps and places") } else { - log.Debugf("config: requesting new api key for maps and places") + log.Tracef("config: requesting new api keys for maps and places") } // Create JSON request.