curl "http://localhost:8000/" \
--no-progress-meter --fail-with-body Create a custom vault backend
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
decK v1.65.1+
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.
Docker and Docker Compose
Install Docker and Docker Compose.
Export your Kong Gateway license
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/httpCreate 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.luaThe 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.luaStart 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.
-
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 -
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.ymlThe two key environment variables for custom vaults are:
-
KONG_VAULTS: bundled,http: Registers the built-in Vaults plus the customhttpvault -
KONG_LUA_PACKAGE_PATH: /custom-code/?.lua;;: Tells Kong Gateway where to find the Lua modules
The
cert-generatorservice creates the short-lived certificate automatically before the tutorial-only secret store starts. No certificate setup is required outside Docker Compose. -
-
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:
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
Destroy the Docker Compose environment
Stop and remove all containers and volumes:
docker compose down -vFAQs
When is get called?
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.
Can my vault return a value other than a string?
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.
Can I configure Vault in a different way without using the Vault entity directly?
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.