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

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

View file

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

View file

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

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`",
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",

View file

@ -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"`

View file

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

View file

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