- Rust toolchain with
wasm32-wasip1target installed - Ed25519 signing key (see K8s secret injection)
# Install Wasm target
rustup target add wasm32-wasip1
# Generate Ed25519 key pair
openssl genpkey -algorithm ed25519 | xxd -p -c 64
# Output: 64-char hex string (32 bytes) — use as signing_key_hex# From repo root
make wasm
# or equivalently:
cargo build --target wasm32-wasip1 --release
# Output:
# target/wasm32-wasip1/release/proxy_wasm_evidence.wasmsudo mkdir -p /etc/envoy/wasm
sudo cp target/wasm32-wasip1/release/proxy_wasm_evidence.wasm /etc/envoy/wasm/Use the provided config as a starting point (see deploy/envoy/envoy.yaml):
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
name: wasmagent_proxy_evidence
root_id: wasmagent_evidence_root
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: |
{
"default_mode": "validation",
"key_id": "wasmagent-dev-key",
"trace_id_header": "x-b3-traceid",
"agent_id_header": "x-agent-id"
}
vm_config:
runtime: envoy.wasm.runtime.v8
code:
local:
filename: /etc/envoy/wasm/proxy_wasm_evidence.wasm
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerenvoy -c deploy/envoy/envoy.yaml# Read request → should get "validation" mode
curl -v http://localhost:8080/api/data
# Look for the response header:
# < x-aep-recording-mode: validation
# Mutation request → should get "full" mode
curl -v -X POST http://localhost:8080/api/users
# Look for:
# < x-aep-recording-mode: full
# The header value mirrors the RecordingMode enum's snake_case serialization
# ("validation" | "delta" | "full"), matching the recording_mode field in AEP records.kubectl apply -f deploy/istio/wasmplugin.yamlThe plugin config in deploy/istio/wasmplugin.yaml:
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: wasmagent-proxy-evidence
namespace: default
spec:
selector:
matchLabels:
app: your-agent-app
url: oci://ghcr.io/wasmAgent/wasmagent-proxy:latest
phase: AUTHN
pluginConfig:
default_mode: validation
key_id: wasmagent-prod-key
trace_id_header: x-b3-traceid
agent_id_header: x-agent-idKey fields:
| Field | Description |
|---|---|
selector.matchLabels.app |
Set to your workload's label so the plugin attaches to the correct pods |
url |
OCI image registry reference for the Wasm module |
phase |
AUTHN runs the filter in the authentication phase (before routing) |
pluginConfig |
Passes configuration to the Wasm module at runtime (see configuration.md) |
# Port-forward to a pod in the mesh
kubectl port-forward svc/your-agent-app 8080:80
# Send test request
curl -v http://localhost:8080/health
# Check for x-aep-recording-mode in response headersThe Ed25519 signing key must be injected securely — never hard-coded in the Wasm module or Envoy config.
# Generate private key and extract hex
openssl genpkey -algorithm ed25519 -out /tmp/signing.key
xxd -p -c 64 /tmp/signing.key | tr -d '\n'
# Save the output — this is your WASMAGENT_SIGNING_KEY_HEX value
# Extract public key for verification
openssl pkey -pubout -outform DER /tmp/signing.key | \
tail -c 32 | xxd -p -c 32 | tr -d '\n'
# Save this for downstream consumers to verify signaturesEdit deploy/k8s/signing-secret.yaml with your generated key:
apiVersion: v1
kind: Secret
metadata:
name: wasmagent-signing-key
namespace: default
type: Opaque
stringData:
WASMAGENT_SIGNING_KEY_HEX: "<your-64-char-hex-key>"Apply it:
kubectl apply -f deploy/k8s/signing-secret.yamlThe signing key is injected into the gateway via environment variable or
volume mount, then passed to the Wasm module through the Proxy-Wasm
signing_key_hex configuration field (see configuration.md).
For Istio, mount the secret as an environment variable on the gateway proxy
or use the WasmPlugin's pluginConfig to pass it directly.
The Wasm module exports three Prometheus counters that track how many AEP
evidence records have been produced per recording mode. These are emitted via
proxy_wasm::hostcalls::define_metric / increment_metric and appear in
Envoy's stats endpoint when the Prometheus exporter is enabled.
| Envoy stat name | Prometheus label | Description |
|---|---|---|
aep.evidence.recorded_total.validation |
mode="validation" |
Evidence records produced for read-only (validation-mode) requests |
aep.evidence.recorded_total.delta |
mode="delta" |
Evidence records produced for local-mutation (delta-mode) requests |
aep.evidence.recorded_total.full |
mode="full" |
Evidence records produced for external-mutation or network-egress (full-mode) requests |
All three counters share the metric base name aep_evidence_recorded_total and
are distinguished by the mode label after Envoy's Prometheus tag extraction.
Add the following to your Envoy bootstrap or admin configuration to expose the
counters via /stats/prometheus:
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9901Then scrape Envoy's Prometheus endpoint:
scrape_configs:
- job_name: 'envoy'
static_configs:
- targets: ['localhost:9901']
metrics_path: /stats/prometheusThe following Grafana panel JSON visualises recording-mode distribution as a stacked area chart over time. Import it into a new dashboard or append it to an existing one.
{
"type": "timeseries",
"title": "AEP Evidence Recording Mode Distribution",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"custom": {
"drawStyle": "line",
"fillOpacity": 40,
"lineWidth": 1,
"pointSize": 5,
"showPoints": "never",
"stacking": { "mode": "normal", "group": "A" }
},
"color": { "mode": "palette-classic" }
},
"overrides": []
},
"options": {
"tooltip": { "mode": "multi", "sort": "desc" },
"legend": { "displayMode": "table", "placement": "bottom" }
},
"targets": [
{
"expr": "sum(rate(aep_evidence_recorded_total{mode=\"validation\"}[$__rate_interval])) by (mode)",
"legendFormat": "validation",
"refId": "A"
},
{
"expr": "sum(rate(aep_evidence_recorded_total{mode=\"delta\"}[$__rate_interval])) by (mode)",
"legendFormat": "delta",
"refId": "B"
},
{
"expr": "sum(rate(aep_evidence_recorded_total{mode=\"full\"}[$__rate_interval])) by (mode)",
"legendFormat": "full",
"refId": "C"
}
]
}