Publish OCSP snapshots through Redis
This commit is contained in:
@@ -13,14 +13,14 @@ class OcspExpirySettingsTest(unittest.TestCase):
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
settings = Settings.from_env()
|
||||
self.assertEqual(30, settings.ocsp_heartbeat_interval_seconds)
|
||||
self.assertEqual(60, settings.ocsp_object_expiry_seconds)
|
||||
self.assertEqual(60, settings.ocsp_redis_expiry_seconds)
|
||||
|
||||
def test_expiry_must_exceed_heartbeat(self) -> None:
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"KCA_OCSP_HEARTBEAT_INTERVAL_SECONDS": "30",
|
||||
"KCA_OCSP_OBJECT_EXPIRY_SECONDS": "30",
|
||||
"KCA_OCSP_REDIS_EXPIRY_SECONDS": "30",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
|
||||
@@ -1073,7 +1073,7 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
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")
|
||||
configured = replace(ocsp.settings, redis_url="redis://localhost:6379/0")
|
||||
with patch.object(ocsp, "settings", configured):
|
||||
response = await client.post(
|
||||
f"/api/vaults/{vault_id}/certificates/{leaf['id']}/revoke",
|
||||
@@ -1100,7 +1100,7 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
leaf = await _cert(
|
||||
client, vault_id, leaf_key["id"], "Existing Dirty Leaf", signer_id=ca["id"]
|
||||
)
|
||||
configured = replace(ocsp.settings, ocsp_s3_bucket="test-bucket")
|
||||
configured = replace(ocsp.settings, redis_url="redis://localhost:6379/0")
|
||||
with patch.object(ocsp, "settings", configured):
|
||||
response = await client.post(
|
||||
f"/api/vaults/{vault_id}/certificates/{leaf['id']}/revoke",
|
||||
@@ -1129,7 +1129,7 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
vault_id = await _vault(client)
|
||||
ca_key = await _key(client, vault_id, "manual dirty ca")
|
||||
ca = await _cert(client, vault_id, ca_key["id"], "Manual Dirty CA", ca=True)
|
||||
configured = replace(ocsp.settings, ocsp_s3_bucket="test-bucket")
|
||||
configured = replace(ocsp.settings, redis_url="redis://localhost:6379/0")
|
||||
with patch.object(ocsp, "settings", configured):
|
||||
await OcspSnapshot.create(ca_id=ca["id"], dirty=False)
|
||||
response = await client.post(
|
||||
@@ -1177,7 +1177,7 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
)
|
||||
published = []
|
||||
|
||||
async def fake_publish(publish_ca, snapshot):
|
||||
async def fake_publish(publish_ca, snapshot, client=None):
|
||||
published.append(snapshot)
|
||||
if len(published) == 1:
|
||||
await CertificateRevocation.create(
|
||||
@@ -1187,9 +1187,9 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
reason="keyCompromise",
|
||||
)
|
||||
await ocsp.mark_ocsp_dirty(publish_ca.id)
|
||||
return ocsp.object_key(str(publish_ca.id))
|
||||
return ocsp.redis_key(str(publish_ca.id))
|
||||
|
||||
configured = replace(ocsp.settings, ocsp_s3_bucket="test-bucket")
|
||||
configured = replace(ocsp.settings, redis_url="redis://localhost:6379/0")
|
||||
with patch.object(ocsp, "settings", configured), patch.object(
|
||||
ocsp, "publish_snapshot", fake_publish
|
||||
):
|
||||
@@ -1222,20 +1222,14 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
from kca import ocsp
|
||||
from kca.models import OcspSnapshot
|
||||
|
||||
class FakeS3:
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, traceback):
|
||||
return None
|
||||
|
||||
class FakeRedis:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
async def put_object(self, **kwargs):
|
||||
self.calls.append(kwargs)
|
||||
async def set(self, name, value, ex=None):
|
||||
self.calls.append({"name": name, "value": value, "ex": ex})
|
||||
|
||||
fake = FakeS3()
|
||||
fake = FakeRedis()
|
||||
async with api_client() as client:
|
||||
with oidc_user("alice"):
|
||||
vault_id = await _vault(client)
|
||||
@@ -1243,28 +1237,24 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
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",
|
||||
redis_url="redis://localhost:6379/0",
|
||||
ocsp_redis_prefix="ocsp",
|
||||
)
|
||||
with patch.object(ocsp, "settings", configured), patch.object(
|
||||
ocsp, "_s3_client", lambda: fake
|
||||
):
|
||||
with patch.object(ocsp, "settings", configured):
|
||||
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)
|
||||
mixin = ocsp.OcspPublisherMixin(1, ensure_context, redis_client=fake)
|
||||
await mixin.run_once()
|
||||
assert len(fake.calls) == 1
|
||||
call = fake.calls[0]
|
||||
assert call["Bucket"] == "test-bucket"
|
||||
assert call["Key"] == f"ocsp/{ca['id']}.json"
|
||||
assert b"kca-ocsp-status/v2" in call["Body"]
|
||||
assert b"generated_at" not in call["Body"]
|
||||
expires_in = (call["Expires"] - datetime.now(timezone.utc)).total_seconds()
|
||||
assert 50 < expires_in <= 60
|
||||
assert call["name"] == f"ocsp:{ca['id']}"
|
||||
assert b"kca-ocsp-status/v2" in call["value"]
|
||||
assert b"generated_at" not in call["value"]
|
||||
assert call["ex"] == 60
|
||||
row = await OcspSnapshot.get(ca_id=ca["id"])
|
||||
assert row.dirty is False
|
||||
assert row.published_at is not None
|
||||
@@ -1289,7 +1279,7 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
)
|
||||
published = []
|
||||
|
||||
async def fake_publish(publish_ca, snapshot):
|
||||
async def fake_publish(publish_ca, snapshot, client=None):
|
||||
published.append(snapshot)
|
||||
return "key"
|
||||
|
||||
@@ -1327,7 +1317,7 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
)
|
||||
published = []
|
||||
|
||||
async def fake_publish(publish_ca, snapshot):
|
||||
async def fake_publish(publish_ca, snapshot, client=None):
|
||||
published.append(snapshot)
|
||||
return "key"
|
||||
|
||||
@@ -1351,20 +1341,16 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
from kca import ocsp
|
||||
from kca.models import Certificate, OcspSnapshot
|
||||
|
||||
class FakeS3:
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, traceback):
|
||||
return None
|
||||
|
||||
class FakeRedis:
|
||||
def __init__(self):
|
||||
self.objects = {}
|
||||
self.expiries = {}
|
||||
|
||||
async def put_object(self, **kwargs):
|
||||
self.objects[kwargs["Key"]] = kwargs["Body"]
|
||||
async def set(self, name, value, ex=None):
|
||||
self.objects[name] = value
|
||||
self.expiries[name] = ex
|
||||
|
||||
fake = FakeS3()
|
||||
fake = FakeRedis()
|
||||
async with api_client() as client:
|
||||
with oidc_user("alice"):
|
||||
vault_id = await _vault(client)
|
||||
@@ -1388,14 +1374,12 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
|
||||
configured = replace(
|
||||
ocsp.settings,
|
||||
ocsp_s3_bucket="test-bucket",
|
||||
ocsp_s3_prefix="ocsp",
|
||||
redis_url="redis://localhost:6379/0",
|
||||
ocsp_redis_prefix="ocsp",
|
||||
)
|
||||
with patch.object(ocsp, "settings", configured), patch.object(
|
||||
ocsp, "_s3_client", lambda: fake
|
||||
):
|
||||
with patch.object(ocsp, "settings", configured):
|
||||
target = await Certificate.get(id=ca["id"])
|
||||
target_key = await ocsp.publish_snapshot(target)
|
||||
target_key = await ocsp.publish_snapshot(target, client=fake)
|
||||
|
||||
with oidc_user("bob"):
|
||||
attacker_vault_id = await _vault(client)
|
||||
@@ -1423,12 +1407,13 @@ class OcspSnapshotTests(unittest.TestCase):
|
||||
assert len(response.json()["skipped"]) == 1
|
||||
row = await OcspSnapshot.get(ca_id=imported.id)
|
||||
assert row.dirty is True
|
||||
imported_key = await ocsp.publish_snapshot(imported)
|
||||
imported_key = await ocsp.publish_snapshot(imported, client=fake)
|
||||
|
||||
assert target_key == f"ocsp/{ca['id']}.json"
|
||||
assert imported_key == f"ocsp/{imported.id}.json"
|
||||
assert target_key == f"ocsp:{ca['id']}"
|
||||
assert imported_key == f"ocsp:{imported.id}"
|
||||
assert imported_key != target_key
|
||||
assert set(fake.objects) == {target_key, imported_key}
|
||||
assert fake.expiries == {target_key: 60, imported_key: 60}
|
||||
target_snapshot = json.loads(fake.objects[target_key])
|
||||
target_entries = {entry["serial_hex"]: entry for entry in target_snapshot["entries"]}
|
||||
assert target_entries[leaf["serial_hex"].lower()]["status"] == "revoked"
|
||||
|
||||
Reference in New Issue
Block a user