Builds CIS and PCI compliance views from data Ragnar already collects.
| 30 | |
| 31 | |
| 32 | class ComplianceReporter: |
| 33 | """Builds CIS and PCI compliance views from data Ragnar already collects.""" |
| 34 | |
| 35 | def __init__(self, shared_data=None, db=None): |
| 36 | self.shared_data = shared_data |
| 37 | self.db = db or getattr(shared_data, "db", None) |
| 38 | self.mappings = self._load_mappings() |
| 39 | |
| 40 | def _load_mappings(self) -> Dict[str, Any]: |
| 41 | try: |
| 42 | with open(_MAPPINGS_FILE, "r", encoding="utf-8") as handle: |
| 43 | return json.load(handle) |
| 44 | except Exception as exc: |
| 45 | logger.error(f"Could not load compliance mappings: {exc}") |
| 46 | return {"cis": {"lynis_to_cis": {}, "prefix_to_cis": {}}, "pci": {"requirements": []}} |
| 47 | |
| 48 | # ------------------------------------------------------------------ |
| 49 | # Data collection |
| 50 | # ------------------------------------------------------------------ |
| 51 | |
| 52 | def _vuln_dir(self) -> Optional[str]: |
| 53 | return getattr(self.shared_data, "vulnerabilities_dir", None) |
| 54 | |
| 55 | def _collect_lynis(self, host: Optional[str] = None) -> List[Dict[str, Any]]: |
| 56 | records = [] |
| 57 | base = self._vuln_dir() |
| 58 | if not base or not os.path.isdir(base): |
| 59 | return records |
| 60 | |
| 61 | for root, _dirs, files in os.walk(base): |
| 62 | for filename in files: |
| 63 | match = _LYNIS_DAT_RE.match(filename) |
| 64 | if not match: |
| 65 | continue |
| 66 | rec_host = match.group("host") |
| 67 | if host and rec_host != host: |
| 68 | continue |
| 69 | path = os.path.join(root, filename) |
| 70 | try: |
| 71 | with open(path, "r", encoding="utf-8", errors="ignore") as handle: |
| 72 | parsed = parse_lynis_dat(handle.read()) or {} |
| 73 | except Exception as exc: |
| 74 | logger.warning(f"Failed to parse Lynis dat {filename}: {exc}") |
| 75 | continue |
| 76 | |
| 77 | metadata = parsed.get("metadata", {}) if isinstance(parsed, dict) else {} |
| 78 | scan_date = datetime.fromtimestamp(os.path.getmtime(path)).strftime("%Y-%m-%d %H:%M:%S") |
| 79 | records.append({ |
| 80 | "host": rec_host, |
| 81 | "scan_date": scan_date, |
| 82 | "hardening_index": metadata.get("hardening_index"), |
| 83 | "warnings": parsed.get("warnings", []), |
| 84 | "suggestions": parsed.get("suggestions", []), |
| 85 | "vulnerable_packages": parsed.get("vulnerable_packages", []), |
| 86 | }) |
| 87 | |
| 88 | records.sort(key=lambda r: r["scan_date"], reverse=True) |
| 89 | seen = set() |
no outgoing calls
no test coverage detected