Observability¶
Every security enforcement event produces a trace span. Every authz decision produces a log record. Both are correlated by trace_id in the OTEL Collector.
Signal Sources¶
| Source | Signal type | Collector receiver | Key attributes |
|---|---|---|---|
| Envoy (ZT gateway + Istio sidecars) | Traces (OTLP) | otlp (gRPC 4317) |
zt.svid_verification, zt.opa_authz, zt.protocol_translation |
| OPA DaemonSet | Decision logs (JSON) | filelog |
allow, deny_reason, obo_chain, trace_id |
| SPIRE agent | Metrics (Prometheus) | prometheus scrape |
spire_agent_svid_rotation_total, spire_agent_attestation_failures_total |
| SPIRE server | Metrics (Prometheus) | prometheus scrape |
spire_server_ca_signing_requests_total |
ZT Gateway Spans¶
Every request through the ZT gateway produces three child spans within the distributed trace:
gantt
title ZT Gateway Span Breakdown (example: 4.3ms total)
dateFormat SSS
axisFormat %L ms
section zt.svid_verification
SPIRE SDS cache hit :0, 0.2ms
section zt.opa_authz
OBO chain validate : 0.2ms, 2.1ms
section zt.protocol_translation
HTTP/1.1→gRPC bridge : 2.3ms, 0.4ms
section forward_to_legacy
Loopback roundtrip : 2.7ms, 1.6ms
zt.svid_verification — SDS cache hit is ~0.2ms. A cache miss (SVID rotation, first request) is ~15ms while the SPIRE agent delivers the new cert.
zt.opa_authz — OPA DaemonSet on the same node: p99 < 3ms. Remote OPA (across nodes) is 10–20ms. The DaemonSet topology is deliberately chosen to bound this latency.
zt.protocol_translation — Only present when protocol translation is active. REST→gRPC with JSON transcoder adds ~1ms for protobuf encoding.
OBO Chain in Traces¶
The OTEL Collector's transform/obo-enrichment processor promotes OBO headers to first-class span attributes. No application instrumentation required.
# Result: every span in the distributed trace has these searchable attributes
obo.caller_principal: "spiffe://chakra.internal/ns/orders/sa/orders-svc"
obo.chain: "spiffe://chakra.internal/ns/api-gateway/sa/api-gateway,spiffe://chakra.internal/ns/orders/sa/orders-svc"
obo.user_principal: "user@chakra.internal"
A Tempo query to find all requests where orders-svc acted as an OBO delegator:
OPA Decision Log Correlation¶
OPA decision logs are tailed by the OTEL Collector filelog receiver and forwarded to Loki. Each record includes trace_id from the request's traceparent header — enabling a jump from a Tempo trace directly to the OPA decision that allowed or denied it.
Loki query — all OBO chain failures in the last hour:
{log_source="opa_decision_log"} | json | deny_reason != "none" | line_format "{{.timestamp}} {{.deny_reason}} caller={{.caller}} target={{.destination}}"
Loki query — trace-correlated OPA decision:
SPIRE Health Metrics¶
| Metric | Alert threshold | Meaning |
|---|---|---|
spire_agent_svid_rotation_total |
< expected rotation rate | Agent stopped rotating SVIDs |
spire_agent_attestation_failures_total |
> 5 in 5 minutes | Workload attestation failing (pod identity issue) |
spire_agent_svid_expire_soon_total |
> 0 for > 2 minutes | SVIDs not rotating before expiry |
spire_server_ca_signing_requests_total |
sudden spike | Large number of pods starting simultaneously |
SLOs¶
| SLO | Target | Alert |
|---|---|---|
| mTLS coverage | 100% | Any non-mTLS traffic fires PlaintextTrafficDetected (critical, 1m) |
| OPA authz deny rate | < 0.01% | OpaAuthzDenialSpike (warning, 5m) |
| OBO chain failure rate | < 0.001% | OboChainFailureSpike (critical, 2m) |
The mTLS coverage SLO is the most critical: a single non-mTLS connection in steady state indicates either a missing Istio sidecar or a bypassed PeerAuthentication policy.
Reference Implementation¶
| File | Purpose |
|---|---|
observability/otel/collector-config.yaml |
Full OTEL Collector pipeline: OTLP + Prometheus + filelog receivers; OBO enrichment processor; Tempo + Mimir + Loki exporters |
observability/slos/mtls-coverage-slo.yaml |
mTLS coverage SLO + PrometheusRule alert |
observability/slos/policy-violation-slo.yaml |
OPA denial rate + OBO chain failure rate SLOs |