diff --git a/config/crd/bases/k8s.nginx.org_policies.yaml b/config/crd/bases/k8s.nginx.org_policies.yaml index de6bef324b..43ff1f183e 100644 --- a/config/crd/bases/k8s.nginx.org_policies.yaml +++ b/config/crd/bases/k8s.nginx.org_policies.yaml @@ -134,6 +134,8 @@ spec: type: boolean authEndpoint: type: string + logoutEndpoint: + type: string authExtraArgs: items: type: string @@ -146,6 +148,8 @@ spec: type: string redirectURI: type: string + redirectPostLogout: + type: string scope: type: string tokenEndpoint: diff --git a/deploy/crds.yaml b/deploy/crds.yaml index aed959d337..5f0d65da8c 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -336,6 +336,8 @@ spec: type: boolean authEndpoint: type: string + logoutEndpoint: + type: string authExtraArgs: items: type: string @@ -348,6 +350,8 @@ spec: type: string redirectURI: type: string + redirectPostLogout: + type: string scope: type: string tokenEndpoint: diff --git a/docs/content/configuration/policy-resource.md b/docs/content/configuration/policy-resource.md index 892f72edc0..28d2f53ee0 100644 --- a/docs/content/configuration/policy-resource.md +++ b/docs/content/configuration/policy-resource.md @@ -448,6 +448,8 @@ spec: authEndpoint: https://idp.example.com/openid-connect/auth tokenEndpoint: https://idp.example.com/openid-connect/token jwksURI: https://idp.example.com/openid-connect/certs + logoutEndpoint: https://idp.example.com/openid-connect/logout + redirectPostLogout: https://cafe.example.com accessTokenEnable: true ``` @@ -475,6 +477,8 @@ The OIDC policy defines a few internal locations that can't be customized: `/_jw |``authExtraArgs`` | A list of extra URL arguments to pass to the authorization endpoint provided by your OpenID Connect provider. Arguments must be URL encoded, multiple arguments may be included in the list, for example ``[ arg1=value1, arg2=value2 ]`` | ``string[]`` | No | |``tokenEndpoint`` | URL for the token endpoint provided by your OpenID Connect provider. | ``string`` | Yes | |``jwksURI`` | URL for the JSON Web Key Set (JWK) document provided by your OpenID Connect provider. | ``string`` | Yes | +|``logoutEndpoint`` | URL for the logout endpoint provided by your OpenID Connect provider. | ``string`` | Yes | +|``redirectPostLogout`` | URL to redirect after a successful logout. | ``string`` | Yes | |``scope`` | List of OpenID Connect scopes. The scope ``openid`` always needs to be present and others can be added concatenating them with a ``+`` sign, for example ``openid+profile+email``, ``openid+email+userDefinedScope``. The default is ``openid``. | ``string`` | No | |``redirectURI`` | Allows overriding the default redirect URI. The default is ``/_codexch``. | ``string`` | No | |``zoneSyncLeeway`` | Specifies the maximum timeout in milliseconds for synchronizing ID/access tokens and shared values between Ingress Controller pods. The default is ``200``. | ``int`` | No | diff --git a/internal/configs/oidc/oidc.conf b/internal/configs/oidc/oidc.conf index 50ae4c1151..c25146aec1 100644 --- a/internal/configs/oidc/oidc.conf +++ b/internal/configs/oidc/oidc.conf @@ -67,16 +67,21 @@ } location = /logout { + # This location is called by UI to handle OIDC logout with IDP status_zone "OIDC logout"; - add_header Set-Cookie "auth_token=; $oidc_cookie_flags"; # Send empty cookie - add_header Set-Cookie "auth_redir=; $oidc_cookie_flags"; # Erase original cookie js_content oidc.logout; } location = /_logout { - # This location is the default value of $oidc_logout_redirect (in case it wasn't configured) - default_type text/plain; - return 200 "Logged out\n"; + # This location is a default value of $oidc_logout_redirect called by the + # IDP after closing user ssion in the IDP. + + # Clean cookies + add_header Set-Cookie "auth_nonce=; $oidc_cookie_flags"; # Send empty cookie + add_header Set-Cookie "auth_token=; $oidc_cookie_flags"; # Erase original cookie + add_header Set-Cookie "auth_redir=; $oidc_cookie_flags"; + + js_content oidc.redirectPostLogout; } location @oidc_error { diff --git a/internal/configs/oidc/openid_connect.js b/internal/configs/oidc/openid_connect.js index 68195a3f0a..147015124f 100644 --- a/internal/configs/oidc/openid_connect.js +++ b/internal/configs/oidc/openid_connect.js @@ -5,7 +5,7 @@ */ var newSession = false; // Used by oidcAuth() and validateIdToken() -export default {auth, codeExchange, validateIdToken, logout}; +export default {auth, codeExchange, validateIdToken, logout, redirectPostLogout}; function retryOriginalRequest(r) { delete r.headersOut["WWW-Authenticate"]; // Remove evidence of original failed auth_jwt @@ -265,10 +265,17 @@ function validateIdToken(r) { function logout(r) { r.log("OIDC logout for " + r.variables.cookie_auth_token); - r.variables.session_jwt = "-"; - r.variables.access_token = "-"; - r.variables.refresh_token = "-"; - r.return(302, r.variables.oidc_logout_redirect); + var logoutArgs = "?post_logout_redirect_uri=" + r.variables.redirect_base + r.variables.oidc_logout_redirect + "&id_token_hint=" + r.variables.session_jwt; + + r.variables.session_jwt = '-'; + r.variables.access_token = '-'; + r.variables.refresh_token = '-'; + r.return(302, r.variables.oidc_logout_endpoint + logoutArgs); +} + +// Redirect URL after logged-out from the IDP. +function redirectPostLogout(r) { + r.return(302, r.variables.redir_post_logout); } function getAuthZArgs(r) { diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index c79590fe5e..21508cf8ca 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -125,16 +125,18 @@ type EgressMTLS struct { // OIDC holds OIDC configuration data. type OIDC struct { - AuthEndpoint string - ClientID string - ClientSecret string - JwksURI string - Scope string - TokenEndpoint string - RedirectURI string - ZoneSyncLeeway int - AuthExtraArgs string - AccessTokenEnable bool + AuthEndpoint string + LogoutEndpoint string + ClientID string + ClientSecret string + JwksURI string + Scope string + TokenEndpoint string + RedirectURI string + RedirectPostLogout string + ZoneSyncLeeway int + AuthExtraArgs string + AccessTokenEnable bool } // WAF defines WAF configuration. diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index b943e2d227..501326d408 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -95,6 +95,7 @@ server { set $zone_sync_leeway {{ $oidc.ZoneSyncLeeway }}; set $oidc_authz_endpoint "{{ $oidc.AuthEndpoint }}"; + set $oidc_logout_endpoint "{{ $oidc.LogoutEndpoint }}"; set $oidc_authz_extra_args "{{ $oidc.AuthExtraArgs }}"; set $oidc_token_endpoint "{{ $oidc.TokenEndpoint }}"; set $oidc_jwt_keyfile "{{ $oidc.JwksURI }}"; @@ -102,6 +103,7 @@ server { set $oidc_client "{{ $oidc.ClientID }}"; set $oidc_client_secret "{{ $oidc.ClientSecret }}"; set $redir_location "{{ $oidc.RedirectURI }}"; + set $redir_post_logout "{{ $oidc.RedirectPostLogout }}"; {{- end }} {{- with $ssl := $s.SSL }} diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index c715e3410b..94d624ed03 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -1269,16 +1269,18 @@ func (p *policiesCfg) addOIDCConfig( } oidcPolCfg.oidc = &version2.OIDC{ - AuthEndpoint: oidc.AuthEndpoint, - AuthExtraArgs: authExtraArgs, - TokenEndpoint: oidc.TokenEndpoint, - JwksURI: oidc.JWKSURI, - ClientID: oidc.ClientID, - ClientSecret: string(clientSecret), - Scope: scope, - RedirectURI: redirectURI, - ZoneSyncLeeway: generateIntFromPointer(oidc.ZoneSyncLeeway, 200), - AccessTokenEnable: oidc.AccessTokenEnable, + AuthEndpoint: oidc.AuthEndpoint, + LogoutEndpoint: oidc.LogoutEndpoint, + AuthExtraArgs: authExtraArgs, + TokenEndpoint: oidc.TokenEndpoint, + JwksURI: oidc.JWKSURI, + ClientID: oidc.ClientID, + ClientSecret: string(clientSecret), + Scope: scope, + RedirectURI: redirectURI, + RedirectPostLogout: oidc.RedirectPostLogout, + ZoneSyncLeeway: generateIntFromPointer(oidc.ZoneSyncLeeway, 200), + AccessTokenEnable: oidc.AccessTokenEnable, } oidcPolCfg.key = polKey } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index 640a86f831..355c42f5c5 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -6228,15 +6228,17 @@ func TestGeneratePolicies(t *testing.T) { }, Spec: conf_v1.PolicySpec{ OIDC: &conf_v1.OIDC{ - AuthEndpoint: "http://example.com/auth", - TokenEndpoint: "http://example.com/token", - JWKSURI: "http://example.com/jwks", - ClientID: "client-id", - ClientSecret: "oidc-secret", - Scope: "scope", - RedirectURI: "/redirect", - ZoneSyncLeeway: createPointerFromInt(20), - AccessTokenEnable: true, + AuthEndpoint: "http://example.com/auth", + TokenEndpoint: "http://example.com/token", + JWKSURI: "http://example.com/jwks", + ClientID: "client-id", + ClientSecret: "oidc-secret", + Scope: "scope", + RedirectURI: "/redirect", + ZoneSyncLeeway: createPointerFromInt(20), + AccessTokenEnable: true, + LogoutEndpoint: "http://example.com/logout", + RedirectPostLogout: "http://example.com/", }, }, }, @@ -7444,11 +7446,13 @@ func TestGeneratePoliciesFails(t *testing.T) { }, Spec: conf_v1.PolicySpec{ OIDC: &conf_v1.OIDC{ - ClientSecret: "oidc-secret", - AuthEndpoint: "http://foo.com/bar", - TokenEndpoint: "http://foo.com/bar", - JWKSURI: "http://foo.com/bar", - AccessTokenEnable: true, + ClientSecret: "oidc-secret", + AuthEndpoint: "http://foo.com/bar", + TokenEndpoint: "http://foo.com/bar", + JWKSURI: "http://foo.com/bar", + LogoutEndpoint: "http://foo.com/bar", + RedirectPostLogout: "http://foo.com/bar", + AccessTokenEnable: true, }, }, }, @@ -7491,12 +7495,14 @@ func TestGeneratePoliciesFails(t *testing.T) { }, Spec: conf_v1.PolicySpec{ OIDC: &conf_v1.OIDC{ - ClientID: "foo", - ClientSecret: "oidc-secret", - AuthEndpoint: "https://foo.com/auth", - TokenEndpoint: "https://foo.com/token", - JWKSURI: "https://foo.com/certs", - AccessTokenEnable: true, + ClientID: "foo", + ClientSecret: "oidc-secret", + AuthEndpoint: "https://foo.com/auth", + TokenEndpoint: "https://foo.com/token", + JWKSURI: "https://foo.com/certs", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + AccessTokenEnable: true, }, }, }, @@ -7507,12 +7513,14 @@ func TestGeneratePoliciesFails(t *testing.T) { }, Spec: conf_v1.PolicySpec{ OIDC: &conf_v1.OIDC{ - ClientID: "foo", - ClientSecret: "oidc-secret", - AuthEndpoint: "https://bar.com/auth", - TokenEndpoint: "https://bar.com/token", - JWKSURI: "https://bar.com/certs", - AccessTokenEnable: true, + ClientID: "foo", + ClientSecret: "oidc-secret", + AuthEndpoint: "https://bar.com/auth", + TokenEndpoint: "https://bar.com/token", + JWKSURI: "https://bar.com/certs", + LogoutEndpoint: "https://bar.com/logout", + RedirectPostLogout: "https://bar.com/", + AccessTokenEnable: true, }, }, }, @@ -7532,15 +7540,17 @@ func TestGeneratePoliciesFails(t *testing.T) { context: "route", oidcPolCfg: &oidcPolicyCfg{ oidc: &version2.OIDC{ - AuthEndpoint: "https://foo.com/auth", - TokenEndpoint: "https://foo.com/token", - JwksURI: "https://foo.com/certs", - ClientID: "foo", - ClientSecret: "super_secret_123", - RedirectURI: "/_codexch", - Scope: "openid", - ZoneSyncLeeway: 0, - AccessTokenEnable: true, + AuthEndpoint: "https://foo.com/auth", + TokenEndpoint: "https://foo.com/token", + JwksURI: "https://foo.com/certs", + ClientID: "foo", + ClientSecret: "super_secret_123", + RedirectURI: "/_codexch", + Scope: "openid", + ZoneSyncLeeway: 0, + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + AccessTokenEnable: true, }, key: "default/oidc-policy-1", }, @@ -7556,14 +7566,16 @@ func TestGeneratePoliciesFails(t *testing.T) { }, expectedOidc: &oidcPolicyCfg{ oidc: &version2.OIDC{ - AuthEndpoint: "https://foo.com/auth", - TokenEndpoint: "https://foo.com/token", - JwksURI: "https://foo.com/certs", - ClientID: "foo", - ClientSecret: "super_secret_123", - RedirectURI: "/_codexch", - Scope: "openid", - AccessTokenEnable: true, + AuthEndpoint: "https://foo.com/auth", + TokenEndpoint: "https://foo.com/token", + JwksURI: "https://foo.com/certs", + ClientID: "foo", + ClientSecret: "super_secret_123", + RedirectURI: "/_codexch", + Scope: "openid", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + AccessTokenEnable: true, }, key: "default/oidc-policy-1", }, @@ -7588,12 +7600,14 @@ func TestGeneratePoliciesFails(t *testing.T) { }, Spec: conf_v1.PolicySpec{ OIDC: &conf_v1.OIDC{ - ClientSecret: "oidc-secret", - AuthEndpoint: "https://foo.com/auth", - TokenEndpoint: "https://foo.com/token", - JWKSURI: "https://foo.com/certs", - ClientID: "foo", - AccessTokenEnable: true, + ClientSecret: "oidc-secret", + AuthEndpoint: "https://foo.com/auth", + TokenEndpoint: "https://foo.com/token", + JWKSURI: "https://foo.com/certs", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "foo", + AccessTokenEnable: true, }, }, }, @@ -7604,12 +7618,14 @@ func TestGeneratePoliciesFails(t *testing.T) { }, Spec: conf_v1.PolicySpec{ OIDC: &conf_v1.OIDC{ - ClientSecret: "oidc-secret", - AuthEndpoint: "https://bar.com/auth", - TokenEndpoint: "https://bar.com/token", - JWKSURI: "https://bar.com/certs", - ClientID: "bar", - AccessTokenEnable: true, + ClientSecret: "oidc-secret", + AuthEndpoint: "https://bar.com/auth", + TokenEndpoint: "https://bar.com/token", + JWKSURI: "https://bar.com/certs", + LogoutEndpoint: "https://bar.com/logout", + RedirectPostLogout: "https://bar.com/", + ClientID: "bar", + AccessTokenEnable: true, }, }, }, @@ -7637,15 +7653,17 @@ func TestGeneratePoliciesFails(t *testing.T) { }, expectedOidc: &oidcPolicyCfg{ &version2.OIDC{ - AuthEndpoint: "https://foo.com/auth", - TokenEndpoint: "https://foo.com/token", - JwksURI: "https://foo.com/certs", - ClientID: "foo", - ClientSecret: "super_secret_123", - RedirectURI: "/_codexch", - Scope: "openid", - ZoneSyncLeeway: 200, - AccessTokenEnable: true, + AuthEndpoint: "https://foo.com/auth", + TokenEndpoint: "https://foo.com/token", + JwksURI: "https://foo.com/certs", + ClientID: "foo", + ClientSecret: "super_secret_123", + RedirectURI: "/_codexch", + Scope: "openid", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + ZoneSyncLeeway: 200, + AccessTokenEnable: true, }, "default/oidc-policy", }, diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 067391cd65..2c6e843e39 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -647,16 +647,18 @@ type EgressMTLS struct { // OIDC defines an Open ID Connect policy. type OIDC struct { - AuthEndpoint string `json:"authEndpoint"` - TokenEndpoint string `json:"tokenEndpoint"` - JWKSURI string `json:"jwksURI"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - Scope string `json:"scope"` - RedirectURI string `json:"redirectURI"` - ZoneSyncLeeway *int `json:"zoneSyncLeeway"` - AuthExtraArgs []string `json:"authExtraArgs"` - AccessTokenEnable bool `json:"accessTokenEnable"` + AuthEndpoint string `json:"authEndpoint"` + LogoutEndpoint string `json:"logoutEndpoint"` + TokenEndpoint string `json:"tokenEndpoint"` + JWKSURI string `json:"jwksURI"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + Scope string `json:"scope"` + RedirectURI string `json:"redirectURI"` + RedirectPostLogout string `json:"redirectPostLogout"` + ZoneSyncLeeway *int `json:"zoneSyncLeeway"` + AuthExtraArgs []string `json:"authExtraArgs"` + AccessTokenEnable bool `json:"accessTokenEnable"` } // WAF defines an WAF policy. diff --git a/pkg/apis/configuration/validation/policy.go b/pkg/apis/configuration/validation/policy.go index a762146446..fafcffe5b7 100644 --- a/pkg/apis/configuration/validation/policy.go +++ b/pkg/apis/configuration/validation/policy.go @@ -243,6 +243,9 @@ func validateOIDC(oidc *v1.OIDC, fieldPath *field.Path) field.ErrorList { if oidc.AuthEndpoint == "" { return field.ErrorList{field.Required(fieldPath.Child("authEndpoint"), "")} } + if oidc.LogoutEndpoint == "" { + return field.ErrorList{field.Required(fieldPath.Child("logoutEndpoint"), "")} + } if oidc.TokenEndpoint == "" { return field.ErrorList{field.Required(fieldPath.Child("tokenEndpoint"), "")} } @@ -255,6 +258,9 @@ func validateOIDC(oidc *v1.OIDC, fieldPath *field.Path) field.ErrorList { if oidc.ClientSecret == "" { return field.ErrorList{field.Required(fieldPath.Child("clientSecret"), "")} } + if oidc.RedirectPostLogout == "" { + return field.ErrorList{field.Required(fieldPath.Child("redirectPostLogout"), "")} + } allErrs := field.ErrorList{} if oidc.Scope != "" { @@ -271,8 +277,10 @@ func validateOIDC(oidc *v1.OIDC, fieldPath *field.Path) field.ErrorList { } allErrs = append(allErrs, validateURL(oidc.AuthEndpoint, fieldPath.Child("authEndpoint"))...) + allErrs = append(allErrs, validateURL(oidc.LogoutEndpoint, fieldPath.Child("logoutEndpoint"))...) allErrs = append(allErrs, validateURL(oidc.TokenEndpoint, fieldPath.Child("tokenEndpoint"))...) allErrs = append(allErrs, validateURL(oidc.JWKSURI, fieldPath.Child("jwksURI"))...) + allErrs = append(allErrs, validateURL(oidc.RedirectPostLogout, fieldPath.Child("redirectPostLogout"))...) allErrs = append(allErrs, validateSecretName(oidc.ClientSecret, fieldPath.Child("clientSecret"))...) return append(allErrs, validateClientID(oidc.ClientID, fieldPath.Child("clientID"))...) } diff --git a/pkg/apis/configuration/validation/policy_test.go b/pkg/apis/configuration/validation/policy_test.go index 36a4736d71..132bc9d77a 100644 --- a/pkg/apis/configuration/validation/policy_test.go +++ b/pkg/apis/configuration/validation/policy_test.go @@ -287,15 +287,17 @@ func TestValidatePolicy_PassesOnValidInput(t *testing.T) { policy: &v1.Policy{ Spec: v1.PolicySpec{ OIDC: &v1.OIDC{ - AuthEndpoint: "https://foo.bar/auth", - AuthExtraArgs: []string{"foo=bar"}, - TokenEndpoint: "https://foo.bar/token", - JWKSURI: "https://foo.bar/certs", - ClientID: "random-string", - ClientSecret: "random-secret", - Scope: "openid", - ZoneSyncLeeway: createPointerFromInt(10), - AccessTokenEnable: true, + AuthEndpoint: "https://foo.bar/auth", + AuthExtraArgs: []string{"foo=bar"}, + TokenEndpoint: "https://foo.bar/token", + JWKSURI: "https://foo.bar/certs", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "random-string", + ClientSecret: "random-secret", + Scope: "openid", + ZoneSyncLeeway: createPointerFromInt(10), + AccessTokenEnable: true, }, }, }, @@ -392,13 +394,15 @@ func TestValidatePolicy_FailsOnInvalidInput(t *testing.T) { policy: &v1.Policy{ Spec: v1.PolicySpec{ OIDC: &v1.OIDC{ - AuthEndpoint: "https://foo.bar/auth", - TokenEndpoint: "https://foo.bar/token", - JWKSURI: "https://foo.bar/certs", - ClientID: "random-string", - ClientSecret: "random-secret", - Scope: "openid", - AccessTokenEnable: true, + AuthEndpoint: "https://foo.bar/auth", + TokenEndpoint: "https://foo.bar/token", + JWKSURI: "https://foo.bar/certs", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "random-string", + ClientSecret: "random-secret", + Scope: "openid", + AccessTokenEnable: true, }, }, }, @@ -410,13 +414,15 @@ func TestValidatePolicy_FailsOnInvalidInput(t *testing.T) { policy: &v1.Policy{ Spec: v1.PolicySpec{ OIDC: &v1.OIDC{ - AuthEndpoint: "https://foo.bar/auth", - TokenEndpoint: "https://foo.bar/token", - JWKSURI: "https://foo.bar/certs", - ClientID: "random-string", - ClientSecret: "random-secret", - Scope: "openid", - AccessTokenEnable: true, + AuthEndpoint: "https://foo.bar/auth", + TokenEndpoint: "https://foo.bar/token", + JWKSURI: "https://foo.bar/certs", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "random-string", + ClientSecret: "random-secret", + Scope: "openid", + AccessTokenEnable: true, }, }, }, @@ -441,14 +447,16 @@ func TestValidatePolicy_FailsOnInvalidInput(t *testing.T) { policy: &v1.Policy{ Spec: v1.PolicySpec{ OIDC: &v1.OIDC{ - AuthEndpoint: "https://foo.bar/auth", - TokenEndpoint: "https://foo.bar/token", - JWKSURI: "https://foo.bar/certs", - ClientID: "random-string", - ClientSecret: "random-secret", - Scope: "openid", - ZoneSyncLeeway: createPointerFromInt(-1), - AccessTokenEnable: false, + AuthEndpoint: "https://foo.bar/auth", + TokenEndpoint: "https://foo.bar/token", + JWKSURI: "https://foo.bar/certs", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "random-string", + ClientSecret: "random-secret", + Scope: "openid", + ZoneSyncLeeway: createPointerFromInt(-1), + AccessTokenEnable: false, }, }, }, @@ -460,13 +468,15 @@ func TestValidatePolicy_FailsOnInvalidInput(t *testing.T) { policy: &v1.Policy{ Spec: v1.PolicySpec{ OIDC: &v1.OIDC{ - AuthEndpoint: "https://foo.bar/auth", - AuthExtraArgs: []string{"foo;bar"}, - TokenEndpoint: "https://foo.bar/token", - JWKSURI: "https://foo.bar/certs", - ClientID: "random-string", - ClientSecret: "random-secret", - Scope: "openid", + AuthEndpoint: "https://foo.bar/auth", + AuthExtraArgs: []string{"foo;bar"}, + TokenEndpoint: "https://foo.bar/token", + JWKSURI: "https://foo.bar/certs", + LogoutEndpoint: "https://foo.com/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "random-string", + ClientSecret: "random-secret", + Scope: "openid", }, }, }, @@ -1230,66 +1240,76 @@ func TestValidateOIDC_PassesOnValidOIDC(t *testing.T) { }{ { oidc: &v1.OIDC{ - AuthEndpoint: "https://accounts.google.com/o/oauth2/v2/auth", - AuthExtraArgs: []string{"foo=bar", "baz=zot"}, - TokenEndpoint: "https://oauth2.googleapis.com/token", - JWKSURI: "https://www.googleapis.com/oauth2/v3/certs", - ClientID: "random-string", - ClientSecret: "random-secret", - Scope: "openid", - RedirectURI: "/foo", - ZoneSyncLeeway: createPointerFromInt(20), - AccessTokenEnable: true, + AuthEndpoint: "https://accounts.google.com/o/oauth2/v2/auth", + AuthExtraArgs: []string{"foo=bar", "baz=zot"}, + TokenEndpoint: "https://oauth2.googleapis.com/token", + JWKSURI: "https://www.googleapis.com/oauth2/v3/certs", + LogoutEndpoint: "https://oauth2.googleapis.com/revoke", + RedirectPostLogout: "https://foo.com/", + ClientID: "random-string", + ClientSecret: "random-secret", + Scope: "openid", + RedirectURI: "/foo", + ZoneSyncLeeway: createPointerFromInt(20), + AccessTokenEnable: true, }, msg: "verify full oidc", }, { oidc: &v1.OIDC{ - AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", - TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", - JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", - ClientID: "ff", - ClientSecret: "ff", - Scope: "openid+profile", - RedirectURI: "/_codexe", - AccessTokenEnable: true, + AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", + TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", + JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", + LogoutEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "ff", + ClientSecret: "ff", + Scope: "openid+profile", + RedirectURI: "/_codexe", + AccessTokenEnable: true, }, msg: "verify azure endpoint", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/auth", - AuthExtraArgs: []string{"kc_idp_hint=foo"}, - TokenEndpoint: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "bar", - ClientSecret: "foo", - Scope: "openid", - AccessTokenEnable: true, + AuthEndpoint: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/auth", + AuthExtraArgs: []string{"kc_idp_hint=foo"}, + TokenEndpoint: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "bar", + ClientSecret: "foo", + Scope: "openid", + AccessTokenEnable: true, }, msg: "domain with port number", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", - TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "client", - ClientSecret: "secret", - Scope: "openid", - AccessTokenEnable: true, + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "client", + ClientSecret: "secret", + Scope: "openid", + AccessTokenEnable: true, }, msg: "ip address", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", - TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "client", - ClientSecret: "secret", - Scope: "openid+offline_access", - AccessTokenEnable: true, + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "client", + ClientSecret: "secret", + Scope: "openid+offline_access", + AccessTokenEnable: true, }, msg: "offline access scope", }, @@ -1355,131 +1375,153 @@ func TestValidateOIDC_FailsOnInvalidOIDC(t *testing.T) { }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", - TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "client", - ClientSecret: "secret", - Scope: "bogus", - AccessTokenEnable: true, + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "client", + ClientSecret: "secret", + Scope: "bogus", + AccessTokenEnable: true, }, msg: "missing openid in scope", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", - TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "client", - ClientSecret: "secret", - Scope: "openid+bogus\x7f", - AccessTokenEnable: true, + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "client", + ClientSecret: "secret", + Scope: "openid+bogus\x7f", + AccessTokenEnable: true, }, msg: "invalid unicode in scope", }, { oidc: &v1.OIDC{ - AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", - JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", - ClientID: "ff", - ClientSecret: "ff", - Scope: "openid+profile", - AccessTokenEnable: true, + AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", + JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", + LogoutEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "ff", + ClientSecret: "ff", + Scope: "openid+profile", + AccessTokenEnable: true, }, msg: "missing required field token", }, { oidc: &v1.OIDC{ - AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", - TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", - ClientID: "ff", - ClientSecret: "ff", - Scope: "openid+profile", - AccessTokenEnable: true, + AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", + TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", + LogoutEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "ff", + ClientSecret: "ff", + Scope: "openid+profile", + AccessTokenEnable: true, }, msg: "missing required field jwk", }, { oidc: &v1.OIDC{ - AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", - TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", - JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", - ClientSecret: "ff", - Scope: "openid+profile", - AccessTokenEnable: true, + AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", + TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", + JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", + LogoutEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/logout", + RedirectPostLogout: "https://foo.com/", + ClientSecret: "ff", + Scope: "openid+profile", + AccessTokenEnable: true, }, msg: "missing required field clientid", }, { oidc: &v1.OIDC{ - AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", - TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", - JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", - ClientID: "ff", - Scope: "openid+profile", - AccessTokenEnable: true, + AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", + TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", + JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", + LogoutEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "ff", + Scope: "openid+profile", + AccessTokenEnable: true, }, msg: "missing required field client secret", }, { oidc: &v1.OIDC{ - AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", - TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", - JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", - ClientID: "ff", - ClientSecret: "-ff-", - Scope: "openid+profile", - AccessTokenEnable: true, + AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", + TokenEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/token", + JWKSURI: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/keys", + LogoutEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/discovery/v2.0/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "ff", + ClientSecret: "-ff-", + Scope: "openid+profile", + AccessTokenEnable: true, }, msg: "invalid secret name", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://foo.\bar.com", - TokenEndpoint: "http://keycloak.default", - JWKSURI: "http://keycloak.default", - ClientID: "bar", - ClientSecret: "foo", - Scope: "openid", - AccessTokenEnable: true, + AuthEndpoint: "http://foo.\bar.com", + TokenEndpoint: "http://keycloak.default", + JWKSURI: "http://keycloak.default", + LogoutEndpoint: "http://keycloak.default", + RedirectPostLogout: "https://foo.com/", + ClientID: "bar", + ClientSecret: "foo", + Scope: "openid", + AccessTokenEnable: true, }, msg: "invalid URL", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", - TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "$foo$bar", - ClientSecret: "secret", - Scope: "openid", - AccessTokenEnable: true, + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "$foo$bar", + ClientSecret: "secret", + Scope: "openid", + AccessTokenEnable: true, }, msg: "invalid chars in clientID", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", - AuthExtraArgs: []string{"foo;bar"}, - TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "foobar", - ClientSecret: "secret", - Scope: "openid", + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + AuthExtraArgs: []string{"foo;bar"}, + TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "foobar", + ClientSecret: "secret", + Scope: "openid", }, msg: "invalid chars in authExtraArgs", }, { oidc: &v1.OIDC{ - AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", - TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", - JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", - ClientID: "foobar", - ClientSecret: "secret", - Scope: "openid", - ZoneSyncLeeway: createPointerFromInt(-1), - AccessTokenEnable: true, + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token", + JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs", + LogoutEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout", + RedirectPostLogout: "https://foo.com/", + ClientID: "foobar", + ClientSecret: "secret", + Scope: "openid", + ZoneSyncLeeway: createPointerFromInt(-1), + AccessTokenEnable: true, }, msg: "invalid zoneSyncLeeway value", },