OPA vs Envoy WASM for Spatial Contract Validation

Choosing where spatial contract validation runs — an Open Policy Agent sidecar over ext_authz, or an Envoy WASM filter compiled into the proxy — decides your per-request latency budget, how expressive your geometry checks can be, and how much operational surface you carry. Both enforce the same intent: a payload crossing a domain boundary must satisfy its schema, declare EPSG:4326, and stay within a geometry-precision tolerance. But they make opposite trade-offs, and picking wrong shows up as either p99 latency creep or a validator that cannot do the numeric work spatial data demands. This decision page sits under Schema Contracts for Vector Tile Data, part of the federated routing architecture, and complements the format-level checks in Enforcing Schema Contracts for GeoJSON and Shapefiles; both approaches also serve the identity gate described in Zero-Trust Security for Spatial Endpoints.

Figure — Criterion-by-criterion comparison of OPA/Rego and an Envoy WASM filter for validating spatial data contracts at a domain boundary.

Comparison matrix: OPA/Rego versus Envoy WASM for spatial contract validation A six-row comparison table. Latency: OPA adds an external ext_authz hop while WASM runs in-process, favouring WASM. Language: OPA uses declarative Rego while WASM uses compiled C++, Rust, Go or AssemblyScript, favouring OPA for policy simplicity. Hot-reload: OPA supports live bundle push while WASM needs rebuild and redeploy, favouring OPA. Data access: OPA can consult external documents while WASM is request-scoped, favouring OPA for richness. Geometry checks: OPA is limited to set and JSON logic while WASM can run full numeric compute, favouring WASM. Ops burden: OPA needs an extra sidecar service while WASM is mesh-native, favouring WASM. Criterion OPA / Rego Envoy WASM Latency external ext_authz hop in-process filter Language Rego (declarative) C++ / Rust / Go / AS Hot-reload live bundle push rebuild + redeploy Data access external documents request-scoped only Geometry checks set / JSON logic full numeric compute Ops burden extra sidecar service mesh-native, no dep

Prerequisites

Requirement Value / Constraint
Proxy / mesh Envoy 1.28+ (WASM ABI v0.2.1) or Istio 1.20+; OPA 0.60+ for the ext_authz path
CLI tools opa (for opa eval/opa test), istioctl, kubectl, curl, a WASM toolchain (tinygo or rustc + wasm32 target)
CRS assumption Both validators enforce x-crs: EPSG:4326 for vector; a geometry-precision tolerance of 1e-6 or 1e-7 decimal degrees
Contract inputs JSON Schema for feature collections; a geometry-precision bound; the pinned schema version, e.g. v1.2.0-crs:EPSG:4326-res:10m
Access roles policy-author to publish Rego bundles / WASM modules; mesh-routing-admin to bind the filter
Env vars OPA_BUNDLE_URL, WASM_MODULE_SHA, GEOM_PRECISION=1e-6

Both validators must be deterministic and idempotent: the same request yields the same allow/deny with no side effects, so retries under a routing retry storm never mutate state. Default-deny is the zero-trust baseline in either engine — a request that fails to parse is rejected, never admitted.

Decision Reference

If you need… Choose Why
Sub-millisecond validation on the hot path Envoy WASM Runs in-process; no extra network hop or sidecar round-trip
Real numeric geometry work (precision, bbox containment, vertex counts) Envoy WASM Compiled code does floating-point math Rego cannot express cleanly
Policy authored and reviewed by non-C++ engineers OPA / Rego Declarative rules, unit-testable with opa test, readable in review
Live policy changes without redeploying the proxy OPA / Rego Push a new signed bundle; no proxy rebuild or restart
Decisions that consult external data (tenant residency, revocation lists) OPA / Rego Bundles carry reference data documents into the decision
Minimal moving parts and no extra service to run Envoy WASM Mesh-native filter; nothing else to deploy, scale, or monitor
A pragmatic default for most spatial contracts Both, layered WASM for cheap structural/geometry checks at the edge; OPA for policy that needs external context

The common production answer is layered: a WASM filter does the cheap, high-frequency structural and geometry-precision checks in-process, and rejects fast; OPA handles the lower-frequency, context-rich policy (residency, scope claims, revocation) where expressiveness and hot-reload matter more than the extra hop.

Configuration Reference — OPA / Rego

The Rego module validates CRS and geometry precision and is testable offline with opa eval before it ever binds to the proxy.

rego
package spatial.contract

import rego.v1

default allow := false

# Allow only EPSG:4326 vector payloads within the precision tolerance.
allow if {
    input.attributes.request.http.headers["x-crs"] == "EPSG:4326"
    prec := input.attributes.request.http.headers["x-geometry-precision"]
    prec in ["1e-6", "1e-7"]
    input.attributes.request.http.headers["x-schema-version"] == "v1.2.0-crs:EPSG:4326-res:10m"
}

# Structured diagnostic returned verbatim to the caller on denial.
violation contains msg if {
    not allow
    msg := sprintf("contract denied: crs=%v precision=%v", [
        input.attributes.request.http.headers["x-crs"],
        input.attributes.request.http.headers["x-geometry-precision"],
    ])
}

Test the decision without a running proxy, then publish it as a signed bundle:

bash
# Evaluate the policy against a sample ext_authz input document.
opa eval -d spatial_contract.rego -i sample_input.json \
  'data.spatial.contract.allow' --format pretty

Configuration Reference — Envoy WASM Filter

The WASM path compiles the same intent into a filter Envoy loads in-process. The EnvoyFilter sketch below pins the module by SHA-256 so a tampered binary is refused at load.

yaml
# spatial-contract-wasm.yaml — bind a WASM validator to the ingress listener
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: spatial-contract-validator
  namespace: spatial
spec:
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        listener:
          filterChain:
            filter: { name: envoy.filters.network.http_connection_manager }
      patch:
        operation: INSERT_BEFORE
        value:
          name: spatial.contract
          typed_config:
            "@type": type.googleapis.com/udpa.type.v1.TypedStruct
            type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
            value:
              config:
                name: spatial_contract
                root_id: spatial_contract
                configuration:
                  "@type": type.googleapis.com/google.protobuf.StringValue
                  value: '{"crs":"EPSG:4326","precision":"1e-6"}'
                vm_config:
                  runtime: envoy.wasm.runtime.v8
                  code:
                    local: { filename: /etc/envoy/wasm/spatial_contract.wasm }
                  # Refuse to load a module whose hash does not match.
                  sha256: "${WASM_MODULE_SHA}"

Verify the filter is loaded and rejects a bad-CRS request in-process:

bash
kubectl apply -f spatial-contract-wasm.yaml
curl -so /dev/null -w "%{http_code}\n" \
  -H "x-crs: EPSG:3857" -H "x-geometry-precision: 1e-6" \
  "https://spatial.mesh.enterprise.internal/v1/features/collections"   # expect 403

Common Failure Modes & Fixes

  • Symptom: p99 latency jumps ~3-8ms after enabling validation. Root cause: the OPA ext_authz hop is on the synchronous hot path for every request. Fix: move high-frequency structural/geometry checks to the WASM filter and reserve OPA for context-rich policy; or co-locate the OPA sidecar to cut the round-trip.
  • Symptom: a geometry-precision rule is impossible to express in Rego without brittle string math. Root cause: Rego is set/JSON logic, not a numeric engine. Fix: implement precision, bbox-containment, and vertex-count checks in the WASM filter where floating-point compute is native; keep Rego for the allow/deny policy shape.
  • Symptom: a policy change requires a proxy rebuild and rolling restart. Root cause: the logic lives in the compiled WASM module. Fix: for logic that changes often (residency lists, revocation), move it to an OPA bundle and push updates live; keep only stable structural checks in WASM.
  • Symptom: WASM filter fails to load with a hash-mismatch error. Root cause: the deployed .wasm binary does not match the pinned sha256. Fix: rebuild, recompute the digest, update WASM_MODULE_SHA, and re-apply — never relax the pin, which is the zero-trust supply-chain guard.
  • Symptom: both validators disagree on the same payload. Root cause: the Rego and WASM implementations drifted (different precision constant or schema version). Fix: derive both from one contract manifest and add a conformance test that replays the same fixtures through opa eval and the WASM module.

FAQ

Which is faster in practice for spatial contract checks?

Envoy WASM, by design. It runs inside the proxy with no extra network hop, so it typically adds well under a millisecond, whereas the OPA ext_authz pattern adds a synchronous round-trip to a separate process — a few milliseconds even when co-located. For a validation that fires on every request, that gap dominates the choice on latency-sensitive routes.

Can Rego do real geometry math like bbox containment or vertex counts?

Not cleanly. Rego excels at declarative set and JSON logic — is this header present, does this value belong to an allowed set, does the schema version match. Floating-point geometry work (precision tolerance, containment, ring vertex counts) is awkward and brittle in Rego and belongs in a compiled WASM filter. Use OPA for the policy decision, WASM for the numeric checks.

How do I change policy without redeploying the proxy?

Use OPA. Publish a new signed bundle to OPA_BUNDLE_URL and the sidecar hot-loads it — no proxy rebuild or restart. A WASM module, by contrast, is compiled into the filter, so any change means rebuild, re-pin the sha256, and roll the proxy. If your policy churns often, that alone argues for OPA.

Do these replace the GeoJSON/Shapefile ingest checks?

No — they complement them. The format-level structural gate in Enforcing Schema Contracts for GeoJSON and Shapefiles runs at ingest, while OPA or WASM validate the contract at the routing edge on every cross-domain hop. Identity and authorization for those hops are handled separately under Zero-Trust Security for Spatial Endpoints.

Is it wrong to run both OPA and WASM?

No — layering both is the common production answer. A WASM filter rejects malformed structure and bad geometry precision fast and in-process; OPA evaluates the policy that needs external context and frequent change. Derive both from one contract manifest and add a conformance test so they never disagree.