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.65.1).

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.

Important: The Compose file creates a self-signed certificate solely for this tutorial. Use certificates issued and managed according to your organization’s security requirements in production.

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 "https://secret-store:9876/sekretz"

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

  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 = "https://secret-store:9876/sekretz",
              description = "Base URL of the HTTPS 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:
      cert-generator:
        image: alpine/openssl:latest
        command: >
          req -x509 -newkey rsa:2048 -nodes -days 1
          -keyout /certs/secret-store.key
          -out /certs/secret-store.crt
          -subj /CN=secret-store
          -addext subjectAltName=DNS:secret-store
        volumes:
          - secret-store-certs:/certs
    
      secret-store:
        image: python:3.14
        command: >
          python -m http.server 9876 --directory /www
          --tls-cert /certs/secret-store.crt --tls-key /certs/secret-store.key
        volumes:
          - ./secrets:/www
          - secret-store-certs:/certs:ro
        depends_on:
          cert-generator:
            condition: service_completed_successfully
        healthcheck:
          test: ["CMD", "python", "-c", "import ssl, urllib.request; urllib.request.urlopen(\"https://secret-store:9876/sekretz/x-from-vault\", context=ssl.create_default_context(cafile=\"/certs/secret-store.crt\"))"]
          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;;
          KONG_LUA_SSL_TRUSTED_CERTIFICATE: /certs/secret-store.crt
        ports:
          - "8000:8000"
          - "8001:8001"
        volumes:
          - ./kong:/custom-code/kong
          - secret-store-certs:/certs:ro
        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
    
    volumes:
      secret-store-certs:' > 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

    The cert-generator service creates the short-lived certificate automatically before the tutorial-only secret store starts. No certificate setup is required outside Docker Compose.

  3. Start all services:

    docker compose up -d --wait

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 HTTPS vault backend
    config:
      base_url: https://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. The Compose file includes secret-store as a DNS subject alternative name in the temporary certificate, and configures Kong Gateway to trust that certificate. The vault client enables TLS verification, so it verifies both the certificate chain and hostname.

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!