Docs For organizations
Rotation
Rotate an active issuer's CA in two reviewed steps with an explicit overlap, renew before expiry, and respond to a compromise with an emergency rotation.
Rotation proposes a new version N+1 of an active, unexpired binding with POST /v1/issuer-bindings/{id}/rotate (owner or issuer administrator, human, step-up). The binding keeps serving version N while N+1 goes through the same validation as registration and its own full issuer-control ceremony: a new challenge, the federation endpoint proof and a challenge SVID from every x509-svid authority of the new bundle.
{
"expected_version": 3,
"bundle": "{\"keys\":[ ...old CA..., ...new CA... ]}",
"overlap_seconds": 86400
}
expected_version must equal the binding's current version. bundle_endpoint is optional and defaults to the served version's endpoint.
Why there are two steps
Every assertion pins exactly one bundle digest, so there is no overlap between versions. The overlap lives inside the bundle, which is standard SPIFFE practice. The server compares the new bundle's x509-svid authorities (the CA certificates' public keys; jwt-svid keys never count) with the served version's and classifies the rotation:
| Kind | The new bundle | Approvals | What relying parties see |
|---|---|---|---|
add |
Keeps every authority of the served version and adds new ones. Records overlap_until = activation + overlap_seconds. |
1 | Assertions name N+1. Its bundle holds the old and the new CA, so SVIDs from either validate. |
remove |
A strict subset of the served authorities, keeping at least one. Allowed only after the overlap in force has ended, and approved by a reviewer who did not approve the add step it completes. | 1 | Only the remaining CAs validate. |
renewal |
Byte-identical to the served bundle. Re-proves control and recomputes expires_at. |
1 | Same keys, a later expires_at. |
emergency |
emergency: true with a reason. Replaces or drops authorities at once. |
2 | The new version at once. |
So a normal CA rotation is: add the new CA alongside the old one, let SPIRE move to the new CA during the overlap, then remove the old CA.
Refusals are 422 semantic_check_failed, with the reason code in field_errors[0].message on the bundle field:
| Reason | Meaning |
|---|---|
no_shared_authority |
Nothing in common with the served version. Add the new CA alongside the old one first, or declare an emergency. |
replace_requires_two_steps |
The proposal adds and removes authorities at once. |
overlap_not_elapsed |
A remove step before the served version's overlap_until. |
The overlap
overlap_seconds is required for every kind and ranges from 330 seconds to 7 days.
- In production an add step needs at least 86400 seconds (24 hours); a shorter value is refused with
400onoverlap_seconds. Staging and development keep the 330-second minimum. - 330 seconds is the assertion lifetime (300 s) plus the verifier's clock skew (30 s). By the time the old CA can be removed, every assertion that named a bundle without the new CA has expired.
- The overlap is sticky. A renewal carries it forward, and a second add step can extend it but never shorten it. A remove step is allowed only after the longest overlap since the last remove step has ended, and it clears the overlap.
Renewal
A binding is endorsed until its expires_at (at most one year). Renew before then by rotating to the byte-identical bundle: the renewal re-proves issuer control and recomputes expires_at. A binding that is not renewed in time becomes expired, a terminal state with a public binding.expired event, and must be registered again. An expired binding cannot be rotated (409).
Emergency rotation
If a CA is compromised, send emergency: true with a reason (1 to 2000 characters, newlines allowed, no other control characters). The new bundle may replace or drop authorities at once, and it always needs two reviewer approvals. reason is refused without emergency: true.
An emergency rotation changes what the directory endorses from the moment it activates. Assertions issued before that can be accepted for up to 330 seconds, and SVIDs your issuer already minted stay valid until they expire: revoke or shorten them on the SPIRE side.
Limits and replacements
- A proposal that has not activated yet can be replaced by another rotate call.
- At most 20 versions that never became active per binding in a rolling 90 days. Activated rotations and renewals do not count.
- Superseded versions stay fetchable at their immutable
bundle_url, but they are no longer what the directory endorses.