Olena Borzenko
Posted on November 6, 2020
Kong and Auth0 are very powerful platforms.
Of course, the more you can do with a tool, the more documentation you need to read, and sometimes it could be frustrating 😬
So, I've created this guide to show how you can easily configure everything on your local environment and play around in any way you like.
To keep this article short, I've assumed that you already know a bit about Docker, Kong and Auth0. In any case here are some links:
Also, you'll need three things to follow this guide:
- API you can use for testing;
- Auth0 account/tenant where you can generate access token;
- IDE to run python scripts (I use PyCharm);
I use python scripts as an example of how you can automate configuration process. That might be a great help for those who need to configure Kong for a running system or a different environments.
First things first!
Let's start with a Kong running in a docker container.
We need to create a Docker network and database for Kong:
docker network create kong-net
docker run -d --name kong-database \
--network=kong-net \
-p 5432:5432 \
-e "POSTGRES_USER=kong" \
-e "POSTGRES_DB=kong" \
-e "POSTGRES_PASSWORD=kong" \
postgres:9.6
Database migration:
docker run --rm \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=kong" \
-e "KONG_PG_PASSWORD=kong" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
kong:latest kong migrations bootstrap
And now, we can run our container:
docker run -d --name kong \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=kong" \
-e "KONG_PG_PASSWORD=kong" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
-p 8000:8000 \
-p 8443:8443 \
-p 127.0.0.1:8001:8001 \
-p 127.0.0.1:8444:8444 \
kong:latest
Try to make a test:
curl -i http://localhost:8001/
If you have some troubles, check if your containers are running:
docker ps
If you don't see kong
and kong-database
containers in a list, then you'll need to start them explicitly:
docker start kong kong-database
You can also use Postman for testing:
Time for some scripts!
Btw, using python is not mandatory. If you don't like it, you still can follow those steps with any other language or use curl commands.
Lets first define a configuration object for our new service and route.
# you'll need those imports later
import requests
import json
host = "192.168.1.98"
config = {
"service": {
"name": "test-service",
"url": "http://" + host + ":7400/api/test/data",
},
"route": {
"protocols": ["http"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"hosts": [host],
"paths": ["/(data)/*"],
"headers": None,
},
}
Here service.url
is a place where Kong will redirect us if we make a call to localhost:8000/data
with a header Host 192.168.1.98
(theoretically 🙃).
If you don't want to use local API, then you can change service.url
and route.hosts
to use your values. Otherwise, I bet, you might have a question: "Why 192.168.1.98 and not localhost?". Hold that thought, I'll get back to it.
Next step - creating Service and Route in Kong.
First of all, copy this script to the same file where you have your config object and take a close look on what's going on there. Don't run it yet - we still need to check one more thing.
# create a service using our config object
print(config["service"]["name"])
serviceUrl = "http://localhost:8001/services"
serviceResponse = requests.post(url=serviceUrl, json=config["service"])
# print service id to ensure that it was successfully created
serviceId = (json.loads(serviceResponse.content))["id"]
print("serviceId ", serviceId)
# create a route using our config object
routeUrl = "http://localhost:8001/routes"
config["route"]["service"] = {
"id": serviceId
}
routeResponse = requests.post(url=routeUrl, json=config["route"])
routeId = (json.loads(routeResponse.content))["id"]
# print route id because you'll need it to add a plugin later
print("routeId", routeId)
And now - why do we use '192.168.1.98' and not localhost for our service.
I've been playing around, trying to proxy my local service with Kong running in a docker container. When I was calling my local API through Kong, it kept returning 502 error with a message An invalid response was received from the upstream server
.
The reason is because Kong was pointed to localhost
of my docker container. Obviously that didn't work because my service wasn't hosted there.
Of course, you don't need to care about it if you're using external API for testing.
Easy solution here is to register your Kong service with your local host machine’s IP address (e.g. 192.168.1.98). Btw, it will be different for your machine. Probably you might want to check it. Run /sbin/ifconfig
for mac or ipconfig
for Windows machine.
You should be able to see smth like that:
Don't forget to update host
in python config with your IP and keep in mind, that it should be different than the one Kong uses.
Ok, and now you can try to run your script. If you don't see any errors in IDE output, then you can try to receive a list of services or routes (Just to make sure they're really created).
Now, if you're lucky enough, you should be able to access your API directly or through the Kong.
Note: your API should be up and running.
Direct request:
Request through Kong:
Last step. Let's make it secure!
I already had a public key for my Auth0 account, but if you don't have it, then execute commands below and save a file with a public key in the same directory with your python script.
Note: don't forget to replace those values {COMPANYNAME}.{REGION-ID}
You'll need to download your Auth0 account’s X509 Certificate:
$ curl -o {COMPANYNAME}.pem https://{COMPANYNAME}.{REGION-ID}.auth0.com/pem
And extract the public key from the X509 Certificate:
$ openssl x509 -pubkey -noout -in {COMPANYNAME}.pem > pubkey.pem
Great! Now we need to add JWT Plugin and consumer
If you forgot to save your routeId, then you can request it from http://localhost:8001/routes
(find your route if you have more than one) and copy its id from the response.
# create a plugin for our route
# here you'll need to insert your routeId
pluginUrl = "http://localhost:8001/routes/" + {ROUTEID} + "/plugins"
pluginData = {
"name": "jwt"
}
requests.post(url=pluginUrl, json=pluginData)
Note: script won't work, if you forgot to put your public key in the same folder.
# create a global consumer
consumerUrl = "http://localhost:8001/consumers"
consumerData = {
"username": "test-consumer",
"custom_id": "test-consumer-id",
}
consumerResponse = requests.post(url=consumerUrl, json=consumerData)
# print consumer id to ensure that it was successfully created
consumerId = (json.loads(consumerResponse.content))["id"]
print("consumerId ", consumerId)
# Add JWT plugin with Auth0 public key
basicUrl = consumerUrl + "/" + consumerId + "/jwt"
basicData = {
"algorithm": "RS256",
"key": "https://{COMPANYNAME}.{REGION-ID}.auth0.com/",
"rsa_public_key": open('pubkey.pem', 'rb')
}
requests.post(url=basicUrl, files=basicData)
Vualá 🥳
Now you won't be be able to call your API through Kong without bearer token. Also, only tokens signed by Auth0 will work.
You can try to make the same call localhost:8000/data
and, if you did everything right, you should receive 401 Unauthorized.
Header Host 192.168.1.98
should be attached to the request when you're calling Kong.
When bearer token is attached, your request should be accepted:
Few more things
If you need to verify scopes or claims in your token and you're using Kong Enterprise version, you might want to have a look at a JWT Signer Plugin. This plugin will verify and (re-)sign token for you.
If you have, for example, SPA application which is supposed to access multiple APIs, you can check this topic from Auht0 documentation Represent Multiple APIs Using a Single Logical API.
If you have Kong and all your APIs are protected with it and trusted, you don't have to generate M2M tokens. Of course, if you need it or find it valuable, then sure, go for it. It's just something you should think about, will it really make your system more secure or it will just bring unnecessary expenses?
Thank you!
I hope this guide was helpful. Thank you for reading. Don't hesitate to contact me, ask questions or let me know if you found a mistake 🤗
Posted on November 6, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.