Add --json output to the check command
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5e560f4307
commit
826daa3ab7
2 changed files with 50 additions and 4 deletions
|
|
@ -146,7 +146,7 @@ def _reconcile_list(cfg: Config, store, args) -> int:
|
||||||
return 1 if any_stale else 0
|
return 1 if any_stale else 0
|
||||||
|
|
||||||
|
|
||||||
def _cmd_check(cfg: Config) -> int:
|
def _cmd_check(cfg: Config, as_json: bool = False) -> int:
|
||||||
# One-shot: arm everything immediately, force the SUID scan.
|
# One-shot: arm everything immediately, force the SUID scan.
|
||||||
sentinel = Sentinel(cfg)
|
sentinel = Sentinel(cfg)
|
||||||
sentinel.start_time = 0.0 # past the grace window
|
sentinel.start_time = 0.0 # past the grace window
|
||||||
|
|
@ -154,10 +154,15 @@ def _cmd_check(cfg: Config) -> int:
|
||||||
if not sentinel.listener_baseline:
|
if not sentinel.listener_baseline:
|
||||||
sentinel.build_baselines()
|
sentinel.build_baselines()
|
||||||
alerts = sentinel.sweep(force_suid=True)
|
alerts = sentinel.sweep(force_suid=True)
|
||||||
|
ordered = sorted(alerts, key=lambda x: -x.severity)
|
||||||
|
if as_json:
|
||||||
|
import json
|
||||||
|
print(json.dumps([a.to_dict() for a in ordered], indent=2))
|
||||||
|
return 0
|
||||||
if not alerts:
|
if not alerts:
|
||||||
print("No alerts.")
|
print("No alerts.")
|
||||||
return 0
|
return 0
|
||||||
for a in sorted(alerts, key=lambda x: -x.severity):
|
for a in ordered:
|
||||||
print(f"[{a.severity}] {a.signature:<14} {a.detail}")
|
print(f"[{a.severity}] {a.signature:<14} {a.detail}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
@ -172,7 +177,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
parser.add_argument("-c", "--config", help="path to TOML config")
|
parser.add_argument("-c", "--config", help="path to TOML config")
|
||||||
sub = parser.add_subparsers(dest="cmd")
|
sub = parser.add_subparsers(dest="cmd")
|
||||||
sub.add_parser("run", help="run the daemon loop (default)")
|
sub.add_parser("run", help="run the daemon loop (default)")
|
||||||
sub.add_parser("check", help="run detectors once and print alerts")
|
chk = sub.add_parser("check", help="run detectors once and print alerts")
|
||||||
|
chk.add_argument("--json", action="store_true", help="emit alerts as JSON")
|
||||||
bl = sub.add_parser(
|
bl = sub.add_parser(
|
||||||
"baseline",
|
"baseline",
|
||||||
help="rebuild baselines (default), or accept/revoke/list drift")
|
help="rebuild baselines (default), or accept/revoke/list drift")
|
||||||
|
|
@ -259,7 +265,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
return _cmd_baseline(cfg)
|
return _cmd_baseline(cfg)
|
||||||
return _cmd_reconcile(cfg, args)
|
return _cmd_reconcile(cfg, args)
|
||||||
if args.cmd == "check":
|
if args.cmd == "check":
|
||||||
return _cmd_check(cfg)
|
return _cmd_check(cfg, args.json)
|
||||||
if args.cmd == "web":
|
if args.cmd == "web":
|
||||||
from .web import serve
|
from .web import serve
|
||||||
serve(cfg)
|
serve(cfg)
|
||||||
|
|
|
||||||
40
tests/test_check_json.py
Normal file
40
tests/test_check_json.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
"""The `check --json` flag emits a parseable JSON alert array."""
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
from contextlib import redirect_stdout
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from enodia_sentinel import cli
|
||||||
|
from enodia_sentinel.alert import Alert, Severity
|
||||||
|
from enodia_sentinel.config import Config
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeSentinel:
|
||||||
|
def __init__(self, cfg):
|
||||||
|
self.start_time = 1.0
|
||||||
|
self.listener_baseline = {"x"}
|
||||||
|
|
||||||
|
def load_baselines(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def build_baselines(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sweep(self, force_suid=False):
|
||||||
|
return [Alert(Severity.HIGH, "rsh", "rsh:1", "reverse shell", sid=1001)]
|
||||||
|
|
||||||
|
|
||||||
|
class TestCheckJson(unittest.TestCase):
|
||||||
|
def test_check_json_emits_alert_array(self):
|
||||||
|
cfg = Config()
|
||||||
|
with mock.patch.object(cli, "Sentinel", _FakeSentinel):
|
||||||
|
buf = io.StringIO()
|
||||||
|
with redirect_stdout(buf):
|
||||||
|
rc = cli._cmd_check(cfg, True)
|
||||||
|
self.assertEqual(rc, 0)
|
||||||
|
data = json.loads(buf.getvalue())
|
||||||
|
self.assertEqual(len(data), 1)
|
||||||
|
self.assertEqual(data[0]["signature"], "rsh")
|
||||||
|
self.assertEqual(data[0]["sid"], 1001)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue