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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user