Istio JWT Authentication & Authorization at the edge
Ever wanted to know how you can use a JWT token to authenticate & authorize requests coming from an API gateway. But found it to be confusing and the information you found was scattered, and you wanted to know how it all fits together?3
Fear not! I’ve also experienced those scenarios, and I’ve built my own playground for that, and I will walk you through the process, tips and how you can implement it in your use case.
The following scenarios will be reviewed in the article:
- First, what is a JWT, and why should you care?
- Setting up the playground
- Dissection Istio’s JWT edge authentication & authorization
- How to build an external authz service for istio
First, what is a JWT, and why should you care?
A JWT (short for JSON Web Token) is a web standard for sharing claims between two parties.
Many systems out there use JWTs, chances are that you go to your favorite website, inspect the persistent stores (local storage, cookies, session storage, etc.) you will find a JWT
They look like this
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NTM4NzU4MDUsImV4cCI6MTY4NTQxMTgwNSwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.3KtBCvZAieEJvZou7-49vjcrmd4sU-RypSqlqBGm4v
They consist of 3 base 64 encoded parts, each separated by a dot (.)
1. The header
2. The payload
3. Signature
The beauty of them is that the signature is generated by an algorithm specified in the header, so that we can be sure that the token wasn’t tampered with. You can find more information in here
Enough of this JWT introduction, let’s get our hands dirty.
Setting up the playground
Requirements:
- git(2.35.1 recommended)
- golang (1.18 is recommended)
- docker(Another container manager will suffice if the alias is docker, 20.10.12 recommended)
- k3d (v5.4.1 with k3s v1.22.7-k3s1 versions recommended)
- terraform (~> 1.0.0)
- kubectl (To match accordingly with the clushttps://tl7x52xzircx5gpv3bmkhkxvp4.appsync-api.us-east-1.amazonaws.com/graphqlter, in this case 1.22)
- task (v3.12.0 recommended)
- jq (1.6 recommended)
Clone the repo
Run git clone https://github.com/JorgeReus/istio-jwt
Start the playground
You just need to run task setup
Wait for a couple of minutes, and you’ll have a complete k8s playground with istio and all the required services & configuration applied.
Test JWTs
- You can generate a valid JWT with
task get-valid-jwt
- And an invalid JWT with
task get-invalid-jwt
- Get the istio gateway ip with
task get-istio-ip
All together you can run:
As you can see, with the valid JWT you will get an HTML response with a 200 response code.
With the invalid JWT, you will get the message Your role doesn’t have te required permissions with a status code 403.
Let’s break down what happened
Ok, so what happened?
First, task is a task runner (weirdly enough), this will allow us to run commands by simply specifying the task to run, the neat thing is we can set up dependencies between tasks, so by simply one command we can set up the development environment.
The tasks executed by running task setup
are the following ones
- create-cluster
Creates a k3d cluster with 3 nodes and setups a container registry in the port 5050 - push-all-images
Push images of the services we will use in our environment - install-istio
Installs istio via helm charts using terraform - configure-istio
Configures istio gateways and setups up all custom resources required by the environment
Dissecting Istio’s JWT edge authentication & authorization
Authn (Authentication)
Istio has the concept of request authentication, which applies JWT Rules to a request which can come from a workload inside the cluster or a request coming from outside the cluster. You can check the reference for more information.
Let’s now take a look at the request authentication manifest we have defined in the repo, it’s located in terraform/ops/main.tf.
First of all you can see that we have an array of jwtRules in the spec, every jwtRules contains an issuer and a jwksUri.
An issuer maps to a field in the JWT called iss which is the “party” that created the JWT, istio will decode the JWT and compare the iss field with this one.
A jwksUri is a resolvable URL which contains a public JWT Key Set that istio uses to validate that the token was signed by a trusted private JWT key set.
Effectively, this rule states that any JWT evaluated must have the iss field with the value “my.jwt.issuer” and should be signed by any key of the private part of the keys present in http://auth-service.default.svc.cluster.local/jwk/public.
Just remember that this will create the policy but to apply if to the gateway we must use an AuthorizationPolicy
You can find the code responsible for evaluating the rules in here.
Authz (Authorization)
In istio you can configure access control to the mesh, namespace and workloads using an AuthorizationPolicy.
In this CRD we will apply the request authentication in the previous step and, we will go further by decoding the jwt and evaluate other fields.
First of all we’ll take a look at how we can write an application to do custom authorization.
Why?
Because istio’s policies for JWT authorization are static, so pulling data from a database is impossible with vanilla policies.
Istio supports a method called for using an external service to apply our custom authorization logic, useful when we want a dynamic way tomanage access controls.
To configure external authorization, we need to supply a custom mesh config
There are two protocols that istio support to communicate with your custom authz service: http & grpc, for both you need to supply a port, the hostname of the service and optionally in http the headers you want to pass from the request.
How to build an external authz service for istio
Since istio is open source, we can use the same libraries to develop the service, we’ll see a couple of snippets showing the important bits.
For grpc, istio exposes two versions of the API v2 & v3, you can implement both of them if you have different versions of istio and want this to work in any of them.
In here, we can see how to get headers from the request and process them. In this case, we’re getting the Authorization: Bearer <jwt>
header, decoding the jwt and apply a custom validator function.
When using plain http, is simpler, we only need to implement a function and apply out
After the service is deployed and ready, you can use by setting a provider in the authorization policy
Effectively, with this configuration, the policy forward the request to the custom authorization service to decide if the request will be allowed or denied.
Authorization policies evaluation rules
Since we’re applying multiple policies to the same path, istio applies some internal rules to know if the request should be allowed or denied, which are the following:
- If there are any CUSTOM policies that match the request, evaluate and deny the request if the evaluation result is denied.
- If there are any DENY policies that match the request, deny the request.
- If there are no ALLOW policies for the workload, allow the request.
- If any of the ALLOW policies match the request, allow the request.
- Deny the request.
In this specific case, the authorization service will be called first and then request authentication policy. You can find more information here
Conclusion
In this article, we dived into how istio handles authentication & authorization using JWTs, being a widely used standard, JWT pretty important to learn, istio gives us a powerful yet easy way on applying our own rules to authn & authz several types of workloads.
Extra Content!
Wondered how to authn & authz completely serverless in AWS?
Check out this repo