mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-04 01:09:36 +02:00
options refactor (#1088)
* refactor config loading * wip * move autocert to its own config source * refactor options updaters * fix stuttering * fix autocert validate check
This commit is contained in:
parent
eef4c6f2c0
commit
d3a7ee38be
18 changed files with 385 additions and 489 deletions
|
@ -1,16 +1,5 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
)
|
||||
|
||||
// AutocertOptions contains the options to control the behavior of autocert.
|
||||
type AutocertOptions struct {
|
||||
// Enable enables fully automated certificate management including issuance
|
||||
|
@ -35,94 +24,3 @@ type AutocertOptions struct {
|
|||
// defaults to $XDG_DATA_HOME/pomerium
|
||||
Folder string `mapstructure:"autocert_dir" yaml:"autocert_dir,omitempty"`
|
||||
}
|
||||
|
||||
// AutocertManager manages Let's Encrypt certificates based on configuration options.
|
||||
var AutocertManager = newAutocertManager()
|
||||
|
||||
type autocertManager struct {
|
||||
mu sync.RWMutex
|
||||
certmagic *certmagic.Config
|
||||
acmeMgr *certmagic.ACMEManager
|
||||
}
|
||||
|
||||
func newAutocertManager() *autocertManager {
|
||||
mgr := &autocertManager{}
|
||||
return mgr
|
||||
}
|
||||
|
||||
func (mgr *autocertManager) getConfig(options *Options) (*certmagic.Config, error) {
|
||||
mgr.mu.Lock()
|
||||
defer mgr.mu.Unlock()
|
||||
|
||||
cm := mgr.certmagic
|
||||
if cm == nil {
|
||||
cm = certmagic.NewDefault()
|
||||
cm.MustStaple = options.AutocertOptions.MustStaple
|
||||
}
|
||||
|
||||
cm.OnDemand = nil // disable on-demand
|
||||
cm.Storage = &certmagic.FileStorage{Path: options.AutocertOptions.Folder}
|
||||
// add existing certs to the cache, and staple OCSP
|
||||
for _, cert := range options.Certificates {
|
||||
if err := cm.CacheUnmanagedTLSCertificate(cert, nil); err != nil {
|
||||
return nil, fmt.Errorf("config: failed caching cert: %w", err)
|
||||
}
|
||||
}
|
||||
acmeMgr := certmagic.NewACMEManager(cm, certmagic.DefaultACME)
|
||||
acmeMgr.Agreed = true
|
||||
if options.AutocertOptions.UseStaging {
|
||||
acmeMgr.CA = certmagic.LetsEncryptStagingCA
|
||||
}
|
||||
acmeMgr.DisableTLSALPNChallenge = true
|
||||
cm.Issuer = acmeMgr
|
||||
mgr.acmeMgr = acmeMgr
|
||||
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func (mgr *autocertManager) update(options *Options) error {
|
||||
if !options.AutocertOptions.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
cm, err := mgr.getConfig(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, domain := range options.sourceHostnames() {
|
||||
cert, err := cm.CacheManagedCertificate(domain)
|
||||
if err != nil {
|
||||
log.Info().Str("domain", domain).Msg("obtaining certificate")
|
||||
err = cm.ObtainCert(context.Background(), domain, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: failed to obtain client certificate: %w", err)
|
||||
}
|
||||
cert, err = cm.CacheManagedCertificate(domain)
|
||||
}
|
||||
if err == nil && cert.NeedsRenewal(cm) {
|
||||
log.Info().Str("domain", domain).Msg("renewing certificate")
|
||||
err = cm.RenewCert(context.Background(), domain, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: failed to renew client certificate: %w", err)
|
||||
}
|
||||
cert, err = cm.CacheManagedCertificate(domain)
|
||||
}
|
||||
if err == nil {
|
||||
options.Certificates = append(options.Certificates, cert.Certificate)
|
||||
} else {
|
||||
log.Error().Err(err).Msg("config: failed to obtain client certificate")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mgr *autocertManager) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||
mgr.mu.RLock()
|
||||
acmeMgr := mgr.acmeMgr
|
||||
mgr.mu.RUnlock()
|
||||
if acmeMgr == nil {
|
||||
return false
|
||||
}
|
||||
return acmeMgr.HandleHTTPChallenge(w, r)
|
||||
}
|
||||
|
|
103
config/config_source.go
Normal file
103
config/config_source.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config holds pomerium configuration options.
|
||||
type Config struct {
|
||||
Options *Options
|
||||
}
|
||||
|
||||
// Clone creates a deep clone of the config.
|
||||
func (cfg *Config) Clone() *Config {
|
||||
return copystructure.Must(copystructure.Config{
|
||||
Copiers: map[reflect.Type]copystructure.CopierFunc{
|
||||
reflect.TypeOf((*viper.Viper)(nil)): func(i interface{}) (interface{}, error) {
|
||||
return i, nil
|
||||
},
|
||||
},
|
||||
}.Copy(cfg)).(*Config)
|
||||
}
|
||||
|
||||
// A ChangeListener is called when configuration changes.
|
||||
type ChangeListener = func(*Config)
|
||||
|
||||
// A ChangeDispatcher manages listeners on config changes.
|
||||
type ChangeDispatcher struct {
|
||||
sync.Mutex
|
||||
onConfigChangeListeners []ChangeListener
|
||||
}
|
||||
|
||||
// Trigger triggers a change.
|
||||
func (dispatcher *ChangeDispatcher) Trigger(cfg *Config) {
|
||||
dispatcher.Lock()
|
||||
defer dispatcher.Unlock()
|
||||
|
||||
for _, li := range dispatcher.onConfigChangeListeners {
|
||||
li(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// OnConfigChange adds a listener.
|
||||
func (dispatcher *ChangeDispatcher) OnConfigChange(li ChangeListener) {
|
||||
dispatcher.Lock()
|
||||
defer dispatcher.Unlock()
|
||||
dispatcher.onConfigChangeListeners = append(dispatcher.onConfigChangeListeners, li)
|
||||
}
|
||||
|
||||
// A Source gets configuration.
|
||||
type Source interface {
|
||||
GetConfig() *Config
|
||||
OnConfigChange(ChangeListener)
|
||||
}
|
||||
|
||||
// A FileOrEnvironmentSource retrieves config options from a file or the environment.
|
||||
type FileOrEnvironmentSource struct {
|
||||
configFile string
|
||||
|
||||
mu sync.RWMutex
|
||||
config *Config
|
||||
|
||||
ChangeDispatcher
|
||||
}
|
||||
|
||||
// NewFileOrEnvironmentSource creates a new FileOrEnvironmentSource.
|
||||
func NewFileOrEnvironmentSource(configFile string) (*FileOrEnvironmentSource, error) {
|
||||
options, err := newOptionsFromConfig(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
src := &FileOrEnvironmentSource{
|
||||
configFile: configFile,
|
||||
config: &Config{Options: options},
|
||||
}
|
||||
options.viper.OnConfigChange(src.onConfigChange)
|
||||
go options.viper.WatchConfig()
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func (src *FileOrEnvironmentSource) onConfigChange(evt fsnotify.Event) {
|
||||
src.mu.Lock()
|
||||
newOptions := handleConfigUpdate(src.configFile, src.config.Options)
|
||||
cfg := &Config{Options: newOptions}
|
||||
src.config = cfg
|
||||
src.mu.Unlock()
|
||||
|
||||
src.Trigger(cfg)
|
||||
}
|
||||
|
||||
// GetConfig gets the config.
|
||||
func (src *FileOrEnvironmentSource) GetConfig() *Config {
|
||||
src.mu.RLock()
|
||||
defer src.mu.RUnlock()
|
||||
|
||||
return src.config
|
||||
}
|
|
@ -15,7 +15,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
@ -285,9 +284,9 @@ func NewDefaultOptions() *Options {
|
|||
return &newOpts
|
||||
}
|
||||
|
||||
// NewOptionsFromConfig builds the main binary's configuration options by parsing
|
||||
// newOptionsFromConfig builds the main binary's configuration options by parsing
|
||||
// environmental variables and config file
|
||||
func NewOptionsFromConfig(configFile string) (*Options, error) {
|
||||
func newOptionsFromConfig(configFile string) (*Options, error) {
|
||||
o, err := optionsFromViper(configFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config: options from config file %w", err)
|
||||
|
@ -366,13 +365,6 @@ func (o *Options) parsePolicy() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// OnConfigChange starts a go routine and watches for any changes. If any are
|
||||
// detected, via an fsnotify event the provided function is run.
|
||||
func (o *Options) OnConfigChange(run func(in fsnotify.Event)) {
|
||||
go o.viper.WatchConfig()
|
||||
o.viper.OnConfigChange(run)
|
||||
}
|
||||
|
||||
func (o *Options) viperUnmarshalKey(key string, rawVal interface{}) error {
|
||||
return o.viper.UnmarshalKey(key, &rawVal)
|
||||
}
|
||||
|
@ -457,8 +449,6 @@ func bindEnvs(o *Options, v *viper.Viper) error {
|
|||
|
||||
// Validate ensures the Options fields are valid, and hydrated.
|
||||
func (o *Options) Validate() error {
|
||||
var err error
|
||||
|
||||
if !IsValidService(o.Services) {
|
||||
return fmt.Errorf("config: %s is an invalid service type", o.Services)
|
||||
}
|
||||
|
@ -605,47 +595,18 @@ func (o *Options) Validate() error {
|
|||
// strip quotes from redirect address (#811)
|
||||
o.HTTPRedirectAddr = strings.Trim(o.HTTPRedirectAddr, `"'`)
|
||||
|
||||
RedirectAndAutocertServer.update(o)
|
||||
|
||||
err = AutocertManager.update(o)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: failed to setup autocert: %w", err)
|
||||
}
|
||||
|
||||
// sort the certificates so we get a consistent hash
|
||||
sort.Slice(o.Certificates, func(i, j int) bool {
|
||||
return compareByteSliceSlice(o.Certificates[i].Certificate, o.Certificates[j].Certificate) < 0
|
||||
})
|
||||
|
||||
if !o.InsecureServer && len(o.Certificates) == 0 {
|
||||
if !o.InsecureServer && len(o.Certificates) == 0 && !o.AutocertOptions.Enable {
|
||||
return fmt.Errorf("config: server must be run with `autocert`, " +
|
||||
"`insecure_server` or manually provided certificates to start")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options) sourceHostnames() []string {
|
||||
if len(o.Policies) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dedupe := map[string]struct{}{}
|
||||
for _, p := range o.Policies {
|
||||
dedupe[p.Source.Hostname()] = struct{}{}
|
||||
}
|
||||
if o.AuthenticateURL != nil {
|
||||
dedupe[o.AuthenticateURL.Hostname()] = struct{}{}
|
||||
}
|
||||
|
||||
var h []string
|
||||
for k := range dedupe {
|
||||
h = append(h, k)
|
||||
}
|
||||
sort.Strings(h)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// GetAuthenticateURL returns the AuthenticateURL in the options or localhost.
|
||||
func (o *Options) GetAuthenticateURL() *url.URL {
|
||||
if o != nil && o.AuthenticateURL != nil {
|
||||
|
@ -697,11 +658,6 @@ func (o *Options) GetOauthOptions() oauth.Options {
|
|||
}
|
||||
}
|
||||
|
||||
// OptionsUpdater updates local state based on an Options struct
|
||||
type OptionsUpdater interface {
|
||||
UpdateOptions(Options) error
|
||||
}
|
||||
|
||||
// Checksum returns the checksum of the current options struct
|
||||
func (o *Options) Checksum() uint64 {
|
||||
hash, err := hashstructure.Hash(o, &hashstructure.HashOptions{Hasher: xxhash.New()})
|
||||
|
@ -712,40 +668,13 @@ func (o *Options) Checksum() uint64 {
|
|||
return hash
|
||||
}
|
||||
|
||||
// WatchChanges takes a configuration file, an existing options struct, and
|
||||
// updates each service in the services slice OptionsUpdater with a new set
|
||||
// of options if any change is detected. It also periodically rechecks if
|
||||
// any computed properties have changed.
|
||||
func WatchChanges(configFile string, opt *Options, services []OptionsUpdater) {
|
||||
onchange := make(chan struct{}, 1)
|
||||
ticker := time.NewTicker(10 * time.Minute) // force check every 10 minutes
|
||||
defer ticker.Stop()
|
||||
|
||||
opt.OnConfigChange(func(fs fsnotify.Event) {
|
||||
log.Info().Str("file", fs.Name).Msg("config: file changed")
|
||||
select {
|
||||
case onchange <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
})
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-onchange:
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
opt = handleConfigUpdate(configFile, opt, services)
|
||||
}
|
||||
}
|
||||
|
||||
// handleConfigUpdate takes configuration file, an existing options struct, and
|
||||
// updates each service in the services slice OptionsUpdater with a new set of
|
||||
// options if any change is detected.
|
||||
func handleConfigUpdate(configFile string, opt *Options, services []OptionsUpdater) *Options {
|
||||
func handleConfigUpdate(configFile string, opt *Options) *Options {
|
||||
serviceName := telemetry.ServiceName(opt.Services)
|
||||
|
||||
newOpt, err := NewOptionsFromConfig(configFile)
|
||||
newOpt, err := newOptionsFromConfig(configFile)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("config: could not reload configuration")
|
||||
metrics.SetConfigInfo(serviceName, false)
|
||||
|
@ -761,19 +690,6 @@ func handleConfigUpdate(configFile string, opt *Options, services []OptionsUpdat
|
|||
return opt
|
||||
}
|
||||
|
||||
var updateFailed bool
|
||||
for _, service := range services {
|
||||
if err := service.UpdateOptions(*newOpt); err != nil {
|
||||
log.Error().Err(err).Msg("config: could not update options")
|
||||
updateFailed = true
|
||||
metrics.SetConfigInfo(serviceName, false)
|
||||
}
|
||||
}
|
||||
|
||||
if !updateFailed {
|
||||
metrics.SetConfigInfo(serviceName, true)
|
||||
metrics.SetConfigChecksum(serviceName, newOptChecksum)
|
||||
}
|
||||
return newOpt
|
||||
}
|
||||
|
||||
|
|
|
@ -265,7 +265,7 @@ func TestOptionsFromViper(t *testing.T) {
|
|||
return
|
||||
}
|
||||
if diff := cmp.Diff(got, tt.want, opts...); diff != "" {
|
||||
t.Errorf("NewOptionsFromConfig() = %s", diff)
|
||||
t.Errorf("newOptionsFromConfig() = %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -305,9 +305,9 @@ func Test_NewOptionsFromConfigEnvVar(t *testing.T) {
|
|||
os.Setenv(k, v)
|
||||
defer os.Unsetenv(k)
|
||||
}
|
||||
_, err := NewOptionsFromConfig("")
|
||||
_, err := newOptionsFromConfig("")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewOptionsFromConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
t.Errorf("newOptionsFromConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
@ -327,7 +327,7 @@ func Test_AutoCertOptionsFromEnvVar(t *testing.T) {
|
|||
defer os.Unsetenv(k)
|
||||
}
|
||||
|
||||
o, err := NewOptionsFromConfig("")
|
||||
o, err := newOptionsFromConfig("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -343,160 +343,6 @@ func Test_AutoCertOptionsFromEnvVar(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
type mockService struct {
|
||||
fail bool
|
||||
Updated bool
|
||||
}
|
||||
|
||||
func (m *mockService) UpdateOptions(o Options) error {
|
||||
|
||||
m.Updated = true
|
||||
if m.fail {
|
||||
return fmt.Errorf("failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_HandleConfigUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
oldEnvKeyPairs map[string]string
|
||||
newEnvKeyPairs map[string]string
|
||||
service *mockService
|
||||
wantUpdate bool
|
||||
}{
|
||||
{"good",
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
&mockService{fail: false},
|
||||
true},
|
||||
{"good set debug",
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
map[string]string{
|
||||
"POMERIUM_DEBUG": "true",
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
&mockService{fail: false},
|
||||
true},
|
||||
{"bad",
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
&mockService{fail: true},
|
||||
true},
|
||||
{"bad policy file unmarshal error",
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
map[string]string{
|
||||
"POLICY": base64.StdEncoding.EncodeToString([]byte("{json:}")),
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
&mockService{fail: false},
|
||||
false},
|
||||
{"bad header key",
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
map[string]string{
|
||||
"SERVICES": "error",
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
&mockService{fail: false},
|
||||
false},
|
||||
{"bad header header value",
|
||||
map[string]string{
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
map[string]string{
|
||||
"HEADERS": "x;y;z",
|
||||
"INSECURE_SERVER": "true",
|
||||
"AUTHENTICATE_SERVICE_URL": "https://authenticate.example",
|
||||
"AUTHORIZE_SERVICE_URL": "https://authorize.example"},
|
||||
&mockService{fail: false},
|
||||
false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for k, v := range tt.oldEnvKeyPairs {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
oldOpts, err := NewOptionsFromConfig("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for k := range tt.oldEnvKeyPairs {
|
||||
os.Unsetenv(k)
|
||||
}
|
||||
for k, v := range tt.newEnvKeyPairs {
|
||||
os.Setenv(k, v)
|
||||
defer os.Unsetenv(k)
|
||||
}
|
||||
handleConfigUpdate("", oldOpts, []OptionsUpdater{tt.service})
|
||||
if tt.service.Updated != tt.wantUpdate {
|
||||
t.Errorf("Failed to update config on service")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptions_sourceHostnames(t *testing.T) {
|
||||
t.Parallel()
|
||||
testOptions := func() *Options {
|
||||
o := NewDefaultOptions()
|
||||
o.SharedKey = "test"
|
||||
o.Services = "all"
|
||||
o.InsecureServer = true
|
||||
return o
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
policies []Policy
|
||||
authenticateURL string
|
||||
want []string
|
||||
}{
|
||||
{"empty", []Policy{}, "", nil},
|
||||
{"good no authN", []Policy{{From: "https://from.example", To: "https://to.example"}}, "", []string{"from.example"}},
|
||||
{"good with authN", []Policy{{From: "https://from.example", To: "https://to.example"}}, "https://authn.example.com", []string{"authn.example.com", "from.example"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.Policies = tt.policies
|
||||
o.AuthenticateURLString = tt.authenticateURL
|
||||
err := o.Validate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := o.sourceHostnames()
|
||||
if diff := cmp.Diff(got, tt.want); diff != "" {
|
||||
t.Errorf("Options.sourceHostnames() = %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPRedirectAddressStripQuotes(t *testing.T) {
|
||||
o := NewDefaultOptions()
|
||||
o.InsecureServer = true
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
)
|
||||
|
||||
// RedirectAndAutocertServer is an HTTP server which handles redirecting to HTTPS and autocerts.
|
||||
var RedirectAndAutocertServer = newRedirectAndAutoCertServer()
|
||||
|
||||
type redirectAndAutoCertServer struct {
|
||||
mu sync.Mutex
|
||||
srv *http.Server
|
||||
}
|
||||
|
||||
func newRedirectAndAutoCertServer() *redirectAndAutoCertServer {
|
||||
return &redirectAndAutoCertServer{}
|
||||
}
|
||||
|
||||
func (srv *redirectAndAutoCertServer) update(options *Options) {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
if srv.srv != nil {
|
||||
// nothing to do if the address hasn't changed
|
||||
if srv.srv.Addr == options.HTTPRedirectAddr {
|
||||
return
|
||||
}
|
||||
// close immediately, don't care about the error
|
||||
_ = srv.srv.Close()
|
||||
srv.srv = nil
|
||||
}
|
||||
|
||||
if options.HTTPRedirectAddr == "" {
|
||||
return
|
||||
}
|
||||
|
||||
redirect := httputil.RedirectHandler()
|
||||
|
||||
hsrv := &http.Server{
|
||||
Addr: options.HTTPRedirectAddr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if AutocertManager.HandleHTTPChallenge(w, r) {
|
||||
return
|
||||
}
|
||||
redirect.ServeHTTP(w, r)
|
||||
}),
|
||||
}
|
||||
go func() {
|
||||
log.Info().Str("addr", hsrv.Addr).Msg("starting http redirect server")
|
||||
err := hsrv.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to run http redirect server")
|
||||
}
|
||||
}()
|
||||
srv.srv = hsrv
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue