mTLS/SPIFFE Bootstrap

A third Credential Issuance bootstrap adapter, alongside preshared secrets and OIDC: instead of a secret or an ID token, the caller’s identity comes from an already-verified SPIFFE ID, forwarded by a terminating mTLS proxy or service mesh (Envoy, Istio, SPIRE’s own agent-side proxy, nginx with ssl_verify_client, …) via a trusted HTTP header.

Wardline never terminates TLS or parses an X.509 certificate itself. The existing Helm chart decision — an Ingress/LB terminates TLS in front of Wardline — stands unchanged. This adapter adopts the same pattern every SPIFFE-aware mesh already uses: the sidecar or gateway completes the mTLS handshake, verifies the peer certificate against the mesh’s trust bundle, extracts the verified SPIFFE ID (the certificate’s URI SAN), and forwards it to the application — Wardline — as a header.

features:
  credential_issuance: true
credential:
  identities_file: "credentials.yaml"
  bootstrap_source: "mtls"
  mtls:
    header: "X-Wardline-Verified-Spiffe-Id"   # required, no default -- name a header your own proxy/mesh actually sets

credential.identities_file is required whenever features.credential_issuance is on, regardless of bootstrap_source — same non-obvious quirk as the OIDC bootstrap source (see SSO).

credentials.yaml maps each allowed SPIFFE ID to an identity and (optional, defaults to default) tenant:

identities:
  - name: payments-worker
    spiffe_id: "spiffe://example.org/ns/prod/sa/payments-worker"
    tenant: acme

An identity bootstraps by presenting the header — no request body is read at all on this path:

POST /credentials/token
X-Wardline-Verified-Spiffe-Id: spiffe://example.org/ns/prod/sa/payments-worker

Trust boundary — read this before enabling

This is a header-based trust handoff, the same class of mechanism as X-Forwarded-For or Envoy’s x-forwarded-client-cert: safe only if Wardline is unreachable except through the proxy/mesh that sets the header, and that proxy/mesh strips or overwrites any client-supplied value of the same header before forwarding. Wardline cannot verify either condition from inside its own process — this is a deployment requirement your network topology must guarantee, not something this feature enforces.

credential.mtls.header has no default value on purpose: an operator must explicitly name a header their own ingress/mesh is actually configured to set, so there’s no accidental behavior from a header nobody intended to trust. A missing or empty header on an actual request is a generic 401, the same non-enumerable-failure posture every other bootstrap source uses.

Known limitations

  • No X.509 parsing, no SPIFFE Workload API client, no SPIRE agent integration in Wardline itself — by design (see “Wardline never terminates TLS” above). This adapter bootstraps callers via an already-verified SPIFFE identity; it does not make Wardline itself a SPIFFE workload.
  • No dynamic/live SPIFFE-ID-to-tenant mapping — the static credentials.yaml allowlist mirrors the preshared-secret bootstrap source’s model exactly; there’s no pattern-based or trust-domain-based automatic mapping.
  • No enforcement, from inside Wardline, that its own network ingress path is actually mTLS-only — same documented-not-enforced posture as the existing Ingress-terminates-TLS decision generally.
  • Cross-tenant credential-revoke scoping falls back to requiring a global ClusterRoleBinding grant whenever a target identity name is registered under more than one tenant — the same fallback the preshared-secret and OIDC bootstrap sources already have, see RBAC’s known limitations.