Skip to main content

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:

ImplementationLanguageRuntimeBest For
LuaLua (native APISIX plugin)OpenRestyStandard deployments, fast iteration, no compilation step
WASMGo (compiled with TinyGo)WASM VM via proxy-wasmHigh-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:

  1. Captures the original request headers, method, URI (path + query string), and body
  2. Removes protected headers to prevent client-side spoofing (Keops-Enforcer, Keops-Target-URI, Keops-Target-Method, and Keops-Client-Id, plus the older Keymate-* spelling of each)
  3. 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
  4. Sends a POST request to the Access Gateway with the original request body
  5. 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:

APISIX route configuration (Lua)
plugins:
keops-access-gateway:
gateway_url: "http://access-gateway.example.com:8080"
endpoint: "/gateway/api/v1/access/plugins/check-permission"
timeout: 5000

Configuration parameters:

ParameterTypeRequiredDefaultDescription
gateway_urlStringYes—Base URL of the Access Gateway instance
endpointStringNo/authAuthorization endpoint path appended to gateway_url
timeoutIntegerNo5000Request 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:

APISIX route configuration (WASM)
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):

HeaderValueDescription
Keops-Enforcerlayer="gateway", tech="apisix", version="<plugin-version>"Identifies the enforcer type, technology, and version
Keops-Target-URIOriginal request path and query stringThe target URI for access rule matching
Keops-Target-MethodOriginal HTTP methodThe HTTP method for resource resolution
Keops-Client-IdClient 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, and Keymate-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 Authorization and Host.
  • WASM plugin: it also drops the HTTP/2 pseudo-headers (it sets its own for the gateway call), Host, Transfer-Encoding, Connection, Keep-Alive and Expect, and forwards every other header as-is, including Authorization.

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:

HeaderSourceDescription
Keops-DecisionAccess GatewayAuthorization decision result and authority source
Keops-Decision-LatencyAccess GatewayTotal decision processing time in milliseconds
Keops-Decision-Authority-LatencyAccess GatewayAuthority call latency in milliseconds
Keops-Decision-CacheAccess GatewayCache hit or miss indicator
Keops-GatewayAccess GatewayAccess Gateway version
Keops-Gateway-Auth-DurationPluginTotal plugin execution time in milliseconds
Keops-Gateway-Auth-StatusPluginPlugin outcome: GRANTED, DENIED, or GATEWAY_FAILED
note

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​

ConditionHTTP StatusResponse BodyKeops-Gateway-Auth-Status
Access Gateway unreachable or timeout502{"error": "Authorization Service Unavailable"}GATEWAY_FAILED
Access Gateway returns 2xx—Request forwarded to upstreamGRANTED
Access Gateway returns 500, 502, 503 or 504Same as gatewayGateway response body forwarded to clientGATEWAY_FAILED
Access Gateway returns any other 5xx status, such as 501Same as gatewayGateway response body forwarded to clientGATEWAY_FAILED (Lua), DENIED (WASM)
Access Gateway returns any other status, such as 4xxSame as gatewayGateway response body forwarded to clientDENIED

The plugin does not retry failed gateway requests. If the Access Gateway is unavailable, the request is rejected with a 502 status.

note

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:

Route-specific configuration
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.

warning

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​

  1. The upstream service receives the request
  2. The client response includes Keops-Decision and Keops-Gateway-Auth-Duration headers
  3. Keops-Gateway-Auth-Status is GRANTED

How to Verify​

  • API evidence: Send a request with a valid Bearer token and check the response headers for Keops-Decision, Keops-Decision-Cache, and Keops-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_url is correct and the Access Gateway is running. Check network connectivity between APISIX and the Access Gateway.
  • Timeout errors — Increase the timeout value 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, and Keops-Client-Id, and the older Keymate-* 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 build and 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.