authenticate: make callback path configurable (#493)

Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
Bobby DeSimone 2020-02-08 09:06:23 -08:00 committed by GitHub
parent 1901cb5ca0
commit 5716113c2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 26 deletions

View file

@ -27,8 +27,6 @@ import (
"github.com/pomerium/pomerium/internal/urlutil"
)
const callbackPath = "/oauth2/callback"
// ValidateOptions checks that configuration are complete and valid.
// Returns on first error found.
func ValidateOptions(o config.Options) error {
@ -47,6 +45,9 @@ func ValidateOptions(o config.Options) error {
if o.ClientSecret == "" {
return errors.New("authenticate: 'IDP_CLIENT_SECRET' is required")
}
if o.AuthenticateCallbackPath == "" {
return errors.New("authenticate: 'AUTHENTICATE_CALLBACK_PATH' is required")
}
return nil
}
@ -149,7 +150,7 @@ func New(opts config.Options) (*Authenticate, error) {
headerStore := header.NewStore(encryptedEncoder, "Pomerium")
redirectURL, _ := urlutil.DeepCopy(opts.AuthenticateURL)
redirectURL.Path = callbackPath
redirectURL.Path = opts.AuthenticateCallbackPath
// configure our identity provider
provider, err := identity.New(
opts.Provider,

View file

@ -43,6 +43,8 @@ func TestOptions_Validate(t *testing.T) {
badSharedKey.SharedKey = ""
badAuthenticateURL := newTestOptions(t)
badAuthenticateURL.AuthenticateURL = nil
badCallbackPath := newTestOptions(t)
badCallbackPath.AuthenticateCallbackPath = ""
tests := []struct {
name string
@ -60,6 +62,7 @@ func TestOptions_Validate(t *testing.T) {
{"no client id", emptyClientID, true},
{"no client secret", emptyClientSecret, true},
{"empty authenticate url", badAuthenticateURL, true},
{"empty callback path", badCallbackPath, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View file

@ -31,8 +31,8 @@ func (a *Authenticate) Handler() http.Handler {
a.cookieSecret,
csrf.Secure(a.cookieOptions.Secure),
csrf.Path("/"),
csrf.UnsafePaths([]string{callbackPath}), // enforce CSRF on "safe" handler
csrf.FormValueName("state"), // rfc6749 section-10.12
csrf.UnsafePaths([]string{a.RedirectURL.Path}), // enforce CSRF on "safe" handler
csrf.FormValueName("state"), // rfc6749 section-10.12
csrf.CookieName(fmt.Sprintf("%s_csrf", a.cookieOptions.Name)),
csrf.ErrorHandler(httputil.HandlerFunc(httputil.CSRFFailureHandler)),
))

View file

@ -89,6 +89,12 @@ type Options struct {
AuthenticateURLString string `mapstructure:"authenticate_service_url" yaml:"authenticate_service_url,omitempty"`
AuthenticateURL *url.URL `yaml:"-,omitempty"`
// AuthenticateCallbackPath is the path to the HTTP endpoint that will
// receive the response from your identity provider. The value must exactly
// match one of the authorized redirect URIs for the OAuth 2.0 client.
// Defaults to: `/oauth2/callback`
AuthenticateCallbackPath string `mapstructure:"authenticate_callback_path" yaml:"authenticate_callback_path,omitempty"`
// Session/Cookie management
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
CookieName string `mapstructure:"cookie_name" yaml:"cookie_name,omitempty"`
@ -211,16 +217,17 @@ var defaultOptions = Options{
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
},
Addr: ":443",
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 0, // support streaming by default
IdleTimeout: 5 * time.Minute,
RefreshCooldown: 5 * time.Minute,
GRPCAddr: ":443",
GRPCClientTimeout: 10 * time.Second, // Try to withstand transient service failures for a single request
GRPCClientDNSRoundRobin: true,
CacheStore: "autocache",
Addr: ":443",
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 0, // support streaming by default
IdleTimeout: 5 * time.Minute,
RefreshCooldown: 5 * time.Minute,
GRPCAddr: ":443",
GRPCClientTimeout: 10 * time.Second, // Try to withstand transient service failures for a single request
GRPCClientDNSRoundRobin: true,
CacheStore: "autocache",
AuthenticateCallbackPath: "/oauth2/callback",
}
// NewDefaultOptions returns a copy the default options. It's the caller's

View file

@ -219,11 +219,12 @@ func TestOptionsFromViper(t *testing.T) {
{"good",
[]byte(`{"insecure_server":true,"policy":[{"from": "https://from.example","to":"https://to.example"}]}`),
&Options{
Policies: []Policy{{From: "https://from.example", To: "https://to.example"}},
CookieName: "_pomerium",
CookieSecure: true,
InsecureServer: true,
CookieHTTPOnly: true,
Policies: []Policy{{From: "https://from.example", To: "https://to.example"}},
CookieName: "_pomerium",
CookieSecure: true,
InsecureServer: true,
CookieHTTPOnly: true,
AuthenticateCallbackPath: "/oauth2/callback",
Headers: map[string]string{
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
"X-Frame-Options": "SAMEORIGIN",
@ -233,12 +234,13 @@ func TestOptionsFromViper(t *testing.T) {
{"good disable header",
[]byte(`{"insecure_server":true,"headers": {"disable":"true"},"policy":[{"from": "https://from.example","to":"https://to.example"}]}`),
&Options{
Policies: []Policy{{From: "https://from.example", To: "https://to.example"}},
CookieName: "_pomerium",
CookieSecure: true,
CookieHTTPOnly: true,
InsecureServer: true,
Headers: map[string]string{}},
Policies: []Policy{{From: "https://from.example", To: "https://to.example"}},
CookieName: "_pomerium",
AuthenticateCallbackPath: "/oauth2/callback",
CookieSecure: true,
CookieHTTPOnly: true,
InsecureServer: true,
Headers: map[string]string{}},
false},
{"bad url", []byte(`{"policy":[{"from": "https://","to":"https://to.example"}]}`), nil, true},
{"bad policy", []byte(`{"policy":[{"allow_public_unauthenticated_access": "dog","to":"https://to.example"}]}`), nil, true},

View file

@ -503,6 +503,24 @@ Identity provider scopes correspond to access privilege scopes as defined in Sec
Identity Provider Service Account is field used to configure any additional user account or access-token that may be required for querying additional user information during authentication. For a concrete example, Google an additional service account and to make a follow-up request to query a user's group membership. For more information, refer to the [identity provider] docs to see if your provider requires this setting.
### Authenticate Callback Path
- Environmental Variable: `AUTHENTICATE_CALLBACK_PATH`
- Config File Key: `authenticate_callback_path`
- Type: `string`
- Default: `/oauth2/callback`
- Optional
The authenticate callback path is the path/url from the authenticate service that will receive the response from your identity provider. The value must exactly match one of the authorized redirect URIs for the OAuth 2.0 client.
This value is referred to as the `redirect_url` in the [OpenIDConnect](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) and OAuth2 specs.
See also:
- [OAuth2 RFC 6749](https://tools.ietf.org/html/rfc6749#section-3.1.2)
- [OIDC Spec](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
- [Google - Setting Redirect URI](https://developers.google.com/identity/protocols/OpenIDConnect#setredirecturi)
## Proxy Service
### Signing Key