Zero-Trust Blueprint¶
Three enforcement planes. One identity standard. Complete legacy application onboarding — without modifying the application.
The Three-Plane Model¶
graph TB
subgraph PLANE1["PLANE 1 — IDENTITY (SPIFFE/SPIRE)"]
SPIRE["SPIRE Server\nCA + registration entries"]
AGENT["SPIRE Agent DaemonSet\nworkload attestation"]
SVID["X.509 SVID\n1h TTL · auto-rotated"]
JWT["JWT SVID\n5m TTL · OBO delegation"]
SPIRE --> AGENT --> SVID
SPIRE --> AGENT --> JWT
end
subgraph PLANE2["PLANE 2 — NETWORK (Istio mTLS)"]
PA["PeerAuthentication STRICT\ncluster-wide"]
AP["AuthorizationPolicy\ndefault deny · SPIFFE allow-list"]
NP["NetworkPolicy\nL3/L4 default deny"]
OBO_FILTER["EnvoyFilter\nOBO header propagation"]
PA --- AP --- NP
OBO_FILTER --- AP
end
subgraph PLANE3["PLANE 3 — POLICY (OPA/Gatekeeper)"]
GK["Gatekeeper\nadmission control"]
OPA["OPA DaemonSet\next_authz · per-request"]
OBO_POLICY["validate-obo-chain.rego\nchain depth · audience · TTL"]
GK --- OPA --- OBO_POLICY
end
SVID -->|"consumed by"| PA
JWT -->|"validated by"| OBO_POLICY
PLANE1 --> PLANE2 --> PLANE3
style PLANE1 fill:#dbeafe,color:#1e3a8a,stroke:#93c5fd
style PLANE2 fill:#d1fae5,color:#064e3b,stroke:#6ee7b7
style PLANE3 fill:#ede9fe,color:#2e1065,stroke:#c4b5fd
style SPIRE fill:#93c5fd,color:#000000,stroke:#3b82f6
style AGENT fill:#93c5fd,color:#000000,stroke:#3b82f6
style SVID fill:#93c5fd,color:#000000,stroke:#3b82f6
style JWT fill:#93c5fd,color:#000000,stroke:#3b82f6
style PA fill:#6ee7b7,color:#000000,stroke:#10b981
style AP fill:#6ee7b7,color:#000000,stroke:#10b981
style NP fill:#6ee7b7,color:#000000,stroke:#10b981
style OBO_FILTER fill:#6ee7b7,color:#000000,stroke:#10b981
style GK fill:#c4b5fd,color:#000000,stroke:#8b5cf6
style OPA fill:#c4b5fd,color:#000000,stroke:#8b5cf6
style OBO_POLICY fill:#c4b5fd,color:#000000,stroke:#8b5cf6
Each plane is independently replaceable. A failure in one plane does not bypass the others.
ZT Onboarding Gateway¶
The centrepiece of this blueprint: bring legacy applications into the trust fabric without modifying their code.
The legacy app binds only to 127.0.0.1. The ZT gateway container is the sole network-accessible endpoint.
[mTLS caller]
│
▼
[zt-gateway:8443] ← SPIRE SDS SVID
│ ← OPA ext_authz OBO check
▼
[legacy-app:127.0.0.1:8080]
Best for: Stateful legacy apps, per-instance isolation, smaller deployments.
One gateway pod (HPA-scaled) serves multiple legacy service pods. Services expose no external port; NetworkPolicy restricts ingress to the gateway pod only.
[mTLS callers]
│
▼
[zt-gateway-pod:8443] ← xDS-driven routing
├──────────────▶ [svc-a-pod:8080]
├──────────────▶ [svc-b-pod:8080]
└──────────────▶ [svc-c-pod:8080]
Best for: Many legacy services, shared gateway economics, horizontal scale.
On-Behalf-Of Token Chain¶
Every inter-service call preserves the identity of the originating principal through the full call chain. No service receives the user's session token; each service presents its own SPIFFE identity plus a delegated JWT SVID proving it was authorized by the upstream caller.
sequenceDiagram
participant U as User
participant GW as API Gateway
participant O as Orders
participant I as Inventory
U->>GW: HTTP request + session token
GW->>GW: Validate session → user@chakra.internal
GW->>GW: Request JWT SVID for Orders (audience: orders-svc)
GW->>O: Request + X-SPIFFE-OBO-Token (GW→O) + X-SPIFFE-User-Principal
O->>O: OPA validates OBO: GW allowed → O ✓
O->>O: Request JWT SVID for Inventory (audience: inventory-svc)
O->>I: Request + X-SPIFFE-OBO-Token (O→I) + chain: [GW,O]
I->>I: OPA validates OBO: chain depth ≤ 3 ✓, O in allow-list ✓
I-->>O: Response
O-->>GW: Response
GW-->>U: Response
Every OPA decision in this chain is logged with obo.chain as a structured field, queryable in Loki and correlated with the Tempo trace by trace_id.
OpenTelemetry Integration¶
Every security event produces a trace span. Every authz decision produces a log record. Both are correlated by trace_id.
flowchart LR
ENV["Envoy\n(ZT Gateway + Istio sidecars)\nOTLP traces"]
OPA["OPA DaemonSet\nDecision log JSON"]
SPIRE["SPIRE\nPrometheus metrics"]
COL["OTEL Collector\nDaemonSet"]
TEMPO["Grafana Tempo\nTraces"]
LOKI["Grafana Loki\nLogs"]
PROM["Mimir\nMetrics"]
ENV -->|OTLP/gRPC| COL
OPA -->|filelog| COL
SPIRE -->|Prometheus scrape| COL
COL -->|OBO enrichment| TEMPO
COL -->|trace_id correlation| LOKI
COL -->|SVID rotation metrics| PROM
style COL fill:#92400e,color:#fff
style TEMPO fill:#1e3a5f,color:#fff
style LOKI fill:#14532d,color:#fff
style PROM fill:#4a044e,color:#fff
The OBO enrichment processor in the OTEL Collector promotes OBO chain headers to first-class span attributes — obo.caller_principal, obo.chain, obo.user_principal — making every request's delegation chain searchable in Tempo without custom instrumentation.
Repository Map¶
| Directory | What's there |
|---|---|
identity/ |
SPIRE server + agent manifests, SPIFFE ID registry, OBO policy |
mesh/ |
Istio PeerAuthentication, AuthorizationPolicy, OBO EnvoyFilter |
policy/ |
OPA/Gatekeeper policies + Kyverno alternative |
secrets/ |
Vault (primary) + AWS Secrets Manager (alternative) |
gateway/ |
ZT gateway — both topologies, protocol translation, OBO enforcement |
observability/ |
SLOs, OTEL Collector, dashboards, alerts |
docs/adrs/ |
8 ADRs covering every major architectural decision |