🦍

KongのRequest Transformerプラグインをカスタマイズする

2022/11/30に公開

はじめに

OSSのKongは世界で最も人気のあるAPI Gatewayで、認証認可や流量制御などプラグインを有効化するだけで用意することができます。

プラグインはこちらのPlugin Hubにリストアップされており、必要なものを選んで導入することができますが、必要な機能がサポートされていない、といったケースもあるかと思います。
その際にはKong提供のプラグインをベースにカスタマイズすることも可能です。

本記事ではリクエスト情報の編集を行うRequest Transformer Pluginへの機能追加を例に、Kong提供プラグインのカスタマイズ方法を記載します。
下記のサンプルコードはこちらになります。

また、OSSのAPI Gateway「Kong」の日本コミュニティ Kong Community, Japan として活動も行っています。興味のある方はslackにご参加いただければ幸いです。

プラグインのコードの入手

request transformerのようなKong提供のプラグインはkongのイメージに予め含まれているのでコピーしてきます。
今回はDBレスモード(yamlファイルでの設定管理)でのKong利用とします。

まず、適当なkong.ymlを作成します

_format_version: "1.1"

services:
  - name: example_service
    url: http://httpbin.org/status/200
    routes:
      - name: example_route
        paths:
          - /example

次章以降のためにDockerfileを作っておきます

Dockerfile
FROM kong:3.0.0-alpine
USER root

COPY kong.yml /kong/declarative/kong.yml

# DBレスモード
ENV KONG_DATABASE off 
ENV KONG_DECLARATIVE_CONFIG=/kong/declarative/kong.yml
ENV KONG_PROXY_ACCESS_LOG /dev/stdout
ENV KONG_ADMIN_ACCESS_LOG /dev/stdout
ENV KONG_PROXY_ERROR_LOG /dev/stderr
ENV KONG_ADMIN_ERROR_LOG /dev/stderr

以下でrequest-transformerのコードを入手します。

docker build -t custom-kong .
docker run -d --name kong-dbless -p 8000:8000 custom-kong
docker cp kong-dbless:/usr/local/share/lua/5.1/kong/plugins/request-transformer request-transformer

以下のフォルダ/ファイルがコピーしたrequest-transformerディレクトリに含まれていればokです

  • migrations
  • access.lua
  • handler.lua
  • schema.lua

Kongプラグインについて

KongのプラグインはLuaという言語で書かれており、主な構成ファイルはhandler.luaschema.luaになります。

  1. handler.lua

    ライフサイクル(request, response, logなど)の各フェーズで実行するプラグインの処理を記載する。

  2. schema.lua

    ユーザーが動作を変更するための変数を定義する。

request-transformerではhandler.lua内で処理を記載したaccess.luaを呼び出す構成になっています。
(migrations配下はDBのマイグレーション用)

request-transformerのカスタマイズ

今回はカスタマイズ例として、リクエストボディに環境変数をマッピングできるように変更します。
UIでは保持できない機密情報などのパラメータをKongでマッピングし、後続のAPIに流すことができます。

必要なライブラリのインストール

環境変数を扱うことのできるlua-resty-envを導入します。
Dockerfile内でluarocks installします。

luarocksがない方はbrew install luarocksでインストールしてください。

Dockerfile
FROM kong:3.0.0-alpine
...
# 以下を追加
RUN luarocks install lua-resty-env

ENV KONG_DATABASE off
...

access.luaの書き換え

request-transformerではtemplate文字列($(...))でヘッダーやクエリ文字列、URI文字列を使用することができます。(Template as a Value)
ここでは$(env.~)で環境変数を使用できるようにします

resty.envを呼び出し、envという文字列に対してenv.list()で環境変数を返すように変更します

access.lua
local multipart = require "multipart"
local cjson = require "cjson"
local pl_template = require "pl.template"
local pl_tablex = require "pl.tablex"
--- 以下を追加 ---
local resty_env = require "resty.env"
...

local __meta_environment = {
  __index = function(self, key)
    local lazy_loaders = {
      headers = function(self)
        return get_headers() or EMPTY
      end,
      query_params = function(self)
        return get_uri_args() or EMPTY
      end,
      uri_captures = function(self)
        return (ngx.ctx.router_matches or EMPTY).uri_captures or EMPTY
      end,
      shared = function(self)
        return ((kong or EMPTY).ctx or EMPTY).shared or EMPTY
      end,
      --- 以下を追加 ---
      env = function(self) 
        return resty_env.list() or EMPTY
      end
    }
    ...
}

template_environment = setmetatable({
  -- here we can optionally add functions to expose to the sandbox, eg:
  -- tostring = tostring,  -- for example
  -- because headers may contain array elements such as duplicated headers
  -- type is a useful function in these cases. See issue #25.
  type = type,
}, __meta_environment)

local function clear_environment(conf)
  rawset(template_environment, "headers", nil)
  rawset(template_environment, "query_params", nil)
  rawset(template_environment, "uri_captures", nil)
  rawset(template_environment, "shared", nil)
  --- 以下を追加 ---
  rawset(template_environment, "env", nil)
end

...

変更したプラグインの有効化

プラグインの追加

今回編集したプラグインを使えるようにDockerfileに記述を追加します
編集したrequest-transformerディレクトリを/usr/local/share/lua/5.1/kong/plugins/にコピーします。
また、以下の環境変数を設定します

Env Variable Value Description
KONG_UNTRUSTED_LUA on カスタムLuaコードを使えるようにする設定
Dockerfile
FROM kong:3.0.0-alpine
...

# 以下を追加
ENV KONG_UNTRUSTED_LUA on
COPY request-transformer /usr/local/share/lua/5.1/kong/plugins/request-transformer

変更したプラグインを使用してみる

環境変数の設定

まずはリクエストボディにマッピングする環境変数を定義します。
コンテナに設定した環境変数をlua-resty-envで呼び出すには環境変数名をNginxで指定する必要があります。
以下のようにKONG_NGINX_MAIN_ENVに環境変数を指定します。
複数指定したい場合はKONG_NGINX_MAIN_ENV=FOO; env BAR;と指定します。
(詳しくはこちらのissueに記載があります)

version: "3"
services:
  kong:
     build: .
     container_name: kong-dbless
     environment:
        - KONG_NGINX_MAIN_ENV=ENV; env SECRET
        - ENV=dev
        - SECRET=secret
     command: >
        /bin/bash -c "
        kong start"
     restart: always
     ports:
      - 8000:8000

routeへのプラグインの追加

kong.ymlにrequest-transformerで環境変数のSECRETをリクエストボディにマッピングしてみます

_format_version: "1.1"

services:
  - name: example_service
    url: http://httpbin.org/post
    routes:
      - name: example_route
        methods:
          - POST
        paths:
          - /httpbin/post
    plugins:
      - name: request-transformer
        config:
          add:
            body:
              - secret:$(env.SECRET)

これでコンテナを起動します

docker-compose up -d --build

リクエスト

localhost:8000/httpbin/postにリクエストしてみます
今回upstreamに指定しているhttpbin.org/postはリクエストした内容をレスポンスとして返してくれます

curl -X POST -H "Content-Type: application/json" -d '{"test": "test"}' http://localhost:8000/httpbin/post

>>> 
{
  "args": {}, 
  "data": "{\"secret\":\"secret\",\"test\":\"test\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "33", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.79.1", 
    "X-Forwarded-Host": "localhost", 
    "X-Forwarded-Path": "/httpbin/post", 
    "X-Forwarded-Prefix": "/httpbin/post"
  }, 
  "json": {
    "secret": "secret", 
    "test": "test"
  }, 
  "url": "http://localhost/post"
}

ちゃんとSECRETの内容がボディにマッピングされていることが確認できます。
上記を応用することで、UIでは保持できない機密情報などのパラメータをKongでマッピングし、後続のAPIに流すことができます。

Discussion