Skip to content

feat: Add access token support in the OIDC #3474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deployments/common/crds/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ spec:
description: OIDC defines an Open ID Connect policy.
type: object
properties:
accessTokenEnable:
type: boolean
authEndpoint:
type: string
authExtraArgs:
Expand Down
2 changes: 2 additions & 0 deletions deployments/helm-chart/crds/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ spec:
description: OIDC defines an Open ID Connect policy.
type: object
properties:
accessTokenEnable:
type: boolean
authEndpoint:
type: string
authExtraArgs:
Expand Down
4 changes: 3 additions & 1 deletion docs/content/configuration/policy-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ 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
accessTokenEnable: true
```

NGINX Plus will pass the ID of an authenticated user to the backend in the HTTP header `username`.
Expand Down Expand Up @@ -384,7 +385,8 @@ The OIDC policy defines a few internal locations that can't be customized: `/_jw
|``jwksURI`` | URL for the JSON Web Key Set (JWK) document provided by your OpenID Connect provider. | ``string`` | Yes |
|``scope`` | List of OpenID Connect scopes. Possible values are ``openid``, ``profile``, ``email``, ``address`` and ``phone``. The scope ``openid`` always needs to be present and others can be added concatenating them with a ``+`` sign, for example ``openid+profile+email``. 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 tokens and shared values between Ingress Controller pods. The default is ``200``. | ``int`` | 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 |
|``accessTokenEnable`` | Option of whether Bearer token is used to authorize NGINX to access protected backend. | ``boolean`` | No |
{{% /table %}}

> **Note**: Only one OIDC policy can be referenced in a VirtualServer and its VirtualServerRoutes. However, the same policy can still be applied to different routes in the VirtualServer and VirtualServerRoutes.
Expand Down
1 change: 1 addition & 0 deletions examples/custom-resources/oidc/oidc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ spec:
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
scope: openid+profile+email
accessTokenEnable: true
15 changes: 9 additions & 6 deletions internal/configs/oidc/oidc_common.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ map $http_x_forwarded_proto $proto {
proxy_cache_path /var/cache/nginx/jwk levels=1 keys_zone=jwk:64k max_size=1m;

# Change timeout values to at least the validity period of each token type
keyval_zone zone=oidc_id_tokens:1M timeout=1h sync;
keyval_zone zone=refresh_tokens:1M timeout=8h sync;
keyval_zone zone=oidc_id_tokens:1M timeout=1h sync;
keyval_zone zone=oidc_access_tokens:1M timeout=1h sync;
keyval_zone zone=refresh_tokens:1M timeout=8h sync;
#keyval_zone zone=oidc_pkce:128K timeout=90s sync; # Temporary storage for PKCE code verifier.

keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens; # Exchange cookie for JWT
keyval $cookie_auth_token $refresh_token zone=refresh_tokens; # Exchange cookie for refresh token
keyval $request_id $new_session zone=oidc_id_tokens; # For initial session creation
keyval $request_id $new_refresh zone=refresh_tokens; # ''
keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens; # Exchange cookie for ID token(JWT)
keyval $cookie_auth_token $access_token zone=oidc_access_tokens; # Exchange cookie for access token
keyval $cookie_auth_token $refresh_token zone=refresh_tokens; # Exchange cookie for refresh token
keyval $request_id $new_session zone=oidc_id_tokens; # For initial session creation
keyval $request_id $new_access_token zone=oidc_access_tokens;
keyval $request_id $new_refresh zone=refresh_tokens; # ''
#keyval $pkce_id $pkce_code_verifier zone=oidc_pkce;

auth_jwt_claim_set $jwt_audience aud; # In case aud is an array
Expand Down
13 changes: 12 additions & 1 deletion internal/configs/oidc/openid_connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ function auth(r, afterSyncCheck) {

// ID Token is valid, update keyval
r.log("OIDC refresh success, updating id_token for " + r.variables.cookie_auth_token);
r.variables.session_jwt = tokenset.id_token; // Update key-value store
r.variables.session_jwt = tokenset.id_token;
if (tokenset.access_token) {
r.variables.access_token = tokenset.access_token;
} else {
r.variables.access_token = "";
}

// Update refresh token (if we got a new one)
if (r.variables.refresh_token != tokenset.refresh_token) {
Expand Down Expand Up @@ -187,6 +192,11 @@ function codeExchange(r) {
// Add opaque token to keyval session store
r.log("OIDC success, creating session " + r.variables.request_id);
r.variables.new_session = tokenset.id_token; // Create key-value store entry
if (tokenset.access_token) {
r.variables.new_access_token = tokenset.access_token;
} else {
r.variables.new_access_token = "";
}
r.headersOut["Set-Cookie"] = "auth_token=" + r.variables.request_id + "; " + r.variables.oidc_cookie_flags;
r.return(302, r.variables.redirect_base + r.variables.cookie_auth_redir);
}
Expand Down Expand Up @@ -256,6 +266,7 @@ 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);
}
Expand Down
19 changes: 10 additions & 9 deletions internal/configs/version2/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,16 @@ 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
AuthEndpoint string
ClientID string
ClientSecret string
JwksURI string
Scope string
TokenEndpoint string
RedirectURI string
ZoneSyncLeeway int
AuthExtraArgs string
AccessTokenEnable bool
}

// WAF defines WAF configuration.
Expand Down
3 changes: 3 additions & 0 deletions internal/configs/version2/nginx-plus.virtualserver.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,9 @@ server {
error_page 401 = @do_oidc_flow;
auth_jwt_key_request /_jwks_uri;
{{ $proxyOrGRPC }}_set_header username $jwt_claim_sub;
{{- if $s.OIDC.AccessTokenEnable }}
{{ $proxyOrGRPC }}_set_header Authorization "Bearer $access_token";
{{- end }}
{{ end }}

{{ with $l.WAF }}
Expand Down
19 changes: 10 additions & 9 deletions internal/configs/virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1052,15 +1052,16 @@ 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),
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,
}
oidcPolCfg.key = polKey
}
Expand Down
119 changes: 64 additions & 55 deletions internal/configs/virtualserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3136,14 +3136,15 @@ 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),
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,
},
},
},
Expand Down Expand Up @@ -4252,10 +4253,11 @@ 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",
ClientSecret: "oidc-secret",
AuthEndpoint: "http://foo.com/bar",
TokenEndpoint: "http://foo.com/bar",
JWKSURI: "http://foo.com/bar",
AccessTokenEnable: true,
},
},
},
Expand Down Expand Up @@ -4298,11 +4300,12 @@ 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",
ClientID: "foo",
ClientSecret: "oidc-secret",
AuthEndpoint: "https://foo.com/auth",
TokenEndpoint: "https://foo.com/token",
JWKSURI: "https://foo.com/certs",
AccessTokenEnable: true,
},
},
},
Expand All @@ -4313,11 +4316,12 @@ 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",
ClientID: "foo",
ClientSecret: "oidc-secret",
AuthEndpoint: "https://bar.com/auth",
TokenEndpoint: "https://bar.com/token",
JWKSURI: "https://bar.com/certs",
AccessTokenEnable: true,
},
},
},
Expand All @@ -4337,14 +4341,15 @@ 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,
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,
},
key: "default/oidc-policy-1",
},
Expand All @@ -4360,13 +4365,14 @@ 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",
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,
},
key: "default/oidc-policy-1",
},
Expand All @@ -4391,11 +4397,12 @@ 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",
ClientSecret: "oidc-secret",
AuthEndpoint: "https://foo.com/auth",
TokenEndpoint: "https://foo.com/token",
JWKSURI: "https://foo.com/certs",
ClientID: "foo",
AccessTokenEnable: true,
},
},
},
Expand All @@ -4406,11 +4413,12 @@ 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",
ClientSecret: "oidc-secret",
AuthEndpoint: "https://bar.com/auth",
TokenEndpoint: "https://bar.com/token",
JWKSURI: "https://bar.com/certs",
ClientID: "bar",
AccessTokenEnable: true,
},
},
},
Expand Down Expand Up @@ -4438,14 +4446,15 @@ 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,
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,
},
"default/oidc-policy",
},
Expand Down
19 changes: 10 additions & 9 deletions pkg/apis/configuration/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,15 +475,16 @@ 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"`
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"`
}

// WAF defines an WAF policy.
Expand Down
Loading