#!/usr/bin/env python3 """ Vulnerability Remediation SLA Tracking Engine Calculates SLA deadlines, monitors compliance, generates breach notifications, and produces executive reporting dashboards. Requirements: pip install pandas jinja2 Usage: python process.py calculate --vulns vulns.csv --assets assets.csv --output sla_assignments.csv python process.py monitor --sla-csv sla_assignments.csv --report sla_report.html """ import argparse import json import sys from datetime import datetime, timedelta import pandas as pd class SLACalculator: """Calculate remediation SLA deadlines based on severity and asset tier.""" DEFAULT_SLA_MATRIX = { ("Critical", "Tier1"): 2, ("Critical", "Tier2"): 3, ("Critical", "Tier3"): 7, ("High", "Tier1"): 7, ("High", "Tier2"): 14, ("High", "Tier3"): 30, ("Medium", "Tier1"): 30, ("Medium", "Tier2"): 45, ("Medium", "Tier3"): 60, ("Low", "Tier1"): 90, ("Low", "Tier2"): 90, ("Low", "Tier3"): 90, } SLA_ACCELERATORS = { "in_cisa_kev": 0.5, "exploit_available": 0.5, "internet_facing": 0.5, "epss_high": 0.5, } def __init__(self, sla_matrix: dict = None): self.sla_matrix = sla_matrix or self.DEFAULT_SLA_MATRIX def get_severity_label(self, cvss_score: float) -> str: """Map CVSS score to severity label.""" if cvss_score >= 9.0: return "Critical" elif cvss_score >= 7.0: return "High" elif cvss_score >= 4.0: return "Medium" elif cvss_score > 0: return "Low" return "Info" def calculate_sla(self, severity: str, tier: str, accelerators: dict = None) -> int: """Calculate SLA days based on severity, tier, and accelerators.""" base_sla = self.sla_matrix.get((severity, tier), 90) if accelerators: for accel, factor in self.SLA_ACCELERATORS.items(): if accelerators.get(accel, False): base_sla = int(base_sla * factor) return max(base_sla, 1) def assign_slas(self, vulns_df: pd.DataFrame, assets_df: pd.DataFrame) -> pd.DataFrame: """Assign SLA deadlines to all vulnerabilities.""" merged = vulns_df.merge( assets_df[["hostname", "tier", "internet_facing"]], on="hostname", how="left" ) results = [] for _, row in merged.iterrows(): severity = row.get("severity", self.get_severity_label(float(row.get("cvss_score", 0)))) tier = row.get("tier", "Tier3") accelerators = { "in_cisa_kev": row.get("in_cisa_kev", False), "exploit_available": row.get("exploit_available", False), "internet_facing": row.get("internet_facing", False), "epss_high": float(row.get("epss_score", 0)) > 0.5, } sla_days = self.calculate_sla(severity, tier, accelerators) discovery_date = pd.to_datetime(row.get("discovery_date", datetime.now())) deadline = discovery_date + timedelta(days=sla_days) results.append({ **row.to_dict(), "severity": severity, "sla_days": sla_days, "discovery_date": discovery_date.isoformat(), "sla_deadline": deadline.isoformat(), "days_remaining": (deadline - datetime.now()).days, "sla_status": self._get_sla_status(deadline, row.get("remediated_date")), }) return pd.DataFrame(results) def _get_sla_status(self, deadline: datetime, remediated_date=None) -> str: """Determine SLA status.""" now = datetime.now() if remediated_date and pd.notna(remediated_date): rem_date = pd.to_datetime(remediated_date) if isinstance(deadline, str): deadline = pd.to_datetime(deadline) return "compliant" if rem_date <= deadline else "breached_remediated" if isinstance(deadline, str): deadline = pd.to_datetime(deadline) days_remaining = (deadline - now).days if days_remaining < 0: return "breached" elif days_remaining <= 3: return "critical" elif days_remaining <= 7: return "warning" return "on_track" class SLAMonitor: """Monitor SLA compliance and generate reports.""" def __init__(self, sla_df: pd.DataFrame): self.sla_df = sla_df def get_compliance_summary(self) -> dict: """Calculate overall SLA compliance metrics.""" total = len(self.sla_df) status_counts = self.sla_df["sla_status"].value_counts().to_dict() compliant = status_counts.get("compliant", 0) + status_counts.get("on_track", 0) breached = status_counts.get("breached", 0) + status_counts.get("breached_remediated", 0) warning = status_counts.get("warning", 0) + status_counts.get("critical", 0) remediated = self.sla_df[self.sla_df["sla_status"].isin(["compliant", "breached_remediated"])] if not remediated.empty: mttr_data = remediated.copy() mttr_data["disc"] = pd.to_datetime(mttr_data["discovery_date"]) mttr_data["rem"] = pd.to_datetime(mttr_data.get("remediated_date", datetime.now())) avg_mttr = (mttr_data["rem"] - mttr_data["disc"]).dt.days.mean() else: avg_mttr = 0 return { "total_vulns": total, "compliant": compliant, "compliance_rate": f"{compliant / max(total, 1) * 100:.1f}%", "breached": breached, "breach_rate": f"{breached / max(total, 1) * 100:.1f}%", "at_risk": warning, "avg_mttr_days": round(avg_mttr, 1), "by_severity": self.sla_df.groupby("severity")["sla_status"].value_counts().to_dict(), } def get_breach_list(self) -> pd.DataFrame: """Get list of SLA-breached vulnerabilities.""" return self.sla_df[self.sla_df["sla_status"] == "breached"].sort_values("days_remaining") def generate_report(self, output_path: str): """Generate SLA compliance HTML report.""" summary = self.get_compliance_summary() breaches = self.get_breach_list().head(30) by_sev = self.sla_df.groupby("severity").agg( total=("sla_status", "count"), compliant=("sla_status", lambda x: (x.isin(["compliant", "on_track"])).sum()), breached=("sla_status", lambda x: (x.isin(["breached", "breached_remediated"])).sum()), ).reset_index() by_sev["rate"] = (by_sev["compliant"] / by_sev["total"] * 100).round(1) html = f"""
Report Date: {datetime.now().strftime('%Y-%m-%d %H:%M')}
SLA Compliance
SLA Breaches
At Risk
Avg MTTR
| Severity | Total | Compliant | Breached | Rate |
|---|---|---|---|---|
| {r.severity} | {r.total} | {r.compliant} | {r.breached} | {r.rate}% |
| Host | CVE | Severity | SLA Days | Days Overdue |
|---|---|---|---|---|
| {r.hostname if hasattr(r,'hostname') else ''} | {r.cve if hasattr(r,'cve') else ''} | {r.severity} | {r.sla_days} | {abs(r.days_remaining)} |