Module 3: Exercises
This exercise section provides hands-on practice with advanced DestinationRule features and Istio security. You will learn how to configure load balancing, implement failover and circuit breaking, set up request authentication, and create authorization policies.
Prerequisites
Before starting these exercises, ensure you have:
-
Access to an OpenShift cluster with OpenShift Service Mesh 3.4 (Istio 1.30) installed
-
Your namespace (
$NAMESPACE) configured with sidecar injection enabled -
The
ocCLI tool installed and configured -
Basic understanding of DestinationRules, VirtualServices, and Kubernetes Services
Exercise 1: Configure Load Balancing in DestinationRules
In this exercise, you will configure different load balancing algorithms in DestinationRules.
Step 1: Set Your Namespace
Ensure your namespace variable is set:
export NAMESPACE=user1-sandbox
The exercises send test requests with oc exec from the curl-client Deployment that you created in Module 1 (Exercise 1, Step 7). Check that it is running:
oc get deployment curl-client -n $NAMESPACE
Step 2: Deploy a Multi-Pod Service
Deploy a service with multiple replicas to observe load balancing:
oc apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: loadtest-service
namespace: ${NAMESPACE}
spec:
replicas: 3
selector:
matchLabels:
app: loadtest
version: v1
template:
metadata:
labels:
app: loadtest
version: v1
spec:
containers:
- name: nginx
image: quay.io/nginx/nginx-unprivileged:latest
ports:
- containerPort: 8080
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
---
apiVersion: v1
kind: Service
metadata:
name: loadtest-service
namespace: ${NAMESPACE}
spec:
selector:
app: loadtest
ports:
- port: 80
targetPort: 8080
name: http
EOF
Wait for all pods to be ready:
oc get pods \
-n $NAMESPACE \
-l app=loadtest
Step 3: Create DestinationRule with Round-Robin Load Balancing
Create a DestinationRule with round-robin load balancing:
oc apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: loadtest-service
namespace: ${NAMESPACE}
spec:
host: loadtest-service
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
EOF
Verify the DestinationRule:
oc get destinationrule loadtest-service \
-n $NAMESPACE \
-o yaml
Step 4: Test Round-Robin Load Balancing
Send multiple requests and observe the distribution. Check the logs to see which pod handles each request:
for i in {1..9}; do
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -s http://loadtest-service > /dev/null
done
Check the access logs to see the distribution:
oc logs \
-n $NAMESPACE \
-l app=loadtest \
-c istio-proxy \
--tail=20 |
grep -i "GET"
Step 5: Change to Least-Connection Load Balancing
Update the DestinationRule to use least-connection load balancing:
oc apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: loadtest-service
namespace: ${NAMESPACE}
spec:
host: loadtest-service
trafficPolicy:
loadBalancer:
simple: LEAST_CONN
EOF
Test the least-connection algorithm:
for i in {1..9}; do
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -s http://loadtest-service > /dev/null
done
Exercise 2: Implement Failover and Outlier Detection
In this exercise, you will configure outlier detection so that the client sidecars stop sending requests to an endpoint that returns errors, and combine it with retries so that requests fail over to a healthy endpoint.
Envoy’s outlier detection counts the responses that it receives from each endpoint (upstream host). Faults that a VirtualService injects are not counted: the client sidecar generates a fault.abort response itself without contacting an endpoint, and a fault.delay only postpones a request that then succeeds. This exercise therefore deploys one version of a service that really returns 503 Service Unavailable.
Step 1: Deploy a Healthy and a Failing Version
Deploy two versions of failover-service behind one Service: v1 serves the nginx welcome page, and v2 uses an nginx configuration from a ConfigMap that answers every request with 503:
oc apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: failover-v2-nginx
namespace: ${NAMESPACE}
data:
default.conf: |
server {
listen 8080;
location / {
return 503;
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: failover-v1
namespace: ${NAMESPACE}
spec:
replicas: 1
selector:
matchLabels:
app: failover
version: v1
template:
metadata:
labels:
app: failover
version: v1
spec:
containers:
- name: nginx
image: quay.io/nginx/nginx-unprivileged:latest
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: failover-v2
namespace: ${NAMESPACE}
spec:
replicas: 1
selector:
matchLabels:
app: failover
version: v2
template:
metadata:
labels:
app: failover
version: v2
spec:
containers:
- name: nginx
image: quay.io/nginx/nginx-unprivileged:latest
ports:
- containerPort: 8080
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d
volumes:
- name: nginx-conf
configMap:
name: failover-v2-nginx
---
apiVersion: v1
kind: Service
metadata:
name: failover-service
namespace: ${NAMESPACE}
spec:
selector:
app: failover
ports:
- port: 80
targetPort: 8080
name: http
EOF
Wait for both pods to be ready and note their IP addresses:
oc rollout status deployment/failover-v1 -n $NAMESPACE
oc rollout status deployment/failover-v2 -n $NAMESPACE
oc get pods \
-n $NAMESPACE \
-l app=failover \
-o wide
Step 2: Create DestinationRule with Outlier Detection
Create a DestinationRule with a subset for each version and outlier detection for the whole service:
oc apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: failover-service
namespace: ${NAMESPACE}
spec:
host: failover-service
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 3
interval: 10s
baseEjectionTime: 60s
maxEjectionPercent: 50
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
EOF
With this configuration, an endpoint that returns three 5xx responses in a row is ejected from the load balancing pool for 60 seconds. At most 50% of the endpoints, here one of the two, can be ejected at the same time. The subsets inherit the outlier detection settings and let you route to a single version, as in Module 2.
Step 3: Make the Failures Visible
By default, Istio retries a request that fails with 503 up to two times on a different endpoint, so the client would hardly ever see the errors of v2. Disable retries for failover-service with a VirtualService to watch the outlier detection on its own:
oc apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: failover-service
namespace: ${NAMESPACE}
spec:
hosts:
- failover-service
http:
- route:
- destination:
host: failover-service
retries:
attempts: 0
EOF
Step 4: Test Outlier Detection
Send 20 requests and print the status code of each response:
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
sh -c \
'for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" http://failover-service; done'
The first requests are spread over both versions, so you see a mix of 200 and 503. After the third 503 from v2, the client sidecar ejects the v2 endpoint, and all following requests return 200.
Check the endpoints of failover-service in the client sidecar. The OUTLIER CHECK column shows FAILED for the ejected v2 endpoint (compare the IP address with the output of Step 1) and OK for v1:
POD_NAME=$(oc get pod \
-n $NAMESPACE \
-l app=curl-client \
-o jsonpath='{.items[0].metadata.name}')
istioctl proxy-config endpoint $POD_NAME \
-n $NAMESPACE \
--cluster \
"outbound|80||failover-service.$NAMESPACE.svc.cluster.local"
Without istioctl, read the same information from the Envoy admin interface of the sidecar. The ejected endpoint has the health flag /failed_outlier_check:
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c istio-proxy -- \
pilot-agent request GET clusters |
grep "outbound|80||failover-service" |
grep health_flags
The ejection ends after baseEjectionTime (60 seconds), and the endpoint returns to the load balancing pool; Envoy checks this every interval (10 seconds). If you repeat the requests after that, v2 again returns three 503 responses before it is ejected once more, possibly for longer: Envoy multiplies baseEjectionTime by the number of times that an endpoint has recently been ejected.
|
Step 5: Fail Over with Retries
Outlier detection only reacts after an endpoint has failed several times. Retries on a different endpoint hide these failures from the client. Replace the VirtualService with one that retries 5xx responses:
oc apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: failover-service
namespace: ${NAMESPACE}
spec:
hosts:
- failover-service
http:
- route:
- destination:
host: failover-service
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx
EOF
Wait until the ejection of v2 has ended (about two minutes), then send the requests again:
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
sh -c \
'for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" http://failover-service; done'
All requests return 200. A request that reaches v2 gets a 503, and the client sidecar retries it on another endpoint, v1. The 503 responses still count for outlier detection, so v2 is ejected again after three of them. Check it with the commands of Step 4.
Exercise 3: Implement Circuit Breaking
In this exercise, you will configure circuit breaking to prevent cascading failures.
Step 1: Deploy a Service for Circuit Breaking
Deploy a service that we’ll use to test circuit breaking:
oc apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: circuit-service
namespace: ${NAMESPACE}
spec:
replicas: 2
selector:
matchLabels:
app: circuit
template:
metadata:
labels:
app: circuit
spec:
containers:
- name: nginx
image: quay.io/nginx/nginx-unprivileged:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: circuit-service
namespace: ${NAMESPACE}
spec:
selector:
app: circuit
ports:
- port: 80
targetPort: 8080
EOF
Step 2: Create DestinationRule with Circuit Breaking
Create a DestinationRule with circuit breaking configuration:
oc apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: circuit-service
namespace: ${NAMESPACE}
spec:
host: circuit-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 2
http:
http1MaxPendingRequests: 2
http2MaxRequests: 2
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 2
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 100
EOF
Step 3: Verify Circuit Breaking Configuration
Check the DestinationRule:
oc get destinationrule circuit-service \
-n $NAMESPACE \
-o yaml
Step 4: Test Circuit Breaking
Deploy a client that will generate load to test circuit breaking:
oc apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: circuit-client
namespace: ${NAMESPACE}
spec:
replicas: 1
selector:
matchLabels:
app: circuit-client
template:
metadata:
labels:
app: circuit-client
spec:
containers:
- name: curl
image: quay.io/curl/curl:latest
command: ["sleep", "3600"]
EOF
Send multiple concurrent requests to trigger circuit breaking:
POD_NAME=$(oc get pod \
-n $NAMESPACE \
-l app=circuit-client \
-o jsonpath='{.items[0].metadata.name}')
oc exec \
-n $NAMESPACE \
$POD_NAME -- \
sh -c \
'for i in $(seq 1 10); do curl -s http://circuit-service & done; wait'
Check the logs to see circuit breaking behavior:
oc logs \
-n $NAMESPACE \
-l app=circuit-client \
-c istio-proxy \
--tail=20
oc logs \
-n $NAMESPACE \
-l app=circuit \
-c istio-proxy \
--tail=20
Some of the concurrent requests exceed the connection pool limits. The client sidecar rejects them immediately with 503 without sending them to circuit-service; in the client’s access log these requests have the response flag UO (upstream overflow) and do not appear in the logs of circuit-service.
Exercise 4: Configure Request Authentication with JWT
In this exercise, you will set up RequestAuthentication to validate JWT tokens.
Step 1: Deploy a Test Application
Deploy an application that will require authentication:
oc apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-service
namespace: ${NAMESPACE}
spec:
replicas: 2
selector:
matchLabels:
app: auth-service
template:
metadata:
labels:
app: auth-service
spec:
containers:
- name: nginx
image: quay.io/nginx/nginx-unprivileged:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: auth-service
namespace: ${NAMESPACE}
spec:
selector:
app: auth-service
ports:
- port: 80
targetPort: 8080
EOF
Step 2: Create a Simple JWT for Testing
For this exercise, you use the sample JWT issuer testing@secure.istio.io from the Istio project. In production, you would use a proper authentication service. Create a RequestAuthentication policy. The jwks field contains the public key of the issuer (a JSON Web Key Set) inline, so the sidecars do not need to fetch it from a URL:
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: jwt-auth
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: auth-service
jwtRules:
- issuer: "testing@secure.istio.io"
jwks: '{"keys":[{"e":"AQAB","kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ","kty":"RSA","n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"}]}'
EOF
The key and the matching test token are Istio’s samples jwks.json and demo.jwt from security/tools/jwt/samples in the Istio repository. In production, use your own JWT issuer, and usually a jwksUri that points to the JWKS endpoint of the issuer, so that rotated keys are picked up automatically.
|
Step 3: Verify RequestAuthentication
Check that the RequestAuthentication was created:
oc get requestauthentication -n $NAMESPACE
oc get requestauthentication jwt-auth \
-n $NAMESPACE \
-o yaml
Step 4: Test Without JWT Token
Try to access the service without a JWT token:
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v http://auth-service
The request should succeed because RequestAuthentication only validates tokens if they are present. To require tokens, you need AuthorizationPolicy (covered in the next exercise).
Step 5: Test With Invalid JWT Token
Test with an invalid token:
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v -H "Authorization: Bearer invalid-token" http://auth-service
Step 6: Test With Valid JWT Token
Set TOKEN to Istio’s sample token demo.jwt. It is signed with the key from the RequestAuthentication, and both its issuer (iss) and subject (sub) are testing@secure.istio.io:
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg
echo $TOKEN
Test with a valid token:
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v -H "Authorization: Bearer $TOKEN" http://auth-service
Exercise 5: Create Authorization Policies
In this exercise, you will create AuthorizationPolicies to control access to your services.
Step 1: Create an AuthorizationPolicy to Require Authentication
Create an AuthorizationPolicy that requires a valid JWT token:
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: auth-service
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["*"]
EOF
This policy allows requests from any authenticated principal (any valid JWT).
Step 2: Test Authorization Policy
Try accessing without a token (should be denied):
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v http://auth-service
The sidecar of curl-client reuses its connections to auth-service, and a request on a connection that was opened before a policy change can still be evaluated with the previous policy. If the first request after a change (up to one per auth-service pod) does not show the expected result, run the command again. This applies to every policy change in this module.
|
Try with a valid token (should be allowed):
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v -H "Authorization: Bearer $TOKEN" http://auth-service
Step 3: Create Method-Based Authorization
ALLOW policies are combined with OR: a request is allowed if it matches any ALLOW policy that applies to the workload. As long as require-jwt exists, it allows every authenticated request, whatever its method. Replace it with an AuthorizationPolicy that only allows GET and POST methods:
oc delete authorizationpolicy require-jwt -n $NAMESPACE
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-methods
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: auth-service
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["*"]
to:
- operation:
methods: ["GET", "POST"]
EOF
Step 4: Test Method-Based Authorization
Test with GET (should work):
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v -X GET -H "Authorization: Bearer $TOKEN" http://auth-service
Test with DELETE (should be denied with 403 and RBAC: access denied):
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v \
-X DELETE \
-H "Authorization: Bearer $TOKEN" \
http://auth-service
A 405 Not Allowed page comes from nginx, not from the mesh: it means that the authorization policies let the request through.
|
Step 5: Create Path-Based Authorization
Replace the method-based policy with an AuthorizationPolicy that restricts access to specific paths. If both ALLOW policies existed, allow-methods would still allow GET and POST requests to every path:
oc delete authorizationpolicy allow-methods -n $NAMESPACE
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-paths
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: auth-service
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["*"]
to:
- operation:
paths: ["/api/*", "/public/*"]
EOF
Step 6: Create a Deny Policy
Create an explicit deny policy for admin endpoints:
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-admin
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: auth-service
action: DENY
rules:
- to:
- operation:
paths: ["/admin/*"]
EOF
oc apply may print a warning that the policy will deny all traffic to TCP ports in some cases because it uses HTTP-only attributes. The auth-service port is HTTP, so the warning does not apply here.
Test the deny policy:
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v \
-H "Authorization: Bearer $TOKEN" \
http://auth-service/admin/test
Exercise 6: Combine Authentication and Authorization
In this exercise, you will combine RequestAuthentication and AuthorizationPolicy for complete security.
Step 1: Deploy a New Service
Deploy a service for the combined authentication and authorization exercise:
oc apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-service
namespace: ${NAMESPACE}
spec:
replicas: 2
selector:
matchLabels:
app: secure-service
template:
metadata:
labels:
app: secure-service
spec:
containers:
- name: nginx
image: quay.io/nginx/nginx-unprivileged:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: secure-service
namespace: ${NAMESPACE}
spec:
selector:
app: secure-service
ports:
- port: 80
targetPort: 8080
EOF
Step 2: Create RequestAuthentication
Create a RequestAuthentication policy for the service:
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: secure-jwt-auth
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: secure-service
jwtRules:
- issuer: "testing@secure.istio.io"
jwks: '{"keys":[{"e":"AQAB","kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ","kty":"RSA","n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"}]}'
EOF
Step 3: Create Comprehensive AuthorizationPolicy
Create an AuthorizationPolicy that combines multiple rules:
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: secure-policy
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: secure-service
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["testing@secure.istio.io/*"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]
- from:
- source:
requestPrincipals: ["testing@secure.istio.io/*"]
to:
- operation:
methods: ["GET"]
paths: ["/public/*"]
EOF
Step 4: Create Deny Policy for Admin
Create a deny policy for admin endpoints:
oc apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-secure-admin
namespace: ${NAMESPACE}
spec:
selector:
matchLabels:
app: secure-service
action: DENY
rules:
- to:
- operation:
paths: ["/admin/*"]
EOF
Step 5: Test Complete Security Configuration
Test without token (should be denied):
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v http://secure-service/api/test
Test with valid token on allowed path (should work):
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v \
-H "Authorization: Bearer $TOKEN" \
http://secure-service/api/test
The demo application has no /api/test or /public/test page, so an allowed request returns 404 Not Found from nginx. A request denied by the mesh returns 403 with RBAC: access denied.
|
Test with valid token on denied path (should be denied):
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v \
-H "Authorization: Bearer $TOKEN" \
http://secure-service/admin/test
Test with valid token on public path (should work):
oc exec \
-n $NAMESPACE \
deploy/curl-client \
-c curl -- \
curl -v \
-H "Authorization: Bearer $TOKEN" \
http://secure-service/public/test
Step 6: Verify Complete Configuration
View all security policies:
oc get requestauthentication,authorizationpolicy -n $NAMESPACE
View detailed configurations:
oc get requestauthentication secure-jwt-auth \
-n $NAMESPACE \
-o yaml
oc get authorizationpolicy secure-policy \
-n $NAMESPACE \
-o yaml
oc get authorizationpolicy deny-secure-admin \
-n $NAMESPACE \
-o yaml
Summary
In these exercises, you have:
-
Configured different load balancing algorithms (round-robin, least-connection, random) in DestinationRules
-
Implemented outlier detection and failover behavior
-
Configured circuit breaking to prevent cascading failures
-
Set up RequestAuthentication to validate JWT tokens
-
Created AuthorizationPolicies with various rules (method-based, path-based, deny policies)
-
Combined authentication and authorization for complete security
These advanced features enable you to build resilient, secure microservices with fine-grained access control.
Troubleshooting
If you encounter issues:
-
Load balancing not working: Verify the DestinationRule is applied and check that multiple endpoints are available
-
Failover not triggering: Check outlier detection settings and ensure an endpoint really returns
5xxresponses (faults injected by a VirtualService are not counted). Istio retries503responses by default, so setretries.attempts: 0to see the errors -
Circuit breaker not opening: Verify connection pool limits are appropriate and that load is sufficient
-
JWT authentication failing: Verify that the token is signed with a key from the
jwks(orjwksUri) of the RequestAuthentication, that itsissclaim matches theissuer, and that the token format is correct -
Authorization denying valid requests: Check the request principal matches the policy rules and verify policy order (DENY takes precedence)
-
Policies not applying: Verify selector labels match the workload labels and check namespace scope