Add baseline reconciliation: accept audited drift with a reason
Implements the v0.9 roadmap item from the approved design spec. Operators accept a specific FIM/package/listener/SUID drift item with a mandatory reason; the ack suppresses that one alert only while the live state still matches the recorded fingerprint. Content kinds (fim/pkgfile) re-alert on further change; identity kinds (listener/suid) retire on TTL or revoke. - reconcile.py: ReconcileStore (mtime-cached, fails closed on missing/corrupt store), fingerprint builders, and the filter_alerts chokepoint. - CLI: baseline accept/revoke/list with --reason/--expires/--force/--stale/--json. - Wired at the daemon sweep + eBPF chokepoint, fim-check, and /api/integrity. - RECONCILE_V1 schema, config knob, COMMAND_REFERENCE/SCHEMAS/OPERATIONS/ROADMAP docs, and reconcile unit/CLI/integration tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3d2047fde2
commit
dbbe35b3fc
18 changed files with 1155 additions and 28 deletions
|
|
@ -9,7 +9,7 @@ from io import StringIO
|
|||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from enodia_sentinel import incident, respond, schemas, snapshot, web
|
||||
from enodia_sentinel import incident, reconcile, respond, schemas, snapshot, web
|
||||
from enodia_sentinel.alert import Alert, Severity
|
||||
from enodia_sentinel.cli import main
|
||||
from enodia_sentinel.config import Config
|
||||
|
|
@ -77,6 +77,27 @@ class TestSchemaContracts(unittest.TestCase):
|
|||
"pids": list,
|
||||
})
|
||||
|
||||
def test_reconcile_v1_contract(self):
|
||||
self.assertEqual(schemas.RECONCILE_V1, "enodia.reconcile.v1")
|
||||
store = reconcile.ReconcileStore.load(self.cfg)
|
||||
store.accept("fim", "/etc/hosts",
|
||||
{"sha256": "a", "mode": 1, "uid": 0, "gid": 0},
|
||||
"operator edit", "luna")
|
||||
reconcile.ReconcileStore.invalidate_cache()
|
||||
data = json.loads(
|
||||
reconcile.ReconcileStore.store_path(self.cfg).read_text())
|
||||
self.assertEqual(data["schema"], schemas.RECONCILE_V1)
|
||||
_assert_types(self, data, {"schema": str, "records": list})
|
||||
_assert_types(self, data["records"][0], {
|
||||
"kind": str,
|
||||
"target": str,
|
||||
"fingerprint": dict,
|
||||
"reason": str,
|
||||
"actor": str,
|
||||
"accepted_at": str,
|
||||
"status": str,
|
||||
})
|
||||
|
||||
def test_alert_snapshot_v1_contract(self):
|
||||
with patch("enodia_sentinel.snapshot._run", return_value=""):
|
||||
path = snapshot.capture([_alert()], self._State(), self.cfg)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue