Add S3 OCSP status publishing and standalone Rust Axum responder

This commit is contained in:
2026-10-23 09:18:00 +00:00
parent 4ed14b42a7
commit 0384995570
30 changed files with 5274 additions and 5 deletions
+100
View File
@@ -401,3 +401,103 @@ class BulkRevocationTests(unittest.TestCase):
json={"certificate_ids": ["00000000-0000-0000-0000-000000000000"], "reason": "superseded"},
)
assert response.status_code == 404
class OcspSnapshotTests(unittest.TestCase):
@async_test
async def test_snapshot_contains_status_and_reason(self) -> None:
async with api_client() as client:
with oidc_user("alice"):
vault_id = await _vault(client)
ca_key = await _key(client, vault_id, "ocsp ca")
ca = await _cert(client, vault_id, ca_key["id"], "OCSP CA", ca=True)
leaf_key = await _key(client, vault_id, "ocsp leaf")
leaf = await _cert(client, vault_id, leaf_key["id"], "OCSP Leaf", signer_id=ca["id"])
await client.post(
f"/api/vaults/{vault_id}/certificates/{leaf['id']}/revoke",
json={"reason": "keyCompromise"},
)
response = await client.get(
f"/api/vaults/{vault_id}/certificates/{ca['id']}/ocsp-status"
)
assert response.status_code == 200, response.text
payload = response.json()
assert payload["enabled"] is False
snapshot = payload["snapshot"]
assert snapshot["schema"] == "kca-ocsp-status/v1"
assert snapshot["issuer"]["sha256"] == ca["fingerprint_sha256"]
entries = {entry["serial_hex"]: entry for entry in snapshot["entries"]}
assert entries[leaf["serial_hex"]]["status"] == "revoked"
assert entries[leaf["serial_hex"]]["reason"] == "keyCompromise"
assert entries[leaf["serial_hex"]]["revoked_at"]
@async_test
async def test_revocation_marks_snapshot_dirty(self) -> None:
from dataclasses import replace
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, "dirty ca")
ca = await _cert(client, vault_id, ca_key["id"], "Dirty CA", ca=True)
leaf_key = await _key(client, vault_id, "dirty leaf")
leaf = await _cert(client, vault_id, leaf_key["id"], "Dirty Leaf", signer_id=ca["id"])
configured = replace(ocsp.settings, ocsp_s3_bucket="test-bucket")
with patch.object(ocsp, "settings", configured):
response = await client.post(
f"/api/vaults/{vault_id}/certificates/{leaf['id']}/revoke",
json={"reason": "superseded"},
)
assert response.status_code == 200, response.text
row = await OcspSnapshot.get(ca_id=ca["id"])
assert row.dirty is True
@async_test
async def test_publisher_marks_dirty_and_uploads(self) -> None:
from dataclasses import replace
from unittest.mock import patch
from kca import ocsp
from kca.models import OcspSnapshot
class FakeS3:
def __init__(self):
self.calls = []
def put_object(self, **kwargs):
self.calls.append(kwargs)
fake = FakeS3()
async with api_client() as client:
with oidc_user("alice"):
vault_id = await _vault(client)
ca_key = await _key(client, vault_id, "ocsp publish ca")
ca = await _cert(client, vault_id, ca_key["id"], "OCSP Publish CA", ca=True)
configured = replace(
ocsp.settings,
ocsp_s3_bucket="test-bucket",
ocsp_s3_prefix="ocsp",
)
with patch.object(ocsp, "settings", configured), patch.object(
ocsp, "_s3_client", lambda: fake
):
await ocsp.mark_ocsp_dirty(ca["id"])
row = await OcspSnapshot.get(ca_id=ca["id"])
assert row.dirty is True
async def ensure_context():
return None
mixin = ocsp.OcspPublisherMixin(1, ensure_context)
await mixin.run_once()
assert len(fake.calls) == 1
call = fake.calls[0]
assert call["Bucket"] == "test-bucket"
assert call["Key"] == f"ocsp/{ca['fingerprint_sha256']}.json"
assert b"kca-ocsp-status/v1" in call["Body"]
row = await OcspSnapshot.get(ca_id=ca["id"])
assert row.dirty is False
assert row.published_at is not None