Sample Envoy Proxy config to validate JWT authentication headers used by GCP Identity Aware Proxy.
When you use IAP, Google will handle the application-level access control model for you by default by making sure only those users or groups are allowed through.
However, its advisable to always validate those headers in your applicaiton as well as a secondary check:
You can always validate the headers in your application code using middleware as shown in http_server.py
. However, its not always possible to modify your applicaiton code to do this and its desireable to inject anotother proxy infront of your application that handles this for you. This is similar to how istio and Google Cloud Endpoints ESP works.
In this example, we're going to spin up a simple Envoy proxy that just does the JWT validaiton for you and then passes that header as-is or transformed to your app anyway so you can identity the actual user.
That is
user --> IAP --> envoy --> your_app
The JWT header sent by IAP is re validated for you by envoy
NOTE: this repo uses
envoy 1.17
you can get the the -dev channel as of 12/24/20:
docker cp `docker create envoyproxy/envoy-dev:latest`:/usr/local/bin/envoy .
This technique mostly applies to an application running in Google Kubernetes Engine or Compute Engine where you are free to run a docker container or envoy stand-alone.
Once you've configured IAP in either of these enviornments,
Note down the Audience
the X-Goog-Iap-Jwt-Assertion
will use in Verify IAP Payload
We will use that to setup Envoy
The envoy config uses the jwt_authn
http filter capability (see jwt_authn)
Which in the envoy config format, looks like:
http_filters:
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
google-jwt:
issuer: https://cloud.google.com/iap
audiences:
- /projects/1071284184436/apps/mineral-minutia-820
forward: true
remote_jwks:
http_uri:
uri: https://www.gstatic.com/iap/verify/public_key-jwk
cluster: jwt.www.googleapis.com|443
timeout:
seconds: 5
from_headers:
- name: X-Goog-Iap-Jwt-Assertion
Envoy will process a JWT in the format below and look for it in the X-Goog-Iap-Jwt-Assertion
header
{
"alg": "ES256",
"typ": "JWT",
"kid": "5PuDqQ"
}
.
{
"iss": "https://cloud.google.com/iap",
"sub": "accounts.yourcompany.com:1081579130932224845548",
"email": "srashid@yourcompany.com",
"aud": "/projects/1071284184436/apps/mineral-minutia-820",
"exp": 1531490127,
"iat": 1531489527,
"hd": "yourcompany.com"
}
Note the audience i've used there and the full config sample envoy_iap.yaml
The following link shows the JWK endpoint to use for the EC public keys:
If you want to run envoy standaline, start your backend/upstream service and run start envoy:
envoy -c envoy_iap.yaml -l debug
If you would rather run this in a docker container (recommended), generate the image in anyway you'd like using envoy's dockerhub:
FROM envoyproxy/envoy-dev:latest
COPY envoy_iap.yaml /etc/envoy/envoy.yaml
then if your upstreamservice is local, you need to point
docker build -t envoy:v1 .
docker run -p 10000:10000 --net host -t envoy:v1
In this repo, you'll also find a smaple client/server app in python.
to use, first
virtualenv env --python=/usr/bin/python3.7
source env/bin/activate
pip install -r requirements.txt
then
python http_server.py
If you want to test locally with http_client.py
, first get an IAP token that is sent to your app that is currently on GCP (eg, by logging it to cloud logging).
Then start envoy and the backend http_server.py
. Copy the JWT into the code and invoke it. This is only for testing your envoy and backend config.
You dont' have to statically configure envoy proxy. One of its many capabilities is to allow remote config.
For more information, see
As an extra, this repo also contains a sample for validating a google_id_token
as issued by gcloud cli.
This is really just a test but if you want to try it out, run envoy with envoy_google.yaml
and then note the sections
commented out in http_client.py
.
export ID_TOKEN=$(gcloud config config-helper --format 'value(credential.id_token)')
curl -H "Authorization: Bearer $ID_TOKEN" http://localhost:10000/```