Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e6f1-443f-7783-9920-a16b2ffc467f
158 lines
7.3 KiB
Python
158 lines
7.3 KiB
Python
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()
|