Create a custom vault backend

Incompatible with
konnect
Minimum Version
Kong Gateway - 3.4
TL;DR

Write a Lua module in kong/vaults/<name>.lua that exports name, VERSION, init, and get. Add a schema at kong/vaults/<name>/schema.lua. Start Kong Gateway with KONG_VAULTS: bundled,<name> and KONG_LUA_PACKAGE_PATH pointing to your code. Create a Vault entity, then reference secrets with {vault://<prefix>/<key>} in any referenceable field.

Prerequisites

To complete this tutorial, install decK. We recommend keeping decK up to date with the latest version (1.64.0).

decK is a CLI tool for managing Kong Gateway declaratively with state files. This guide uses deck gateway apply, which directly applies entity configuration to your Gateway instance.

You can check your current decK version with deck version.

Export your Kong Gateway Enterprise license as an environment variable:

export KONG_LICENSE_DATA='your-license-contents'

Create the vault module

You can build a custom vault backend when your secrets live in a store that isn’t one of the provided Kong Gateway Vaults. For example, if your secrets live in an internal secrets service, a proprietary HTTP-based secret manager, or a legacy system that exposes secrets over an API only your organization uses.

A custom vault backend is a Lua module with two required functions:

Function

Description

init(conf) Called once at startup. Use it to validate config or open persistent connections.
get(conf, resource, version) Called to resolve a secret reference. Returns the secret value as a string, or nil if not found. resource is the key portion of the {vault://prefix/key} reference.

Create the directory structure:

mkdir -p kong/vaults/http

Create kong/vaults/http.lua:

echo '
local http = require("resty.http")
local cjson = require("cjson")

local function init(conf)
end

local function get(conf, resource, version)
  local base_url = conf.base_url or "http://localhost:9876/sekretz"

  local httpc = http.new()
  local res, err = httpc:request_uri(base_url .. "/" .. resource, {
    method = "GET",
    headers = {
      ["Accept"] = "application/json",
    },
  })

  if not res then
    ngx.log(ngx.WARN, "http vault: request failed for ", resource, ": ", err)
    return nil
  end

  if res.status ~= 200 then
    ngx.log(ngx.WARN, "http vault: status ", res.status, " for ", resource)
    return nil
  end

  local ok, data = pcall(cjson.decode, res.body)
  if not ok or data == nil then
    ngx.log(ngx.WARN, "http vault: failed to decode JSON for ", resource)
    return nil
  end

  return data.value
end

return {
  name = "http",
  VERSION = "1.0.0",
  init = init,
  get = get,
}' > kong/vaults/http.lua

The module fetches <base_url>/<resource> and returns the value field from the JSON response body.

Create the vault schema

The schema declares the configuration fields exposed on the Vault entity. Create kong/vaults/http/schema.lua:

echo 'return {
  name = "http",
  fields = {
    {
      config = {
        type = "record",
        fields = {
          {
            base_url = {
              type = "string",
              default = "http://localhost:9876/sekretz",
              description = "Base URL of the HTTP secret store. Secrets are fetched from <base_url>/<key>.",
            },
          },
        },
      },
    },
  },
}' > kong/vaults/http/schema.lua

Start Kong with the custom vault

Kong must be started with the custom vault registered. This requires setting KONG_VAULTS and mounting the Lua files into the container before Kong Gateway starts.

  1. Create a secret that the vault will serve:

    mkdir -p secrets/sekretz
    echo '{"value":"X-From-Vault:top-secret-value"}' > secrets/sekretz/x-from-vault
  2. Create docker-compose.yml:

   echo '
   services:
     secret-store:
       image: busybox
       command: httpd -f -p 9876 -h /www
       volumes:
         - ./secrets:/www
       healthcheck:
         test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:9876/sekretz/x-from-vault"]
         interval: 3s
         timeout: 5s
         retries: 10
         start_period: 5s

     postgres:
       image: postgres:16
       environment:
         POSTGRES_USER: kong
         POSTGRES_PASSWORD: kong
         POSTGRES_DB: kong
       healthcheck:
         test: ["CMD", "pg_isready", "-U", "kong"]
         interval: 5s
         timeout: 5s
         retries: 10
       restart: unless-stopped

     kong-migrations:
       image: kong/kong-gateway:latest
       command: kong migrations bootstrap
       environment:
         KONG_DATABASE: postgres
         KONG_PG_HOST: postgres
         KONG_PG_USER: kong
         KONG_PG_PASSWORD: kong
         KONG_PG_DATABASE: kong
         KONG_LICENSE_DATA: ${KONG_LICENSE_DATA}
       depends_on:
         postgres:
           condition: service_healthy

     kong:
       image: kong/kong-gateway:latest
       environment:
         KONG_DATABASE: postgres
         KONG_PG_HOST: postgres
         KONG_PG_USER: kong
         KONG_PG_PASSWORD: kong
         KONG_PG_DATABASE: kong
         KONG_LICENSE_DATA: ${KONG_LICENSE_DATA}
         KONG_PROXY_LISTEN: "0.0.0.0:8000"
         KONG_ADMIN_LISTEN: "0.0.0.0:8001"
         KONG_VAULTS: bundled,http
         KONG_LUA_PACKAGE_PATH: /custom-code/?.lua;;
       ports:
         - "8000:8000"
         - "8001:8001"
       volumes:
         - ./kong:/custom-code/kong
       depends_on:
         postgres:
           condition: service_healthy
         kong-migrations:
           condition: service_completed_successfully
         secret-store:
           condition: service_healthy
       healthcheck:
         test: ["CMD", "kong", "health"]
         interval: 10s
         timeout: 5s
         retries: 10
       restart: unless-stopped' > docker-compose.yml

The two key environment variables for custom vaults are:

  • KONG_VAULTS: bundled,http: Registers the built-in Vaults plus the custom http vault
  • KONG_LUA_PACKAGE_PATH: /custom-code/?.lua;;: Tells Kong Gateway where to find the Lua modules
  1. Start all services:

    docker compose up -d

Create the Vault entity

Create a Vault entity that configures the http vault backend. The prefix value (http-vault) is used in secret references:

echo '
_format_version: "3.0"
vaults:
  - name: http
    prefix: http-vault
    description: Custom HTTP vault backend
    config:
      base_url: http://secret-store:9876/sekretz
' | deck gateway apply -

The base_url uses the Docker Compose service name (secret-store) as the hostname, since Kong Gateway and the secret store run in the same Docker network.

Configure the plugin with a vault reference

Create a Gateway Service and Route, then enable the Request Transformer Advanced plugin. The add.headers list accepts items in header-name:value format. Here the vault reference is used as the entire item; Kong Gateway resolves {vault://http-vault/x-from-vault} to X-From-Vault:top-secret-value, which the plugin then parses into header name X-From-Vault and value top-secret-value.

echo '
_format_version: "3.0"
services:
  - name: httpbin
    url: http://httpbin.konghq.com/anything
    routes:
    - name: httpbin-route
      paths:
      - "/"
      protocols:
      - http
    plugins:
    - name: request-transformer-advanced
      config:
        add:
          headers:
          - "{vault://http-vault/x-from-vault}"
' | deck gateway apply -

Validate

Send a request through the proxy:

curl "http://localhost:8000/" \
     --no-progress-meter --fail-with-body 

The upstream echoes the full incoming request as a JSON response body. Check the headers object for X-From-Vault:

{
  "headers": {
    "X-From-Vault": "top-secret-value"
  }
}

If the header is present with the value from secrets/sekretz/x-from-vault, the custom vault is working correctly.

Cleanup

Stop and remove all containers and volumes:

docker compose down -v

FAQs

Kong Gateway calls get whenever it needs to resolve a {vault://...} reference. With the default TTL, the resolved value is cached and get is not called again until the TTL expires. Set KONG_VAULT_<NAME>_TTL=0 to disable caching and call get on every request.

No. get must return a string or nil. Kong Gateway stores the resolved value as a string and injects it into the plugin config field as-is.

Yes, you can also configure a Vault in one of the following ways:

  • Using environment variables, set at Kong Gateway startup
  • Using parameters in kong.conf, set at Kong Gateway startup

See the Vault reference for your provider for the available parameters and their format in each method.

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!