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.64.0+
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.
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.
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 "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.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 = "http://localhost:9876/sekretz",
description = "Base URL of the HTTP 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:
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.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
-
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:
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.