Secret and cert rotation
This page is the operator runbook for rotating listener certificates, upstream client certificates, and upstream CA trust material, and for reasoning about secret-backed config in general.
Read Reload and Drain first if you have not already — this page assumes you understand the generation model and the difference between reload-certs and generation activation.
Which Path Applies
| Material | Path | Why |
|---|---|---|
| Downstream listener cert/key, listener client-auth CA | POST /admin/runtime/reload-certs |
Listener-scoped hot swap; does not touch runtime generation |
| Upstream client certificate/key (mTLS) | validate → preview → activate |
Generation-owned; rebuilds the affected backend connection pool |
Upstream CA bundle (ca_file/ca_dir) |
validate → preview → activate |
Generation-owned; same reasoning as client cert/key |
secrets.providers registry shape |
validate → preview → activate |
Generation-owned config, not a listener concern |
Upstream TLS material is never rotated through reload-certs, even when the change is "just a certificate." reload-certs is intentionally narrow: it only reloads listener identity and listener client-auth trust material for new downstream handshakes. Upstream client identity and upstream trust roots always go through the normal activation path so the change gets a diff, a generation number, and rollback semantics.
Secret References
Prefer file-backed secret references over plaintext values for anything sensitive:
secrets:
default_provider: local_filesystem
providers:
local_filesystem:
kind: file
base_dir: "/etc/impulse/secrets"
upstream:
payments:
tls:
client_certificate_ref:
ref: "file:///etc/impulse/secrets/upstream/payments-client.crt"
client_key_ref:
ref: "file:///etc/impulse/secrets/upstream/payments-client.key"
A _ref field and its plaintext sibling (client_certificate / client_certificate_ref, client_key / client_key_ref, secret / secret_ref, client_secret / client_secret_ref, auth_token / auth_token_ref, token / token_ref) are mutually exclusive — setting both fails validation. Supported ref schemes are literal:<value> and file://<path>.
Secret references resolve eagerly during activation, not lazily on first request. A missing file, unreadable file, empty file, or malformed PEM fails validate/activate before the candidate generation goes live — it never reaches request-serving code.
Downstream Listener Certificate Rotation
- Replace the cert/key files on disk. Prefer an atomic rename into place — see Secret File Replacement Safety below.
- Call:
bash
curl -k --http1.1 -X POST https://127.0.0.1:9902/admin/runtime/reload-certs \
-H "Authorization: Bearer <token>"
The path is whatever observability.control_api.reload_certs_path is configured to (default /admin/runtime/reload-certs).
3. Confirm the new cert in the runtime snapshot: GET /admin/runtime → tls.listeners.<listener> shows an updated generation, last_loaded_at_unix_ms, and default_cert_not_after_unix_seconds.
4. Confirm impulse_control_plane_cert_reload_total{result="success"} incremented and downstream cert-expiry metrics reflect the new material.
This does not rebuild the runtime generation, mutate route/policy state, or affect already-negotiated sessions — only new handshakes see the new certificate.
Upstream Client Certificate Rotation (mTLS)
- Write the new client cert/key to the staged secret path (atomic rename — see below).
- Run the staged activation flow against the config, using the same reference (if rotating in place at the same path) or a new reference (if introducing a new path):
```bash
curl -k --http1.1 -X POST https://127.0.0.1:9902/admin/runtime/validate \
-H "Authorization: Bearer
curl -k --http1.1 -X POST https://127.0.0.1:9902/admin/runtime/activate \
-H "Authorization: Bearer
- Confirm the generation changed:
GET /admin/runtime/historyshows a newentriesrecord, and its diff includes abackend_policiesdomain entry withsecret_material_changed: true. - Confirm the connection pool rebuilt rather than mutated in place — a client identity change always produces a new pool instance for that backend, never a live in-place swap.
- Confirm upstream mTLS handshakes succeed against the rotated identity, and
impulse_upstream_tls_failure_totalis not climbing for that upstream/backend pair.
Same-path rotation is detected. If the file at the same file:// reference path changes content, activation recomputes the fingerprint and the diff still reports secret_material_changed: true even though the reference string itself did not change — you do not need to introduce a new reference to force detection.
Upstream CA Rotation
Upstream CA rotation (ca_file / ca_dir) follows the exact same validate → preview → activate flow as client certificate rotation, since it is also generation-owned, not reload-certs-scoped.
For safe overlap during a CA transition:
- Stage the new CA alongside the old one — either append the new CA to the existing
ca_filebundle, or add it as an additional file inca_dir(all PEM files in the directory are trusted). - Activate with both CAs trusted. Confirm upstream connections still succeed against backends serving certs from either CA.
- Once all backends have rotated to certs signed by the new CA, remove the old CA and activate again.
- Roll back (see below) if backend handshakes start failing at any step — do not proceed to the next step under active failures.
Do not remove the old CA in the same activation that introduces the new one unless you have already confirmed every backend has rotated — that removes your rollback safety margin.
Secret File Replacement Safety
Prefer an atomic rename into place over in-place truncate-and-write for any file a file:// secret reference points at:
# Write to a temp file in the same directory, then rename atomically.
install -m 0640 payments-client.crt.new /etc/impulse/secrets/upstream/payments-client.crt
install, mv within the same filesystem, or an equivalent atomic rename avoids a window where a reader observes a partially-written file. Truncate-and-write can produce a transient empty or malformed file that activation reads as EmptySecret or MalformedPemCertificate even though the final content would have been fine — an atomic rename avoids that window entirely.
Rollback Expectations
Runtime rollback (POST /admin/runtime/rollback) restores a previously retained runtime generation view — the resolved policy state Impulse held for that generation. It does not, and cannot, restore external secret files.
Concretely:
- if you roll back to a generation that referenced
file:///etc/impulse/secrets/upstream/payments-client.crt, and that file has since been deleted or overwritten, the rollback will re-resolve the reference against whatever is on disk now, not what was on disk when that generation was originally active. - a rollback that re-reads a now-missing or now-different secret file can fail activation, or silently activate with different material than the original generation had, if the fingerprint at rollback time doesn't match what was recorded historically.
- do not treat retained runtime history as a backup of secret material. If you need to guarantee exact secret-content recovery, that is a secret-file backup/versioning problem, separate from runtime generation retention.
Before relying on rollback as your recovery path for a bad secret rotation, confirm the previous secret file content is still present and unchanged on disk.
Failure Handling
When a secret or cert rotation does not behave as expected, inspect in this order:
GET /admin/runtime— checktls.listeners,tls.upstreams, andsecrets.materialfor the affected scope. Each material item reportssource_kind, a sanitizedreference,fingerprint,last_loaded_at_unix_ms,last_reload_status, and (for certificates)expiry_not_after_unix_seconds. None of these ever include raw secret bytes.GET /admin/runtime/history— confirm whether the activation attempt succeeded, and read therejected_changesdetail if it did not. A rejected activation leaves the active generation unchanged.- Audit logs — look for these action values:
| Action | Meaning |
|---|---|
cert_reload_applied |
listener cert reload succeeded |
secret_resolution_failed |
a secret reference failed to resolve during activation (missing file, bad permissions, malformed PEM, etc.) |
upstream_mtls_material_changed |
an activation changed upstream client cert/key or CA fingerprint |
upstream_mtls_material_invalid |
an activation was rejected because upstream TLS material was invalid |
- TLS and secret metrics:
| Metric | Use |
|---|---|
impulse_secret_reload_total{scope,result,reason} |
reload attempts by scope (listeners/upstreams) and outcome |
impulse_secret_resolve_total{provider,result,reason} |
resolution attempts by provider and outcome |
impulse_secret_last_success_unixtime{scope} |
staleness — how long since the last successful resolve for a scope |
impulse_upstream_tls_failure_total{upstream,backend,phase,reason} |
live TLS/mTLS handshake failures against a rotated or misconfigured identity, by request phase |
impulse_upstream_client_certificate_not_after_seconds{upstream} |
absolute expiry timestamp per upstream |
impulse_upstream_client_certificate_days_remaining{upstream} |
days-remaining gauge for alerting ahead of expiry |
impulse_control_plane_cert_reload_total{result,reason} |
listener cert reload outcomes |
None of the control-plane JSON, audit events, or metrics expose secret contents, private key bytes, or full provider URIs that could leak credentials — only sanitized scope, source kind, fingerprint, and timing/status metadata.