Initial release: offline NetScaler triage with Sigma rules and MIT license
This commit is contained in:
commit
711c42b270
52 files changed
+61222
No files matched your search
@@ -0,0 +1,113 @@
|
||||
"""CLI presentation must not corrupt evidence output or generate assessments."""
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
from netscaler_triage.cli import display_result, main
|
||||
from netscaler_triage.engine import scan
|
||||
|
||||
|
||||
class CliTests(unittest.TestCase):
|
||||
def test_help_and_argument_errors(self) -> None:
|
||||
output = io.StringIO()
|
||||
with contextlib.redirect_stdout(output), self.assertRaises(SystemExit) as raised:
|
||||
main(["scan", "--help"])
|
||||
self.assertEqual(raised.exception.code, 0)
|
||||
self.assertIn("Log folders or archives", output.getvalue())
|
||||
self.assertIn("Example:", output.getvalue())
|
||||
self.assertNotIn("--regex-timeout", output.getvalue())
|
||||
advanced = io.StringIO()
|
||||
with contextlib.redirect_stdout(advanced), self.assertRaises(SystemExit):
|
||||
main(["scan", "--help-all"])
|
||||
self.assertIn("--regex-timeout", advanced.getvalue())
|
||||
error = io.StringIO()
|
||||
with contextlib.redirect_stderr(error), self.assertRaises(SystemExit) as raised:
|
||||
main(["scan", "--not-a-valid-option"])
|
||||
self.assertEqual(raised.exception.code, 2)
|
||||
self.assertIn("Invalid arguments", error.getvalue())
|
||||
self.assertIn("--help", error.getvalue())
|
||||
|
||||
def test_json_and_interactive_output(self) -> None:
|
||||
result = {"status": "complete", "counts": {"records": 7}}
|
||||
output = io.StringIO()
|
||||
with contextlib.redirect_stdout(output):
|
||||
display_result(result, json_output=True)
|
||||
self.assertEqual(json.loads(output.getvalue()), result)
|
||||
self.assertNotIn("\x1b", output.getvalue())
|
||||
terminal = io.StringIO()
|
||||
with patch(
|
||||
"netscaler_triage.cli.Console",
|
||||
return_value=Console(
|
||||
file=terminal,
|
||||
force_terminal=True,
|
||||
color_system=None,
|
||||
),
|
||||
):
|
||||
display_result(result)
|
||||
self.assertIn("NetScaler triage results", terminal.getvalue())
|
||||
|
||||
def test_missing_file_is_friendly_and_has_no_stdout(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output, error = io.StringIO(), io.StringIO()
|
||||
with contextlib.redirect_stdout(output), contextlib.redirect_stderr(error):
|
||||
code = main(["decode", str(Path(tmp) / "missing.txt")])
|
||||
self.assertEqual(code, 2)
|
||||
self.assertEqual(output.getvalue(), "")
|
||||
self.assertIn("Check that the input file or directory exists", error.getvalue())
|
||||
|
||||
def test_scan_does_not_write_an_assessment(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp).resolve()
|
||||
source = root / "logs"
|
||||
source.mkdir()
|
||||
(source / "sample.log").write_text("plain text\n", encoding="utf-8")
|
||||
scan([("sample", source)], root / "output", [], defaults=False)
|
||||
self.assertTrue((root / "output/summary.json").is_file())
|
||||
self.assertFalse((root / "output/BERICHT.txt").exists())
|
||||
|
||||
def test_scan_requires_only_a_folder_and_never_overwrites(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp).resolve()
|
||||
logs = root / "appliance"
|
||||
logs.mkdir()
|
||||
original = "ordinary text\n"
|
||||
(logs / "sample.log").write_text(original, encoding="utf-8")
|
||||
for _ in range(2):
|
||||
output, error = io.StringIO(), io.StringIO()
|
||||
with contextlib.redirect_stdout(output), contextlib.redirect_stderr(error):
|
||||
self.assertEqual(main(["scan", str(logs)]), 0)
|
||||
self.assertEqual(json.loads(output.getvalue())["files"], 1)
|
||||
self.assertIn("Results saved to:", error.getvalue())
|
||||
destinations = list(root.glob("triage-results-*"))
|
||||
self.assertEqual(len(destinations), 2)
|
||||
self.assertEqual((logs / "sample.log").read_text(), original)
|
||||
for destination in destinations:
|
||||
config = json.loads((destination / "configuration.json").read_text())
|
||||
self.assertIsNone(config["year"])
|
||||
self.assertEqual(config["timezones"], {})
|
||||
|
||||
def test_named_inputs_remain_supported(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp).resolve()
|
||||
logs = root / "logs"
|
||||
logs.mkdir()
|
||||
with (
|
||||
contextlib.redirect_stdout(io.StringIO()),
|
||||
contextlib.redirect_stderr(io.StringIO()),
|
||||
):
|
||||
self.assertEqual(
|
||||
main(["scan", "--input", f"ADC={logs}", "--output", str(root / "out")]), 0
|
||||
)
|
||||
config = json.loads((root / "out/configuration.json").read_text())
|
||||
self.assertEqual(config["appliances"], ["ADC"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user