Route-level JWT policy
This feature is available in Gloo Gateway Enterprise only. If you are using the open source version of Gloo, these instructions do not work. Route-level JWT policy is available in version 1.18.0 or later.
You can create a JWT policy that applies to specific routes with the VirtualService resource.
About
Review how the VirtualService route option and VirtualHost option configurations interact with each other in the following table.
The scenarios correspond with the steps in this guide. Each builds upon the previous scenario, updating the resources to test different scenarios. For example, you might update the stage of a JWT policy in a VirtualHost, or create a VirtualService with a JWT policy for a different issuer and JWKS provider.
Scenario | Route option | VirtualHost option | Behavior |
---|---|---|---|
1 - Protect all routes on the gateway | No JWT policy | JWT policy | All routes on the gateway require a JWT from the provider in the VirtualHost. |
2 - Protect a specific route | JWT policy | No JWT policy | Only the routes that are configured by the route option require a JWT. |
3 - Revise same-stage conflicts | JWT policy at same stage | JWT policy at same stage | For routes configured by the route option, the route option configuration overrides the VirtualHost configuration. These routes require only the JWT that meets the route option JWT policy. JWTs that meet the VirtualHost do not work on these routes. Other routes on the gateway still require a JWT that meets the VirtualHost JWT policy. You cannot use a JWT with the provider from the route option for the other routes on the gateway. |
4 - Set up separate stages | JWT policy at different stage | JWT policy at different stage | The routes that are configured by the route option require two JWTs: one from the provider in the route option, and one from the provider in the VirtualHost. Make sure to configure the token source to come from different headers so that requests can pass in both tokens. Note that if you also configure RBAC policies on both options, the route option’s RBAC policy takes precedence because only one RBAC policy is supported per route. |
5 - Add validation policy | JWT policy at different stage | JWT policy at different stage with validation policy | Depending on the validation policy, the routes that are configured by the route option require at least one JWT. When the validationPolicy field is set to ALLOW_MISSING or ALLOW_MISSING_OR_FAILED , the JWT can be for the provider that is configured in either the route option or the VirtualHost. Note that if you set a permissive validation policy on both options, the route does not require any JWT authentication. Make sure to set up the validation policy according to your security requirements. |
6 - Delegated routes | Different JWT policies select delegated routes | N/A | For delegated routes, the JWT policies in the child route option override the parent route option when they differ. The same configuration interactions between the route-level options and the gateway-level VirtualHost options apply, as described in the previous scenarios. |
Before you begin
-
Deploy the sample httpbin app.
kubectl apply -f- <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: httpbin --- apiVersion: v1 kind: Service metadata: name: httpbin labels: app: httpbin spec: ports: - name: http port: 8000 targetPort: 80 selector: app: httpbin --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: labels: app: httpbin version: v1 spec: serviceAccountName: httpbin containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 80 EOF
-
Create an Upstream for the httpbin destination.
kubectl apply -f- <<EOF apiVersion: gloo.solo.io/v1 kind: Upstream metadata: name: httpbin spec: kube: serviceName: httpbin serviceNamespace: default servicePort: 8000 EOF
Scenario 1: All routes on gateway
Protect multiple routes that are configured on the gateway by configuring the options for a VirtualHost.
-
Create a VirtualService with a VirtualHost as in the following example.
- The
domains
section sets the host domain that the VirtualHost listens on towww.example.com
. - The
options
section creates a JWT policy with the details of the JSON Web Key Set (JWKS) server to use to verify the signature of JWTs in future protected requests. - The
routes
section of the VirtualHost defines two routes,/get
and/status/200
.
kubectl apply -f - <<EOF apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin namespace: default spec: virtualHost: domains: - 'www.example.com' options: jwt: providers: vhost-gateway-provider: issuer: solo.io jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskFAGESgB22iOsGk/UgX BXTmMtd8R0vphvZ4RkXySOIra/vsg1UKay6aESBoZzeLX3MbBp5laQenjaYJ3U8P QLCcellbaiyUuE6+obPQVIa9GEJl37GQmZIMQj4y68KHZ4m2WbQVlZVIw/Uw52cw eGtitLMztiTnsve0xtgdUzV0TaynaQrRW7REF+PtLWitnvp9evweOrzHhQiPLcdm fxfxCbEJHa0LRyyYatCZETOeZgkOHlYSU0ziyMhHBqpDH1vzXrM573MQ5MtrKkWR T4ZQKuEe0Acyd2GhRg9ZAxNqs/gbb8bukDPXv4JnFLtWZ/7EooKbUC/QBKhQYAsK bQIDAQAB -----END PUBLIC KEY----- routes: - matchers: - prefix: /get routeAction: single: upstream: name: httpbin namespace: default - matchers: - prefix: /status/200 routeAction: single: upstream: name: httpbin namespace: default EOF
- The
-
Send unauthenticated requests to both routes to verify that the JWT policy in the VirtualHost applies to all routes on the gateway.
curl -vik $(glooctl proxy url)/get -H "host: www.example.com" curl -vik $(glooctl proxy url)/status/200 -H "host: www.example.com"
curl -vik localhost:31500/get -H "host: www.example.com" curl -vik localhost:31500/status/200 -H "host: www.example.com"
Example output:
HTTP/1.1 401 Unauthorized www-authenticate: Bearer realm="http://www.example.com/get" Jwt is missing ... HTTP/1.1 401 Unauthorized www-authenticate: Bearer realm="http://www.example.com/status/200" Jwt is missing ...
-
Create two environment variables to save the JWT tokens for the users Alice and Bob. To review the details of the token, you can use the jwt.io website. Optionally, you can create other JWT tokens by using the JWT generator tool.
export ALICE_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyAiaXNzIjogInNvbG8uaW8iLCAib3JnIjogInNvbG8uaW8iLCAic3ViIjogImFsaWNlIiwgInRlYW0iOiAiZGV2IiwgImxsbXMiOiB7ICJvcGVuYWkiOiBbICJncHQtMy41LXR1cmJvIiBdIH0gfQ.I7whTti0aDKxlILc5uLK9oo6TljGS6JUrjPVd6z1PxzucUa_cnuKkY0qj_wrkzyVN5djy4t2ggE1uBO8Llpwi-Ygru9hM84-1m53aO07JYFya1VTDsI25tCRG8rYhShDdAP5L935SIARta2QtHhrVcd1Ae7yfTDZ8G1DXLtjR2QelszCd2R8PioCQmqJ8PeKg4sURhu05GlBCZoXES9-rtPVbe6j3YLBTodJAvLHhyy3LgV_QbN7IiZ5qEywdKHoEF4D4aCUf_LqPp4NoqHXnGT4jLzWJEtZXHQ4sgRy_5T93NOLzWLdIjgMjGO_F0aVLwBzU-phykOVfcBPaMvetg
export BOB_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyAiaXNzIjogInNvbG8uaW8iLCAib3JnIjogInNvbG8uaW8iLCAic3ViIjogImJvYiIsICJ0ZWFtIjogIm9wcyIsICJsbG1zIjogeyAibWlzdHJhbGFpIjogWyAibWlzdHJhbC1sYXJnZS1sYXRlc3QiIF0gfSB9.p7J2UFwnUJ6C7eXsFCSKb5b7ecWZ75JO4TUJHafjLv8jJ7GzKfJVk7ney19PYUrWrO4ntwnnK5_sY7yaLUBCJ3fv9pcoKyRtJTw1VMMTQsKkWFgvy-jEwc9M-D5lrUfR1HXGEUm6NBaj_Ja78XScPZb_-APPqMIvzDZU04vd6hna3UMc4DZE0wcnTjOqoND0GllHLupYTfgX0v9_AYJiKRAcJvol1W14dI7szpY5GFZtPqq0kl1g0sJPg-HQKwf7Cfvr_JLjkepNJ6A1lsrG8QbuUvMUAdaHzwLvF3L_G6VRjEte6okZpaq0g2urWpZgdNmPVN71Q_0WhyrJTr6SyQ
-
Send another request to both routes. This time, include Alice’s JWT token in the
Authorization
header. Because these JWT tokens were signed by the JWT issuer that is used in the VirtualHost, the request now succeeds. Verify that you get back a 200 HTTP response code.curl -vik $(glooctl proxy url)/get -H "host: www.example.com" --header "Authorization: Bearer $ALICE_TOKEN" curl -vik $(glooctl proxy url)/status/200 -H "host: www.example.com" --header "Authorization: Bearer $ALICE_TOKEN"
curl -vik localhost:31500/get -H "host: www.example.com" --header "Authorization: Bearer $ALICE_TOKEN" curl -vik localhost:31500/status/200 -H "host: www.example.com" --header "Authorization: Bearer $ALICE_TOKEN"
Example output:
HTTP/1.1 200 OK ... HTTP/1.1 200 OK ...
Scenario 2: Specific routes
Use a route option in the VirtualService to protect a specific route with a JWT policy.
-
Update the VirtualService from the previous scenario as follows.
- Add an option to the
/get
route to configure a JWT policy. - Name each route so that you can configure a JWT policy. Unlike the VirtualHost option, the route option does not support unnamed routes.
- Configure the JWT policy by using the
jwtProvidersStaged
option, instead of thejwt
option. Thejwt
option does not support configuring a route-level JWT policy. - Set up a different provider than what is used in the VirtualHost.
kubectl apply -f - <<EOF apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin namespace: default spec: virtualHost: domains: - 'www.example.com' options: jwt: providers: vhost-gateway-provider: issuer: solo.io jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskFAGESgB22iOsGk/UgX BXTmMtd8R0vphvZ4RkXySOIra/vsg1UKay6aESBoZzeLX3MbBp5laQenjaYJ3U8P QLCcellbaiyUuE6+obPQVIa9GEJl37GQmZIMQj4y68KHZ4m2WbQVlZVIw/Uw52cw eGtitLMztiTnsve0xtgdUzV0TaynaQrRW7REF+PtLWitnvp9evweOrzHhQiPLcdm fxfxCbEJHa0LRyyYatCZETOeZgkOHlYSU0ziyMhHBqpDH1vzXrM573MQ5MtrKkWR T4ZQKuEe0Acyd2GhRg9ZAxNqs/gbb8bukDPXv4JnFLtWZ/7EooKbUC/QBKhQYAsK bQIDAQAB -----END PUBLIC KEY----- routes: - name: get matchers: - prefix: /get routeAction: single: upstream: name: httpbin namespace: default options: jwtProvidersStaged: afterExtAuth: providers: route-provider: issuer: solo.io jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- - name: status-200 matchers: - prefix: /status/200 routeAction: single: upstream: name: httpbin namespace: default EOF
- Add an option to the
-
Send a request to the
/status/200
route with a JWT for the provider in the VirtualHost, such as Bob. The request succeeds because the JWT policy from the VirtualHost still applies.curl -vik $(glooctl proxy url)/status/200 \ -H "host: www.example.com" \ --header "Authorization: Bearer $BOB_TOKEN"
curl -vik localhost:31500/status/200 \ -H "host: www.example.com" \ --header "Authorization: Bearer $BOB_TOKEN"
Example output:
< HTTP/1.1 200 OK ...
-
Repeat the previous request to the
/get
endpoint. Now, the request fails because you need a JWT from the provider in the route option.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "Authorization: Bearer $BOB_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "Authorization: Bearer $BOB_TOKEN"
Example output:
< HTTP/1.1 401 Unauthorized ... Jwt verification fails
-
Create an environment variable to save a JWT token for the user Carol. Carol’s JWT comes from the provider in the route option. Optionally, you can review the token information by debugging the token in jwt.io.
export CAROL_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyAiaXNzIjogInNvbG8uaW8iLCAib3JnIjogInNvbG8uaW8iLCAic3ViIjogImNhcm9sIiwgInRlYW0iOiAiZmluYW5jZSIsICJsbG1zIjogeyAib3BlbmFpIjogWyAiZ3B0LTRvIiBdIH0gfQ.UdcOin9UrFdw_42eoypGsAi2eYE4Cr_oe0GYUPD6MePwr6TnWnny3cEQHFFRA9KdntjWBSPtZGKqNlOqg5Juf2-lt7NBLC3ly4esNEKrx_Ul5iKPxelKjNKzdOLdjITOa9FoZ9hZEn2lsn4MG-iftTXPeVn66-nWZryY0BE0Gt2fL1xvZe4Otbj598IY6Z5iPSxQ_fGNRe6f8boW31ePUgTiOthHs7OQv25-eiL8dl1BPBFYywFVGdiiSWrgd_hwRblMegJRhAiOZHRig1sK-NTKRKJpbLhukspM-CZaT1PJgjiOQb_1seeW7mvwUTlqDQA5FZKBCbhihb0TPfo6cw
-
Repeat the previous request to the
/get
endpoint, this time with Carol’s token. Now, the request succeeds.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "Authorization: Bearer $CAROL_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "Authorization: Bearer $CAROL_TOKEN"
Example output:
< HTTP/1.1 200 OK ...
-
Still with Carol’s token, send a request to the
/status/200
endpoint. This time, the request fails because Carol’s token is not from the provider in the VirtualHost.curl -vik $(glooctl proxy url)/status/200 \ -H "host: www.example.com" \ --header "Authorization: Bearer $CAROL_TOKEN"
curl -vik localhost:31500/status/200 \ -H "host: www.example.com" \ --header "Authorization: Bearer $CAROL_TOKEN"
Example output:
< HTTP/1.1 401 Unauthorized ... Jwt verification fails
-
Update the VirtualService to remove the VirtualHost JWT option.
kubectl apply -f - <<EOF apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin namespace: default spec: virtualHost: domains: - 'www.example.com' routes: - name: get matchers: - prefix: /get routeAction: single: upstream: name: httpbin namespace: default options: jwtProvidersStaged: afterExtAuth: providers: route-provider: issuer: solo.io jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- - name: status-200 matchers: - prefix: /status/200 routeAction: single: upstream: name: httpbin namespace: default EOF
-
Repeat the request to the
/status/200
route with Carol’s token. Now, the request succeeds because the gateway no longer enforces a JWT policy on all its routes.curl -vik $(glooctl proxy url)/status/200 \ -H "host: www.example.com" \ --header "Authorization: Bearer $CAROL_TOKEN"
curl -vik localhost:31500/status/200 \ -H "host: www.example.com" \ --header "Authorization: Bearer $CAROL_TOKEN"
Example output:
< HTTP/1.1 200 OK ...
Scenario 3: Same-stage conflicts
As you saw in the previous scenario, route-level JWT policies configured in the VirtualService use the jwtProvidersStaged
option to provide the JWT provider and server details. However, what happens if the gateway-level JWT policies in the VirtualHost are set at the same stage as the route option, such as before or after external auth? In this case, the route-level JWT policy takes precedence.
-
Update the VirtualService to demonstrate same-stage conflicts.
- In the VirtualHost options, use the
jwtStaged
option to set the stage toafterExtAuth
. - In the route option for the
/get
route, use thejwtProvidersStaged
option to keep theafterExtAuth
stage. - In the route option, set the token source to the
x-after-ext-auth-bearer-token
header instead of the defaultAuthorization
header. This way, you can pass in different tokens on requests to the/get
endpoint, such as Bob’s token for the gateway-level JWT policy and Carol’s token for the route-level JWT policy.
kubectl apply -f - <<EOF apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin namespace: default spec: virtualHost: domains: - 'www.example.com' options: jwtStaged: afterExtAuth: providers: vhost-gateway-provider: issuer: solo.io jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskFAGESgB22iOsGk/UgX BXTmMtd8R0vphvZ4RkXySOIra/vsg1UKay6aESBoZzeLX3MbBp5laQenjaYJ3U8P QLCcellbaiyUuE6+obPQVIa9GEJl37GQmZIMQj4y68KHZ4m2WbQVlZVIw/Uw52cw eGtitLMztiTnsve0xtgdUzV0TaynaQrRW7REF+PtLWitnvp9evweOrzHhQiPLcdm fxfxCbEJHa0LRyyYatCZETOeZgkOHlYSU0ziyMhHBqpDH1vzXrM573MQ5MtrKkWR T4ZQKuEe0Acyd2GhRg9ZAxNqs/gbb8bukDPXv4JnFLtWZ/7EooKbUC/QBKhQYAsK bQIDAQAB -----END PUBLIC KEY----- routes: - name: get matchers: - prefix: /get routeAction: single: upstream: name: httpbin namespace: default options: jwtProvidersStaged: afterExtAuth: providers: route-provider: issuer: solo.io tokenSource: headers: - header: x-after-ext-auth-bearer-token jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- - name: status-200 matchers: - prefix: /status/200 routeAction: single: upstream: name: httpbin namespace: default EOF
- In the VirtualHost options, use the
-
Repeat the request to the
/get
endpoint with Carol’s token. The request succeeds with only Carol’s token, because the JWT policy on the route takes precedence and overwrites the VirtualHost configuration at the same stage for routes on the gateway.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
Example output:
< HTTP/1.1 200 OK ...
-
Send a request to the
/status/200
endpoint without a valid token for the gateway-level JWT policy. Even though the VirtualHost configures a gateway-level JWT policy, the request succeeds, because the route option policy takes precedence.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
Example output:
< HTTP/1.1 200 OK ... Jwt is missing
Scenario 4: Separate stages
Set up the VirtualService to configure JWT policies at different stages before and after external auth. This way, the protected routes require both tokens, such as Bob’s token for the gateway-level JWT policy and Carol’s token for the route-level JWT policy.
-
Update the VirtualService as follows.
-
VirtualHost for gateway-level options:
- In the VirtualHost options, use the
jwtStaged
option to set the stage tobeforeExtAuth
. This way, the stage is different than the stage that is configured in the route option. - Add an RBAC policy to allow tokens from the
ops
team, which corresponds to Bob’s token.
- In the VirtualHost options, use the
-
Route options for the
/get
route:- Use the
jwtProvidersStaged
option to keep theafterExtAuth
stage. - Keep the token source to the
x-after-ext-auth-bearer-token
header instead of the defaultAuthorization
header. This way, you can pass in both tokens on requests to the/get
endpoint. - Add an RBAC policy that allows tokens from the
finance
team, which corresponds to Carol’s token. In cases where multiple RBAC policies apply to a route, the VirtualService takes precedence.
- Use the
kubectl apply -f - <<EOF apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin namespace: default spec: virtualHost: domains: - 'www.example.com' options: jwtStaged: beforeExtAuth: providers: vhost-gateway-provider: issuer: solo.io jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskFAGESgB22iOsGk/UgX BXTmMtd8R0vphvZ4RkXySOIra/vsg1UKay6aESBoZzeLX3MbBp5laQenjaYJ3U8P QLCcellbaiyUuE6+obPQVIa9GEJl37GQmZIMQj4y68KHZ4m2WbQVlZVIw/Uw52cw eGtitLMztiTnsve0xtgdUzV0TaynaQrRW7REF+PtLWitnvp9evweOrzHhQiPLcdm fxfxCbEJHa0LRyyYatCZETOeZgkOHlYSU0ziyMhHBqpDH1vzXrM573MQ5MtrKkWR T4ZQKuEe0Acyd2GhRg9ZAxNqs/gbb8bukDPXv4JnFLtWZ/7EooKbUC/QBKhQYAsK bQIDAQAB -----END PUBLIC KEY----- rbac: policies: viewer: nestedClaimDelimiter: . principals: - jwtPrincipal: claims: team: ops routes: - name: get matchers: - prefix: /get routeAction: single: upstream: name: httpbin namespace: default options: jwtProvidersStaged: afterExtAuth: providers: route-provider: issuer: solo.io tokenSource: headers: - header: x-after-ext-auth-bearer-token jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- rbac: policies: viewer: nestedClaimDelimiter: . principals: - jwtPrincipal: claims: team: finance - name: status-200 matchers: - prefix: /status/200 routeAction: single: upstream: name: httpbin namespace: default EOF
-
-
Send a request to the
/get
endpoint with Carol’s token. The request fails even though Carol’s token is valid and meets the route-level RBAC policy. Instead, you need separate tokens for the two different stages of the JWT policies in the route-level and gateway-level options.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
Example output:
< HTTP/1.1 401 Unauthorized ... Jwt is missing
-
Repeat the request to the
/get
endpoint with both Bob and Carol’s tokens. Now, the request succeeds. Both tokens are required to pass the JWT policy at the different stages. The claims from Carol’s token passes the RBAC policy.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN" \ --header "Authorization: Bearer $BOB_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN" \ --header "Authorization: Bearer $BOB_TOKEN"
Example output:
< HTTP/1.1 200 OK ...
Scenario 5: Validation policy
In the previous scenario, you protected a route by requiring JWT authentication at two stages of a request, before and after external auth. To do so, you configured separate JWT policies at the route and gateway layers with VirtualService and VirtualHost resources. But what if you want to enforce just one layer of JWT policy, without being picky about which JWT is used?
You can achieve that by setting up a validation policy. The validation policy has several options as follows. For more details, see the API reference docs.
REQUIRE_VALID
: The default value, which allows only requests that have a valid JWT.ALLOW_MISSING
: Let requests succeed when a JWT is missing. However, if an invalid JWT is provided, such as in an incorrect header or an expired token, the request fails.ALLOW_MISSING_OR_FAILED
: Let requests succeed even when a JWT is missing or fails verification, such as an expired JWT.
Note that if you set a permissive validation policy on both options, the route does not require any JWT authentication. Make sure to set up the validation policy according to your security requirements.
-
Verify that your current setup expects two JWT tokens by sending a request to the
/get
endpoint with Carol’s token. The request fails even though Carol’s token is valid and meets the route-level JWT policy. Instead, you need both tokens as required by the different JWT stages of the route and VirtualHost options.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
Example output:
< HTTP/1.1 401 Unauthorized ... Jwt is missing
-
Update the VirtualService to add a validation policy to the VirtualHost that allows for a missing token. This way, you only have to include the JWT for the route-level configuration on the
/get
endpoint.kubectl apply -f - <<EOF apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin namespace: default spec: virtualHost: domains: - 'www.example.com' options: jwtStaged: beforeExtAuth: validationPolicy: ALLOW_MISSING providers: vhost-gateway-provider: issuer: solo.io jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskFAGESgB22iOsGk/UgX BXTmMtd8R0vphvZ4RkXySOIra/vsg1UKay6aESBoZzeLX3MbBp5laQenjaYJ3U8P QLCcellbaiyUuE6+obPQVIa9GEJl37GQmZIMQj4y68KHZ4m2WbQVlZVIw/Uw52cw eGtitLMztiTnsve0xtgdUzV0TaynaQrRW7REF+PtLWitnvp9evweOrzHhQiPLcdm fxfxCbEJHa0LRyyYatCZETOeZgkOHlYSU0ziyMhHBqpDH1vzXrM573MQ5MtrKkWR T4ZQKuEe0Acyd2GhRg9ZAxNqs/gbb8bukDPXv4JnFLtWZ/7EooKbUC/QBKhQYAsK bQIDAQAB -----END PUBLIC KEY----- rbac: policies: viewer: nestedClaimDelimiter: . principals: - jwtPrincipal: claims: team: ops routes: - name: get matchers: - prefix: /get routeAction: single: upstream: name: httpbin namespace: default options: jwtProvidersStaged: afterExtAuth: providers: route-provider: issuer: solo.io tokenSource: headers: - header: x-after-ext-auth-bearer-token jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- rbac: policies: viewer: nestedClaimDelimiter: . principals: - jwtPrincipal: claims: team: finance - name: status-200 matchers: - prefix: /status/200 routeAction: single: upstream: name: httpbin namespace: default EOF
-
Repeat the request to the
/get
endpoint with Carol’s token. Now, the request succeeds, even without Bob’s token. The validation policy at the VirtualHost allows the JWT to be missing on requests.curl -vik $(glooctl proxy url)/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
curl -vik localhost:31500/get \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
Example output:
< HTTP/1.1 200 OK ...
Scenario 6: Delegated routes
In a delegation scenario, the JWT policy of child routes take precedence over parent routes.
Note that this example focuses on setting up delegation with route-level configuration on the VirtualService and RouteTable resources. The example does not discuss gateway-level policies that are set by the VirtualHost. The same configuration interactions between the route-level and the gateway-level options as described in the previous scenarios still apply. For example, route-level JWT policies at the same stage as gateway-level JWT policies take precedence.
-
Update the VirtualService to delegate the
/status
route to a child RouteTable namedhttpbin-child
. Leave the JWT policy configuration on the/status
route to keep the original route-level JWT policy.kubectl apply -f - <<EOF apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin namespace: default spec: virtualHost: domains: - 'www.example.com' routes: - name: status matchers: - prefix: /status delegateAction: ref: name: httpbin-child namespace: default options: jwtProvidersStaged: afterExtAuth: providers: route-provider: issuer: solo.io tokenSource: headers: - header: x-after-ext-auth-bearer-token jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- EOF
-
Create the child with two
/status
child routes of/status/200
and/status/418
.- The
/status/200
route does not have a JWT policy. As such, the parent JWT policy applies. - The
/status/418
route does have a JWT policy with a different JWT configuration than the parent route. This JWKS has a differentdocs.xyz
issuer than thesolo.io
issuer of the parent route. This child JWT policy overwrites the parent policy.
kubectl apply -f- <<EOF apiVersion: gateway.solo.io/v1 kind: RouteTable metadata: name: httpbin-child namespace: default spec: routes: - name: status-200 matchers: - prefix: /status/200 routeAction: single: upstream: name: httpbin namespace: default - name: status-418 matchers: - prefix: /status/418 routeAction: single: upstream: name: httpbin namespace: default options: jwtProvidersStaged: afterExtAuth: providers: child-route-provider: issuer: docs.xyz tokenSource: headers: - header: x-after-ext-auth-bearer-token jwks: local: key: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- EOF
- The
-
Send a request to the
/status/200
child route with Carol’s token. The request succeeds, because Carol’s token is from thesolo.io
issuer as required by the JWT policy of the parent route.curl -vik $(glooctl proxy url)/status/200 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
curl -vik localhost:31500/status/200 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
Example output:
HTTP/1.1 200 OK ...
-
Send another request to the
/status/418
child route with Carol’s token. The request fails, because Carol’s token is from thesolo.io
issuer as required by the JWT policy of the parent route, but the child route for/status/418
requires its own JWT policy from a different issuer,docs.xyz
.curl -vik $(glooctl proxy url)/status/418 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
curl -vik localhost:31500/status/418 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $CAROL_TOKEN"
Example output:
< HTTP/1.1 401 Unauthorized Jwt issuer is not configured ...
-
Create an environment variable to save a JWT token for the user Dan. Dan’s JWT comes from the
docs.xyz
provider in thejwt-child
VirtualService. Optionally, you can review the token information by debugging the token in jwt.io.export DAN_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmciOiJkb2NzLnh5eiIsInN1YiI6ImRhbiIsInRlYW0iOiJvcHMiLCJpc3MiOiJkb2NzLnh5eiIsImxsbXMiOnsiY2xhdWRlIjpbIjMuNS1zb25uZXQiXX19.ny19crTIAsmlVlKjpdp52v4MJ037rNI5xyMoIqqA-jl6FK2XwhL0kn_xqvA3XDdKhMqy8hmH4nWbZPhHGzvs4gxXQW-_LPO0dDR5J_TOAqmR2j5epEyBWV7SvORGciG3nqpsJSBEzb6-artbbX8ehRpRZAyVvPQnfEYRkuPmmuzxUjyQpeWveCOJ9-HP3-PACqo2snMYoztsqR3mq2_kDWqvuxbhwuvFKEDQKe6tsvoVVc_7-qV4rHxiSmCQKagRtf0ALr7pzSOEVJ4JTWzRkkw5S5lO93sUbTittxEchZFEa7O3qKclvm5MqauF-UzFaB5YR9g2bUwGiRoYIV0BTA
-
Send another request to the
/status/200
child route, this time with Dan’s token. The request fails, because Dans’s token is not valid for the JWKS provider with thesolo.io
issuer as required by the JWT policy of the parent route.curl -vik $(glooctl proxy url)/status/200 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $DAN_TOKEN"
curl -vik localhost:31500/status/200 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $DAN_TOKEN"
Example output:
HTTP/1.1 401 Unauthorized Jwt issuer is not configured ...
-
Send another request to the
/status/418
child route, this time with Dan’s token. The request succeeds, because the JWT is valid for the JWKS provider with thedocs.xyz
issuer of the JWT policy on the child route, which takes precedence over the parent route.curl -vik $(glooctl proxy url)/status/418 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $DAN_TOKEN"
curl -vik localhost:31500/status/418 \ -H "host: www.example.com" \ --header "x-after-ext-auth-bearer-token: $DAN_TOKEN"
Example output:
HTTP/1.1 418 Unknown ... -=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`
Cleanup
You can optionally remove the resources that you set up as part of this guide.-
Delete the httpbin sample app.
kubectl delete -n default ServiceAccount httpbin kubectl delete -n default Service httpbin kubectl delete -n default Deployment httpbin kubectl delete -n default Upstream httpbin
-
Delete the resources that you created in this guide.
kubectl delete -n default VirtualService httpbin kubectl delete -n default RouteTable httpbin-child