JWT Claim Based Routing
The features used here were introduced with Gloo Gateway Enterprise, release 0.14.0. If you are using an earlier version, this tutorial will not work.
In this guide, we will show how to configure Gloo Gateway to route requests to different services based on the claims contained in a JSON Web Token (JWT). In our example scenario, Solo.io employees will be routed to the canary instance instance of a service, while all other authenticated parties will be routed to the primary version of the same service.
Setup
This guide assumes that you have deployed Gloo to the gloo-system
namespace and that the glooctl
command line utility
is installed on your machine. glooctl
provides several convenient functions to view, manipulate, and debug Gloo resources;
in particular, it is worth mentioning the following command, which we will use each time we need to retrieve the URL of
the Gloo Gateway that is running inside your cluster:
glooctl proxy url
Deploy sample application
Let’s deploy a sample application which will simulate a canary deployment. We will use Hashicorp’s http-echo application, which listens for HTTP requests and echoes back a configurable string. We will deploy:
- a pod that responds with the string “primary” to simulate the primary deployment
- a pod that responds with the string “canary” to simulate the canary deployment
- a service to route to them
First, let’s create the primary deployment.
apiVersion: apps/v1
kind: Deployment
metadata:
name: primary
labels:
app: echoapp
stage: primary
spec:
replicas: 1
selector:
matchLabels:
app: echoapp
stage: primary
template:
metadata:
labels:
app: echoapp
stage: primary
spec:
containers:
- name: primary
image: hashicorp/http-echo
imagePullPolicy: IfNotPresent
args:
- -listen=:8080
- -text=primary
Next, the canary deployment.
apiVersion: apps/v1
kind: Deployment
metadata:
name: canary
labels:
app: echoapp
stage: canary
spec:
replicas: 1
selector:
matchLabels:
app: echoapp
stage: canary
template:
metadata:
labels:
app: echoapp
stage: canary
spec:
containers:
- name: canary
image: hashicorp/http-echo
imagePullPolicy: IfNotPresent
args:
- -listen=:8080
- -text=canary
Finally, we will create the service.
kubectl create service clusterip echoapp --tcp=80:8080
The pods have a label named stage that indicates whether they are canary or primary pods.
Next let’s create a Gloo Gateway upstream for the kube service. We will use Gloo Gateway’s subset routing feature and set it to use the ‘stage’ key to create subsets for the service:
apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
name: echoapp
namespace: gloo-system
spec:
kube:
serviceName: echoapp
serviceNamespace: default
servicePort: 80
subsetSpec:
selectors:
- keys:
- stage
The subsetSpec
configuration instructs Gloo Gateway to partition the endpoints of the echoapp
service into subsets based on
the values of the stage
label. In our case, this will result in two subsets, primary
and canary
.
Generate JWTs
Let’s create a private/public key pair that we will use to sign our JWTs:
openssl genrsa 2048 > private-key.pem
openssl rsa -in private-key.pem -pubout > public-key.pem
Please refer to the JWT and Access Control guide to see how to use the jwt.io website to create two RS256 JWTs:
- one for
solo.io
employees with the following payload:
{
"iss": "solo.io",
"sub": "1234567890",
"org": "solo.io"
}
- one for
othercompany.com
employees with the following payload:
{
"iss": "solo.io",
"sub": "0987654321",
"org": "othercompany.com"
}
Save the resulting tokens to two shell variables:
SOLO_TOKEN=<encoded token with solo.io payload from jwt.io>
OTHER_TOKEN=<encoded token with othercompany.com payload from jwt.io>
Create a Virtual Service
Now let’s configure our Virtual Service to route requests to one of the two subset based on the JWTs in the incoming requests themselves.
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
name: echo
namespace: gloo-system
spec:
virtualHost:
domains:
- '*'
routes:
- matchers:
- prefix: /
headers:
- name: x-company
value: solo.io
routeAction:
single:
upstream:
name: echoapp
namespace: gloo-system
subset:
values:
stage: canary
- matchers:
- prefix: /
routeAction:
single:
upstream:
name: echoapp
namespace: gloo-system
subset:
values:
stage: primary
options:
jwt:
providers:
solo:
tokenSource:
headers:
- header: x-token
queryParams:
- token
claimsToHeaders:
- claim: org
header: x-company
issuer: solo.io
jwks:
local:
key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnkwSRAoyViwIuoDUiMv
ylnMjXgCYAAGH43IwzuCAtSyJG5Ufp5PnJ4jJJlINMwN3Wv887AYCSndoC8P83L3
1JPPqDgJY+MYuLmaP9vnjNN5GiDt15bG4Gby6XYFnKmjgvy2ugOP1QnTkedeVFAw
Y7dmSeBU1E55Xq+PxrDTKdDKHB9oiO77bfn4lWjzDECtcX+YPRbTufLJPWCNAhpF
41N6SdozbRenhOqgOWoHSPBsQtKir6+5NOKZjJt6amDSFYc08M7ESXZVymtCFUJ9
X7DtYS5ppyaW+Cyt8v5vgjrs5Cu4by//77mHWuxd918O047GhKP17l14O/DySeOF
7QIDAQAB
-----END PUBLIC KEY-----
The jwt
configuration defined on the Virtual Host instructs Gloo Gateway to verify whether a JWT is present on incoming
requests and whether the JWT is valid using the provided public key. If the JWT is valid, the claimsToHeaders
field
will cause Gloo Gateway to copy the org
claim to a header name x-company
.
At this point we can use normal header matching to do the routing:
- Our first route matches if the
x-company
header contains the valuesolo.io
and routes to the canary subset. - If the first route does not match, we fall back to the second one, which that routes to the primary subset.
For convenience, we added the tokenSource
settings so we can pass the token as a query parameter named token
.
Testing our configuration
Send a request as a solo.io team member:
curl "$(glooctl proxy url)?token=$SOLO_TOKEN"
The output should be canary
.
Send a request as a othercompany.com team member:
curl "$(glooctl proxy url)?token=$OTHER_TOKEN"
The output should be primary
.
Cleanup
You can clean up the resources created in this guide by running:
rm private-key.pem public-key.pem
kubectl delete virtualservice -n gloo-system echo
kubectl delete upstream -n gloo-system echoapp
kubectl delete pod primary-pod canary-pod