DEV Community

Matheus Mina
Matheus Mina

Posted on

3

Escrevendo plugins para o Kong em Go

O Kong, segundo a própia documentação, é um apigateway open source, criado para a nuvem, agnóstico em plataforma, feito para alta performance e extensível via plugins. O Kong possui uma grande gama de plugins oficiais que nos permitem fazer grande parte das customizações que necessitamos e quando o plugin não está disponível, podemos criar nosso própio plugin.

A linguagem padrão para se criar plugins é Lua, contudo outras linguagens são suportadas e uma delas é Go. Pessoalmente, não tenho experiência em escrever códigos em Lua, portanto, utilizar Go me permite uma maior velocidade de desenvolvimento e maior qualidade no plugin.

Desenvolvimento

Criar um plugin usando Go é bem simples, é necessário seguir a assinatura proposta pelo plugin development kit, ou PDK, do Kong, mas ela é de fácil entendimento. Para demonstrar isso, vamos criar um plugin que vai ler um header da requisição e a configuração do plugin. Com essas duas informações, vamos adicionar um novo header na resposta.

A primeira coisa a ser feita é definir as constantes version e priority. Como o própio nome diz, isso define a versão do plugin que está sendo desenvolvido e a prioridade em execução a relação a outros plugins. Vale a pena pontuar que quanto maior o valor da prioridade, mais cedo o plugin vai ser executado. Para ver mais sobre a prioridades dos plugins, este post explica bem.

var Version = "0.0.1"
var Priority = 1
Enter fullscreen mode Exit fullscreen mode

Com isso realizado, vamos definir quais os parametros que o plugin aceita, criando uma struct chamada Config. No nosso exemplo, aceitamos o campo message, que é uma string.

type Config struct {
    Message string `json:"message"`
}
Enter fullscreen mode Exit fullscreen mode

Os plugins do Kong funcionam baseados em quais fases do ciclo de vida da requisição eles devem ser executados. As fases possíveis são:

  • Certificate
  • Rewrite
  • Access
  • Response
  • Preread
  • Log

No nosso exemplo, vamos utilizar o Access, pois vamos atualizar a resposta antes de encaminharmos a requisição para o serviço responsável. Contudo, a assinatura de todas as fases são iguais. Para entender mais sobre as fases, recomendo ler essa documentação.

func (conf Config) Access(kong *pdk.PDK) {
    host, err := kong.Request.GetHeader("host")
    if err != nil {
        log.Printf("Error reading 'host' header: %s", err.Error())
    }

    message := conf.Message
    if message == "" {
        message = "hello"
    }
    kong.Response.SetHeader("x-hello-from-go", fmt.Sprintf("Go says %s to %s", message, host))
}
Enter fullscreen mode Exit fullscreen mode

Essa função utiliza o PDK para ler os headers disponíveis, através de kong.Request.GetHeader e depois adiciona um header na resposta utilizando o kong.Response.SetHeader. Para mais informações dos funções dísponíveis, dê uma olhada na documentação da Go PDK.

Por fim, vamos a função principal:

package main

import (
    "fmt"
    "log"

    "github.com/Kong/go-pdk"
    "github.com/Kong/go-pdk/server"
)

func main() {
    server.StartServer(New, Version, Priority)
}

func New() interface{} {
    return &Config{}
}
Enter fullscreen mode Exit fullscreen mode

Como podemos notar, a funcão principal só inicializa o servidor do Kong, passando a Config, Version e Priority definidas anteriormente. A implementação completa fica assim:

package main

import (
    "fmt"
    "log"

    "github.com/Kong/go-pdk"
    "github.com/Kong/go-pdk/server"
)

var Version = "0.0.1"
var Priority = 1

func main() {
    server.StartServer(New, Version, Priority)
}

type Config struct {
    Message string `json:"message"`
}

func New() interface{} {
    return &Config{}
}

func (conf Config) Access(kong *pdk.PDK) {
    host, err := kong.Request.GetHeader("host")
    if err != nil {
        log.Printf("Error reading 'host' header: %s", err.Error())
    }

    message := conf.Message
    if message == "" {
        message = "hello"
    }
    kong.Response.SetHeader("x-hello-from-go", fmt.Sprintf("Go says %s to %s", message, host))
}
Enter fullscreen mode Exit fullscreen mode

Testes

Testar os plugins em Go também é bem simples, uma vez que o própio PDK nos oferece ferramentas que nos permitem fazer isso de forma direta. Então vamos testar nosso plugin:

package main

import (
    "testing"

    "github.com/Kong/go-pdk/test"
    "github.com/stretchr/testify/assert"
)

func TestPluginWithoutConfig(t *testing.T) {
    env, err := test.New(t, test.Request{
        Method:  "GET",
        Url:     "http://example.com?q=search&x=9",
        Headers: map[string][]string{"host": {"localhost"}},
    })
    assert.NoError(t, err)

    env.DoHttps(&Config{})
    assert.Equal(t, 200, env.ClientRes.Status)
    assert.Equal(t, "Go says hello to localhost", env.ClientRes.Headers.Get("x-hello-from-go"))
}

func TestPluginWithConfig(t *testing.T) {
    env, err := test.New(t, test.Request{
        Method:  "GET",
        Url:     "http://example.com?q=search&x=9",
        Headers: map[string][]string{"host": {"localhost"}},
    })
    assert.NoError(t, err)

    env.DoHttps(&Config{Message: "nice to meet you"})
    assert.Equal(t, 200, env.ClientRes.Status)
    assert.Equal(t, "Go says nice to meet you to localhost", env.ClientRes.Headers.Get("x-hello-from-go"))
}
Enter fullscreen mode Exit fullscreen mode

Deploy

Para o deploy, precisamos gerar um executável e colocar isso na imagem que vamos utilizar do Kong.

# Build Golang plugins

FROM golang:1.20 AS plugin-builder

WORKDIR /builder

COPY ./hello ./go_plugins/hello

RUN find ./go_plugins -maxdepth 1 -mindepth 1 -type d -not -path "*/.git*" | \
    while read dir; do \
        cd $dir && go build -o /builds/$dir main.go  ; \
    done

# Build Kong
FROM kong:3.4.0-ubuntu

COPY --from=plugin-builder ./builds/go_plugins/  ./kong/

USER kong
Enter fullscreen mode Exit fullscreen mode

Para o Kong conseguir encontrar e carregar o plugin customizado é necessário especificar algumas configurações. Seguindo nosso exemplo, alterei o docker-compose para incluir:

KONG_PLUGINS: "bundled,hello"
KONG_PLUGINSERVER_NAMES: hello
KONG_PLUGINSERVER_HELLO_START_CMD: /kong/hello
KONG_PLUGINSERVER_HELLO_QUERY_CMD: /kong/hello -dump
Enter fullscreen mode Exit fullscreen mode

Considerações sobre performance

A própia Kong fez um estudo sobre a performance, que pode ser encontrado aqui.

Conclusão

Go é uma ótima ferramenta para se escrever plugins para o Kong. Podemos contar com toda a facilidade e performance da linguagem, além de todas as ferramentas que a linguagem oferece. No meu caso, consegui ganhar velocidade na entrega e uma maior cobertura de testes. Também é possível isolar a lógica dos plugins com a utilização da pdk, facilitando assim escrever plugins que funcionam pra diversas tecnologia, desacoplando do Kong.

Para ver todo o contéudo apresentado nesse post, é só acessar o repositório.

Você também pode me encontrar no Twitter, Github ou LinkedIn. Você também pode ler esse post e outros em meu blog.

Referências

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more