Creating Your First Custom Plugin for Kong Gateway in Lua

By on March 18, 2021

Creating Your First Custom Lua Plugin for Kong Gateway


← Back to Blog Home

This tutorial shows you how easy it is to build a custom Lua plugin for Kong Gateway. My Kong Lua plugin example will automatically add a custom header to any response sent out, indicating the current plugin version.

Kong Gateway is built on OpenResty, which extends the NGINX proxy server to run Lua scripts. It sits as a proxy between a client’s requests and routes them to defined services. 

With the open source Kong Gateway, developers can build plugins on a per-service basis. These plugins help you build policies and Lua functions important to your architecture. You can build plugins in real time, on requests your services receive or on custom responses sent back to the client. These plugins provide an easy way to add advanced functionality and features to your implementation. For example, you could use a plugin to implement logging, rate limiting, authentication and more. Kong even had a customer create a custom plugin for Open Policy Agent.

Kong Hub has a wide range of existing plugins created by both Kong and third-party plugin authors. A few exciting plugins include:

  • Bot Detection – protects a service or route from most common bots
  • StatsD – logs metrics to a StatsD server
  • Rate limiting – limits how many HTTP requests are received in a given period of time
  • Kong Upstream API – a community-built plugin that adds a signed JWT into the HTTP header
  • Inspur Response Transform – a community-built plugin that transforms the response sent by the upstream server from JSON to XML

In addition to using pre-built plugins, you can also build your own. For example, you could make a custom Lua plugin for Kong Gateway that hooks into the request/response phases or even works with streaming data. Kong’s plugin development kit (PDK) allows you to write plugins with functionality ranging from authentication and traffic control to logging and monitoring. Plugins can be written in Lua, Go, Python or Javascript. The plugin development kit has open source templates and tools designed to get you started.

Setting Up Your Development Environment

Kong helps you get started building plugins through Pongo, which serves several roles. Pongo provides a framework for you to test your plugin. It also ensures that you configure your system for Kong, including fetching any dependencies. And it sets up a test Kong environment so that you can run your plugin in real time.

Pongo requires docker-compose and curl. After installing those, clone Pongo and install the Pongo CLI (a symlink to a script file) like this:

The kong-plugin repository provides a simple template to get started writing a custom Lua plugin for Kong Gateway. Go ahead and clone that repo into a new directory called  kong-api-version-plugin:

In the kong-api-version-plugin project, there are two directories to focus on:

  1. kong/plugins/myplugin: this is where the source code for the plugin lives. Go ahead and rename this to kong/plugins/api-version.
  2. spec/myplugin: this is where the tests for your plugin reside. Rename this directory to spec/api-version and then open up each spec file and set PLUGIN_NAME to “api-version.”

With those files renamed, type pongo run to get started. Docker will fetch the images necessary to get an environment set up. After some time, your terminal will print lines like these:

Once those tests pass, the test setup is complete! Next, get access to a local Kong instance. Type pongo shell, which drops you into the Kong container. No need to start the dependencies since they were already started automatically by the pongo run command above. In this prompt, enter the next two lines:

While still in the Kong shell, run curl -i http://localhost:8000/ to ensure that you can communicate with Kong on its default port, 8000. If you get an error that says {"message":"no Route matched with those values"}, that’s perfectly fine! The critical thing to observe is that Kong handled the request.

Manually Testing the Custom Lua Plugin for Kong Gateway

Before testing the plugin, you’ll need to set up a dummy Service. A Service is an upstream API that Kong considers as a microservice to manage. Since this is an example, I’m going to set up a dummy service that points to any URL. You can use Kong’s Admin API to do this:

With the Service set up, you’ll need to add a route so that the requests can be proxied through Kong:

Last but not least, you’ll need to add the plugin to the Service:

Next, test this setup with a final cURL call. Only this time, you can ignore the body and just look at the headers:

If you see a header with the key name of Bye-World, you’re ready to write some code!

Writing Your Custom Lua Plugin for Kong Gateway

Structuring Your Kong Plugin Project Files

Within kong/plugins/api-version, you’ll see two files:

  1. handler.lua: This is where the main functionality of your plugin resides. Each phase of the request/response lifecycle has a function, which your plugin implements to provide custom behavior. I’ll go into this in more detail below.
  2. schema.lua: If your plugin requires additional configuration, such as key/value pairs a user can provide to alter behavior, the logic for that is stored here.

In handler.lua, you’ll notice several methods that take the form of function plugin:<name>. These methods run during the execution lifecycle of Kong. You can find a complete list of their descriptions in the API reference documentation. For this example plugin, I’ll concentrate on the plugin:header_filter, since my plugin will send a custom header.

Adding a Custom Header

There should already be a line in the plugin that contains a custom header:

When you run curl -i http://127.0.0.1:8000/, you should see a response header with the following line:

As you might surmise based on the set_header function, plugin_conf.response_header controls the header key, while the header value is explicitly defined here in the plugin:header_filter method.

Change “this is on the response” to read plugin.VERSION, which is a constant set at the very top of the file. In your Kong shell, run kong reload, execute the same curl command and notice that the value changes. Any time you modify your plugin code, you can run kong reload to reload it and iterate quickly on changes.

Next, take a look at schema.lua. There’s a field named response_header with a default value of Bye-World. You could just change this to something like Version and expect it to reflect in the response header. But I’ll dig a little deeper. One of the primary use cases of this schema is to allow users to set their own values in their plugin. When adding a plugin to a service, you can set up its configuration by passing some data values. 

Here’s an example. First, get a list of all of the Kong plugins:

Grab the id of the api-version plugin, and use it to delete the plugin:

Add the plugin back. Only this time, set a non-default value for the response_header:

Make one more call to curl -i http://127.0.0.1:8000/ and notice that while the schema.lua code defines a default of Version, the response is Plugin-Version, just like I set up in my configuration. You can use various properties in a schema, so be sure to check out the reference documentation for more information. 

Testing Your Custom Lua Plugin for Kong Gateway

The implementation is complete. You can now begin writing tests to ensure the functionality remains consistent. 

Pongo comes with its test runner, which loads your plugin in Kong and tests two significant aspects: 1) it provides a unit test to validate the schema, and 2) it provides an integration test that simulates an end-to-end request. It does this by automating all of the steps we went through above: 

  1. It creates a route to an existing service
  2. It attaches your plugin to that service
  3. It then starts the Kong node with the plugin and a predefined NGINX template

The logic to perform this setup is available to you automatically by the fixtures and methods available in Pongo. All you’ll need to do is write the test logic. To make requests to this test Kong node, you can use the client.

If your terminal is still in the Kong shell, type exit to return to your command prompt. Then, go ahead and run pongo run one more time. You should receive an error that resembles the following:

If you scroll down to around line 80, you’ll notice that the client makes a request to the Kong node, but in the response, it expects a header value that we changed. To fix this, you’ll need to change the existing strings to match what we provided in our handler:

Another pongo run should confirm that the test now passes.

As well, it’s a good idea to add a unit test for any large schema changes you might have made. We didn’t really do that in this tutorial. Nonetheless, let’s go ahead and add a test in 01-unit_spec.lua that asserts that the response header will have a default value. We can test this by passing nil during the configuration step:

For a final code cleanup, you can run pongo lint to run LuaCheck. LuaCheck will do a static analysis of the code and report any obvious problems (e.g., accidental global variables, etc.). It’s always a good idea to lint your code because Lua is a dynamic language, and you could easily miss typos.

Enhancing Your Kong Gateway

Kong plugins are a powerful way to instantly implement policies and functionality in your API gateway. With Pongo and the kong-plugin template, Kong provides much of the starting boilerplate for you. In this example, I’ve only just begun to explore how plugins can allow Kong Gateway to gain expanded capabilities. If you’d like a more in-depth look at some of the things you can build, check out the Plugin Development Guide in Kong’s documentation.

Check out the following further information:

 

Have questions or want to stay in touch with the Kong community? Join us wherever you hang out:

⭐ Star us on GitHub

🐦 Follow us on Twitter

🌎 Join the Kong Community

🍻 Join our Meetups

❓ ️Ask and answer questions on Kong Nation

💯 Apply to become a Kong Champion

 

Tags:

Share Post: