"""Data models for the CIS NGINX benchmark auditor.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime from typing import List, Optional # Possible check outcomes PASS = "PASS" FAIL = "FAIL" MANUAL = "MANUAL" # check ran but result needs human judgment ERROR = "ERROR" # could not run the check (command failed, not found, etc.) @dataclass class CheckResult: """Result of running a single CIS check.""" check_id: str title: str profiles: List[str] # which CIS profiles this check belongs to status: str # PASS | FAIL | MANUAL | ERROR actual_value: str # what was found on the system expected_value: str # what the benchmark requires commands_run: List[str] # shell commands executed remediation: str # how to fix (from benchmark) notes: str = "" # extra context or error detail @dataclass class AuditReport: """Complete audit result for a target system.""" target: str # hostname or "localhost" profile: str # e.g. "Level 1 - Webserver" timestamp: datetime = field(default_factory=datetime.utcnow) results: List[CheckResult] = field(default_factory=list) # ── convenience aggregates ────────────────────────────────────────────── @property def passed(self) -> List[CheckResult]: return [r for r in self.results if r.status == PASS] @property def failed(self) -> List[CheckResult]: return [r for r in self.results if r.status == FAIL] @property def manual(self) -> List[CheckResult]: return [r for r in self.results if r.status == MANUAL] @property def errors(self) -> List[CheckResult]: return [r for r in self.results if r.status == ERROR] @property def total(self) -> int: return len(self.results) def summary(self) -> str: return ( f"Target: {self.target} | Profile: {self.profile} | " f"Total: {self.total} | PASS: {len(self.passed)} | " f"FAIL: {len(self.failed)} | MANUAL: {len(self.manual)} | " f"ERROR: {len(self.errors)}" )