Config: Add option to set a proxy for outgoing connections #3132

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-01-19 20:46:27 +01:00
parent 41774fcec9
commit 6dd55170fe
9 changed files with 108 additions and 20 deletions

View file

@ -13,6 +13,7 @@ import (
// ShowFlagsCommand configures the command name, flags, and action. // ShowFlagsCommand configures the command name, flags, and action.
var ShowFlagsCommand = cli.Command{ var ShowFlagsCommand = cli.Command{
Name: "flags", Name: "flags",
Aliases: []string{"env", "vars"},
Usage: "Displays supported environment variables and CLI flags", Usage: "Displays supported environment variables and CLI flags",
Flags: report.CliFlags, Flags: report.CliFlags,
Action: showFlagsAction, Action: showFlagsAction,
@ -23,7 +24,7 @@ var faceFlagsInfo = `!!! info ""
We recommend that only advanced users change these parameters:` 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 { func showFlagsAction(ctx *cli.Context) error {
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
conf.SetLogLevel(logrus.FatalLevel) conf.SetLogLevel(logrus.FatalLevel)
@ -53,6 +54,7 @@ func showFlagsAction(ctx *cli.Context) error {
{Start: "PHOTOPRISM_READONLY", Title: "Feature Flags"}, {Start: "PHOTOPRISM_READONLY", Title: "Feature Flags"},
{Start: "PHOTOPRISM_DEFAULT_LOCALE", Title: "Customization"}, {Start: "PHOTOPRISM_DEFAULT_LOCALE", Title: "Customization"},
{Start: "PHOTOPRISM_CDN_URL", Title: "Site Information"}, {Start: "PHOTOPRISM_CDN_URL", Title: "Site Information"},
{Start: "PHOTOPRISM_HTTPS_PROXY", Title: "HTTPS Proxy"},
{Start: "PHOTOPRISM_TRUSTED_PROXY", Title: "Web Server"}, {Start: "PHOTOPRISM_TRUSTED_PROXY", Title: "Web Server"},
{Start: "PHOTOPRISM_DATABASE_DRIVER", Title: "Database Connection"}, {Start: "PHOTOPRISM_DATABASE_DRIVER", Title: "Database Connection"},
{Start: "PHOTOPRISM_DARKTABLE_BIN", Title: "File Converters"}, {Start: "PHOTOPRISM_DARKTABLE_BIN", Title: "File Converters"},

View file

@ -18,7 +18,7 @@ var ShowOptionsCommand = cli.Command{
Action: showOptionsAction, Action: showOptionsAction,
} }
// showOptionsAction shows supported YAML config file options. // showOptionsAction displays supported YAML config options and CLI flag.
func showOptionsAction(ctx *cli.Context) error { func showOptionsAction(ctx *cli.Context) error {
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
conf.SetLogLevel(logrus.TraceLevel) conf.SetLogLevel(logrus.TraceLevel)

View file

@ -1,9 +1,11 @@
package config package config
import ( import (
"crypto/tls"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"hash/crc32" "hash/crc32"
"net/http"
"net/url" "net/url"
"os" "os"
"path/filepath" "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()) 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. // Set HTTP user agent.
places.UserAgent = c.UserAgent() places.UserAgent = c.UserAgent()
@ -720,20 +731,20 @@ func (c *Config) ResolutionLimit() int {
return result 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() { func (c *Config) UpdateHub() {
_ = c.ResyncHub("") _ = 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 { func (c *Config) ResyncHub(token string) error {
if err := c.hub.ReSync(token); err != nil { 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 != "" { if token != "" {
return i18n.Error(i18n.ErrAccountConnect) return i18n.Error(i18n.ErrAccountConnect)
} }
} else if err = c.hub.Save(); err != nil { } 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 { } else {
c.hub.Propagate() c.hub.Propagate()
} }
@ -751,10 +762,10 @@ func (c *Config) initHub() {
if err := c.hub.Load(); err == nil { if err := c.hub.Load(); err == nil {
// Do nothing. // Do nothing.
} else if err := c.hub.Update(); err != nil { } else if err = c.hub.Update(); err != nil {
log.Debugf("config: %s (init backend api tokens)", err) log.Debugf("config: %s, see https://docs.photoprism.app/getting-started/troubleshooting/firewall/", err)
} else if err := c.hub.Save(); err != nil { } 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)
} }
c.hub.Propagate() c.hub.Propagate()

View file

@ -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
}

View file

@ -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())
}

View file

@ -407,6 +407,16 @@ var Flags = CliFlags{
Usage: "sharing preview image `URL`", Usage: "sharing preview image `URL`",
EnvVar: "PHOTOPRISM_SITE_PREVIEW", EnvVar: "PHOTOPRISM_SITE_PREVIEW",
}, Tags: []string{EnvSponsor}}, { }, 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{ Flag: cli.StringSliceFlag{
Name: "trusted-proxy", Name: "trusted-proxy",
Usage: "`CIDR` range from which reverse proxy headers can be trusted", Usage: "`CIDR` range from which reverse proxy headers can be trusted",

View file

@ -94,6 +94,8 @@ type Options struct {
SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"` SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"`
SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"` SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"`
SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"` 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"` TrustedProxies []string `yaml:"TrustedProxies" json:"-" flag:"trusted-proxy"`
ProxyProtoHeaders []string `yaml:"ProxyProtoHeaders" json:"-" flag:"proxy-proto-header"` ProxyProtoHeaders []string `yaml:"ProxyProtoHeaders" json:"-" flag:"proxy-proto-header"`
ProxyProtoHttps []string `yaml:"ProxyProtoHttps" json:"-" flag:"proxy-proto-https"` ProxyProtoHttps []string `yaml:"ProxyProtoHttps" json:"-" flag:"proxy-proto-https"`

View file

@ -107,6 +107,8 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"app-icon", c.AppIcon()}, {"app-icon", c.AppIcon()},
{"app-name", c.AppName()}, {"app-name", c.AppName()},
{"app-mode", c.AppMode()}, {"app-mode", c.AppMode()},
{"legal-info", c.LegalInfo()},
{"legal-url", c.LegalUrl()},
{"wallpaper-uri", c.WallpaperUri()}, {"wallpaper-uri", c.WallpaperUri()},
// Site Infos. // Site Infos.
@ -120,16 +122,16 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"site-description", c.SiteDescription()}, {"site-description", c.SiteDescription()},
{"site-preview", c.SitePreview()}, {"site-preview", c.SitePreview()},
// Legal info.
{"legal-info", c.LegalInfo()},
{"legal-url", c.LegalUrl()},
// URIs. // URIs.
{"content-uri", c.ContentUri()}, {"content-uri", c.ContentUri()},
{"static-uri", c.StaticUri()}, {"static-uri", c.StaticUri()},
{"api-uri", c.ApiUri()}, {"api-uri", c.ApiUri()},
{"base-uri", c.BaseUri("/")}, {"base-uri", c.BaseUri("/")},
// HTTPS Proxy.
{"https-proxy", c.HttpsProxy()},
{"https-proxy-insecure", fmt.Sprintf("%t", c.HttpsProxyInsecure())},
// HTTP(S) Proxy. // HTTP(S) Proxy.
{"trusted-proxy", c.TrustedProxy()}, {"trusted-proxy", c.TrustedProxy()},
{"proxy-proto-header", strings.Join(c.ProxyProtoHeader(), ", ")}, {"proxy-proto-header", strings.Join(c.ProxyProtoHeader(), ", ")},

View file

@ -208,9 +208,9 @@ func (c *Config) ReSync(token string) (err error) {
if c.Key != "" { if c.Key != "" {
url = fmt.Sprintf(ServiceURL+"/%s", c.Key) url = fmt.Sprintf(ServiceURL+"/%s", c.Key)
method = http.MethodPut 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 { } 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. // Create JSON request.