Skip to content

Latest commit

 

History

History
299 lines (241 loc) · 8.25 KB

File metadata and controls

299 lines (241 loc) · 8.25 KB

Deployment

Prerequisites

# 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

Build the Wasm module

# From repo root
make wasm
# or equivalently:
cargo build --target wasm32-wasip1 --release

# Output:
# target/wasm32-wasip1/release/proxy_wasm_evidence.wasm

Envoy quickstart

1. Place the Wasm module

sudo mkdir -p /etc/envoy/wasm
sudo cp target/wasm32-wasip1/release/proxy_wasm_evidence.wasm /etc/envoy/wasm/

2. Configure Envoy

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.Router

3. Start Envoy

envoy -c deploy/envoy/envoy.yaml

4. Verify the AEP header

# 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.

Istio WasmPlugin

Deploy with kubectl

kubectl apply -f deploy/istio/wasmplugin.yaml

The 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-id

Key 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)

Verify in Istio

# 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 headers

K8s secret injection

The Ed25519 signing key must be injected securely — never hard-coded in the Wasm module or Envoy config.

1. Generate a key pair

# 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 signatures

2. Create the K8s Secret

Edit 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.yaml

3. Mount in Envoy/Istio

The 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.

Observability

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.

Counters

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.

Envoy Prometheus configuration

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: 9901

Then scrape Envoy's Prometheus endpoint:

scrape_configs:
  - job_name: 'envoy'
    static_configs:
      - targets: ['localhost:9901']
    metrics_path: /stats/prometheus

Sample Grafana panel

The 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"
    }
  ]
}