JWT Token
This task shows you how to set up an Istio authorization policy to enforce access based on a JSON Web Token (JWT). An Istio authorization policy supports string typed, list-of-string typed, and space-delimited string typed JWT claims.
Before you begin
Before you begin this task, do the following:
Complete the Istio end user authentication task.
Read the Istio authorization concepts.
Install Istio using Istio installation guide.
Deploy two workloads:
httpbinandcurl. Deploy these in one namespace, for examplefoo. Both workloads run with an Envoy proxy in front of each. Deploy the example namespace and workloads using these commands:$ kubectl create ns foo $ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n foo $ kubectl apply -f <(istioctl kube-inject -f @samples/curl/curl.yaml@) -n fooVerify that
curlsuccessfully communicates withhttpbinusing this command:$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl http://httpbin.foo:8000/ip -sS -o /dev/null -w "%{http_code}\n" 200
Allow requests with valid JWT and list-typed claims
The following command creates the
jwt-examplerequest authentication policy for thehttpbinworkload in thefoonamespace. This policy forhttpbinworkload accepts a JWT issued bytesting@secure.istio.io:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1 kind: RequestAuthentication metadata: name: "jwt-example" namespace: foo spec: selector: matchLabels: app: httpbin jwtRules: - issuer: "testing@secure.istio.io" jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json" EOFVerify that a request with an invalid JWT is denied:
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer invalidToken" -w "%{http_code}\n" 401Verify that a request without a JWT is allowed because there is no authorization policy:
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -w "%{http_code}\n" 200The following command creates the
require-jwtauthorization policy for thehttpbinworkload in thefoonamespace. The policy requires all requests to thehttpbinworkload to have a valid JWT withrequestPrincipalset totesting@secure.istio.io/testing@secure.istio.io. Istio constructs therequestPrincipalby combining theissandsubof the JWT token with a/separator as shown:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1 kind: AuthorizationPolicy metadata: name: require-jwt namespace: foo spec: selector: matchLabels: app: httpbin action: ALLOW rules: - from: - source: requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"] EOFGet the JWT that sets the
issandsubkeys to the same value,testing@secure.istio.io. This causes Istio to generate the attributerequestPrincipalwith the valuetesting@secure.istio.io/testing@secure.istio.io:$ TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/demo.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode {"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"testing@secure.istio.io","sub":"testing@secure.istio.io"}Verify that a request with a valid JWT is allowed:
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n" 200Verify that a request without a JWT is denied:
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -w "%{http_code}\n" 403The following command updates the
require-jwtauthorization policy to also require the JWT to have a claim namedgroupscontaining the valuegroup1:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1 kind: AuthorizationPolicy metadata: name: require-jwt namespace: foo spec: selector: matchLabels: app: httpbin action: ALLOW rules: - from: - source: requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"] when: - key: request.auth.claims[groups] values: ["group1"] EOFGet the JWT that sets the
groupsclaim to a list of strings:group1andgroup2:$ TOKEN_GROUP=$(curl https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/groups-scope.jwt -s) && echo "$TOKEN_GROUP" | cut -d '.' -f2 - | base64 --decode {"exp":3537391104,"groups":["group1","group2"],"iat":1537391104,"iss":"testing@secure.istio.io","scope":["scope1","scope2"],"sub":"testing@secure.istio.io"}Verify that a request with the JWT that includes
group1in thegroupsclaim is allowed:$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer $TOKEN_GROUP" -w "%{http_code}\n" 200Verify that a request with a JWT, which doesn’t have the
groupsclaim is rejected:$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n" 403
Allow requests with valid JWT and space-delimited claims
Some JWT claims encode multiple values as a single space-delimited string rather than a JSON
array. The OAuth2 scope claim is a well-known example of this pattern, defined in
RFC 6749 as a space-separated list.
Istio always treats the scope and permission claims as space-delimited, splitting them into
individual values that authorization policies can match against. For other custom claims that use
the same encoding, use the spaceDelimitedClaims field in your JWTRule to opt them in
explicitly. Without this field, a custom claim value like "admin editor" is matched as a
single exact string.
{"iss": "testing@secure.istio.io", "roles": "admin editor"}Download the JWT generation script and signing key:
$ wget --no-verbose https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/gen-jwt.py $ wget --no-verbose https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/key.pemThe following command updates the
jwt-examplerequest authentication policy to declare that therolesclaim is space-delimited:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1 kind: RequestAuthentication metadata: name: "jwt-example" namespace: foo spec: selector: matchLabels: app: httpbin jwtRules: - issuer: "testing@secure.istio.io" jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json" spaceDelimitedClaims: ["roles"] EOFThe following command updates the
require-jwtauthorization policy to require therolesclaim to include the valueadmin:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1 kind: AuthorizationPolicy metadata: name: require-jwt namespace: foo spec: selector: matchLabels: app: httpbin action: ALLOW rules: - from: - source: requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"] when: - key: request.auth.claims[roles] values: ["admin"] EOFGet a JWT whose
rolesclaim is set to the space-delimited string"admin editor":$ TOKEN_ROLES=$(python3 ./gen-jwt.py ./key.pem --claims "roles:admin editor")Verify that a request with that JWT is allowed, because
adminis present in the space-delimitedrolesclaim:$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer $TOKEN_ROLES" -w "%{http_code}\n" 200Get a JWT whose
rolesclaim does not includeadmin:$ TOKEN_NO_ADMIN=$(python3 ./gen-jwt.py ./key.pem --claims "roles:editor")Verify that the request is denied:
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer $TOKEN_NO_ADMIN" -w "%{http_code}\n" 403
Clean up
Remove the namespace foo:
$ kubectl delete namespace fooIf you downloaded gen-jwt.py and key.pem for the space-delimited claims section, remove them:
$ rm -f ./gen-jwt.py ./key.pem