Implement authenticated alert receipt acknowledgments and audit delivery

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e6f1-443f-7783-9920-a16b2ffc467f
This commit is contained in:
tegwick 2026-09-28 11:11:33 +02:00
parent 67283b66c2
commit e7282e493d
25 changed files with 1881 additions and 0 deletions

158
tests/test_alert_ack.py Normal file
View file

@ -0,0 +1,158 @@
from dataclasses import replace
import io
import json
import os
from pathlib import Path
import sqlite3
import sys
import tempfile
import unittest
from urllib.parse import urlencode
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
from alert_ack import Actor, Application, Store, occurrence
NOW = 1790553600 # 2026-09-28 UTC
START = '2026-09-28T00:00:00Z'
class AcknowledgmentTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.path = Path(self.temp.name) / 'ack.db'
self.store = Store(self.path)
self.actor = Actor('https://issuer.example', 'test-human', 'tenant:platform',
('railiance-admin',), NOW + 900, 'c' * 32)
self.decision = {'id': 'test-decision', 'effect': 'allow', 'expires_at': NOW + 30}
self.app = Application(self.store, 'https://telemetry.example', 'w' * 32,
lambda env: self.actor, lambda *args: self.decision,
clock=lambda: NOW)
self.alert = {'status': 'firing', 'fingerprint': '0123456789abcdef',
'startsAt': START, 'labels': {'alertname': 'TestFailure', 'owner': 'railiance-telemetry'}}
self.payload = {'version': '4', 'receiver': 'railiance-admin-email', 'alerts': [self.alert]}
self.identity = occurrence(self.alert['fingerprint'], START)
self.query = urlencode({'fingerprint': self.alert['fingerprint'], 'starts_at': START})
def request(self, method='GET', path='/ack/alerts', body=b'', **extra):
env = {'REQUEST_METHOD': method, 'PATH_INFO': path, 'QUERY_STRING': self.query,
'CONTENT_LENGTH': str(len(body)), 'wsgi.input': io.BytesIO(body),
'HTTP_ORIGIN': 'https://telemetry.example'}
env.update(extra)
result = {}
raw = b''.join(self.app(env, lambda status, headers: result.update(status=status, headers=headers)))
return result['status'], raw.decode()
def receive(self):
self.store.receive(self.payload, NOW)
def post(self, **kw):
return self.request('POST', body=urlencode({'csrf': self.actor.csrf}).encode(), **kw)
def test_scanner_get_does_not_acknowledge(self):
self.receive()
status, page = self.request()
self.assertEqual(status, '200 OK')
self.assertIn('Acknowledge receipt', page)
self.assertIsNone(self.store.get(self.identity)['occurred_at'])
with self.store.connect() as db:
self.assertEqual(db.execute('SELECT COUNT(*) FROM outbox').fetchone()[0], 0)
def test_click_commits_ack_and_audit_once_across_restart(self):
self.receive()
self.assertIn('Audit delivery pending', self.post()[1])
self.store = Store(self.path)
self.app.store = self.store
self.post()
with self.store.connect() as db:
rows = db.execute('SELECT body FROM outbox').fetchall()
self.assertEqual(len(rows), 1)
event = json.loads(rows[0]['body'])
self.assertEqual(event['data']['actor_subject'], 'test-human')
self.assertEqual(event['data']['role'], 'railiance-admin')
self.assertEqual(event['subject'], 'alert:' + self.identity)
self.assertEqual(event['type'], 'telemetry.alert.acknowledged')
def test_failed_outbox_rolls_back_ack(self):
self.receive()
with self.store.connect() as db:
db.execute("CREATE TRIGGER simulate_full BEFORE INSERT ON outbox BEGIN SELECT RAISE(ABORT, 'full'); END")
self.assertEqual(self.post()[0], '503 Service Unavailable')
self.assertIsNone(self.store.get(self.identity)['occurred_at'])
def test_wrong_actor_role_tenant_service_expiry_denied(self):
self.receive()
original = self.actor
for change in ({'roles': ()}, {'tenant': 'tenant:other'}, {'principal_type': 'service'}, {'expires_at': NOW}):
self.actor = replace(original, **change)
self.assertIn(self.post()[0], ('401 Unauthorized', '403 Forbidden'))
self.assertIsNone(self.store.get(self.identity)['occurred_at'])
def test_email_or_headers_do_not_grant_role(self):
self.receive()
self.actor = replace(self.actor, roles=())
self.assertEqual(self.post(HTTP_X_EMAIL='bernd.worsch@gmail.com', HTTP_X_ROLE='railiance-admin')[0], '403 Forbidden')
def test_origin_csrf_and_expired_pdp_denied(self):
self.receive()
self.assertEqual(self.post(HTTP_ORIGIN='https://attacker.example')[0], '403 Forbidden')
self.assertEqual(self.request('POST', body=b'csrf=wrong')[0], '403 Forbidden')
self.decision['expires_at'] = NOW
self.assertEqual(self.post()[0], '403 Forbidden')
self.assertIsNone(self.store.get(self.identity)['occurred_at'])
def test_webhook_auth_and_retry_and_batch_rollback(self):
raw = json.dumps(self.payload).encode()
self.assertEqual(self.request('POST', '/webhook', raw)[0], '401 Unauthorized')
for _ in range(2):
self.assertEqual(self.request('POST', '/webhook', raw, HTTP_AUTHORIZATION='Bearer ' + 'w' * 32)[0], '200 OK')
changed = json.loads(raw)
changed['alerts'][0]['labels']['alertname'] = 'Different'
with self.assertRaises(ValueError): self.store.receive(changed, NOW)
self.assertEqual(self.store.get(self.identity)['alertname'], 'TestFailure')
def test_new_firing_occurrence_requires_new_ack(self):
self.receive()
self.post()
self.alert['startsAt'] = '2026-09-27T23:59:59Z'
other = self.store.receive(self.payload, NOW)[0]
self.assertNotEqual(other, self.identity)
self.assertIsNone(self.store.get(other)['occurred_at'])
def test_lost_audit_receipt_replays_original_event(self):
self.receive()
self.post()
sent = []
def lost(event):
sent.append(event)
raise TimeoutError()
self.store.drain(lost)
def duplicate(event):
self.assertEqual(event, sent[0])
return 200, {'status': 'duplicate', 'reference': 'audit:' + event['id']}
self.store.drain(duplicate)
self.assertEqual(self.store.get(self.identity)['audit_status'], 'delivered')
self.assertIn('Audit record archived', self.request()[1])
def test_audit_refusal_retained_and_wrong_receipt_not_accepted(self):
self.receive()
self.post()
self.store.drain(lambda event: (202, {'status': 'accepted', 'reference': 'wrong'}))
self.assertEqual(self.store.get(self.identity)['audit_status'], 'pending')
self.store.drain(lambda event: (403, {}))
self.assertEqual(self.store.get(self.identity)['audit_status'], 'blocked')
self.assertIn('Audit delivery pending', self.request()[1])
self.assertEqual(self.store.audit_debt(), {'blocked': 1})
with self.store.connect() as db:
event = json.loads(db.execute('SELECT body FROM outbox').fetchone()[0])
self.assertTrue(self.store.requeue(event['id']))
self.store.drain(lambda value: (202, {'status': 'accepted', 'reference': 'audit:' + value['id']}))
self.assertEqual(self.store.audit_debt(), {})
def test_unsafe_database_refused(self):
os.chmod(self.path, 0o644)
with self.assertRaises(ValueError): Store(self.path)
if __name__ == '__main__':
unittest.main()