Use S3 object expiry for OCSP snapshots

This commit is contained in:
2026-10-28 19:39:51 +00:00
parent 9da68a9625
commit a7cfda4167
17 changed files with 321 additions and 238 deletions
+29 -18
View File
@@ -26,10 +26,11 @@ This isolates snapshots when the same public CA certificate exists in multiple
vaults. The responder validates both this ID and the CA certificate fingerprint
against every downloaded snapshot.
KCA publishes these objects when `KCA_OCSP_S3_BUCKET` is configured. The JSON
contains all certificates issued by that CA, both valid and revoked, and a
required `generated_at` timestamp. The responder never infers status from a
partial revoked-only list.
KCA publishes these objects when `KCA_OCSP_S3_BUCKET` is configured. The
`kca-ocsp-status/v2` JSON contains all certificates issued by that CA, both
valid and revoked, without a generation timestamp. Freshness is carried by the
S3 `Expires` header instead. The responder never infers status from a partial
revoked-only list.
## Upgrading from fingerprint-named snapshots
@@ -39,6 +40,13 @@ KCA publisher. The responder deliberately does not fall back to legacy
objects, OCSP requests return `tryLater`. After verifying the new snapshots,
the obsolete fingerprint-named objects can be removed.
## Upgrading from timestamped snapshots
Version 2 snapshots remove `generated_at` and require an S3 `Expires` header.
Upgrade the publisher and responder together: old objects have no `Expires` and
are rejected, while old responders cannot parse the version 2 JSON. Until the
new publisher writes a snapshot, the new responder returns `tryLater`.
## Run locally
1. Create a config based on `responder.example.yaml`, setting each issuer's
@@ -97,20 +105,23 @@ build a trust store containing the CA or validate with your normal PKI tooling.
## Cache and availability
- Per-CA cache TTL: `s3.cache_ttl_ms` (default 1000 ms).
- Concurrent requests for the same CA share one S3 `GetObject`.
- `s3.stale_grace_seconds` is also the hard maximum snapshot age, measured from
`generated_at`. Expired or future-dated snapshots are rejected even when S3
remains readable; if no valid cached snapshot remains, OCSP returns
`tryLater`.
- If a refresh fails, a cached snapshot is served only while both its time since
the last successful fetch and its `generated_at` age remain within
`s3.stale_grace_seconds`.
- Concurrent requests for the same CA share one refresh. Each refresh performs
`HeadObject` to obtain `Expires` and the ETag, then `GetObject` with
`If-Match` for that ETag. This supports RustFS, which does not return the
stored `Expires` value from its local `GetObject` path, and prevents mixing
metadata and bodies from different object versions. Concurrent overwrites
cause a bounded retry of the complete HEAD/GET sequence.
- `Expires` is the hard snapshot expiry. Missing, malformed and expired values
are rejected; if no unexpired cached snapshot remains, OCSP returns
`tryLater`. The header controls responder freshness only—S3 does not delete
the object at that time.
- If a refresh fails, a cached snapshot is served only until its own `Expires`
timestamp, regardless of how recently it was fetched.
- KCA republishes unchanged snapshots every
`KCA_OCSP_HEARTBEAT_INTERVAL_SECONDS` (default 30 seconds). Keep that interval
comfortably below the responder's `s3.stale_grace_seconds` (default 60). For
a rolling upgrade, enable publisher heartbeats before the freshness-enforcing
responder so existing snapshots remain within the age limit.
- OCSP `thisUpdate` is the snapshot's `generated_at`. `nextUpdate` is capped at
the snapshot's hard expiry, while `producedAt` is the response signing time.
`KCA_OCSP_HEARTBEAT_INTERVAL_SECONDS` (default 30 seconds) and sets `Expires`
using `KCA_OCSP_OBJECT_EXPIRY_SECONDS` (default 60 seconds). Keep the
heartbeat comfortably below the object expiry.
- OCSP `thisUpdate` and `producedAt` use the response time. `nextUpdate` is
capped at the snapshot's hard expiry.
- With multiple CAs, the TTL is per CA. If you need a global one-request-per-
second limit across all CAs, that requires a shared rate limiter.