This content originally appeared on Level Up Coding - Medium and was authored by Syafdia Okta
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-plugins
COPY --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 info
RUN /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-pdkpackage, 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:
- https://docs.konghq.com/enterprise/2.3.x/external-plugins/
- https://dev.to/_mertsimsek/go-plugin-development-on-kong-50pb
- https://medium.com/swlh/creating-a-go-plugin-in-kong-e54a1315faaa
Kong custom plugin development using Go was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Syafdia Okta
Syafdia Okta | Sciencx (2021-05-03T13:09:37+00:00) Kong custom plugin development using Go. Retrieved from https://www.scien.cx/2021/05/03/kong-custom-plugin-development-using-go/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.