Protect Options from being mutated by services

- Change Options URLs from pointers to values

- Remove special handling for AuthenticateURL checksum

- Change Options itself to a value
This commit is contained in:
Travis Groth 2019-06-03 22:19:24 -04:00
parent 49bc8274f1
commit 64eb992854
12 changed files with 117 additions and 125 deletions

View file

@ -18,8 +18,8 @@ import (
// ValidateOptions checks to see if configuration values are valid for the authenticate service. // ValidateOptions checks to see if configuration values are valid for the authenticate service.
// The checks do not modify the internal state of the Option structure. Returns // The checks do not modify the internal state of the Option structure. Returns
// on first error found. // on first error found.
func ValidateOptions(o *config.Options) error { func ValidateOptions(o config.Options) error {
if o.AuthenticateURL == nil || o.AuthenticateURL.Hostname() == "" { if o.AuthenticateURL.Hostname() == "" {
return errors.New("authenticate: 'AUTHENTICATE_SERVICE_URL' missing") return errors.New("authenticate: 'AUTHENTICATE_SERVICE_URL' missing")
} }
if o.ClientID == "" { if o.ClientID == "" {
@ -54,10 +54,7 @@ type Authenticate struct {
} }
// New validates and creates a new authenticate service from a set of Options // New validates and creates a new authenticate service from a set of Options
func New(opts *config.Options) (*Authenticate, error) { func New(opts config.Options) (*Authenticate, error) {
if opts == nil {
return nil, errors.New("authenticate: options cannot be nil")
}
if err := ValidateOptions(opts); err != nil { if err := ValidateOptions(opts); err != nil {
return nil, err return nil, err
} }
@ -83,7 +80,7 @@ func New(opts *config.Options) (*Authenticate, error) {
provider, err := identity.New( provider, err := identity.New(
opts.Provider, opts.Provider,
&identity.Provider{ &identity.Provider{
RedirectURL: redirectURL, RedirectURL: &redirectURL,
ProviderName: opts.Provider, ProviderName: opts.Provider,
ProviderURL: opts.ProviderURL, ProviderURL: opts.ProviderURL,
ClientID: opts.ClientID, ClientID: opts.ClientID,
@ -97,7 +94,7 @@ func New(opts *config.Options) (*Authenticate, error) {
return &Authenticate{ return &Authenticate{
SharedKey: opts.SharedKey, SharedKey: opts.SharedKey,
RedirectURL: redirectURL, RedirectURL: &redirectURL,
templates: templates.New(), templates: templates.New(),
csrfStore: cookieStore, csrfStore: cookieStore,
sessionStore: cookieStore, sessionStore: cookieStore,

View file

@ -8,10 +8,10 @@ import (
"github.com/pomerium/pomerium/internal/config" "github.com/pomerium/pomerium/internal/config"
) )
func testOptions() *config.Options { func testOptions() config.Options {
redirectURL, _ := url.Parse("https://example.com/oauth2/callback") redirectURL, _ := url.Parse("https://example.com/oauth2/callback")
return &config.Options{ return config.Options{
AuthenticateURL: redirectURL, AuthenticateURL: *redirectURL,
SharedKey: "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=", SharedKey: "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=",
ClientID: "test-client-id", ClientID: "test-client-id",
ClientSecret: "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw=", ClientSecret: "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw=",
@ -25,7 +25,7 @@ func testOptions() *config.Options {
func TestOptions_Validate(t *testing.T) { func TestOptions_Validate(t *testing.T) {
good := testOptions() good := testOptions()
badRedirectURL := testOptions() badRedirectURL := testOptions()
badRedirectURL.AuthenticateURL = nil badRedirectURL.AuthenticateURL = url.URL{}
emptyClientID := testOptions() emptyClientID := testOptions()
emptyClientID.ClientID = "" emptyClientID.ClientID = ""
emptyClientSecret := testOptions() emptyClientSecret := testOptions()
@ -39,15 +39,15 @@ func TestOptions_Validate(t *testing.T) {
badSharedKey := testOptions() badSharedKey := testOptions()
badSharedKey.SharedKey = "" badSharedKey.SharedKey = ""
badAuthenticateURL := testOptions() badAuthenticateURL := testOptions()
badAuthenticateURL.AuthenticateURL = new(url.URL) badAuthenticateURL.AuthenticateURL = url.URL{}
tests := []struct { tests := []struct {
name string name string
o *config.Options o config.Options
wantErr bool wantErr bool
}{ }{
{"minimum options", good, false}, {"minimum options", good, false},
{"nil options", &config.Options{}, true}, {"nil options", config.Options{}, true},
{"bad redirect url", badRedirectURL, true}, {"bad redirect url", badRedirectURL, true},
{"no cookie secret", emptyCookieSecret, true}, {"no cookie secret", emptyCookieSecret, true},
{"invalid cookie secret", invalidCookieSecret, true}, {"invalid cookie secret", invalidCookieSecret, true},
@ -72,16 +72,16 @@ func TestNew(t *testing.T) {
good.Provider = "google" good.Provider = "google"
badRedirectURL := testOptions() badRedirectURL := testOptions()
badRedirectURL.AuthenticateURL = nil badRedirectURL.AuthenticateURL = url.URL{}
tests := []struct { tests := []struct {
name string name string
opts *config.Options opts config.Options
// want *Authenticate // want *Authenticate
wantErr bool wantErr bool
}{ }{
{"good", good, false}, {"good", good, false},
{"empty opts", nil, true}, {"empty opts", config.Options{}, true},
{"fails to validate", badRedirectURL, true}, {"fails to validate", badRedirectURL, true},
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -14,7 +14,7 @@ import (
// ValidateOptions checks to see if configuration values are valid for the // ValidateOptions checks to see if configuration values are valid for the
// authorize service. Returns first error, if found. // authorize service. Returns first error, if found.
func ValidateOptions(o *config.Options) error { func ValidateOptions(o config.Options) error {
decoded, err := base64.StdEncoding.DecodeString(o.SharedKey) decoded, err := base64.StdEncoding.DecodeString(o.SharedKey)
if err != nil { if err != nil {
return fmt.Errorf("authorize: `SHARED_SECRET` setting is invalid base64: %v", err) return fmt.Errorf("authorize: `SHARED_SECRET` setting is invalid base64: %v", err)
@ -39,10 +39,7 @@ type Authorize struct {
} }
// New validates and creates a new Authorize service from a set of Options // New validates and creates a new Authorize service from a set of Options
func New(opts *config.Options) (*Authorize, error) { func New(opts config.Options) (*Authorize, error) {
if opts == nil {
return nil, errors.New("authorize: options cannot be nil")
}
if err := ValidateOptions(opts); err != nil { if err := ValidateOptions(opts); err != nil {
return nil, err return nil, err
} }
@ -67,7 +64,7 @@ func (a *Authorize) ValidIdentity(route string, identity *Identity) bool {
} }
// UpdateOptions updates internal structres based on config.Options // UpdateOptions updates internal structres based on config.Options
func (a *Authorize) UpdateOptions(o *config.Options) error { func (a *Authorize) UpdateOptions(o config.Options) error {
log.Info().Msg("authorize: updating options") log.Info().Msg("authorize: updating options")
a.identityAccess = NewIdentityWhitelist(o.Policies, o.Administrators) a.identityAccess = NewIdentityWhitelist(o.Policies, o.Administrators)
return nil return nil

View file

@ -22,14 +22,14 @@ func TestNew(t *testing.T) {
{"bad shared secret", "AZA85podM73CjLCjViDNz1EUvvejKpWp7Hysr0knXA==", policies, true}, {"bad shared secret", "AZA85podM73CjLCjViDNz1EUvvejKpWp7Hysr0knXA==", policies, true},
{"really bad shared secret", "sup", policies, true}, {"really bad shared secret", "sup", policies, true},
{"validation error, short secret", "AZA85podM73CjLCjViDNz1EUvvejKpWp7Hysr0knXA==", policies, true}, {"validation error, short secret", "AZA85podM73CjLCjViDNz1EUvvejKpWp7Hysr0knXA==", policies, true},
{"nil options", "", []policy.Policy{}, true}, // special case {"empty options", "", []policy.Policy{}, true}, // special case
{"missing policies", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", []policy.Policy{}, true}, // special case {"missing policies", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", []policy.Policy{}, true}, // special case
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := &config.Options{SharedKey: tt.SharedKey, Policies: tt.Policies} o := config.Options{SharedKey: tt.SharedKey, Policies: tt.Policies}
if tt.name == "nil options" { if tt.name == "empty options" {
o = nil o = config.Options{}
} }
_, err := New(o) _, err := New(o)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -76,7 +76,7 @@ func Test_UpdateOptions(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := &config.Options{SharedKey: tt.SharedKey, Policies: tt.Policies} o := config.Options{SharedKey: tt.SharedKey, Policies: tt.Policies}
authorize, _ := New(o) authorize, _ := New(o)
o.Policies = tt.newPolices o.Policies = tt.newPolices
authorize.UpdateOptions(o) authorize.UpdateOptions(o)

View file

@ -114,8 +114,8 @@ func startRedirectServer(addr string) (*http.Server, error) {
return srv, nil return srv, nil
} }
func newAuthenticateService(opt *config.Options, mux *http.ServeMux, rpc *grpc.Server) (*authenticate.Authenticate, error) { func newAuthenticateService(opt config.Options, mux *http.ServeMux, rpc *grpc.Server) (*authenticate.Authenticate, error) {
if opt == nil || !config.IsAuthenticate(opt.Services) { if !config.IsAuthenticate(opt.Services) {
return nil, nil return nil, nil
} }
service, err := authenticate.New(opt) service, err := authenticate.New(opt)
@ -127,8 +127,8 @@ func newAuthenticateService(opt *config.Options, mux *http.ServeMux, rpc *grpc.S
return service, nil return service, nil
} }
func newAuthorizeService(opt *config.Options, rpc *grpc.Server) (*authorize.Authorize, error) { func newAuthorizeService(opt config.Options, rpc *grpc.Server) (*authorize.Authorize, error) {
if opt == nil || !config.IsAuthorize(opt.Services) { if !config.IsAuthorize(opt.Services) {
return nil, nil return nil, nil
} }
service, err := authorize.New(opt) service, err := authorize.New(opt)
@ -139,8 +139,8 @@ func newAuthorizeService(opt *config.Options, rpc *grpc.Server) (*authorize.Auth
return service, nil return service, nil
} }
func newProxyService(opt *config.Options, mux *http.ServeMux) (*proxy.Proxy, error) { func newProxyService(opt config.Options, mux *http.ServeMux) (*proxy.Proxy, error) {
if opt == nil || !config.IsProxy(opt.Services) { if !config.IsProxy(opt.Services) {
return nil, nil return nil, nil
} }
service, err := proxy.New(opt) service, err := proxy.New(opt)
@ -151,7 +151,7 @@ func newProxyService(opt *config.Options, mux *http.ServeMux) (*proxy.Proxy, err
return service, nil return service, nil
} }
func wrapMiddleware(o *config.Options, mux *http.ServeMux) http.Handler { func wrapMiddleware(o config.Options, mux *http.ServeMux) http.Handler {
c := middleware.NewChain() c := middleware.NewChain()
c = c.Append(log.NewHandler(log.Logger)) c = c.Append(log.NewHandler(log.Logger))
c = c.Append(log.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { c = c.Append(log.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
@ -166,7 +166,7 @@ func wrapMiddleware(o *config.Options, mux *http.ServeMux) http.Handler {
Str("url", r.URL.String()). Str("url", r.URL.String()).
Msg("http-request") Msg("http-request")
})) }))
if o != nil && len(o.Headers) != 0 { if len(o.Headers) != 0 {
c = c.Append(middleware.SetHeaders(o.Headers)) c = c.Append(middleware.SetHeaders(o.Headers))
} }
c = c.Append(log.ForwardedAddrHandler("fwd_ip")) c = c.Append(log.ForwardedAddrHandler("fwd_ip"))
@ -178,10 +178,10 @@ func wrapMiddleware(o *config.Options, mux *http.ServeMux) http.Handler {
return c.Then(mux) return c.Then(mux)
} }
func parseOptions(configFile string) (*config.Options, error) { func parseOptions(configFile string) (config.Options, error) {
o, err := config.OptionsFromViper(configFile) o, err := config.OptionsFromViper(configFile)
if err != nil { if err != nil {
return nil, err return o, err
} }
if o.Debug { if o.Debug {
log.SetDebugMode() log.SetDebugMode()
@ -192,7 +192,7 @@ func parseOptions(configFile string) (*config.Options, error) {
return o, nil return o, nil
} }
func handleConfigUpdate(opt *config.Options, services []config.OptionsUpdater) *config.Options { func handleConfigUpdate(opt config.Options, services []config.OptionsUpdater) config.Options {
newOpt, err := parseOptions(*configFile) newOpt, err := parseOptions(*configFile)
optChecksum := opt.Checksum() optChecksum := opt.Checksum()
newOptChecksum := newOpt.Checksum() newOptChecksum := newOpt.Checksum()

View file

@ -78,11 +78,11 @@ func Test_newAuthenticateService(t *testing.T) {
testOpts.ClientSecret = "TEST" testOpts.ClientSecret = "TEST"
testOpts.SharedKey = "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=" testOpts.SharedKey = "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="
testOpts.CookieSecret = "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=" testOpts.CookieSecret = "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="
testOpts.AuthenticateURL = authURL testOpts.AuthenticateURL = *authURL
testOpts.Services = tt.s testOpts.Services = tt.s
if tt.Field != "" { if tt.Field != "" {
testOptsField := reflect.ValueOf(testOpts).Elem().FieldByName(tt.Field) testOptsField := reflect.ValueOf(&testOpts).Elem().FieldByName(tt.Field)
testOptsField.Set(reflect.ValueOf(tt).FieldByName("Value")) testOptsField.Set(reflect.ValueOf(tt).FieldByName("Value"))
} }
@ -126,7 +126,7 @@ func Test_newAuthorizeService(t *testing.T) {
} }
if tt.Field != "" { if tt.Field != "" {
testOptsField := reflect.ValueOf(testOpts).Elem().FieldByName(tt.Field) testOptsField := reflect.ValueOf(&testOpts).Elem().FieldByName(tt.Field)
testOptsField.Set(reflect.ValueOf(tt).FieldByName("Value")) testOptsField.Set(reflect.ValueOf(tt).FieldByName("Value"))
} }
@ -162,13 +162,17 @@ func Test_newProxyeService(t *testing.T) {
testOpts.Policies = []policy.Policy{ testOpts.Policies = []policy.Policy{
testPolicy, testPolicy,
} }
testOpts.AuthenticateURL, _ = url.Parse("https://authenticate.example.com")
testOpts.AuthorizeURL, _ = url.Parse("https://authorize.example.com") AuthenticateURL, _ := url.Parse("https://authenticate.example.com")
AuthorizeURL, _ := url.Parse("https://authorize.example.com")
testOpts.AuthenticateURL = *AuthenticateURL
testOpts.AuthorizeURL = *AuthorizeURL
testOpts.CookieSecret = "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=" testOpts.CookieSecret = "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="
testOpts.Services = tt.s testOpts.Services = tt.s
if tt.Field != "" { if tt.Field != "" {
testOptsField := reflect.ValueOf(testOpts).Elem().FieldByName(tt.Field) testOptsField := reflect.ValueOf(&testOpts).Elem().FieldByName(tt.Field)
testOptsField.Set(reflect.ValueOf(tt).FieldByName("Value")) testOptsField.Set(reflect.ValueOf(tt).FieldByName("Value"))
} }
_, err := newProxyService(testOpts, mux) _, err := newProxyService(testOpts, mux)
@ -181,7 +185,7 @@ func Test_newProxyeService(t *testing.T) {
} }
func Test_wrapMiddleware(t *testing.T) { func Test_wrapMiddleware(t *testing.T) {
o := &config.Options{ o := config.Options{
Services: "all", Services: "all",
Headers: map[string]string{ Headers: map[string]string{
"X-Content-Type-Options": "nosniff", "X-Content-Type-Options": "nosniff",
@ -232,7 +236,7 @@ func Test_parseOptions(t *testing.T) {
t.Errorf("parseOptions() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("parseOptions() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if got != nil && got.SharedKey != tt.wantSharedKey { if got.SharedKey != tt.wantSharedKey {
t.Errorf("parseOptions()\n") t.Errorf("parseOptions()\n")
t.Errorf("got: %+v\n", got.SharedKey) t.Errorf("got: %+v\n", got.SharedKey)
t.Errorf("want: %+v\n", tt.wantSharedKey) t.Errorf("want: %+v\n", tt.wantSharedKey)
@ -247,7 +251,7 @@ type mockService struct {
Updated bool Updated bool
} }
func (m *mockService) UpdateOptions(o *config.Options) error { func (m *mockService) UpdateOptions(o config.Options) error {
m.Updated = true m.Updated = true
if m.fail { if m.fail {
@ -266,7 +270,7 @@ func Test_handleConfigUpdate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
service *mockService service *mockService
oldOpts *config.Options oldOpts config.Options
wantUpdate bool wantUpdate bool
}{ }{
{"good", &mockService{fail: false}, blankOpts, true}, {"good", &mockService{fail: false}, blankOpts, true},

View file

@ -70,8 +70,8 @@ type Options struct {
// AuthenticateURL represents the externally accessible http endpoints // AuthenticateURL represents the externally accessible http endpoints
// used for authentication requests and callbacks // used for authentication requests and callbacks
AuthenticateURLString string `mapstructure:"authenticate_service_url"` AuthenticateURLString string `mapstructure:"authenticate_service_url"`
AuthenticateURL *url.URL `hash:"ignore"` AuthenticateURL url.URL
// Session/Cookie management // Session/Cookie management
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
@ -103,13 +103,13 @@ type Options struct {
// NOTE: As many load balancers do not support externally routed gRPC so // NOTE: As many load balancers do not support externally routed gRPC so
// this may be an internal location. // this may be an internal location.
AuthenticateInternalAddrString string `mapstructure:"authenticate_internal_url"` AuthenticateInternalAddrString string `mapstructure:"authenticate_internal_url"`
AuthenticateInternalAddr *url.URL AuthenticateInternalAddr url.URL
// AuthorizeURL is the routable destination of the authorize service's // AuthorizeURL is the routable destination of the authorize service's
// gRPC endpoint. NOTE: As many load balancers do not support // gRPC endpoint. NOTE: As many load balancers do not support
// externally routed gRPC so this may be an internal location. // externally routed gRPC so this may be an internal location.
AuthorizeURLString string `mapstructure:"authorize_service_url"` AuthorizeURLString string `mapstructure:"authorize_service_url"`
AuthorizeURL *url.URL AuthorizeURL url.URL
// Settings to enable custom behind-the-ingress service communication // Settings to enable custom behind-the-ingress service communication
OverrideCertificateName string `mapstructure:"override_certificate_name"` OverrideCertificateName string `mapstructure:"override_certificate_name"`
@ -136,8 +136,8 @@ type Options struct {
} }
// NewOptions returns a new options struct with default values // NewOptions returns a new options struct with default values
func NewOptions() *Options { func NewOptions() Options {
o := &Options{ o := Options{
Debug: false, Debug: false,
LogLevel: "debug", LogLevel: "debug",
Services: "all", Services: "all",
@ -153,25 +153,22 @@ func NewOptions() *Options {
"X-XSS-Protection": "1; mode=block", "X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
}, },
Addr: ":https", Addr: ":https",
CertFile: filepath.Join(findPwd(), "cert.pem"), CertFile: filepath.Join(findPwd(), "cert.pem"),
KeyFile: filepath.Join(findPwd(), "privkey.pem"), KeyFile: filepath.Join(findPwd(), "privkey.pem"),
ReadHeaderTimeout: 10 * time.Second, ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second, ReadTimeout: 30 * time.Second,
WriteTimeout: 0, // support streaming by default WriteTimeout: 0, // support streaming by default
IdleTimeout: 5 * time.Minute, IdleTimeout: 5 * time.Minute,
AuthenticateURL: new(url.URL), RefreshCooldown: time.Duration(5 * time.Minute),
AuthenticateInternalAddr: new(url.URL), AllowWebsockets: false,
AuthorizeURL: new(url.URL),
RefreshCooldown: time.Duration(5 * time.Minute),
AllowWebsockets: false,
} }
return o return o
} }
// OptionsFromViper builds the main binary's configuration // OptionsFromViper builds the main binary's configuration
// options by parsing environmental variables and config file // options by parsing environmental variables and config file
func OptionsFromViper(configFile string) (*Options, error) { func OptionsFromViper(configFile string) (Options, error) {
o := NewOptions() o := NewOptions()
// Load up config // Load up config
@ -184,25 +181,25 @@ func OptionsFromViper(configFile string) (*Options, error) {
viper.SetConfigFile(configFile) viper.SetConfigFile(configFile)
err := viper.ReadInConfig() err := viper.ReadInConfig()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read config: %s", err) return o, fmt.Errorf("failed to read config: %s", err)
} }
} }
err := viper.Unmarshal(o) err := viper.Unmarshal(&o)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load options from config: %s", err) return o, fmt.Errorf("failed to load options from config: %s", err)
} }
// Turn URL strings into url structs // Turn URL strings into url structs
err = o.parseURLs() err = o.parseURLs()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse URLs: %s", err) return o, fmt.Errorf("failed to parse URLs: %s", err)
} }
// Load and initialize policy // Load and initialize policy
err = o.parsePolicy() err = o.parsePolicy()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse Policy: %s", err) return o, fmt.Errorf("failed to parse Policy: %s", err)
} }
if o.Debug { if o.Debug {
@ -217,7 +214,7 @@ func OptionsFromViper(configFile string) (*Options, error) {
err = o.validate() err = o.validate()
if err != nil { if err != nil {
return nil, err return o, err
} }
log.Debug(). log.Debug().
@ -302,7 +299,7 @@ func (o *Options) parseURLs() error {
if err != nil { if err != nil {
return fmt.Errorf("internal/config: bad authenticate-url %s : %v", o.AuthenticateURLString, err) return fmt.Errorf("internal/config: bad authenticate-url %s : %v", o.AuthenticateURLString, err)
} }
o.AuthenticateURL = AuthenticateURL o.AuthenticateURL = *AuthenticateURL
} }
if o.AuthorizeURLString != "" { if o.AuthorizeURLString != "" {
@ -310,7 +307,7 @@ func (o *Options) parseURLs() error {
if err != nil { if err != nil {
return fmt.Errorf("internal/config: bad authorize-url %s : %v", o.AuthorizeURLString, err) return fmt.Errorf("internal/config: bad authorize-url %s : %v", o.AuthorizeURLString, err)
} }
o.AuthorizeURL = AuthorizeURL o.AuthorizeURL = *AuthorizeURL
} }
if o.AuthenticateInternalAddrString != "" { if o.AuthenticateInternalAddrString != "" {
@ -318,7 +315,7 @@ func (o *Options) parseURLs() error {
if err != nil { if err != nil {
return fmt.Errorf("internal/config: bad authenticate-internal-addr %s : %v", o.AuthenticateInternalAddrString, err) return fmt.Errorf("internal/config: bad authenticate-internal-addr %s : %v", o.AuthenticateInternalAddrString, err)
} }
o.AuthenticateInternalAddr = AuthenticateInternalAddr o.AuthenticateInternalAddr = *AuthenticateInternalAddr
} }
return nil return nil
@ -396,7 +393,7 @@ func IsProxy(s string) bool {
// OptionsUpdater updates local state based on an Options struct // OptionsUpdater updates local state based on an Options struct
type OptionsUpdater interface { type OptionsUpdater interface {
UpdateOptions(*Options) error UpdateOptions(Options) error
} }
// Checksum returns the checksum of the current options struct // Checksum returns the checksum of the current options struct

View file

@ -16,7 +16,7 @@ import (
func Test_validate(t *testing.T) { func Test_validate(t *testing.T) {
testOptions := func() *Options { testOptions := func() Options {
o := NewOptions() o := NewOptions()
o.SharedKey = "test" o.SharedKey = "test"
o.Services = "all" o.Services = "all"
@ -34,7 +34,7 @@ func Test_validate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
testOpts *Options testOpts Options
wantErr bool wantErr bool
}{ }{
{"good default with no env settings", good, false}, {"good default with no env settings", good, false},
@ -194,10 +194,10 @@ func Test_parseURLs(t *testing.T) {
if (err != nil) != test.wantErr { if (err != nil) != test.wantErr {
t.Errorf("Failed to parse URLs %v: %s", test, err) t.Errorf("Failed to parse URLs %v: %s", test, err)
} }
if o.AuthenticateURL != nil && o.AuthenticateURL.String() != test.authenticateURL { if err == nil && o.AuthenticateURL.String() != test.authenticateURL {
t.Errorf("Failed to update AuthenticateURL: %v", test) t.Errorf("Failed to update AuthenticateURL: %v", test)
} }
if o.AuthorizeURL != nil && o.AuthorizeURL.String() != test.authorizeURL { if err == nil && o.AuthorizeURL.String() != test.authorizeURL {
t.Errorf("Failed to update AuthorizeURL: %v", test) t.Errorf("Failed to update AuthorizeURL: %v", test)
} }
} }
@ -230,8 +230,8 @@ func Test_OptionsFromViper(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
goodOptions.AuthorizeURL = authorize goodOptions.AuthorizeURL = *authorize
goodOptions.AuthenticateURL = authenticate goodOptions.AuthenticateURL = *authenticate
badConfigBytes := []byte("badjson!") badConfigBytes := []byte("badjson!")
badUnmarshalConfigBytes := []byte(`"debug": "blue"`) badUnmarshalConfigBytes := []byte(`"debug": "blue"`)
@ -239,12 +239,12 @@ func Test_OptionsFromViper(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
configBytes []byte configBytes []byte
want *Options want Options
wantErr bool wantErr bool
}{ }{
{"good", goodConfigBytes, goodOptions, false}, {"good", goodConfigBytes, goodOptions, false},
{"bad json", badConfigBytes, nil, true}, {"bad json", badConfigBytes, NewOptions(), true},
{"bad unmarshal", badUnmarshalConfigBytes, nil, true}, {"bad unmarshal", badUnmarshalConfigBytes, NewOptions(), true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -268,8 +268,8 @@ func Test_OptionsFromViper(t *testing.T) {
} }
// Test for missing config file // Test for missing config file
o, err := OptionsFromViper("filedoesnotexist") _, err = OptionsFromViper("filedoesnotexist")
if o != nil || err == nil { if err == nil {
t.Errorf("OptionsFromViper(): Did when loading missing file") t.Errorf("OptionsFromViper(): Did when loading missing file")
} }
} }

View file

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/pomerium/pomerium/internal/config" "github.com/pomerium/pomerium/internal/config"
"github.com/pomerium/pomerium/internal/cryptutil" "github.com/pomerium/pomerium/internal/cryptutil"
"github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/log"
@ -529,7 +530,7 @@ func extendDeadline(ttl time.Duration) time.Time {
} }
// websocketHandlerFunc splits request serving with timeouts depending on the protocol // websocketHandlerFunc splits request serving with timeouts depending on the protocol
func websocketHandlerFunc(baseHandler http.Handler, timeoutHandler http.Handler, o *config.Options) http.Handler { func websocketHandlerFunc(baseHandler http.Handler, timeoutHandler http.Handler, o config.Options) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do not use timeouts for websockets because they are long-lived connections. // Do not use timeouts for websockets because they are long-lived connections.

View file

@ -290,7 +290,7 @@ func TestProxy_Proxy(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
options *config.Options options config.Options
method string method string
header http.Header header http.Header
host string host string
@ -356,7 +356,7 @@ func TestProxy_UserDashboard(t *testing.T) {
opts := testOptions() opts := testOptions()
tests := []struct { tests := []struct {
name string name string
options *config.Options options config.Options
method string method string
cipher cryptutil.Cipher cipher cryptutil.Cipher
session sessions.SessionStore session sessions.SessionStore
@ -408,7 +408,7 @@ func TestProxy_Refresh(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
options *config.Options options config.Options
method string method string
cipher cryptutil.Cipher cipher cryptutil.Cipher
session sessions.SessionStore session sessions.SessionStore
@ -452,7 +452,7 @@ func TestProxy_Impersonate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
malformed bool malformed bool
options *config.Options options config.Options
method string method string
email string email string
groups string groups string

View file

@ -35,7 +35,7 @@ const (
// ValidateOptions checks that proper configuration settings are set to create // ValidateOptions checks that proper configuration settings are set to create
// a proper Proxy instance // a proper Proxy instance
func ValidateOptions(o *config.Options) error { func ValidateOptions(o config.Options) error {
decoded, err := base64.StdEncoding.DecodeString(o.SharedKey) decoded, err := base64.StdEncoding.DecodeString(o.SharedKey)
if err != nil { if err != nil {
return fmt.Errorf("authorize: `SHARED_SECRET` setting is invalid base64: %v", err) return fmt.Errorf("authorize: `SHARED_SECRET` setting is invalid base64: %v", err)
@ -46,13 +46,13 @@ func ValidateOptions(o *config.Options) error {
if len(o.Policies) == 0 { if len(o.Policies) == 0 {
return errors.New("missing setting: no policies defined") return errors.New("missing setting: no policies defined")
} }
if o.AuthenticateURL == nil { if o.AuthenticateURL.String() == "" {
return errors.New("missing setting: authenticate-service-url") return errors.New("missing setting: authenticate-service-url")
} }
if o.AuthenticateURL.Scheme != "https" { if o.AuthenticateURL.Scheme != "https" {
return errors.New("authenticate-service-url must be a valid https url") return errors.New("authenticate-service-url must be a valid https url")
} }
if o.AuthorizeURL == nil { if o.AuthorizeURL.String() == "" {
return errors.New("missing setting: authorize-service-url") return errors.New("missing setting: authorize-service-url")
} }
if o.AuthorizeURL.Scheme != "https" { if o.AuthorizeURL.Scheme != "https" {
@ -106,10 +106,7 @@ type routeConfig struct {
// New takes a Proxy service from options and a validation function. // New takes a Proxy service from options and a validation function.
// Function returns an error if options fail to validate. // Function returns an error if options fail to validate.
func New(opts *config.Options) (*Proxy, error) { func New(opts config.Options) (*Proxy, error) {
if opts == nil {
return nil, errors.New("options cannot be nil")
}
if err := ValidateOptions(opts); err != nil { if err := ValidateOptions(opts); err != nil {
return nil, err return nil, err
} }
@ -137,7 +134,7 @@ func New(opts *config.Options) (*Proxy, error) {
p := &Proxy{ p := &Proxy{
routeConfigs: make(map[string]*routeConfig), routeConfigs: make(map[string]*routeConfig),
// services // services
AuthenticateURL: opts.AuthenticateURL, AuthenticateURL: &opts.AuthenticateURL,
// session state // session state
cipher: cipher, cipher: cipher,
csrfStore: cookieStore, csrfStore: cookieStore,
@ -177,7 +174,7 @@ func New(opts *config.Options) (*Proxy, error) {
} }
// UpdatePolicies updates the handlers based on the configured policies // UpdatePolicies updates the handlers based on the configured policies
func (p *Proxy) UpdatePolicies(opts *config.Options) error { func (p *Proxy) UpdatePolicies(opts config.Options) error {
routeConfigs := make(map[string]*routeConfig) routeConfigs := make(map[string]*routeConfig)
for _, route := range opts.Policies { for _, route := range opts.Policies {
proxy := NewReverseProxy(route.Destination) proxy := NewReverseProxy(route.Destination)
@ -254,7 +251,7 @@ func NewReverseProxy(to *url.URL) *httputil.ReverseProxy {
} }
// NewReverseProxyHandler applies handler specific options to a given route. // NewReverseProxyHandler applies handler specific options to a given route.
func NewReverseProxyHandler(o *config.Options, proxy *httputil.ReverseProxy, route *policy.Policy) (http.Handler, error) { func NewReverseProxyHandler(o config.Options, proxy *httputil.ReverseProxy, route *policy.Policy) (http.Handler, error) {
up := &UpstreamProxy{ up := &UpstreamProxy{
name: route.Destination.Host, name: route.Destination.Host,
handler: proxy, handler: proxy,
@ -287,7 +284,7 @@ func urlParse(uri string) (*url.URL, error) {
} }
// UpdateOptions updates internal structres based on config.Options // UpdateOptions updates internal structres based on config.Options
func (p *Proxy) UpdateOptions(o *config.Options) error { func (p *Proxy) UpdateOptions(o config.Options) error {
log.Info().Msg("proxy: updating options") log.Info().Msg("proxy: updating options")
err := p.UpdatePolicies(o) err := p.UpdatePolicies(o)
if err != nil { if err != nil {

View file

@ -80,7 +80,7 @@ func TestNewReverseProxyHandler(t *testing.T) {
} }
} }
func testOptions() *config.Options { func testOptions() config.Options {
authenticateService, _ := url.Parse("https://authenticate.corp.beyondperimeter.com") authenticateService, _ := url.Parse("https://authenticate.corp.beyondperimeter.com")
authorizeService, _ := url.Parse("https://authorize.corp.beyondperimeter.com") authorizeService, _ := url.Parse("https://authorize.corp.beyondperimeter.com")
@ -88,15 +88,15 @@ func testOptions() *config.Options {
testPolicy := policy.Policy{From: "corp.example.notatld", To: "example.notatld"} testPolicy := policy.Policy{From: "corp.example.notatld", To: "example.notatld"}
testPolicy.Validate() testPolicy.Validate()
opts.Policies = []policy.Policy{testPolicy} opts.Policies = []policy.Policy{testPolicy}
opts.AuthenticateURL = authenticateService opts.AuthenticateURL = *authenticateService
opts.AuthorizeURL = authorizeService opts.AuthorizeURL = *authorizeService
opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=" opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ="
opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw=" opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw="
opts.CookieName = "pomerium" opts.CookieName = "pomerium"
return opts return opts
} }
func testOptionsTestServer(uri string) *config.Options { func testOptionsTestServer(uri string) config.Options {
authenticateService, _ := url.Parse("https://authenticate.corp.beyondperimeter.com") authenticateService, _ := url.Parse("https://authenticate.corp.beyondperimeter.com")
authorizeService, _ := url.Parse("https://authorize.corp.beyondperimeter.com") authorizeService, _ := url.Parse("https://authorize.corp.beyondperimeter.com")
// RFC 2606 // RFC 2606
@ -107,15 +107,15 @@ func testOptionsTestServer(uri string) *config.Options {
testPolicy.Validate() testPolicy.Validate()
opts := config.NewOptions() opts := config.NewOptions()
opts.Policies = []policy.Policy{testPolicy} opts.Policies = []policy.Policy{testPolicy}
opts.AuthenticateURL = authenticateService opts.AuthenticateURL = *authenticateService
opts.AuthorizeURL = authorizeService opts.AuthorizeURL = *authorizeService
opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=" opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ="
opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw=" opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw="
opts.CookieName = "pomerium" opts.CookieName = "pomerium"
return opts return opts
} }
func testOptionsWithCORS(uri string) *config.Options { func testOptionsWithCORS(uri string) config.Options {
testPolicy := policy.Policy{ testPolicy := policy.Policy{
From: "httpbin.corp.example", From: "httpbin.corp.example",
To: uri, To: uri,
@ -127,7 +127,7 @@ func testOptionsWithCORS(uri string) *config.Options {
return opts return opts
} }
func testOptionsWithPublicAccess(uri string) *config.Options { func testOptionsWithPublicAccess(uri string) config.Options {
testPolicy := policy.Policy{ testPolicy := policy.Policy{
From: "httpbin.corp.example", From: "httpbin.corp.example",
To: uri, To: uri,
@ -139,7 +139,7 @@ func testOptionsWithPublicAccess(uri string) *config.Options {
return opts return opts
} }
func testOptionsWithPublicAccessAndWhitelist(uri string) *config.Options { func testOptionsWithPublicAccessAndWhitelist(uri string) config.Options {
testPolicy := policy.Policy{ testPolicy := policy.Policy{
From: "httpbin.corp.example", From: "httpbin.corp.example",
To: uri, To: uri,
@ -155,14 +155,14 @@ func testOptionsWithPublicAccessAndWhitelist(uri string) *config.Options {
func TestOptions_Validate(t *testing.T) { func TestOptions_Validate(t *testing.T) {
good := testOptions() good := testOptions()
badAuthURL := testOptions() badAuthURL := testOptions()
badAuthURL.AuthenticateURL = nil badAuthURL.AuthenticateURL = url.URL{}
authurl, _ := url.Parse("http://authenticate.corp.beyondperimeter.com") authurl, _ := url.Parse("http://authenticate.corp.beyondperimeter.com")
authenticateBadScheme := testOptions() authenticateBadScheme := testOptions()
authenticateBadScheme.AuthenticateURL = authurl authenticateBadScheme.AuthenticateURL = *authurl
authorizeBadSCheme := testOptions() authorizeBadSCheme := testOptions()
authorizeBadSCheme.AuthorizeURL = authurl authorizeBadSCheme.AuthorizeURL = *authurl
authorizeNil := testOptions() authorizeNil := testOptions()
authorizeNil.AuthorizeURL = nil authorizeNil.AuthorizeURL = url.URL{}
emptyCookieSecret := testOptions() emptyCookieSecret := testOptions()
emptyCookieSecret.CookieSecret = "" emptyCookieSecret.CookieSecret = ""
invalidCookieSecret := testOptions() invalidCookieSecret := testOptions()
@ -178,11 +178,11 @@ func TestOptions_Validate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
o *config.Options o config.Options
wantErr bool wantErr bool
}{ }{
{"good - minimum options", good, false}, {"good - minimum options", good, false},
{"nil options", &config.Options{}, true}, {"nil options", config.Options{}, true},
{"authenticate service url", badAuthURL, true}, {"authenticate service url", badAuthURL, true},
{"authenticate service url not https", authenticateBadScheme, true}, {"authenticate service url not https", authenticateBadScheme, true},
{"authorize service url not https", authorizeBadSCheme, true}, {"authorize service url not https", authorizeBadSCheme, true},
@ -213,14 +213,13 @@ func TestNew(t *testing.T) {
badRoutedProxy.SigningKey = "YmFkIGtleQo=" badRoutedProxy.SigningKey = "YmFkIGtleQo="
tests := []struct { tests := []struct {
name string name string
opts *config.Options opts config.Options
wantProxy bool wantProxy bool
numRoutes int numRoutes int
wantErr bool wantErr bool
}{ }{
{"good", good, true, 1, false}, {"good", good, true, 1, false},
{"empty options", &config.Options{}, false, 0, true}, {"empty options", config.Options{}, false, 0, true},
{"nil options", nil, false, 0, true},
{"short secret/validate sanity check", shortCookieLength, false, 0, true}, {"short secret/validate sanity check", shortCookieLength, false, 0, true},
{"invalid ec key, valid base64 though", badRoutedProxy, false, 0, true}, {"invalid ec key, valid base64 though", badRoutedProxy, false, 0, true},
} }
@ -296,7 +295,7 @@ func Test_UpdateOptions(t *testing.T) {
} }
tests := []struct { tests := []struct {
name string name string
opts *config.Options opts config.Options
newPolicy []policy.Policy newPolicy []policy.Policy
host string host string
wantErr bool wantErr bool