Part 2: Defining our Kong API Gateway with Ansible

This is part 2 in the series: Kong Up and Running

In the previous post we set up Kong using Docker and Docker Compose. Now, we need to configure our gateway to proxy our various APIs correctly. We'll go through the sames steps outlined in the official Kong getting started tutorial. Only this time we'll be using Ansible to automate the process

Before we start

  1. Code for this tutorial is available on Github
  2. We'll assume that our kong instance is installed at api.example.com
  3. This tutorial uses Ansible. We assume that you have Ansible installed and a basic understanding of how Ansible works.
  4. I've written some Kong modules which will make it easy for us to configure Kong via the RESTful admin interface. Understanding how to make Ansible modules is beyond the scope of this tutorial. You can read more about creating Ansible modules in: Custom Ansible Module Hello World. For this tutorial we will simply download the files from github.

Configuring Kong with Ansible

In this section we will:

  1. add an API
  2. secure access to our API with the key-auth plugin
  3. and finally we'll add a consumer who has access to the API

Getting started.

Make sure you're in the kong directory which we created in Part 1.

1. Install the kong modules

Download the module files from github and put them in the library directory:

mkdir library && cd library  
curl https://raw.githubusercontent.com/toast38coza/ansible-kong-module/master/library/kong_api.py > kong_api.py  
curl https://raw.githubusercontent.com/toast38coza/ansible-kong-module/master/library/kong_plugin.py > kong_plugin.py  
curl https://raw.githubusercontent.com/toast38coza/ansible-kong-module/master/library/kong_consumer.py > kong_consumer.py  

Note: these modules use the requests library. If you haven't already, you need to: pip install requests

2. Define your Ansible inventory

cd back into the top level kong directory:

cd ../  

Create an inventory file and put the following in it:

nano inventory

[localhost]
localhost              ansible_connection=local  

Since these commands are all essentially HTTP requests we will be running the playbook directly off the local machine.

3. Create a playbook

We'll put all our plays in a playbook called playbook.yml. The rest of the tutorial all takes place in playbook.yml.

touch playbook.yml  

With no tasks defined, our playbook should look like this:

- hosts: localhost
  vars: 
    - kong_admin_base_url: "http://api.example.com:8001"
    - kong_base_url: "http://api.example.com:8000"

  tasks:
  • hosts tell our playbook which inventory to run the plays against
  • We define variables for the Kong admin interface url (kong_admin_base_url) and the base url for Kong: (kong_base_url).

At this point, you should have the following file structure:

Dockerfile  
docker-compose.yml  
inventory  
[library]
  |_ kong_api.py
  |_ kong_plugin.py
  |_ kong_consumer.py
playbook.yml  
  • (the docker files are from part 1)

3. Configuring Kong

Let's get started adding some tasks:

Adding an API

First, we will tell Kong to proxy the Mockbin API. Add the following under tasks:

  ...
  tasks:

    - name: Register APIs
      kong_api:
        kong_admin_uri: "{{kong_admin_base_url}}"     
        name: "mockbin"
        upstream_url: "http://mockbin.com"
        request_host: "mockbin.com"   
        request_path: "/mockbin"   
        strip_request_path: yes
        state: present   

You can run your playbook with the following command:

ansible-playbook playbook.yml -i inventory  

And you should get output something like the following:

PLAY ***************************************************************************

TASK [setup] *******************************************************************  
ok: [localhost]

TASK [Register APIs] ***********************************************************  
changed: [localhost]

PLAY RECAP *********************************************************************  
localhost                  : ok=2    changed=1    unreachable=0    failed=0  

We have successfully added our first API to Kong.

Note: that that step: Register APIs status is changed. That means that Ansible did something. Ansible is designed to be idempotent. We can run this script as many times as we like and be sure that out environment is in exactly the state that this script depicts.

Kong will now proxy our requests nicely. You can try it out:

curl -i http://api.example.com/mockbin  

In fact, we can actually add a verification step to our playbook too:

- name: Verify API was added
  uri: 
    url: "{{kong_admin_base_url}}/apis/mockbin"
    status_code: 200
  • This will ping the admin endpoint for the API with the name mockbin. If a status code other than 200 is returned (e.g.: 404), then this step will fail.

Note: our implementation is slightly different from the Kong docs. We've specified request_path, therefore our API is accessible on the /mockbin path. Since we've also set the request_host, our API could also be accessed using the Host header as per the official quickstart tutorial.
Note also that we've set strip_request_path to True. This is necessary, otherwise, if we go to: api.example.com/mockbin the upstream base url will become: http://mockbin.com/mockbin (instead of just: http://mockbin.com). The default value for strip_request_path is False

Add authentication to our API

Next up, let's add key authentication to our mockbinAPI.

Add the following tasks to playbook.yml

    - name: Add key authentication 
      kong_plugin:
        kong_admin_uri: "{{kong_admin_base_url}}"
        api_name: "mockbin"
        plugin_name: "key-auth"        
        state: present  

and run the playbook with: ansible-playbook playbook.yml -i inventory

Now, if we make a request to our API, we should get a 401 Unauthorized:

curl -i http://api.example.com/mockbin  

Of course, we can automatically check this worked by adding a verification step to our playbook:

    - name: Verify key auth was added
      uri: 
        url: "{{kong_base_url}}/mockbin"
        status_code: 401 

This will ping our API with no credentials and verify that the correct status code is returned.

Add a consumer

Now, since we anticipate that we will need to add a number of different consumers over time, we're going to add our consumers as a variable. To the vars block at the top of the page add the kong_consumers variable:

- hosts: localhost
  vars: 
    - kong_admin_base_url: "http://192.168.99.100:8001"
    - kong_base_url: "http://192.168.99.100:8000"
    - kong_consumers:
      - username: Jack
        key: 123
      - username: Jill
        key: 456
  tasks:
     ...
  • This will create two consumers, Jack and Jill, with the auth-keys: 123, and 456 respectively.

Note: You would probably want to handle your api-keys a little differently, but for the purpose of this tutorial we'll just hard-code them like that.

Now, we add our consumers like so:

   - name: Add a consumer
      kong_consumer:
        kong_admin_uri: "{{kong_admin_base_url}}"
        username: "{{item.username}}"
        state: present
      with_items: "{{kong_consumers}}"
  • Notice that we pass in our kong_consumers list to with_items. That means that should we want to add new consumers, we can simply add them to the variable.

Tip: with_items is also a neat way to add multiple APIs.

and then we need to configure our consumer by providing it with a key:

   - name: Configure consumer
      kong_consumer:
        kong_admin_uri: "{{kong_admin_base_url}}"
        username: "{{item.username}}"
        api_name: key-auth
        data: 
          key: "{{item.key}}"
        state: configure
      with_items: "{{kong_consumers}}" 

Finally: we can verify that these users are now able to access our API with their keys:

    - name: Verify consumers can access API 
      uri: 
        url: "{{kong_base_url}}/mockbin"
        HEADER_apikey: "{{item.key}}"
        status_code: 200
      with_items: "{{kong_consumers}}"

Conclusion

Nicely done. You are now able to configure your Kong API in an easy and reliable manner. You can be confident that it is in the state that you expect by simply running you Ansible playbook.

ansible-playbook playbook.yml -i inventory  

From here, you can add more APIs, plugins and consumers.

Our final playbook looks like the following: