Extending the APISIX Plugin
Goal
Understand the Keymate APISIX access plugin — its dual implementation model, configuration parameters, request flow, and extension points — so you can customize its behavior for your deployment.
Audience
Developers integrating Keymate authorization into Apache APISIX routes, or customizing the plugin behavior for specific use cases.
Prerequisites
- A running Apache APISIX instance (version 3.10.0 or later)
- Access to a running Access Gateway instance
- Familiarity with Apache APISIX route and plugin configuration
- For WASM modifications: Go and TinyGo 0.33.0
- For Lua modifications: basic Lua and OpenResty knowledge
Before You Start
The Keymate APISIX plugin ships in two implementations:
| Implementation | Language | Runtime | Best For |
|---|---|---|---|
| Lua | Lua (native APISIX plugin) | OpenResty | Standard deployments, fast iteration, no compilation step |
| WASM | Go (compiled with TinyGo) | WASM VM via proxy-wasm | High-performance isolation, language-neutral execution |
Both implementations perform the same function: intercept incoming HTTP requests during the APISIX access phase, forward them to the Access Gateway for authorization, and allow or deny the request based on the response.
The Lua plugin is registered as keops-access-gateway in APISIX with priority 2000. The WASM plugin uses the proxy-wasm Go SDK (github.com/tetratelabs/proxy-wasm-go-sdk).
Choose the implementation that fits your operational requirements. Both can run simultaneously on different APISIX routes.
Worked Example
In this guide, you configure the plugin on a route, understand how the request flows through the authorization check, and learn where to extend the plugin behavior.
Steps
1. Understand the request flow
When a client request hits an APISIX route with the Keymate plugin enabled, the following sequence occurs:
The plugin:
- Captures the original request headers, method, URI (path + query string), and body
- Removes protected headers to prevent client-side spoofing (
Keops-Enforcer,Keops-Target-URI,Keops-Target-Method, andKeops-Client-Id, plus the olderKeymate-*spelling of each) - Adds its own
Keops-*headers identifying the gateway enforcer, target URI, and HTTP method, plus the client ID when it can read one from the bearer token - Sends a POST request to the Access Gateway with the original request body
- Interprets the Access Gateway response: 2xx means allow, anything else means deny
2. Configure the Lua plugin
The Lua plugin is configured in the APISIX route definition:
plugins:
keops-access-gateway:
gateway_url: "http://access-gateway.example.com:8080"
endpoint: "/gateway/api/v1/access/plugins/check-permission"
timeout: 5000
Configuration parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
gateway_url | String | Yes | — | Base URL of the Access Gateway instance |
endpoint | String | No | /auth | Authorization endpoint path appended to gateway_url |
timeout | Integer | No | 5000 | Request timeout in milliseconds |
The Lua plugin uses APISIX's resty.http client with connection keepalive (60s timeout, pool size 100).
3. Configure the WASM plugin
The WASM plugin accepts configuration as a JSON string in the conf parameter:
plugins:
keymate-access-gateway-wasm:
conf: |
{
"gateway_url": "http://access-gateway.example.com:8080",
"endpoint": "/gateway/api/v1/access/plugins/check-permission",
"timeout": 5000
}
The WASM plugin accepts the same parameters as the Lua plugin (gateway_url, endpoint, timeout) with the same defaults.
4. Understand the headers
Headers set by the plugin (sent to Access Gateway):
| Header | Value | Description |
|---|---|---|
Keops-Enforcer | layer="gateway", tech="apisix", version="<plugin-version>" | Identifies the enforcer type, technology, and version |
Keops-Target-URI | Original request path and query string | The target URI for access rule matching |
Keops-Target-Method | Original HTTP method | The HTTP method for resource resolution |
Keops-Client-Id | Client ID from the bearer token (azp claim, then client_id, then clientId) | Sent only when the plugin can read a client ID from the token. The plugin does not verify the token; the Access Gateway does |
Headers filtered (not forwarded to Access Gateway):
The plugin removes these headers from the forwarded request to prevent client-side spoofing:
Keops-Enforcer,Keops-Target-URI,Keops-Target-Method,Keops-Client-Id: set by the plugin itself- The older
Keymate-Enforcer,Keymate-Target-URI,Keymate-Target-Method, andKeymate-Client-Id: dropped if a client sends them Content-Length: recalculated for the gateway request
What else is forwarded depends on the implementation:
- Lua plugin: every other original request header is forwarded as-is, including
AuthorizationandHost. - WASM plugin: it also drops the HTTP/2 pseudo-headers (it sets its own for the gateway call),
Host,Transfer-Encoding,Connection,Keep-AliveandExpect, and forwards every other header as-is, includingAuthorization.
In both, a client cannot supply its own client ID: the plugin reads it from the bearer token and sends it as Keops-Client-Id.
Headers added to the client response:
| Header | Source | Description |
|---|---|---|
Keops-Decision | Access Gateway | Authorization decision result and authority source |
Keops-Decision-Latency | Access Gateway | Total decision processing time in milliseconds |
Keops-Decision-Authority-Latency | Access Gateway | Authority call latency in milliseconds |
Keops-Decision-Cache | Access Gateway | Cache hit or miss indicator |
Keops-Gateway | Access Gateway | Access Gateway version |
Keops-Gateway-Auth-Duration | Plugin | Total plugin execution time in milliseconds |
Keops-Gateway-Auth-Status | Plugin | Plugin outcome: GRANTED, DENIED, or GATEWAY_FAILED |
Keops-Decision-* and Keops-Gateway headers are forwarded from the Access Gateway response. Keops-Gateway-Auth-Duration and Keops-Gateway-Auth-Status are set by the plugin itself to track total plugin execution time and the final authorization outcome.
5. Understand error handling
| Condition | HTTP Status | Response Body | Keops-Gateway-Auth-Status |
|---|---|---|---|
| Access Gateway unreachable or timeout | 502 | {"error": "Authorization Service Unavailable"} | GATEWAY_FAILED |
| Access Gateway returns 2xx | — | Request forwarded to upstream | GRANTED |
| Access Gateway returns 500, 502, 503 or 504 | Same as gateway | Gateway response body forwarded to client | GATEWAY_FAILED |
| Access Gateway returns any other 5xx status, such as 501 | Same as gateway | Gateway response body forwarded to client | GATEWAY_FAILED (Lua), DENIED (WASM) |
| Access Gateway returns any other status, such as 4xx | Same as gateway | Gateway response body forwarded to client | DENIED |
The plugin does not retry failed gateway requests. If the Access Gateway is unavailable, the request is rejected with a 502 status.
Both implementations also accept the five Access Gateway headers under their older Keymate-* names (for example Keymate-Decision) from an Access Gateway that predates the rename, plus two older names: Elapsed-Time-Ms for Keops-Decision-Latency and Permission-Gateway-Duration-Ms for Keops-Decision-Authority-Latency. Whichever name the Access Gateway sends, the plugin returns the value to the client under the Keops-* name.
6. Customize per-route behavior
Configure different plugin parameters per APISIX route. This allows different services behind the same APISIX instance to use different Access Gateway endpoints or timeouts:
routes:
- uri: /api/payments/*
plugins:
keops-access-gateway:
gateway_url: "http://access-gateway.example.com:8080"
endpoint: "/gateway/api/v1/access/plugins/check-permission"
timeout: 3000
- uri: /api/reports/*
plugins:
keops-access-gateway:
gateway_url: "http://access-gateway.example.com:8080"
endpoint: "/gateway/api/v1/access/plugins/check-permission"
timeout: 10000
7. Extend the plugin
To add custom behavior, modify the plugin source code:
Adding custom headers: Both implementations construct the gateway request headers in a central location. Add new headers alongside the existing Keops-* headers to pass additional context to the Access Gateway.
Adding protected headers: Expand the protected_headers table (Lua) or the header filter conditions (WASM) to prevent additional client-supplied headers from reaching the Access Gateway.
Custom error responses: Modify the error handling section to return custom error formats or status codes when the Access Gateway is unavailable.
Custom logging: Both implementations include log points for debugging. The Lua plugin uses core.log.info/core.log.warn/core.log.error. The WASM plugin uses proxywasm.LogInfof/proxywasm.LogWarnf/proxywasm.LogErrorf.
After modifying the Lua plugin, reload the APISIX configuration. After modifying the WASM plugin, recompile with TinyGo (make build) and redeploy the WASM binary.
Validation Scenario
Scenario
You configure the Lua plugin on a route and send a request with a valid access token. The Access Gateway grants access and the request reaches the upstream service.
Expected Result
- The upstream service receives the request
- The client response includes
Keops-DecisionandKeops-Gateway-Auth-Durationheaders Keops-Gateway-Auth-StatusisGRANTED
How to Verify
- API evidence: Send a request with a valid Bearer token and check the response headers for
Keops-Decision,Keops-Decision-Cache, andKeops-Gateway-Auth-Status - Logs: Check APISIX error logs for
[KEOPS-ACCESS-GATEWAY]plugin execution entries - Audit evidence: Verify the Access Gateway audit log contains the authorization check event
Troubleshooting
- 502 "Authorization Service Unavailable" — The plugin cannot reach the Access Gateway. Verify the
gateway_urlis correct and the Access Gateway is running. Check network connectivity between APISIX and the Access Gateway. - Timeout errors — Increase the
timeoutvalue in the plugin configuration. The default is 5000ms. For latency-sensitive deployments, ensure the Access Gateway is co-located in the same network. - Client-supplied Keops headers are ignored — This is by design. The plugin filters
Keops-Enforcer,Keops-Target-URI,Keops-Target-Method, andKeops-Client-Id, and the olderKeymate-*spelling of each, from client requests to prevent spoofing. - WASM plugin not loading — Verify the WASM binary is compiled and accessible by APISIX. Rebuild with
make buildand ensure the binary path matches the APISIX plugin configuration. - Missing decision headers in response — The Access Gateway may not be returning decision headers. Verify the Access Gateway version supports the expected response headers. Check for the backward-compatible legacy header names.
Next Steps
Set up a local development environment to test plugin changes — see Plugin Local Development & Test Setup.
For contributing changes back to the plugin repository, see Contributing to Enforcement Plugins.
Related Docs
Access Gateway Overview
Endpoints, headers, and system role of the Access Gateway.
Plugin Local Development & Test Setup
Set up the local container environment for plugin testing.
Extending the Istio Plugin
Istio WASM plugin for authorization enforcement.
Enforcement Pipeline
How the Access Gateway processes permission check requests.