update jwt claim based routing and add tests (#10465)

* update jwt claim based routing and add tests

* fix test

* fix typo

* fix test
This commit is contained in:
Yangmin Zhu 2021-11-09 09:11:52 -08:00 committed by GitHub
parent cfcd701321
commit 90c77c5583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 213 additions and 12 deletions

View File

@ -4,7 +4,7 @@ description: Shows you how to use Istio authentication policy to route requests
weight: 10
keywords: [security,authentication,jwt,route]
owner: istio/wg-security-maintainers
test: no
test: yes
status: Experimental
---
@ -72,6 +72,12 @@ identity and more secure compared using the unauthenticated HTTP attributes (e.g
The request authentication enables JWT validation on the Istio ingress gateway so that the validated JWT claims
can later be used in the virtual service for routing purposes.
The request authentication is applied on the ingress gateway because the JWT claim based routing is only supported
on ingress gateways.
Note: the request authentication will only check the JWT if it exists in the request. To make the JWT required and
reject the request if it does not include JWT, apply the authorization policy as specified in the [task](/docs/tasks/security/authentication/authn-policy#require-a-valid-token).
1. Update the virtual service to route based on validated JWT claims:
{{< text bash >}}
@ -91,7 +97,7 @@ identity and more secure compared using the unauthenticated HTTP attributes (e.g
- uri:
prefix: /headers
headers:
x-jwt-claim.groups: # "x-jwt-claim" is a reserved header for matching JWT claims only.
"@request.auth.claims.groups":
exact: group1
route:
- destination:
@ -101,8 +107,12 @@ identity and more secure compared using the unauthenticated HTTP attributes (e.g
EOF
{{< /text >}}
The virtual service uses the reserved header `x-jwt-claim` to match the validated JWT claims that are made available
by the request authentication.
The virtual service uses the reserved header `"@request.auth.claims.groups"` to match with the JWT claim `groups`.
The prefix `@` denotes it is matching with the metadata derived from the JWT validation and not with HTTP headers.
Claim of type string, list of string and nested claims are supported. Use the `.` as a separator for nested claim
names. For example, `"@request.auth.claims.name.givenName"` matches the nested claim `name` and `givenName`. Claim
name with the `.` character is currently not supported.
## Validating ingress routing based on JWT claims
@ -114,7 +124,7 @@ identity and more secure compared using the unauthenticated HTTP attributes (e.g
...
{{< /text >}}
You can also create the authorization policy to explicitly reject the request with HTTP code 403 when JWT is missing.
You can also create the authorization policy to explicitly reject the request with HTTP code 403 when JWT is missing.
1. Validate the ingress gateway returns the HTTP code 401 with invalid JWT:
@ -124,7 +134,7 @@ identity and more secure compared using the unauthenticated HTTP attributes (e.g
...
{{< /text >}}
The 401 is returned by the request authentication because the JWT failed the validation.
The 401 is returned by the request authentication because the JWT failed the validation.
1. Validate the ingress gateway routes the request with a valid JWT token that includes the claim `groups: group1`:
@ -152,12 +162,6 @@ identity and more secure compared using the unauthenticated HTTP attributes (e.g
...
{{< /text >}}
Note the `x-jwt-claim` is a reserved header name for matching the validated JWT claims only. It will not match the normal
HTTP headers.
Claims of type string or list of string are supported and nested claims are also supported using `.` as a separator for
claim names. For example, `x-jwt-claim.admin` matches the claim "admin" and `x-jwt-claim.group.id` matches the nested claims "group" and "id".
## Cleanup
* Remove the namespace `foo`:

View File

@ -0,0 +1,139 @@
#!/bin/bash
# shellcheck disable=SC2034,SC2153,SC2155,SC2164
# Copyright Istio Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
####################################################################################################
# WARNING: THIS IS AN AUTO-GENERATED FILE, DO NOT EDIT. PLEASE MODIFY THE ORIGINAL MARKDOWN FILE:
# docs/tasks/security/authentication/jwt-route/index.md
####################################################################################################
snip_before_you_begin_1() {
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/httpbin/httpbin-gateway.yaml) -n foo
}
snip_before_you_begin_2() {
curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
}
! read -r -d '' snip_before_you_begin_2_out <<\ENDSNIP
200
ENDSNIP
snip_configuring_ingress_routing_based_on_jwt_claims_1() {
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: ingress-jwt
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: "testing@secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.12/security/tools/jwt/samples/jwks.json"
EOF
}
snip_configuring_ingress_routing_based_on_jwt_claims_2() {
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
namespace: foo
spec:
hosts:
- "*"
gateways:
- httpbin-gateway
http:
- match:
- uri:
prefix: /headers
headers:
"@request.auth.claims.groups":
exact: group1
route:
- destination:
port:
number: 8000
host: httpbin
EOF
}
snip_validating_ingress_routing_based_on_jwt_claims_1() {
curl -s -I "http://$INGRESS_HOST:$INGRESS_PORT/headers"
}
! read -r -d '' snip_validating_ingress_routing_based_on_jwt_claims_1_out <<\ENDSNIP
HTTP/1.1 404 Not Found
...
ENDSNIP
snip_validating_ingress_routing_based_on_jwt_claims_2() {
curl -s -I "http://$INGRESS_HOST:$INGRESS_PORT/headers" -H "Authorization: Bearer some.invalid.token"
}
! read -r -d '' snip_validating_ingress_routing_based_on_jwt_claims_2_out <<\ENDSNIP
HTTP/1.1 401 Unauthorized
...
ENDSNIP
snip_validating_ingress_routing_based_on_jwt_claims_3() {
TOKEN_GROUP=$(curl https://raw.githubusercontent.com/istio/istio/release-1.12/security/tools/jwt/samples/groups-scope.jwt -s) && echo "$TOKEN_GROUP" | cut -d '.' -f2 - | base64 --decode -
}
! read -r -d '' snip_validating_ingress_routing_based_on_jwt_claims_3_out <<\ENDSNIP
{"exp":3537391104,"groups":["group1","group2"],"iat":1537391104,"iss":"testing@secure.istio.io","scope":["scope1","scope2"],"sub":"testing@secure.istio.io"}
ENDSNIP
snip_validating_ingress_routing_based_on_jwt_claims_4() {
curl -s -I "http://$INGRESS_HOST:$INGRESS_PORT/headers" -H "Authorization: Bearer $TOKEN_GROUP"
}
! read -r -d '' snip_validating_ingress_routing_based_on_jwt_claims_4_out <<\ENDSNIP
HTTP/1.1 200 OK
...
ENDSNIP
snip_validating_ingress_routing_based_on_jwt_claims_5() {
TOKEN_NO_GROUP=$(curl https://raw.githubusercontent.com/istio/istio/release-1.12/security/tools/jwt/samples/demo.jwt -s) && echo "$TOKEN_NO_GROUP" | cut -d '.' -f2 - | base64 --decode -
}
! read -r -d '' snip_validating_ingress_routing_based_on_jwt_claims_5_out <<\ENDSNIP
{"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"testing@secure.istio.io","sub":"testing@secure.istio.io"}
ENDSNIP
snip_validating_ingress_routing_based_on_jwt_claims_6() {
curl -s -I "http://$INGRESS_HOST:$INGRESS_PORT/headers" -H "Authorization: Bearer $TOKEN_NO_GROUP"
}
! read -r -d '' snip_validating_ingress_routing_based_on_jwt_claims_6_out <<\ENDSNIP
HTTP/1.1 404 Not Found
...
ENDSNIP
snip_cleanup_1() {
kubectl delete namespace foo
}
snip_cleanup_2() {
kubectl delete requestauthentication ingress-jwt -n istio-system
}

View File

@ -0,0 +1,58 @@
#!/usr/bin/env bash
# shellcheck disable=SC1090,SC2154
# Copyright Istio Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
set -u
set -o pipefail
# @setup profile=default
# Set retries to a higher value because config update is slow.
export VERIFY_TIMEOUT=300
snip_before_you_begin_1
_wait_for_deployment foo httpbin
# Export the INGRESS_ environment variables
_set_ingress_environment_variables
_verify_same snip_before_you_begin_2 "$snip_before_you_begin_2_out"
# Apply the request authentication and virtual service.
snip_configuring_ingress_routing_based_on_jwt_claims_1
snip_configuring_ingress_routing_based_on_jwt_claims_2
_verify_elided snip_validating_ingress_routing_based_on_jwt_claims_1 "$snip_validating_ingress_routing_based_on_jwt_claims_1_out"
_verify_elided snip_validating_ingress_routing_based_on_jwt_claims_2 "$snip_validating_ingress_routing_based_on_jwt_claims_2_out"
# Pull the Istio branch from the docs configuration file.
ISTIO_BRANCH=$(yq r "${REPO_ROOT}"/data/args.yml 'source_branch_name')
TOKEN_GROUP_URL="https://raw.githubusercontent.com/istio/istio/${ISTIO_BRANCH}/security/tools/jwt/samples/groups-scope.jwt"
export TOKEN_GROUP
TOKEN_GROUP=$(curl "${TOKEN_GROUP_URL}" -s)
_verify_elided snip_validating_ingress_routing_based_on_jwt_claims_4 "$snip_validating_ingress_routing_based_on_jwt_claims_4_out"
TOKEN_NO_GROUP_URL="https://raw.githubusercontent.com/istio/istio/${ISTIO_BRANCH}/security/tools/jwt/samples/demo.jwt"
export TOKEN_NO_GROUP
TOKEN_NO_GROUP=$(curl "${TOKEN_NO_GROUP_URL}" -s)
_verify_elided snip_validating_ingress_routing_based_on_jwt_claims_6 "$snip_validating_ingress_routing_based_on_jwt_claims_6_out"
# @cleanup
snip_cleanup_1
snip_cleanup_2