diff --git a/authenticate/authenticate.go b/authenticate/authenticate.go index ecf444663..c20193f20 100644 --- a/authenticate/authenticate.go +++ b/authenticate/authenticate.go @@ -175,13 +175,14 @@ func New(opts config.Options) (*Authenticate, error) { // configure our identity provider provider, err := identity.NewAuthenticator( oauth.Options{ - RedirectURL: redirectURL, - ProviderName: opts.Provider, - ProviderURL: opts.ProviderURL, - ClientID: opts.ClientID, - ClientSecret: opts.ClientSecret, - Scopes: opts.Scopes, - ServiceAccount: opts.ServiceAccount, + RedirectURL: redirectURL, + ProviderName: opts.Provider, + ProviderURL: opts.ProviderURL, + ClientID: opts.ClientID, + ClientSecret: opts.ClientSecret, + Scopes: opts.Scopes, + ServiceAccount: opts.ServiceAccount, + AuthCodeOptions: opts.RequestParams, }) if err != nil { diff --git a/config/options.go b/config/options.go index 06fc322c0..02d3016ff 100644 --- a/config/options.go +++ b/config/options.go @@ -125,6 +125,13 @@ type Options struct { Scopes []string `mapstructure:"idp_scopes" yaml:"idp_scopes,omitempty"` ServiceAccount string `mapstructure:"idp_service_account" yaml:"idp_service_account,omitempty"` + // RequestParams are custom request params added to the signin request as + // part of an Oauth2 code flow. + // + // https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml + // https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters + RequestParams map[string]string `mapstructure:"idp_request_params" yaml:"idp_request_params,omitempty"` + // Administrators contains a set of emails with users who have super user // (sudo) access including the ability to impersonate other users' access Administrators []string `mapstructure:"administrators" yaml:"administrators,omitempty"` @@ -154,7 +161,6 @@ type Options struct { // RefreshCooldown limits the rate a user can refresh her session RefreshCooldown time.Duration `mapstructure:"refresh_cooldown" yaml:"refresh_cooldown,omitempty"` - //Routes map[string]string `mapstructure:"routes" yaml:"routes,omitempty"` DefaultUpstreamTimeout time.Duration `mapstructure:"default_upstream_timeout" yaml:"default_upstream_timeout,omitempty"` // Address/Port to bind to for prometheus metrics diff --git a/docs/configuration/readme.md b/docs/configuration/readme.md index f9732b369..d66625386 100644 --- a/docs/configuration/readme.md +++ b/docs/configuration/readme.md @@ -74,16 +74,11 @@ Autocert requires that ports `80`/`443` be accessible from the internet in order - Type: `bool` - Optional -If true, cause autocert to request a certificate with `status_request` -extension (commonly called `Must-Staple`). This allows the TLS client -(the browser) to fail immediately if the TLS handshake doesn't include -OCSP stapling information. Only used when [Autocert](./#autocert) is -true. +If true, cause autocert to request a certificate with `status_request` extension (commonly called `Must-Staple`). This allows the TLS client (the browser) to fail immediately if the TLS handshake doesn't include OCSP stapling information. Only used when [Autocert](./#autocert) is true. -NOTE: this only takes effect the next time Pomerium renews your -certificates. +NOTE: this only takes effect the next time Pomerium renews your certificates. -See also https://tools.ietf.org/html/rfc7633 for more context. +See also for more context. ### Autocert Directory @@ -294,8 +289,7 @@ spec: #### Traefik docker-compose -If the `forward_auth_url` is also handled by Traefik, you will need to configure Traefik to trust the `X-Forwarded-*` -headers as described in [the documentation](https://docs.traefik.io/v2.2/routing/entrypoints/#forwarded-headers). +If the `forward_auth_url` is also handled by Traefik, you will need to configure Traefik to trust the `X-Forwarded-*` headers as described in [the documentation](https://docs.traefik.io/v2.2/routing/entrypoints/#forwarded-headers). ```yml version: "3" @@ -487,9 +481,7 @@ pomerium_config_last_reload_success_timestamp | Gauge | The timestamp of the #### Envoy Proxy Metrics -As of `v0.9`, Pomerium uses [envoy Proxy]([https://](https://www.envoyproxy.io/) for the data plane. As such, proxy related metrics are sourced -from envoy, and use envoy's internal [stats data model](https://www.envoyproxy.io/docs/envoy/latest/operations/stats_overview). Please see Envoy's documentation for information -about specific metrics. +As of `v0.9`, Pomerium uses [envoy Proxy]([https://](https://www.envoyproxy.io/) for the data plane. As such, proxy related metrics are sourced from envoy, and use envoy's internal [stats data model](https://www.envoyproxy.io/docs/envoy/latest/operations/stats_overview). Please see Envoy's documentation for information about specific metrics. All metrics coming from envoy will be labeled with `service="pomerium"` or `service="pomerium-proxy"`, depending if you're running all-in-one or distributed service mode. @@ -658,6 +650,22 @@ Identity Provider Service Account is field used to configure any additional user Provider URL is the base path to an identity provider's [OpenID connect discovery document](https://openid.net/specs/openid-connect-discovery-1_0.html). For example, google's URL would be `https://accounts.google.com` for [their discover document](https://accounts.google.com/.well-known/openid-configuration). +### Identity Provider Request Params + +- Environmental Variable: `IDP_REQUEST_PARAMS` +- Config File Key: `idp_request_params` +- Type: map of `strings` key value pairs +- Optional + +Request parameters to be added as part of a signin request using OAuth2 code flow. + +For more information see: + +- [OIDC Request Parameters](https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters) +- [IANA OAuth Parameters](https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml) +- [Microsoft Azure Request params](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code) +- [Google Authentication URI parameters](https://developers.google.com/identity/protocols/oauth2/openid-connect) + ## Proxy Service ### Authenticate Service URL @@ -1023,8 +1031,8 @@ If set, enables proxying of websocket connections. When enabled, this option will pass the identity headers to the downstream application. These headers include: - - X-Pomerium-Jwt-Assertion - - X-Pomerium-Claim-* +- X-Pomerium-Jwt-Assertion +- X-Pomerium-Claim-* ## Authorize Service diff --git a/internal/identity/oauth/options.go b/internal/identity/oauth/options.go index df03d13b2..2be2ddf80 100644 --- a/internal/identity/oauth/options.go +++ b/internal/identity/oauth/options.go @@ -29,4 +29,8 @@ type Options struct { // ServiceAccount can be set for those providers that require additional // credentials or tokens to do follow up API calls (e.g. Google) ServiceAccount string + + // AuthCodeOptions specifies additional key value pairs query params to add + // to the request flow signin url. + AuthCodeOptions map[string]string } diff --git a/internal/identity/oidc/azure/microsoft.go b/internal/identity/oidc/azure/microsoft.go index eb89a3e48..9c9eb7889 100644 --- a/internal/identity/oidc/azure/microsoft.go +++ b/internal/identity/oidc/azure/microsoft.go @@ -7,8 +7,6 @@ import ( "context" "fmt" - "golang.org/x/oauth2" - "github.com/pomerium/pomerium/internal/identity/oauth" pom_oidc "github.com/pomerium/pomerium/internal/identity/oidc" ) @@ -21,6 +19,9 @@ const Name = "azure" // an sign in to the application. const defaultProviderURL = "https://login.microsoftonline.com/common" +// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code +var defaultAuthCodeOptions = map[string]string{"prompt": "select_account"} + // Provider is an Azure implementation of the Authenticator interface. type Provider struct { *pom_oidc.Provider @@ -38,11 +39,11 @@ func New(ctx context.Context, o *oauth.Options) (*Provider, error) { return nil, fmt.Errorf("%s: failed creating oidc provider: %w", Name, err) } p.Provider = genericOidc + + p.AuthCodeOptions = defaultAuthCodeOptions + if len(o.AuthCodeOptions) != 0 { + p.AuthCodeOptions = o.AuthCodeOptions + } + return &p, nil } - -// GetSignInURL returns the sign in url with typical oauth parameters -// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow -func (p *Provider) GetSignInURL(state string) string { - return p.Oauth.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "select_account")) -} diff --git a/internal/identity/oidc/google/google.go b/internal/identity/oidc/google/google.go index 1c886080d..20b71db48 100644 --- a/internal/identity/oidc/google/google.go +++ b/internal/identity/oidc/google/google.go @@ -9,7 +9,6 @@ import ( "fmt" oidc "github.com/coreos/go-oidc" - "golang.org/x/oauth2" "github.com/pomerium/pomerium/internal/identity/oauth" pom_oidc "github.com/pomerium/pomerium/internal/identity/oidc" @@ -24,6 +23,9 @@ const ( var defaultScopes = []string{oidc.ScopeOpenID, "profile", "email"} +// https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters +var defaultAuthCodeOptions = map[string]string{"prompt": "select_account consent"} + // Provider is a Google implementation of the Authenticator interface. type Provider struct { *pom_oidc.Provider @@ -45,20 +47,9 @@ func New(ctx context.Context, o *oauth.Options) (*Provider, error) { } p.Provider = genericOidc + p.AuthCodeOptions = defaultAuthCodeOptions + if len(o.AuthCodeOptions) != 0 { + p.AuthCodeOptions = o.AuthCodeOptions + } return &p, nil } - -// GetSignInURL returns a URL to OAuth 2.0 provider's consent page that asks for permissions for -// the required scopes explicitly. -// Google requires an additional access scope for offline access which is a requirement for any -// application that needs to access a Google API when the user is not present. -// Support for this scope differs between OpenID Connect providers. For instance -// Google rejects it, favoring appending "access_type=offline" as part of the -// authorization request instead. -// Google only provide refresh_token on the first authorization from the user. If user clears -// cookies, re-authorization will not bring back refresh_token. A work around to this is to add -// prompt=consent to the OAuth redirect URL and will always return a refresh_token. -// https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess -func (p *Provider) GetSignInURL(state string) string { - return p.Oauth.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "select_account consent")) -} diff --git a/internal/identity/oidc/oidc.go b/internal/identity/oidc/oidc.go index 9ade877be..3df0bc48b 100644 --- a/internal/identity/oidc/oidc.go +++ b/internal/identity/oidc/oidc.go @@ -25,6 +25,8 @@ const Name = "oidc" var defaultScopes = []string{go_oidc.ScopeOpenID, "profile", "email", "offline_access"} +var defaultAuthCodeOptions = []oauth2.AuthCodeOption{oauth2.AccessTypeOffline} + // Provider provides a standard, OpenID Connect implementation // of an authorization identity provider. // https://openid.net/specs/openid-connect-core-1_0.html @@ -45,6 +47,10 @@ type Provider struct { // providers that doesn't implement the revocation endpoint but a logout session. // https://openid.net/specs/openid-connect-frontchannel-1_0.html#RPInitiated EndSessionURL string `json:"end_session_endpoint,omitempty"` + + // AuthCodeOptions specifies additional key value pairs query params to add + // to the request flow signin url. + AuthCodeOptions map[string]string } // New creates a new instance of a generic OpenID Connect provider. @@ -71,6 +77,10 @@ func New(ctx context.Context, o *oauth.Options) (*Provider, error) { RedirectURL: o.RedirectURL.String(), } + if len(o.AuthCodeOptions) != 0 { + p.AuthCodeOptions = o.AuthCodeOptions + } + // add non-standard claims like end-session, revoke, and user info if err := p.Provider.Claims(&p); err != nil { return nil, fmt.Errorf("identity/oidc: could not retrieve additional claims: %w", err) @@ -86,7 +96,11 @@ func New(ctx context.Context, o *oauth.Options) (*Provider, error) { // the state query parameter on your redirect callback. // See http://tools.ietf.org/html/rfc6749#section-10.12 for more info. func (p *Provider) GetSignInURL(state string) string { - return p.Oauth.AuthCodeURL(state, oauth2.AccessTypeOffline) + opts := defaultAuthCodeOptions + for k, v := range p.AuthCodeOptions { + opts = append(opts, oauth2.SetAuthURLParam(k, v)) + } + return p.Oauth.AuthCodeURL(state, opts...) } // Authenticate converts an authorization code returned from the identity