Add HTTPS management console and response planning

This commit is contained in:
Luna 2026-06-12 02:39:53 -07:00
parent a56d72edd6
commit a7129e5666
16 changed files with 927 additions and 160 deletions

View file

@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
"""Tests for the read-only web dashboard: data layer + auth (real server)."""
import json
import ssl
import tempfile
import threading
import unittest
@ -9,6 +10,8 @@ import urllib.request
from pathlib import Path
from enodia_sentinel import web
from enodia_sentinel import incident
from enodia_sentinel.alert import Alert, Severity
from enodia_sentinel.config import Config
@ -27,6 +30,11 @@ def _write_alert(tmp: Path, name: str, severity: str, sigs):
(tmp / f"{name}.log").write_text(f"=== ENODIA SENTINEL ALERT ===\n{severity}\n")
def _incident_alert(sig: str, pids=()):
return Alert(Severity.CRITICAL, sig, f"k:{sig}", f"{sig} detail",
tuple(pids), sid=100010, classtype="test")
class TestDataLayer(unittest.TestCase):
def setUp(self):
self.dir = tempfile.TemporaryDirectory()
@ -60,6 +68,20 @@ class TestDataLayer(unittest.TestCase):
(self.tmp / "events.log").write_text("l1\nl2\nl3\n")
self.assertEqual(web.tail_events(self.cfg, 2), ["l2", "l3"])
def test_incident_and_response_plan_data(self):
_write_alert(self.tmp, "alert-20260531-000001", "CRITICAL", ["reverse_shell"])
iid = incident.record(self.cfg, "alert-20260531-000001.log",
[_incident_alert("reverse_shell", [4242])],
lineage={4242}, when=1000.0, host="h")
incs = web.list_incidents(self.cfg)
self.assertEqual(incs[0]["id"], iid)
inc = web.get_incident(self.cfg, iid)
self.assertEqual(inc["incident"]["id"], iid)
self.assertEqual(len(inc["timeline"]), 1)
plan = web.response_plan(self.cfg, iid)
self.assertEqual(plan["incident_id"], iid)
self.assertEqual(plan["mode"], "dry-run")
class TestNetworkHelpers(unittest.TestCase):
def test_is_loopback(self):
@ -79,6 +101,13 @@ class TestNetworkHelpers(unittest.TestCase):
self.assertTrue(t1)
self.assertEqual(t1, t2) # stable across calls
def test_self_signed_tls_material_is_created(self):
with tempfile.TemporaryDirectory() as d:
c = _make_cfg(Path(d))
cert, key = web.ensure_tls_cert(c, "127.0.0.1")
self.assertTrue(cert.is_file())
self.assertTrue(key.is_file())
class TestAuth(unittest.TestCase):
"""Spin up the real server on loopback and check token enforcement."""
@ -92,20 +121,23 @@ class TestAuth(unittest.TestCase):
self.cfg.web_port = 0 # ephemeral
self.cfg.web_token = "secret-token"
self.httpd, _bind, _tok = web.build_server(self.cfg)
self.assertTrue(Path(self.httpd.tls_cert).is_file())
self.port = self.httpd.server_address[1]
self.t = threading.Thread(target=self.httpd.serve_forever, daemon=True)
self.t.start()
self.ctx = ssl._create_unverified_context()
def tearDown(self):
self.httpd.shutdown()
self.httpd.server_close()
self.dir.cleanup()
def _get(self, path, token=None):
url = f"http://127.0.0.1:{self.port}{path}"
url = f"https://127.0.0.1:{self.port}{path}"
req = urllib.request.Request(url)
if token:
req.add_header("Authorization", f"Bearer {token}")
return urllib.request.urlopen(req, timeout=4)
return urllib.request.urlopen(req, timeout=4, context=self.ctx)
def test_unauthorized_without_token(self):
with self.assertRaises(urllib.error.HTTPError) as cm: