Skip to main content

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

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:
The 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)

The 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’s BundleRefresher 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

The 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 the tracelane_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:
All five series are always present, including at zero, so a rate() rule has something to evaluate on a healthy system rather than appearing only at the moment it fires.
The default bind is loopback. Running ingest in a container means 127.0.0.1 is unreachable from a Prometheus elsewhere — set TRACELANE_METRICS_ADDR=0.0.0.0:9464 and restrict the port at your firewall or container network. The endpoint is unauthenticated; it carries only these auth-outcome counts, never tenant identifiers or span contents.The metrics listener cannot take ingest down. If its port is unavailable, ingest logs the failure and keeps ingesting spans without a scrape endpoint.
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.
Recommended Prometheus alerts:

6. Troubleshooting

7. Further reading