Kong custom plugin development using Go
Since version 2.0, Kong announced Go as one of the supported languages to be used on Kong API Gateway plugins development besides Lua. Kong releases the Plugin Development Kit (PDK) for Go users on their official repository. In this article, we’ll try to develop and deploy custom plugins for Kong 2.3 using Go.
We will use Docker to build Kong plugins using the official go-plugin-tool, this image use Go version 1.13 for development. Our Dockerfile:
# Stage 1 - Build plugin
# Pull image from official go-plugin-tool.FROM kong/go-plugin-tool:2.0.4-alpine-latest AS builder
# Copy current workspace to container.RUN mkdir -p /tmp/go-plugins/COPY . /tmp/go-plugins/
# Build the plugin.RUN cd /tmp/go-plugins/ && \ apk add make && \ make all
# Stage 2 - Create Kong container and add custom plugin# Pull Kong version 2.3 from official repository.
FROM kong:2.3.3-alpine# Copy plugin from build container to Kong container
RUN mkdir /tmp/go-pluginsCOPY --from=builder /tmp/go-plugins/key-checker /usr/local/bin/key-checker# Copy Kong configuration to Kong container
COPY config.yml /tmp/config.yml# Dump plugin infoRUN /usr/local/bin/key-checker -dump
We use Docker multi stage build to build and install our plugin. First, we build the plugin, and then we copy the newly created plugin and Kong configuration to a separate Kong container. And for this plugin development, here is our project structure:
The structure is based on Standard Go Project Layout (for more detailed information, you can refer to embedded link), all our plugins will be placed inside cmd/kong
. Each plugin will be compiled as stand-alone executable binary file. And our main code base will be placed under an internal
directory. We use Clean Architecture to manage our code base.
Based on Kong plugin development documentation, there are some contracts to be implemented on our code.
- Define a structure type to hold configuration
We should create a struct to hold for our plugin configuration, the struct with public property will be filled automatically with Kong plugin config.
Based on the struct above, the APIKey
property will be filled with api_key
from Kong. For example, if we use Kong declarative configuration below, the APIKey
value will mysecretconsumerkey
.
2. Write a function to create instances of config struct
We should define a function to create an instance of our config struct, and the return type should be interface{}
for example:
3. Add methods to config struct to handle phases
We can implement custom logic to be executed at various points of the request processing lifecycle. Based on Kong documentation, there are some phases that will be executed during request / response life cycle:
Certificate: Executed during the SSL certificate serving phase of the SSL handshake.
Rewrite: Executed for every request upon its reception from a client as a rewrite phase handler
Access: Executed for every request from a client and before it is being proxied to the upstream service.
Response: Executed after the whole response has been received from the upstream service, but before sending any part of it to the client.
Preread: Executed once for every connection
Log: Executed once for each connection after it has been closed.
4. Add a main()
function that calls server.StartServer
Since we will compile our plugin as a standalone executable, we will use an embedded server from go-pdk
package, and start the server on our main function.
Now, let’s get our hands dirty by developing our custom plugin. We will develop a key-checker plugin. This plugin gets the secret key from the URL query parameter and validates the given key with the stored key on our config. This simple plugin is inspired by this article and the plugin will be used for our learning purpose.
We will create a struct to hold configuration and implement the Access
hook to validate every request from downstream to upstream. And then we should compare the key from the query parameter with an existing key on our configuration. If the key doesn’t match with our config key, we will respond with an HTTP 401 error, and if the given key match with our config key, the request will be forwarded to the upstream.
// KeyCheckerConfig will hold all Kong configuration data// and injected use case module.type KeyCheckerConfig struct { APIKey string `json:"api_key"` useCaseModule *di.UseCaseModule}
// NewKeyChecker will create an instance of KeyCheckerConfig.func NewKeyChecker() interface{} { return &KeyCheckerConfig{useCaseModule: di.GetUseCaseModule()}}func (conf *KeyCheckerConfig) Access(kong *pdk.PDK) { // Get `key` value from URL query
key, err := kong.Request.GetQueryArg("key") if err != nil { kong.Log.Err(err.Error()) }
// Validate given `key`, compare it with our key
// on configuration. Response with 401 if given key doesn't
// match with our key on configuration ctx := context.Background() respHeaders := make(map[string][]string) respHeaders["Content-Type"] = append(
respHeaders["Content-Type"],
"application/json"
) err = conf.useCaseModule.ValidateKeyUseCase.Execute(
ctx,
keychecker.ValidateKeyInput{
GivenKey: key,
ValidKey: conf.APIKey,
}
) if err != nil { switch err { case keychecker.ErrKeyEmpty, keychecker.ErrKeyNotValid: kong.Response.Exit(401, err.Error(), respHeaders) default: kong.Response.Exit(500, err.Error(), respHeaders) } return } // Get exchange token based on given key, and append it to
// `X-Exchange-Token` header. token, err := conf.useCaseModule.GetTokenUseCase.Execute(
ctx,
key
) if err != nil { kong.Response.Exit(500, err.Error(), respHeaders) return } kong.Response.SetHeader("X-Exchange-Token", token)}
The code above reflects our main logic should be implemented on Access hook, you can check the completed version of the code on this GitHub repo https://github.com/syafdia/go-exercise/tree/master/src/etc/demo-kong-plugin.
Now lets, run on our custom kong plugin, just clone the mentioned repo, and run build the image using docker build -t kong-demo
and run container using:
docker run -ti --rm --name kong-go-plugins \
-e "KONG_DATABASE=off" \
-e "KONG_DECLARATIVE_CONFIG=/tmp/config.yml" \
-e "KONG_PLUGINS=bundled,key-checker" \
-e "KONG_PLUGINSERVER_NAMES=key-checker" \
-e "KONG_PLUGINSERVER_KEY_CHECKER_START_CMD=/usr/local/bin/key-checker" \
-e "KONG_PLUGINSERVER_KEY_CHECKER_QUERY_CMD=/usr/local/bin/key-checker -dump" \
-e "KONG_PROXY_LISTEN=0.0.0.0:8000" \
-e "KONG_LOG_LEVEL=debug" \
-p 8000:8000 \
kong-demo
When we try to access our Kong using an invalid key, we will get HTTP 401 response and key is not valid
on the body. But when we use a valid key, we will get HTTP 200 andX-Exchange-Token
on response header along with response body.
With Kong version 2.x we could build our custom plugin using Go, and since Kong version 2.3, each plugin can be a service, compiled as a standalone executable binary.
Reference: