Skip to main content

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:

RepositoryGatewayLanguages
keymate-apisix-access-pluginApache APISIXLua + Go (TinyGo WASM)
keymate-istio-access-pluginIstio (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 TypeFiles to Modify
Authorization logic, header handling, error responsesLua 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 behaviorwasm/main.go
Configuration schema (APISIX Lua)Schema definition in the Lua source
Configuration schema (WASM)PluginConfig struct in wasm/main.go
Build processMakefile and Dockerfile
Deployment (Istio)Helm chart templates in charts/templates/
Teststest_*_plugin.py, k6-performance-test.js, or local/k6-smoke-test.js
warning

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:

  1. Start the standalone container environment for fast iteration:

    Start local environment
    cd local/docker
    make up
  2. Make your changes to the plugin source files

  3. For Lua changes, restart the gateway container:

    Restart after Lua changes
    make down && make up
  4. For WASM changes, rebuild and restart:

    Rebuild after WASM changes
    cd ../..
    make build
    cd local/docker
    make down && make up
  5. Run the smoke test:

    Run smoke test
    make 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):

Run smoke test
k6 run local/k6-smoke-test.js

Python integration test — End-to-end test with Keycloak authentication:

Run integration tests
python3 test_apisix_plugin.py   # APISIX plugin
python3 test_istio_plugin.py # Istio plugin

The integration test suite runs four scenarios:

  1. Success case through the Lua route (expect HTTP 200)
  2. Failure case through the Lua route (expect HTTP 403)
  3. Success case through the WASM route (expect HTTP 200)
  4. Failure case through the WASM route (expect HTTP 403)

k6 performance test — Load testing to detect performance regressions:

Run performance tests
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:

Build WASM binary
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:

Build container image
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

  1. Create a feature branch from the default branch
  2. Make your changes and verify with the test suite
  3. Push your branch and create a merge request in GitLab
  4. The CI pipeline runs automatically on merge requests

8. Understand the CI/CD pipeline

Both repositories use GitLab CI with a shared pipeline template:

.gitlab-ci.yml structure
include:
- component: $CI_SERVER_FQDN/$CI_COMPONENTS_REPO_PATH/template@develop

The pipeline:

  1. Build stage — Compiles the WASM binary and builds the container image
  2. Deploy stage — Updates the version in deployment configuration and triggers GitOps
  3. 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

  1. make build completes without errors
  2. The k6 smoke test passes
  3. 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 build fails with TinyGo errors — Ensure the container runtime is running and can pull the tinygo/tinygo:0.33.0 image. Check that wasm/go.mod dependencies are compatible with TinyGo.
  • Smoke test fails after changes — Compare the expected response headers in local/k6-smoke-test.js with 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