Sentence app
Sentence app¶
This section is a sample implementation of OpenID Connect Authentication for Kubernetes with Okta and NGINX Ingress Controller. Regarding Okta configuration, read this blog
Web UI access¶
Exercise 1: Flow 2 | Client >> Frontend¶
Using your web browser, try to access to
https://sentence-front{{site_ID}}.f5app.dev
output:
Check that web frontend access is well protected by NGINX App Protect:
https://sentence-front{{site_ID}}.f5app.dev?<script>
output:
<html><head><title>Request Rejected</title></head><body>The requested URL was rejected.
Please consult with your administrator.<br><br>
Your support ID is: 4096465330496922252
<br><br><a href='javascript:history.back();'>[Go Back]</a></body></html>
Exercise 2: Flow 3 | Frontend >> Internal API¶
Install
curl
kubectl create ns debug
kubectl run multitool --image=praqma/network-multitool -n debug
kubectl exec -it multitool -n debug -- sh
Connect to Internal API endpoint /name
curl http://apigw-microapigw.lab4-sentence-api/name
output:
{
"adjectives": "calm",
"animals": "whale",
"colors": "green",
"locations": "valley"
}
Exercise 3: Flow 4 | Generator >> Internal API¶
Connect to Internal API endpoint /adjectives
curl http://apigw-microapigw.lab4-sentence-api/adjectives
output:
[
{
"id": 1,
"name": "kind"
},
{
"id": 2,
"name": "proud"
},
{
"id": 3,
"name": "calm"
}
]
Partner API access¶
Flows 2-7: Functional view¶
Partner API access enable OpenID Connect integration for NGINX Plus as introduced here and described in details here.
Implementation done for this lab:
Okta as an identity provider (IdP)
authorization code flow
NGINX Ingress Controller is configured as a relying party
Okta knows our internal Ingress Controller as a confidential client or a public client using PKCE
In this lab, both the client and NGINX Ingress Controller communicate directly with the IdP at different stages during the initial authentication event.
Exercise 1: Flow 3 | Client >> Okta¶
Using a Web Browser, connect to oidcdebugger
- Fill the form:
Authorize URI:
https://dev-431905.okta.com/oauth2/aus2zvhijqcz1rlq84x7/v1/authorizeRedirect URI:
https://oidcdebugger.com/debugClient ID:
0oa2zvfps3gsoYGHm4x7Scope:
openidState:
FranceNonce:
39pog581mp9Response type:
codeResponse mode:
query
Authenticate user with username
cloudbuilder@acme.comand passwordF5-AKS-KIC-lab!
You should receive a response code
Exercise 2: API GW - K8S configuration¶
View OIDC configuration in VirtualServerRoute resource
kubectl describe virtualserverroute -n lab4-sentence-api generator | grep -A 13 Spec
output:
Host: sentence-api1.f5app.dev
Ingress Class Name: sentence-api-nginx-internal
Subroutes:
Action:
Pass: generator
Path: /
Policies:
Name: oidc-policy
Namespace: lab4-sentence-api
Upstreams:
Name: generator
Port: 80
Service: generator
View OIDC policy resource
kubectl describe policy -n lab4-sentence-api oidc-policy | grep -A 7 Spec
output:
Oidc:
Auth Endpoint: https://dev-431905.okta.com/oauth2/aus2zvhijqcz1rlq84x7/v1/authorize
Client ID: 0oa2zvfps3gsoYGHm4x7
Client Secret: oidc-secret
Jwks URI: https://dev-431905.okta.com/oauth2/aus2zvhijqcz1rlq84x7/v1/keys
Scope: openid
Token Endpoint: https://dev-431905.okta.com/oauth2/aus2zvhijqcz1rlq84x7/v1/token
Capture The Flag
2.1 What is the URI that publish Public Key used by IC to check signature of JWT?
2.2 What is the secret length in bytes?
Exercise 3: API GW - NGINX configuration¶
NGINX Ingress Controller is configured to perform OpenID Connect authentication.
Connect to internal IC (apigw)
kubectl get pods --namespace lab4-sentence-api
kubectl exec --namespace lab4-sentence-api -it apigw-7795fd75c9-492rl bash
View configuration in Location block
grep oidc /etc/nginx/conf.d/vs_lab4-sentence-api_sentence-api-internal.conf
output:
include oidc/oidc.conf;
set $oidc_pkce_enable 0;
set $oidc_logout_redirect "/_logout";
set $oidc_hmac_key "sentence-api-internal";
set $oidc_authz_endpoint "https://dev-431905.okta.com/oauth2/aus2zvhijqcz1rlq84x7/v1/authorize";
set $oidc_token_endpoint "https://dev-431905.okta.com/oauth2/aus2zvhijqcz1rlq84x7/v1/token";
set $oidc_jwt_keyfile "https://dev-431905.okta.com/oauth2/aus2zvhijqcz1rlq84x7/v1/keys";
set $oidc_scopes "openid";
set $oidc_client "0oa2zvfps3gsoYGHm4x7";
set $oidc_client_secret "-lzR61blfzTcU05FEohmXZ0HwPhjZJPGAPmJpSk-";
View configuration of OIDC code exchange request received by client
grep -A 5 _codexch /etc/nginx/oidc/oidc.conf
output:
#set $redir_location "/_codexch";
location = /_codexch {
# This location is called by the IdP after successful authentication
status_zone "OIDC code exchange";
js_content oidc.codeExchange;
error_page 500 502 504 @oidc_error;
}
View configuration of OIDC token request sent by NGINX Ingress Controller to Okta
grep -A 10 /_token /etc/nginx/oidc/oidc.conf
output:
Exercise 4: API GW - OIDC flows¶
Upon a first visit to a protected resource, API GW initiates the OpenID Connect authorization code flow and redirects the client to the OpenID Connect provider (Okta).
Using your Web brower, open Developper tools (F12 or Ctrl + Shift + i)
Browse to
https://sentence-api{{site_ID}}.f5app.dev/Click on request
https://sentence-api{{site_ID}}.f5app.dev/
Capture The Flag
4.1 In parameters of the request, what is the PATH of the redirect uri?
Authenticate user with username
cloudbuilder@acme.comand passwordF5-AKS-KIC-lab!
The client returns to API GW with an authorization code.
API GW exchanges that code for a set of tokens by communicating directly with the IdP. The ID Token received from the IdP is validated.
grep -A 8 /_id_token_validation /etc/nginx/oidc/oidc.conf
output:
location = /_id_token_validation {
# This location is called by oidcCodeExchange() and oidcRefreshRequest(). We use
# the auth_jwt_module to validate the OpenID Connect token response, as per:
# https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
internal;
auth_jwt "" token=$arg_token;
js_content oidc.validateIdToken;
error_page 500 502 504 @oidc_error;
}
API GW then stores the ID token in the key-value store, issues a session cookie to the client using a random string, (which becomes the key to obtain the ID token from the key-value store) and redirects the client to the original URI requested prior to authentication.
Click on request
https://sentence-api{{site_ID}}.f5app.dev/_codexch
Subsequent requests to protected resources are authenticated by exchanging the session cookie for the ID Token in the key-value store.
JWT validation is performed on each request, as normal, so that the ID Token validity period is enforced. For more information on OpenID Connect and JWT validation with NGINX Plus, see Authenticating Users to Existing Applications with OpenID Connect and NGINX Plus.
Browse on
https://sentence-api{{site_ID}}.f5app.dev/adjectives
Capture The Flag
4.2 What is the cookie name for which the value will be exchanged for the ID Token in the key-value store?
Exercise 5: Web Application and API Protection (WAAP)¶
The OpenAPI Specification defines the spec file format needed to describe RESTful APIs. The spec file can be written either in JSON or YAML.
- View the difference for
/nameendpoint between Sentence’s openAPI spec files:
- View the difference for
API protection policy is using an OpenAPI Specification file to import the details of the APIs. NGINX App Protect will automatically create a policy for the following properties (depending on what’s included in the spec file):
Methods
URLs
Parameters
JSON profiles
Using a Web browser, access to
https://sentence-api{{site_ID}}.f5app.dev/colors/1
output:
{
"id": 1,
"name": "red"
}
Using a Web browser, access to
https://sentence-api{{site_ID}}.f5app.dev/colors/tataand the error message in JSON format
output:
{"supportID": "17736894413136180189"}
Capture The Flag
5.1 For /colors/{id} endpoint, what is the defined type for parameter name ‘id’?
Connect to Kibana
https://kibana{{site_ID}}.f5app.dev/Dashboard >> Overview >> add a filter
vs_name is sentence-api{{site_ID}}f5app.dev
Capture The Flag
5.2 What is the violation raised for request ``/colors/tata``?
Exercise 6: WAAP configuration¶
View VirtualServer specifications
kubectl describe virtualservers -n lab4-sentence-api sentence-api | grep -A 100 Spec
output:
Host: sentence-api1.f5app.dev
Ingress Class Name: nginx-external
Policies:
Name: waf-sentence-api
Namespace: lab4-sentence-api
Routes:
Path: /_codexch
Route: lab4-sentence-api/codexch
Action:
Pass: apigw
Path: /
Tls:
Redirect:
Based On: scheme
Code: 301
Enable: true
Secret: sentence-api-secret-tls
Upstreams:
Name: apigw
Port: 80
Service: apigw-microapigw
View WAF policy specifications
kubectl describe policy -n lab4-sentence-api waf-sentence-api | grep -A 100 Spec
output:
Waf:
Ap Policy: external-ingress-controller/sentence-api
Enable: true
Security Log:
Ap Log Conf: external-ingress-controller/naplogformat
Enable: true
Log Dest: syslog:server=10.1.0.10:5144
View App Protect policy and the reference to OpenAPI spec file partner API
kubectl describe appolicy -n external-ingress-controller sentence-api | grep -A 100 Spec
output:
Exercise 7: WAAP configuration and API infra endpoint¶
/_codexch is not present in openAPI spec file because it is an infrastructure API endpoint required for OIDC.
View VirtualServerRoute resource for
/_codexchendpoint
kubectl describe virtualserverroute -n lab4-sentence-api codexch | grep -A 100 Spec
output:
View that WAF is disabled for
/_codexch
kubectl describe policy -n lab4-sentence-api waf-disable | grep -A 2 Spec
output:
Waf:
Enable: false
Note
Ingress resource type does not allow to disable a WAF policy on a Location, only VirtualServerRoute does
Exercise 8: Rate Limit¶
The rate limit policy configures NGINX to limit the processing rate of requests.
View VirtualServer specification
kubectl describe virtualservers -n lab4-sentence-api sentence-api-internal | grep -A 5 Spec
output:
Host: sentence-api1.f5app.dev
Ingress Class Name: sentence-api-nginx-internal
Policies:
Name: rate-limit-sentence-api
Namespace: lab4-sentence-api
View Rate Limit Policy
kubectl describe policy -n lab4-sentence-api rate-limit-sentence-api | grep -A 5 Spec
output:
Rate Limit:
Key: ${request_uri}
Rate: 5r/m
Zone Size: 10M
Using a Web browser, refresh page
https://sentence-api{{site_ID}}.f5app.dev/quickly until a503response codeView Access logs of API GW
kubectl logs --namespace lab4-sentence-api apigw-7795fd75c9-492rl
output:
10.1.1.55 - - [19/May/2021:20:57:11 +0000] "GET /colors/1 HTTP/1.1" 302 145 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62" "91.165.208.158"
2021/05/19 20:57:13 [error] 175#175: *326 limiting requests, excess: 0.848 by zone "pol_rl_lab4-sentence-api_rate-limit-sentence-api_lab4-sentence-api_sentence-api-internal", client: 10.1.1.55, server: sentence-api1.f5app.dev, request: "GET /colors/1 HTTP/1.1", host: "sentence-api1.f5app.dev"
10.1.1.55 - - [19/May/2021:20:57:13 +0000] "GET /colors/1 HTTP/1.1" 503 599 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62" "91.165.208.158"
Capture The Flag
8.1 What is the rate limit threshold per second allowed in this configuration? (3 decimal places)