Skip to content

Commit f8d5420

Browse files
authored
Add OIDC PKCE configuration through policy (#7765)
* Fix leading whitespace in tmpl files * Add PKCE Enabled flag * Implement pkce in configs * Add check for OIDC to guard for a nil pointer * Fix some whitespace alignment in tmpl files * Update snapshots to realign with whitespaces * Add tests for PKCE enabled true * Update CRDs based on policy files * Add pkceEnabled to oidc pytest setup yaml * Terminate include directive with a ; * Set pkce enabled to an int instead of a string * OIDC test doesn't need pkce enabled * Add PKCE pytest * Update snapshot after changing a str -> int * oidc and pkce pytest fixture scope to function * OIDC tests should be class fixtured * Remove a parameter from pkce test * pkce test fixture should also be class scoped * Add debug prints * Merge pkce test into oidc test file * Add unit tests for the bool to int util function * Add docs to create keycloak client via api * Reword options because no tabs * OIDC example deploy keycloak into nginx-ingress ns * Add plus-mgmt-configmap.yaml to instructions * Redo list numbers in oidc example readme * Reset keycloak to be in default namespace * Add note on not using client secret for PKCE * Move applying the plus mgmt to common resources * Add pkceEnabled to policy resource doc * Rename pkceEnabled from past to present tense * Fix product name in example readme * Change console code type to shell * Turn choice into unordered list * Replace const default pkce secret with init val * Change pkce and client secret validations * Update snapshots * Do not use default client secret * Add more validation and tests * Add note to OIDC policy docs about pkce-clientsecret * Remove clientsecret from e2e test for pkce
1 parent bcce9e6 commit f8d5420

File tree

23 files changed

+438
-104
lines changed

23 files changed

+438
-104
lines changed

charts/tests/__snapshots__/helmunit_test.snap

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,6 @@ spec:
442442
- -ssl-dynamic-reload=true
443443
- -enable-telemetry-reporting=true
444444
- -weight-changes-dynamic-reload=false
445-
446-
minReadySeconds: 0
447445
/-/-/-/
448446
# Source: nginx-ingress/templates/controller-ingress-class.yaml
449447
apiVersion: networking.k8s.io/v1
@@ -911,8 +909,6 @@ spec:
911909
- -ssl-dynamic-reload=true
912910
- -enable-telemetry-reporting=true
913911
- -weight-changes-dynamic-reload=false
914-
915-
minReadySeconds: 0
916912
/-/-/-/
917913
# Source: nginx-ingress/templates/controller-ingress-class.yaml
918914
apiVersion: networking.k8s.io/v1
@@ -1449,8 +1445,6 @@ spec:
14491445
- -weight-changes-dynamic-reload=false
14501446
- -agent=true
14511447
- -agent-instance-group=app-protect-waf-agentv2-nginx-ingress-controller
1452-
1453-
minReadySeconds: 0
14541448
/-/-/-/
14551449
# Source: nginx-ingress/templates/controller-ingress-class.yaml
14561450
apiVersion: networking.k8s.io/v1
@@ -1965,7 +1959,6 @@ spec:
19651959
mountPath: /opt/app_protect/config
19661960
- name: app-protect-bundles
19671961
mountPath: /etc/app_protect/bundles
1968-
minReadySeconds: 0
19691962
/-/-/-/
19701963
# Source: nginx-ingress/templates/controller-ingress-class.yaml
19711964
apiVersion: networking.k8s.io/v1
@@ -2547,7 +2540,6 @@ spec:
25472540
mountPath: /opt/app_protect/config
25482541
- name: app-protect-bundles
25492542
mountPath: /etc/app_protect/bundles
2550-
minReadySeconds: 0
25512543
/-/-/-/
25522544
# Source: nginx-ingress/templates/controller-ingress-class.yaml
25532545
apiVersion: networking.k8s.io/v1
@@ -2961,8 +2953,6 @@ spec:
29612953
- -ssl-dynamic-reload=true
29622954
- -enable-telemetry-reporting=true
29632955
- -weight-changes-dynamic-reload=false
2964-
2965-
minReadySeconds: 0
29662956
/-/-/-/
29672957
# Source: nginx-ingress/templates/controller-ingress-class.yaml
29682958
apiVersion: networking.k8s.io/v1
@@ -3406,8 +3396,6 @@ spec:
34063396
- -ssl-dynamic-reload=true
34073397
- -enable-telemetry-reporting=true
34083398
- -weight-changes-dynamic-reload=false
3409-
3410-
minReadySeconds: 0
34113399
/-/-/-/
34123400
# Source: nginx-ingress/templates/controller-ingress-class.yaml
34133401
apiVersion: networking.k8s.io/v1
@@ -3851,8 +3839,6 @@ spec:
38513839
- -ssl-dynamic-reload=true
38523840
- -enable-telemetry-reporting=true
38533841
- -weight-changes-dynamic-reload=false
3854-
3855-
minReadySeconds: 0
38563842
/-/-/-/
38573843
# Source: nginx-ingress/templates/controller-ingress-class.yaml
38583844
apiVersion: networking.k8s.io/v1
@@ -4297,8 +4283,6 @@ spec:
42974283
- -ssl-dynamic-reload=true
42984284
- -enable-telemetry-reporting=true
42994285
- -weight-changes-dynamic-reload=false
4300-
4301-
minReadySeconds: 0
43024286
/-/-/-/
43034287
# Source: nginx-ingress/templates/controller-ingress-class.yaml
43044288
apiVersion: networking.k8s.io/v1
@@ -4763,8 +4747,6 @@ spec:
47634747
- -ssl-dynamic-reload=true
47644748
- -enable-telemetry-reporting=true
47654749
- -weight-changes-dynamic-reload=false
4766-
4767-
minReadySeconds: 0
47684750
/-/-/-/
47694751
# Source: nginx-ingress/templates/controller-ingress-class.yaml
47704752
apiVersion: networking.k8s.io/v1
@@ -5210,8 +5192,6 @@ spec:
52105192
- -ssl-dynamic-reload=true
52115193
- -enable-telemetry-reporting=true
52125194
- -weight-changes-dynamic-reload=false
5213-
5214-
minReadySeconds: 0
52155195
/-/-/-/
52165196
# Source: nginx-ingress/templates/controller-ingress-class.yaml
52175197
apiVersion: networking.k8s.io/v1
@@ -5672,8 +5652,6 @@ spec:
56725652
- -ssl-dynamic-reload=true
56735653
- -enable-telemetry-reporting=true
56745654
- -weight-changes-dynamic-reload=false
5675-
5676-
minReadySeconds: 0
56775655
/-/-/-/
56785656
# Source: nginx-ingress/templates/controller-ingress-class.yaml
56795657
apiVersion: networking.k8s.io/v1
@@ -6141,8 +6119,6 @@ spec:
61416119
- -ssl-dynamic-reload=true
61426120
- -enable-telemetry-reporting=true
61436121
- -weight-changes-dynamic-reload=false
6144-
6145-
minReadySeconds: 0
61466122
/-/-/-/
61476123
# Source: nginx-ingress/templates/controller-ingress-class.yaml
61486124
apiVersion: networking.k8s.io/v1
@@ -6620,8 +6596,6 @@ spec:
66206596
- -ssl-dynamic-reload=true
66216597
- -enable-telemetry-reporting=true
66226598
- -weight-changes-dynamic-reload=false
6623-
6624-
minReadySeconds: 0
66256599
/-/-/-/
66266600
# Source: nginx-ingress/templates/controller-ingress-class.yaml
66276601
apiVersion: networking.k8s.io/v1
@@ -7080,8 +7054,6 @@ spec:
70807054
- -ssl-dynamic-reload=true
70817055
- -enable-telemetry-reporting=true
70827056
- -weight-changes-dynamic-reload=false
7083-
7084-
minReadySeconds: 0
70857057
/-/-/-/
70867058
# Source: nginx-ingress/templates/controller-ingress-class.yaml
70877059
apiVersion: networking.k8s.io/v1
@@ -7540,8 +7512,6 @@ spec:
75407512
- -ssl-dynamic-reload=true
75417513
- -enable-telemetry-reporting=true
75427514
- -weight-changes-dynamic-reload=false
7543-
7544-
minReadySeconds: 0
75457515
/-/-/-/
75467516
# Source: nginx-ingress/templates/controller-ingress-class.yaml
75477517
apiVersion: networking.k8s.io/v1
@@ -8010,8 +7980,6 @@ spec:
80107980
- -ssl-dynamic-reload=true
80117981
- -enable-telemetry-reporting=true
80127982
- -weight-changes-dynamic-reload=false
8013-
8014-
minReadySeconds: 0
80157983
/-/-/-/
80167984
# Source: nginx-ingress/templates/controller-ingress-class.yaml
80177985
apiVersion: networking.k8s.io/v1

config/crd/bases/k8s.nginx.org_policies.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ spec:
163163
type: string
164164
jwksURI:
165165
type: string
166+
pkceEnable:
167+
type: boolean
166168
postLogoutRedirectURI:
167169
type: string
168170
redirectURI:

deploy/crds.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ spec:
325325
type: string
326326
jwksURI:
327327
type: string
328+
pkceEnable:
329+
type: boolean
328330
postLogoutRedirectURI:
329331
type: string
330332
redirectURI:

examples/custom-resources/oidc/README.md

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,24 @@ application using an OpenID Connect policy and [Keycloak](https://www.keycloak.o
55

66
**Note**: The KeyCloak container does not support IPv6 environments.
77

8+
**Note**: This example assumes that your default namespace is set to `default`. You can check this with
9+
10+
```shell
11+
kubectl config view --minify | grep namespace
12+
```
13+
14+
If it's not empty, and anything other than `default`, you can set to `default` with the following command:
15+
16+
```shell
17+
kubectl config set-context --namespace default --current
18+
```
19+
820
## Prerequisites
921

1022
1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/)
11-
instructions to deploy the Ingress Controller. This example requires that the HTTPS port of the Ingress Controller is
12-
`443`.
13-
1. Save the public IP address of the Ingress Controller into `/etc/hosts` of your machine:
23+
instructions to deploy NGINX Ingress Controller. This example requires that the HTTPS port of the Ingress
24+
Controller is `443`.
25+
2. Save the public IP address of the Ingress Controller into `/etc/hosts` of your machine:
1426

1527
```text
1628
...
@@ -27,29 +39,29 @@ application using an OpenID Connect policy and [Keycloak](https://www.keycloak.o
2739
Create a secret with the TLS certificate and key that will be used for TLS termination of the web application and
2840
Keycloak:
2941
30-
```console
42+
```shell
3143
kubectl apply -f tls-secret.yaml
3244
```
3345

3446
## Step 2 - Deploy a Web Application
3547

3648
Create the application deployment and service:
3749

38-
```console
50+
```shell
3951
kubectl apply -f webapp.yaml
4052
```
4153

4254
## Step 3 - Deploy Keycloak
4355

4456
1. Create the Keycloak deployment and service:
4557

46-
```console
58+
```shell
4759
kubectl apply -f keycloak.yaml
4860
```
4961

50-
1. Create a VirtualServer resource for Keycloak:
62+
2. Create a VirtualServer resource for Keycloak:
5163

52-
```console
64+
```shell
5365
kubectl apply -f virtual-server-idp.yaml
5466
```
5567

@@ -59,27 +71,30 @@ To set up Keycloak:
5971

6072
1. Follow the steps in the "Configuring Keycloak" [section of the documentation](https://docs.nginx.com/nginx/deployment-guides/single-sign-on/keycloak/#configuring-keycloak):
6173
1. To connect to Keycloak, use `https://keycloak.example.com`.
62-
1. Make sure to save the client secret for NGINX-Plus client to the `SECRET` shell variable:
74+
2. Make sure to save the client secret for NGINX-Plus client to the `SECRET` shell variable:
6375

64-
```console
76+
```shell
6577
SECRET=value
6678
```
6779

68-
1. Alternatively, [execute the commands](./keycloak_setup.md).
80+
2. Alternatively, [execute the commands](./keycloak_setup.md).
6981

7082
## Step 5 - Deploy the Client Secret
7183

84+
**Note**: If you're using PKCE, skip this step. PKCE clients do not have client secrets. Applying this will result
85+
in a broken deployment.
86+
7287
1. Encode the secret, obtained in the previous step:
7388
74-
```console
89+
```shell
7590
echo -n $SECRET | base64
7691
```
7792
78-
1. Edit `client-secret.yaml`, replacing `<insert-secret-here>` with the encoded secret.
93+
2. Edit `client-secret.yaml`, replacing `<insert-secret-here>` with the encoded secret.
7994
80-
1. Create a secret with the name `oidc-secret` that will be used by the OIDC policy:
95+
3. Create a secret with the name `oidc-secret` that will be used by the OIDC policy:
8196
82-
```console
97+
```shell
8398
kubectl apply -f client-secret.yaml
8499
```
85100
@@ -96,23 +111,23 @@ Steps:
96111
97112
1. Apply the ConfigMap `nginx-config.yaml`, which contains `zone-sync` configuration parameter that enable zone synchronization and the resolver using the kube-dns service.
98113
99-
```console
114+
```shell
100115
kubectl apply -f nginx-config.yaml
101116
```
102117
103118
## Step 7 - Deploy the OIDC Policy
104119
105120
Create a policy with the name `oidc-policy` that references the secret from the previous step:
106121
107-
```console
122+
```shell
108123
kubectl apply -f oidc.yaml
109124
```
110125
111126
## Step 8 - Configure Load Balancing
112127
113128
Create a VirtualServer resource for the web application:
114129
115-
```console
130+
```shell
116131
kubectl apply -f virtual-server.yaml
117132
```
118133
@@ -122,15 +137,15 @@ Note that the VirtualServer references the policy `oidc-policy` created in Step
122137
123138
1. Open a web browser and navigate to the URL of the web application: `https://webapp.example.com`. You will be
124139
redirected to Keycloak.
125-
1. Log in with the username and password for the user you created in Keycloak, `nginx-user` and `test`.
140+
2. Log in with the username and password for the user you created in Keycloak, `nginx-user` and `test`.
126141
![keycloak](./keycloak.png)
127-
1. Once logged in, you will be redirected to the web application and get a response from it. Notice the field `User ID`
142+
3. Once logged in, you will be redirected to the web application and get a response from it. Notice the field `User ID`
128143
in the response, this will match the ID for your user in Keycloak. ![webapp](./webapp.png)
129144
130145
## Step 10 - Log Out
131146
132147
1. To log out, navigate to `https://webapp.example.com/logout`. Your session will be terminated, and you will be
133148
redirected to the default post logout URI `https://webapp.example.com/_logout`.
134149
![logout](./logout.png)
135-
1. To confirm that you have been logged out, navigate to `https://webapp.example.com`. You will be redirected to
150+
2. To confirm that you have been logged out, navigate to `https://webapp.example.com`. You will be redirected to
136151
Keycloak to log in again.

examples/custom-resources/oidc/keycloak_setup.md

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,63 @@ Steps:
1717

1818
1. Save the address of Keycloak into a shell variable:
1919

20-
```console
20+
```shell
2121
KEYCLOAK_ADDRESS=keycloak.example.com
2222
```
2323

24-
1. Retrieve the access token and store it into a shell variable:
24+
2. Retrieve the access token and store it into a shell variable:
2525

26-
```console
26+
```shell
2727
TOKEN=`curl -sS -k --data "username=admin&password=admin&grant_type=password&client_id=admin-cli" "https://${KEYCLOAK_ADDRESS}/realms/master/protocol/openid-connect/token" | jq -r .access_token`
2828
```
2929

3030
Ensure the request was successful and the token is stored in the shell variable by running:
3131

32-
```console
32+
```shell
3333
echo $TOKEN
3434
```
3535

3636
***Note***: The access token lifespan is very short. If it expires between commands, retrieve it again with the
3737
command above.
3838

39-
1. Create the user `nginx-user`:
39+
3. Create the user `nginx-user`:
4040

41-
```console
41+
```shell
4242
curl -sS -k -X POST -d '{ "username": "nginx-user", "enabled": true, "credentials":[{"type": "password", "value": "test", "temporary": false}]}' -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://${KEYCLOAK_ADDRESS}/admin/realms/master/users
4343
```
4444

45-
1. Create the client `nginx-plus` and retrieve the secret:
46-
47-
```console
48-
SECRET=`curl -sS -k -X POST -d '{ "clientId": "nginx-plus", "redirectUris": ["https://webapp.example.com:443/_codexch"], "attributes": {"post.logout.redirect.uris": "https://webapp.example.com:443/*"}}' -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://${KEYCLOAK_ADDRESS}/realms/master/clients-registrations/default | jq -r .secret`
49-
```
50-
51-
If everything went well you should have the secret stored in $SECRET. To double check run:
52-
53-
```console
54-
echo $SECRET
55-
```
45+
4. Create the client `nginx-plus`:
46+
47+
- If you are not using PKCE, use the following command to create an OIDC client that does not use PKCE:
48+
49+
```shell
50+
SECRET=`curl -sS -k -X POST -d '{ "clientId": "nginx-plus", "redirectUris": ["https://webapp.example.com:443/_codexch"], "attributes": {"post.logout.redirect.uris": "https://webapp.example.com:443/*"}}' -H "Content-Type:application/json" -H "Authorization: bearer ${TOKEN}" https://${KEYCLOAK_ADDRESS}/realms/master/clients-registrations/default | jq -r .secret`
51+
```
52+
53+
If everything went well, you should have the secret stored in $SECRET. To double-check, run:
54+
55+
```shell
56+
echo $SECRET
57+
```
58+
59+
- Or if you are using PKCE with OIDC, use the following command to create the client:
60+
61+
```shell
62+
curl -sS -k -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" \
63+
--data '{
64+
"clientId": "nginx-plus",
65+
"enabled": true,
66+
"standardFlowEnabled": true,
67+
"directAccessGrantsEnabled": false,
68+
"publicClient": true,
69+
"redirectUris": [
70+
"https://webapp.example.com:443/_codexch"
71+
],
72+
"attributes": {
73+
"pkce.code.challenge.method":"S256",
74+
"post.logout.redirect.uris": "https://webapp.example.com:443/*"
75+
},
76+
"protocol": "openid-connect"
77+
}' \
78+
https://${KEYCLOAK_ADDRESS}/admin/realms/master/clients
79+
```

0 commit comments

Comments
 (0)