Fix stale OCSP snapshots indefinitely

- Parse and validate  from snapshot JSON, reject future/missing timestamps
- Enforce snapshot age vs  in cache and stale fallback (no lifetime extension on repeat S3 reads)
- OCSP: , ,
- Remove unused  from signer
- Publisher heartbeats: , republish clean snapshots past interval, preserve dirty-only behavior and concurrency
- Config/env/docker/docs updates for heartbeat

Rolling upgrade: enable publisher heartbeats before deploying the age-enforcing responder so existing snapshots remain within the age window.
This commit is contained in:
2026-10-26 12:06:15 +08:00
parent b59b935d1b
commit aee1025015
12 changed files with 299 additions and 43 deletions
+73
View File
@@ -1038,6 +1038,79 @@ class OcspSnapshotTests(unittest.TestCase):
assert row.dirty is False
assert row.published_at is not None
@async_test
async def test_publisher_heartbeat_republishes_due_clean_snapshot(self) -> None:
from unittest.mock import patch
from kca import ocsp
from kca.models import OcspSnapshot
async with api_client() as client:
with oidc_user("alice"):
vault_id = await _vault(client)
ca_key = await _key(client, vault_id, "heartbeat due ca")
ca = await _cert(client, vault_id, ca_key["id"], "Heartbeat Due CA", ca=True)
previous_published_at = datetime.now(timezone.utc) - timedelta(seconds=31)
await OcspSnapshot.create(
ca_id=ca["id"],
dirty=False,
published_at=previous_published_at,
)
published = []
async def fake_publish(publish_ca, snapshot):
published.append(snapshot)
return "key"
async def ensure_context():
return None
mixin = ocsp.OcspPublisherMixin(
1, ensure_context, heartbeat_interval_seconds=30
)
with patch.object(ocsp, "publish_snapshot", fake_publish):
await mixin.run_once()
assert len(published) == 1
row = await OcspSnapshot.get(ca_id=ca["id"])
assert row.dirty is False
assert row.published_at is not None
assert row.published_at != previous_published_at
@async_test
async def test_publisher_heartbeat_skips_recent_clean_snapshot(self) -> None:
from unittest.mock import patch
from kca import ocsp
from kca.models import OcspSnapshot
async with api_client() as client:
with oidc_user("alice"):
vault_id = await _vault(client)
ca_key = await _key(client, vault_id, "heartbeat recent ca")
ca = await _cert(client, vault_id, ca_key["id"], "Heartbeat Recent CA", ca=True)
await OcspSnapshot.create(
ca_id=ca["id"],
dirty=False,
published_at=datetime.now(timezone.utc),
)
published = []
async def fake_publish(publish_ca, snapshot):
published.append(snapshot)
return "key"
async def ensure_context():
return None
mixin = ocsp.OcspPublisherMixin(
1, ensure_context, heartbeat_interval_seconds=30
)
with patch.object(ocsp, "publish_snapshot", fake_publish):
await mixin.run_once()
assert published == []
@async_test
async def test_imported_ca_cannot_overwrite_other_vault_snapshot(self) -> None:
import json