SPIFFE/SPIRE ingest mTLS — operator runbook
Audience: SREs running self-hosted Tracelane or federating with the Tracelane control plane (Enterprise) in a customer cluster.The Tracelane ingest worker authenticates every connection with a SPIFFE X.509-SVID issued by SPIRE. This page covers the three operational moments where you’ll touch SPIRE: bringing it up, registering Tracelane workloads, and rotating the trust bundle without service interruption.
Architecture in one diagram
1. Bring up SPIRE in a customer cluster
Kubernetes (recommended)
The SPIRE Helm chart is the supported path. Minimum settings for a Tracelane install:Bare metal / single-VM (Hetzner-style)
A minimal single-node layout:2. Register the Tracelane ingest workload
Every ingest worker process needs an entry that maps a selector (how SPIRE recognises the workload) to a SPIFFE ID (the identity it’ll hand out). Tracelane’s expected SPIFFE ID format is:tenant/<uuid>/ingest-worker path shape is enforced by
crates/ingest/src/auth.rs::parse_spiffe_id. Any other shape (e.g.
/workspace/<uuid> or missing ingest-worker suffix) is rejected
fail-closed with 401 Unauthorized.
Kubernetes selector
Unix selector (bare metal / VM)
unix:path selector pins the binary on disk so a different
process running as the same UID cannot fetch the SVID. Keep the
ingest binary owned by root, 0755, and only the tracelane-ingest
service user able to execute it.
3. Tracelane ingest environment
Set these env vars on the ingest container / systemd unit:
A misconfigured
TRACELANE_TRUST_DOMAIN is caught at boot
(crates/ingest/src/main.rs:194-199) — ingest refuses to start rather
than silently authenticate with the wrong trust domain.
4. Rotate the trust bundle without dropping spans
SPIRE rotates the CA on a 1h cadence by default. The ingest worker’sBundleRefresher consumes the FetchX509Bundles stream and swaps the
new RootCertStore in atomically via ArcSwap. In-flight
connections keep their old bundle; new handshakes pick up the rotated
roots. Zero rotation downtime in normal operation.
What you should see in logs
What you should NOT see
gave up line means the refresher exhausted its retry budget
(MAX_REFRESH_FAILURES = 8, exponential backoff 500ms → 60s, total
~3.5 min). When that fires, tokio::try_join! in main.rs propagates
the error and the ingest process exits non-zero. This is intentional
fail-closed — a frozen trust bundle is worse than a controlled
outage because SVIDs expire on a 1h cadence and a stale bundle would
silently accept rotated-away certs. The supervisor (systemd /
Kubernetes) restarts the process; once SPIRE is reachable again the
refresher boots clean.
5. Observability
Every authentication outcome bumps thetracelane_ingest_auth_total{result=...} counter. Five-bucket label
set:
Scraping the counter
Ingest serves the Prometheus text format at/metrics on its own listener,
separate from the OTLP port:
rate() rule has
something to evaluate on a healthy system rather than appearing only at the moment
it fires.
These counters only move on the mTLS path. They are incremented by the SPIFFE
authentication middleware, which is attached only when ingest runs with SPIRE. On the
single-tenant self-host path (
TRACELANE_SPIRE_SOCKET unset, plaintext),
there is no SPIFFE authentication to record, so every bucket stays at 0
permanently. Do not read those zeros as “no authentication failures” — on that
deployment the alerts below have nothing to say, and tenant attribution is handled by
the fixed-tenant mechanism instead.6. Troubleshooting
7. Further reading
- SPIFFE X.509-SVID spec §4 — wire format.
- SPIRE Workload API spec — gRPC contract.
- Migration guide — moving an existing customer cluster onto Tracelane ingest mTLS.