Contributing to Enforcement Plugins
Goal
Understand the contribution workflow for Keymate enforcement plugins, including repository structure, code standards, testing requirements, and the CI/CD release pipeline.
Audience
Developers contributing to the Keymate APISIX or Istio enforcement plugin repositories.
Prerequisites
- Git and GitLab access to the plugin repositories
- A working local development environment — see Plugin Local Development & Test Setup
- A container runtime (or nerdctl with containerd)
- TinyGo 0.33.0 (for WASM changes)
Before You Start
The Keymate enforcement plugins are maintained in two separate repositories:
| Repository | Gateway | Languages |
|---|---|---|
keymate-apisix-access-plugin | Apache APISIX | Lua + Go (TinyGo WASM) |
keymate-istio-access-plugin | Istio (Envoy sidecar) | Lua + Go (TinyGo WASM) |
Both repositories follow the same structure, build toolchain, and contribution workflow.
Worked Example
In this guide, you understand the repository layout, make a change, test it locally, and submit a merge request through the CI/CD pipeline.
Steps
1. Understand the repository structure
Both plugin repositories follow this layout:
├── lua/ # Lua plugin source
│ └── keymate-access-gateway.lua (APISIX) or keymate-access.lua (Istio)
├── wasm/ # Go WASM plugin source
│ ├── main.go # Plugin implementation using proxy-wasm-go-sdk
│ ├── go.mod # Go module definition
│ └── go.sum # Go dependency checksums
├── local/ # Local development environments
│ ├── docker/ # Standalone local stack (mock gateway)
│ │ └── Makefile # up, down, test, logs targets
│ ├── containerd/ # k8s-connected development
│ │ └── Makefile # build, image, deploy, test, logs targets
│ ├── config/ # Local configuration files
│ └── k6-smoke-test.js # Smoke test script
├── test/ # Integration test environment (APISIX only)
│ └── docker/ # Full integration test stack with Keycloak, Access Gateway
├── charts/ # Helm chart (Istio only)
│ ├── templates/ # WasmPlugin, EnvoyFilter, echoserver
│ ├── values.yaml
│ └── lua/ # Lua source bundled for Helm
├── Makefile # Top-level build and image targets
├── Dockerfile # Multi-stage container build (TinyGo → Alpine)
├── .gitlab-ci.yml # CI/CD pipeline configuration
├── k6-performance-test.js # Performance test script
├── test_*_plugin.py # Python integration test suite
├── .env # Environment variables for tests
└── docs/ # Internal development documentation
├── development-guide.md
└── technical-design.md
2. Choose what to modify
Each plugin has two implementations. Choose based on what you need to change:
| Change Type | Files to Modify |
|---|---|
| Authorization logic, header handling, error responses | Lua source and WASM main.go |
| Lua-only behavior (APISIX-specific) | lua/keymate-access-gateway.lua |
| Lua-only behavior (Istio-specific) | lua/keymate-access.lua |
| WASM-only behavior | wasm/main.go |
| Configuration schema (APISIX Lua) | Schema definition in the Lua source |
| Configuration schema (WASM) | PluginConfig struct in wasm/main.go |
| Build process | Makefile and Dockerfile |
| Deployment (Istio) | Helm chart templates in charts/templates/ |
| Tests | test_*_plugin.py, k6-performance-test.js, or local/k6-smoke-test.js |
When changing authorization logic or header handling, update both the Lua and WASM implementations to keep them consistent. Both implementations should produce the same headers and the same allow/deny behavior.
3. Set up your development environment
Follow the Plugin Local Development & Test Setup guide to start a local environment. The recommended workflow:
-
Start the standalone container environment for fast iteration:
Start local environmentcd local/docker
make up -
Make your changes to the plugin source files
-
For Lua changes, restart the gateway container:
Restart after Lua changesmake down && make up -
For WASM changes, rebuild and restart:
Rebuild after WASM changescd ../..
make build
cd local/docker
make down && make up -
Run the smoke test:
Run smoke testmake test
4. Run tests
Before submitting a merge request, run the full test suite:
k6 smoke test — Validates basic plugin behavior (grant, deny, skip paths):
k6 run local/k6-smoke-test.js
Python integration test — End-to-end test with Keycloak authentication:
python3 test_apisix_plugin.py # APISIX plugin
python3 test_istio_plugin.py # Istio plugin
The integration test suite runs four scenarios:
- Success case through the Lua route (expect HTTP 200)
- Failure case through the Lua route (expect HTTP 403)
- Success case through the WASM route (expect HTTP 200)
- Failure case through the WASM route (expect HTTP 403)
k6 performance test — Load testing to detect performance regressions:
k6 run -e PROTOCOL=https -e API_HOST=<HOST> -e KEYCLOAK_HOST=<HOST> \
-e CLIENT_ID=<ID> -e CLIENT_SECRET=<SECRET> \
-e USERNAME=<USER> -e PASSWORD=<PASS> \
k6-performance-test.js
5. Verify the WASM build
Ensure the WASM binary compiles without errors:
make build
This runs TinyGo inside a container with the following flags:
tinygo build -o plugin.wasm -scheduler=none -target=wasi -gc=conservative .
-scheduler=none— Disables the goroutine scheduler (proxy-wasm does not support it)-target=wasi— Compiles to the WASM System Interface target-gc=conservative— Uses the conservative garbage collector for WASM compatibility
Verify the image builds successfully:
make image
6. Follow code standards
Plugin version: Update the PLUGIN_VERSION constant (Lua) or pluginVersion constant (WASM) when making changes that affect the plugin behavior. The version appears in the Keymate-Enforcer header sent to the Access Gateway.
Header consistency: Both implementations must produce the same Keymate-Enforcer, Keymate-Target-URI, and Keymate-Target-Method headers. Both must forward the same set of response headers from the Access Gateway.
Protected headers: The list of headers filtered from client requests (to prevent spoofing) must be identical in both implementations.
Error responses: The 502 error response format ({"error": "Authorization Service Unavailable"}) must be consistent across implementations.
Logging: Use structured log messages with the [KEYMATE-ACCESS-GATEWAY] prefix (APISIX) or [ctx N] context ID (Istio) for traceability.
7. Submit a merge request
- Create a feature branch from the default branch
- Make your changes and verify with the test suite
- Push your branch and create a merge request in GitLab
- The CI pipeline runs automatically on merge requests
8. Understand the CI/CD pipeline
Both repositories use GitLab CI with a shared pipeline template:
include:
- component: $CI_SERVER_FQDN/$CI_COMPONENTS_REPO_PATH/template@develop
The pipeline:
- Build stage — Compiles the WASM binary and builds the container image
- Deploy stage — Updates the version in deployment configuration and triggers GitOps
- Release — Tags the image and updates the Helm chart or APISIX deployment
The APISIX plugin includes additional deploy steps that update ci-values-extra-config.yaml with the application version for ArgoCD Helmfile deployment.
The Istio plugin has a simpler pipeline with direct ArgoCD integration disabled (.update_argocd_helmfile set to when: never).
Validation Scenario
Scenario
You modify the Lua plugin to add a custom response header, run the smoke test, and verify the header appears in the response.
Expected Result
make buildcompletes without errors- The k6 smoke test passes
- The custom header appears in the cURL response
How to Verify
- API evidence: cURL response includes the new header
- Logs: Plugin log output confirms the change is active
- Audit evidence: Integration test log file shows the new header in response headers
Troubleshooting
make buildfails with TinyGo errors — Ensure the container runtime is running and can pull thetinygo/tinygo:0.33.0image. Check thatwasm/go.moddependencies are compatible with TinyGo.- Smoke test fails after changes — Compare the expected response headers in
local/k6-smoke-test.jswith the actual plugin output. Update the test if you changed header names or values. - CI pipeline fails — Check the GitLab CI job logs. Common issues include TinyGo compilation errors or container image build failures.
- Lua and WASM behavior diverges — Test both routes (Lua and WASM) with the Python integration test suite. The test runs scenarios through both implementations to catch inconsistencies.
- Image push fails — For local development, ensure the local registry is running at
localhost:5001. For CI, verify the registry credentials are configured.
Next Steps
- Extending the APISIX Plugin — Understand the plugin architecture and extension points
- Extending the Istio Plugin — Understand the Istio-specific deployment and configuration model
Related Docs
Plugin Local Development & Test Setup
Set up local environments for plugin development.
Extending the APISIX Plugin
Apache APISIX plugin architecture and configuration.
Extending the Istio Plugin
Istio service mesh plugin deployment and extension points.
Access Gateway Overview
Endpoints, headers, and system role of the Access Gateway.