Live feature playground

Try every matcher
directly in your browser

Twenty live probes covering Gateway-API matchers, filters, traffic shaping, alternative Route kinds (GRPCRoute, TCPRoute) and the full Envoy-Gateway hardening surface (rate limit, CORS, Basic Auth, security headers, WAF). Click a probe — the browser fires a real fetch through the data plane; responses show the matched rule via the x-gw-matched response header.

every HTTPRoute rule is a card below — count them, don't trust a number here GRPCRoute + TCPRoute: live on this cluster (Stand 2026-08-26: 1 each) hardening policies: one per hardening card below (Stand 2026-08-26: 4) standard + experimental + EG-extension
STANDARD

Path · Exact

Matches the path exactly. Anything longer or shorter does not match.

- matches:
    - path: { type: Exact, value: /features/exact }
Click a button to probe
STANDARD

HTTP Method

First rule matches only POST; a fallback rule catches every other method on the same path.

- matches:
    - path:   { type: PathPrefix, value: /features/post }
      method: POST
Click a button to probe
STANDARD

Header matching

When x-canary: yes is present, routes to echo-v2; otherwise to echo.

- matches:
    - path: { type: PathPrefix, value: /features/header }
      headers:
        - { name: x-canary, value: "yes" }
Click a button to probe
STANDARD

Query param matching

?canary=true routes to echo-v2; without it, default backend.

- matches:
    - path: { type: PathPrefix, value: /features/query }
      queryParams:
        - { name: canary, value: "true" }
Click a button to probe
STANDARD

Weighted traffic split

Splits 80% to echo, 20% to echo-v2. Click to fire 20 requests and see distribution.

- backendRefs:
    - { name: echo,    port: 80, weight: 80 }
    - { name: echo-v2, port: 80, weight: 20 }
Click to fire 20 requests
STANDARD

URLRewrite filter

Gateway strips /features/rewrite and forwards as /rewritten/... to the backend.

- filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /rewritten
Click to see the rewritten path the backend received
STANDARD

RequestHeaderModifier

Gateway injects two extra headers into the request before forwarding. The echo backend shows them in its JSON.

- filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
          - { name: x-injected-by-gateway, value: envoy-gateway-eg }
          - { name: x-trace-id,            value: demo-trace-42 }
Click to see the headers the backend received
STANDARD

RequestRedirect filter

Gateway answers with 302 and a Location header — never even contacts a backend.

- filters:
    - type: RequestRedirect
      requestRedirect:
        path:
          type: ReplaceFullPath
          replaceFullPath: /features/exact
        statusCode: 302
Click to see the 302 + Location header
EXPERIMENTAL

Path · RegularExpression

Matches when the path matches the regex. Anything else falls through. Requires the experimental Gateway-API CRDs (installed on this cluster).

- matches:
    - path:
        type: RegularExpression
        value: "^/features/users/[0-9]+$"
Click a button to probe
STANDARD

Hostname matching

Two HTTPRoutes attach to the same Gateway with different hostnames:. We probe with an explicit Host: header — no DNS needed.

hostnames: [ "v1.features.wolfslight.cc" ]
# second HTTPRoute
hostnames: [ "v2.features.wolfslight.cc" ]
Click a button to probe
STANDARD

ResponseHeaderModifier

Adds x-added-header, sets content-language: en-CH, removes etag — all three sub-modes in one filter.

- filters:
    - type: ResponseHeaderModifier
      responseHeaderModifier:
        add:
          - { name: x-added-header, value: "I was added by the Gateway" }
        set:
          - { name: content-language, value: en-CH }
        remove: [ "etag" ]
Click to see the modified response headers
STANDARD

RequestMirror filter

Primary backend echo answers; echo-v2 receives a fire-and-forget copy. The primary's response is what you see; the mirror runs in the background.

- filters:
    - type: RequestMirror
      requestMirror:
        backendRef:
          kind: Service
          name: echo-v2
          port: 80
Click to probe — primary response shown, mirror is silent
STANDARD

Cross-namespace backendRef

No longer cross-namespace — corrected 2026-08-26: this route was originally built as an HTTPRoute in a demo namespace pointing at a Service in a separate lb-demo namespace, guarded by a ReferenceGrant. On this cluster both moved into the single gateway-demo namespace, so the backendRef is now local — no ReferenceGrant needed here anymore. The route still answers; the live x-gw-matched header below says so itself ("now local: see deviation note"). What follows is the original cross-namespace shape for reference, kept because ReferenceGrant is still exactly how you'd wire this up if the Service lived in another namespace.

- backendRefs:
    - name: hello
      namespace: lb-demo     # cross-NS (historical shape — now local)
      port: 80
# + ReferenceGrant in lb-demo allowing routes from demo
Click to probe — the x-gw-matched header will call out that the backend is now local
STANDARD

Backend timeouts

Slow backend (httpbin /delay/5) sleeps 5 s. HTTPRoute has timeouts.request: 2s. Gateway returns 504 after ~2 s without waiting for the backend.

- matches:
    - path: { type: PathPrefix, value: /features/timeout }
  timeouts:
    request: "2s"
    backendRequest: "2s"
  # backend would take 5s
Click — expect HTTP 504 after ~2s
EG-EXTENSION

BackendTrafficPolicy · Local Rate Limit (60 req/min, per route)

Envoy Gateway's local rate-limit: a fixed bucket of 60 requests per minute. Real behaviour, corrected 2026-08-26 (again — see below): the policy targets the Gateway, not this one route specifically, but Envoy Gateway applies a Gateway-targeted Local limit per HTTPRoute — each of the four routes under this Gateway gets its own independent 60/min bucket. It also has no per-source-IP Distinct selector, so within a route's bucket it's still first-come-first-served, not a per-visitor allowance. Burst past 60 on this route in the current minute and the surplus comes back as 429 Too Many Requests, from any IP — but other routes on this Gateway are unaffected.

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: gateway-demo-ratelimit
spec:
  targetRefs:
    - kind: Gateway
      name: gateway-demo
  rateLimit:
    type: Local
    local:
      rules:
        - limit:
            requests: 60
            unit: Minute
Fires 80 requests in parallel — expect roughly 60×200, 20×429 within the current minute window.
Corrected 2026-08-26: a Gateway-targeted `Local` rate limit is applied PER ROUTE by Envoy Gateway —
each HTTPRoute under gateway-demo gets its own independent 60/min bucket, not one bucket shared
across the whole Gateway. (This exact mistake was already fixed once in this repo, in
apps/gateway-demo/policies.yaml, commit 5025d9b, 2026-07-15 — it crept back in here and had to be
fixed a second time.)

Why the button above doesn't prove that on its own: firing 80 requests at ONE route and seeing
60x200/20x429 is consistent with BOTH "per route" and "gateway-wide" — a single-route burst can't
tell them apart. The distinguishing test needs a SECOND route: burst /features/exact to exhaustion,
then immediately hit /secret and / — if the limit were gateway-wide, those would 429 too. They
don't (401 and 200, live-checked 2026-08-26), so the bucket is per-route.

Why there's still no per-IP fairness: Local rate limiting is scoped per route/gateway target AND
per Envoy replica (gateway-demo runs 1 replica) — never per source IP. One flooding client can
still exhaust a route's bucket for every other visitor of THAT route. Real per-IP fairness needs
Global Rate Limiting (rate-limit-service + Redis) — see apps/gateway-demo/policies.yaml for the
full reasoning.
STANDARD

Security Headers via ResponseHeaderModifier

Six browser-side hardening headers set globally on the UI + JSON + Secret routes: HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Content-Security-Policy.

filters:
  - type: ResponseHeaderModifier
    responseHeaderModifier:
      add:
        - Strict-Transport-Security: "max-age=63072000; includeSubDomains; preload"
        - X-Content-Type-Options: "nosniff"
        - X-Frame-Options: "DENY"
        - Referrer-Policy: "strict-origin-when-cross-origin"
        - Permissions-Policy: "geolocation=(), camera=(), microphone=()"
        - Content-Security-Policy: "default-src 'self'; …"
Click to fetch the page and display the security headers in the response.
EG-EXTENSION + WASM

Coraza WAF · OWASP-style rules via EnvoyExtensionPolicy

Coraza is the OWASP-endorsed ModSecurity successor (Go-based, runs as a WASM filter inside Envoy), running here as EnvoyExtensionPolicy/gateway-demo-waf against the OWASP Core Rule Set. It targets three HTTPRoutes on this Gateway — features, v1-features, v2-features — with a pinned WASM image digest (never :latest) and failOpen: false: if the WASM module crashes, requests are rejected rather than let through unfiltered — a deliberate demo choice, carried over from the same trade-off made in secpol-demo. Probes hit /features/exact with malicious payloads — the WAF returns 403 before the request reaches the backend.

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyExtensionPolicy
metadata:
  name: gateway-demo-waf
spec:
  targetRefs:
    - kind: HTTPRoute
      name: features
    - kind: HTTPRoute
      name: v1-features
    - kind: HTTPRoute
      name: v2-features
  wasm:
    - name: coraza-waf
      code:
        type: Image
        image.url: ghcr.io/corazawaf/coraza-proxy-wasm@sha256:65d6009b…   # pinned, not :latest
      failOpen: false
      config:
        directives_map:
          default:
            - "Include @owasp_crs/*.conf"
            - "SecRuleEngine On"
Click the buttons left → right. Benign passes (200), malicious all blocked (403) by the WAF before reaching the backend.
EXPERIMENTAL

TCPRoute · raw L4 routing to a stateless echo backend

No HTTP, no hostname-matching — just port-based L4 forwarding. Gateway listener tcp on port 9007, TCPRoute tcp-echo hands traffic straight to a registry.k8s.io/e2e-test-images/agnhost porter backend. Corrected 2026-08-26: this was originally advertised as Redis on 6379 with password auth. That was deliberately not rebuilt — an open, writable datastore on the same public ELB as this documentation site is a risk a demo shouldn't carry, and swapping in some other service on 6379 would still have signalled "Redis lives here" to anyone port-scanning. The current backend is stateless: it stores nothing and has nothing to authenticate. Browser can't speak raw TCP; test from your laptop:

apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute
metadata:
  name: tcp-echo
  namespace: gateway-demo
spec:
  parentRefs:
    - name: gateway-demo
      sectionName: tcp     # Gateway listener :9007
  rules:
    - backendRefs:
        - name: tcp-echo
          port: 9007
# TCPRoute has NO hostnames field — TCP carries no host identifier.
# Routing is purely by listener-port. A new Gateway listener means the CCM
# opens a matching port on the ELB — see /howto for that event trail.
Click to see CLI commands you can run from your laptop.
STANDARD

GRPCRoute · gRPC routing through the Gateway

Separate Route kind for gRPC (HTTP/2 + protobuf). Attaches to a Gateway listener that has allowedRoutes.kinds: [GRPCRoute]. Live on grpc.wolfslight.cc with its own listener + cert. Corrected 2026-08-26: the backend is not fortio — that image is not pullable on this cluster (403 from the registry). It's grpc/java-example-hostname, the official example from the Envoy Gateway docs, exposing helloworld.Greeter, grpc.health.v1.Health and grpc.reflection.v1.ServerReflection. There is also no cors-grpc SecurityPolicy — it was deliberately not carried over (see apps/gateway-demo/policies.yaml), so a cross-origin browser call from gateway.wolfslight.cc fails CORS preflight before it reaches the Route. The button below is disabled for that reason; this is a CLI-only demo — grpcurl from your laptop:

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: grpc-echo
  namespace: gateway-demo
spec:
  parentRefs:
    - name: gateway-demo
      sectionName: grpc
  hostnames: [ "grpc.wolfslight.cc" ]
  rules:
    - backendRefs:
        - name: grpc-echo
          port: 50051
# Service port has appProtocol: grpc so Envoy uses HTTP/2 upstream
Click "SHOW" for the real, working grpcurl commands. The POST button is disabled — see the note above for why.
EG-EXTENSION

SecurityPolicy · Basic Auth gate to /secret

Envoy Gateway's SecurityPolicy CR attaches Basic Auth to an HTTPRoute. Demo credentials: test:test. Wrong/missing creds → 401; correct creds → a properly classified Resident-Evil-themed leak from inside the Raccoon Cluster.

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: secret-basic-auth
  namespace: gateway-demo
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind:  HTTPRoute
      name:  secret          # the route to protect
  basicAuth:
    users:
      name: secret-basic-auth  # htpasswd Secret
Try the three buttons in order. The first opens the page in a new tab so you see the auth prompt + the leaked file.