mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-13 19:05:17 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Vulnerability SLA Breach Alerting System.
|
||||
|
||||
Tracks vulnerability remediation timelines, detects SLA breaches,
|
||||
and dispatches notifications through multiple channels.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import smtplib
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
DB_PATH = os.environ.get("SLA_DB_PATH", "vulnerability_sla.db")
|
||||
|
||||
SLA_TIERS = {
|
||||
"critical": {"cvss_min": 9.0, "cvss_max": 10.0, "days": 2},
|
||||
"high": {"cvss_min": 7.0, "cvss_max": 8.9, "days": 15},
|
||||
"medium": {"cvss_min": 4.0, "cvss_max": 6.9, "days": 60},
|
||||
"low": {"cvss_min": 0.1, "cvss_max": 3.9, "days": 90},
|
||||
}
|
||||
|
||||
|
||||
def init_db(db_path=DB_PATH):
|
||||
"""Initialize SQLite database for SLA tracking."""
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS vulnerability_sla (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
cve_id TEXT NOT NULL,
|
||||
finding_id TEXT NOT NULL,
|
||||
asset_hostname TEXT,
|
||||
severity TEXT NOT NULL,
|
||||
cvss_score REAL,
|
||||
discovered_at TEXT NOT NULL,
|
||||
sla_deadline TEXT NOT NULL,
|
||||
remediated_at TEXT,
|
||||
status TEXT DEFAULT 'open',
|
||||
owner_email TEXT,
|
||||
escalation_level INTEGER DEFAULT 0,
|
||||
last_alert_sent TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
|
||||
def get_severity_tier(cvss_score):
|
||||
"""Map CVSS score to severity tier."""
|
||||
for tier_name, tier in SLA_TIERS.items():
|
||||
if tier["cvss_min"] <= cvss_score <= tier["cvss_max"]:
|
||||
return tier_name, tier["days"]
|
||||
return "low", 90
|
||||
|
||||
|
||||
def import_findings(conn, csv_path):
|
||||
"""Import vulnerability findings from CSV and assign SLA deadlines."""
|
||||
imported = 0
|
||||
with open(csv_path, "r", encoding="utf-8") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
cve_id = row.get("cve_id", "").strip()
|
||||
cvss = float(row.get("cvss_score", 0))
|
||||
if not cve_id or cvss == 0:
|
||||
continue
|
||||
discovered = row.get("discovered_at", datetime.now(timezone.utc).isoformat())
|
||||
discovered_dt = datetime.fromisoformat(discovered.replace("Z", "+00:00"))
|
||||
severity, sla_days = get_severity_tier(cvss)
|
||||
deadline = discovered_dt + timedelta(days=sla_days)
|
||||
|
||||
conn.execute(
|
||||
"""INSERT INTO vulnerability_sla
|
||||
(cve_id, finding_id, asset_hostname, severity, cvss_score,
|
||||
discovered_at, sla_deadline, owner_email, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'open')""",
|
||||
(
|
||||
cve_id,
|
||||
row.get("finding_id", f"{cve_id}_{row.get('host', 'unknown')}"),
|
||||
row.get("host", "unknown"),
|
||||
severity,
|
||||
cvss,
|
||||
discovered_dt.isoformat(),
|
||||
deadline.isoformat(),
|
||||
row.get("owner_email", ""),
|
||||
),
|
||||
)
|
||||
imported += 1
|
||||
conn.commit()
|
||||
print(f"[+] Imported {imported} findings with SLA deadlines")
|
||||
return imported
|
||||
|
||||
|
||||
def check_sla_breaches(conn):
|
||||
"""Check all open findings for SLA status and return categorized results."""
|
||||
now = datetime.now(timezone.utc)
|
||||
cursor = conn.execute(
|
||||
"SELECT * FROM vulnerability_sla WHERE status = 'open'"
|
||||
)
|
||||
columns = [d[0] for d in cursor.description]
|
||||
breached = []
|
||||
approaching = []
|
||||
within_sla = []
|
||||
|
||||
for row in cursor.fetchall():
|
||||
record = dict(zip(columns, row))
|
||||
deadline = datetime.fromisoformat(record["sla_deadline"])
|
||||
discovered = datetime.fromisoformat(record["discovered_at"])
|
||||
if deadline.tzinfo is None:
|
||||
deadline = deadline.replace(tzinfo=timezone.utc)
|
||||
if discovered.tzinfo is None:
|
||||
discovered = discovered.replace(tzinfo=timezone.utc)
|
||||
|
||||
if now > deadline:
|
||||
overdue_days = (now - deadline).days
|
||||
record["sla_status"] = f"breached_{overdue_days}d_overdue"
|
||||
record["overdue_days"] = overdue_days
|
||||
breached.append(record)
|
||||
else:
|
||||
total_window = (deadline - discovered).total_seconds()
|
||||
elapsed = (now - discovered).total_seconds()
|
||||
pct = (elapsed / total_window * 100) if total_window > 0 else 0
|
||||
if pct >= 80:
|
||||
record["sla_status"] = "approaching_breach"
|
||||
record["pct_elapsed"] = round(pct, 1)
|
||||
approaching.append(record)
|
||||
else:
|
||||
record["sla_status"] = "within_sla"
|
||||
record["pct_elapsed"] = round(pct, 1)
|
||||
within_sla.append(record)
|
||||
|
||||
return {"breached": breached, "approaching": approaching, "within_sla": within_sla}
|
||||
|
||||
|
||||
def send_slack_notification(webhook_url, findings, alert_type):
|
||||
"""Send SLA alert to Slack channel."""
|
||||
if not webhook_url or not findings:
|
||||
return
|
||||
color_map = {"breached": "#FF0000", "approaching": "#FFA500", "within_sla": "#36A64F"}
|
||||
for finding in findings[:10]:
|
||||
payload = {
|
||||
"attachments": [
|
||||
{
|
||||
"color": color_map.get(alert_type, "#808080"),
|
||||
"title": f"SLA {alert_type.upper()}: {finding['cve_id']}",
|
||||
"fields": [
|
||||
{"title": "Severity", "value": finding["severity"], "short": True},
|
||||
{"title": "CVSS", "value": str(finding["cvss_score"]), "short": True},
|
||||
{"title": "Asset", "value": finding["asset_hostname"], "short": True},
|
||||
{"title": "Deadline", "value": finding["sla_deadline"][:16], "short": True},
|
||||
{"title": "Owner", "value": finding.get("owner_email", "Unassigned"), "short": True},
|
||||
{"title": "Status", "value": finding["sla_status"], "short": True},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
try:
|
||||
requests.post(webhook_url, json=payload, timeout=10)
|
||||
except requests.RequestException as e:
|
||||
print(f"[-] Slack notification failed: {e}")
|
||||
|
||||
|
||||
def send_email_notification(smtp_config, findings, alert_type):
|
||||
"""Send SLA alert via email."""
|
||||
if not smtp_config or not findings:
|
||||
return
|
||||
recipients = set()
|
||||
for f in findings:
|
||||
if f.get("owner_email"):
|
||||
recipients.add(f["owner_email"])
|
||||
if not recipients:
|
||||
return
|
||||
|
||||
body_lines = [f"Vulnerability SLA {alert_type.upper()} Report", "=" * 50, ""]
|
||||
for f in findings:
|
||||
body_lines.extend([
|
||||
f"CVE: {f['cve_id']}",
|
||||
f"Severity: {f['severity']} (CVSS {f['cvss_score']})",
|
||||
f"Asset: {f['asset_hostname']}",
|
||||
f"Deadline: {f['sla_deadline'][:16]}",
|
||||
f"Status: {f['sla_status']}",
|
||||
"-" * 40,
|
||||
])
|
||||
|
||||
msg = MIMEMultipart()
|
||||
msg["Subject"] = f"[VULN SLA {alert_type.upper()}] {len(findings)} findings require attention"
|
||||
msg["From"] = smtp_config.get("from_address", "vuln-alerts@company.com")
|
||||
msg["To"] = ", ".join(recipients)
|
||||
msg.attach(MIMEText("\n".join(body_lines), "plain"))
|
||||
|
||||
try:
|
||||
with smtplib.SMTP(smtp_config["host"], smtp_config.get("port", 587)) as server:
|
||||
server.starttls()
|
||||
if smtp_config.get("username"):
|
||||
server.login(smtp_config["username"], smtp_config["password"])
|
||||
server.send_message(msg)
|
||||
print(f"[+] Email sent to {len(recipients)} recipients")
|
||||
except Exception as e:
|
||||
print(f"[-] Email notification failed: {e}")
|
||||
|
||||
|
||||
def generate_compliance_report(conn, output_path, period_days=30):
|
||||
"""Generate SLA compliance report."""
|
||||
cutoff = (datetime.now(timezone.utc) - timedelta(days=period_days)).isoformat()
|
||||
cursor = conn.execute(
|
||||
"""SELECT severity,
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN remediated_at IS NOT NULL
|
||||
AND remediated_at <= sla_deadline THEN 1 ELSE 0 END) as within_sla,
|
||||
SUM(CASE WHEN remediated_at IS NOT NULL
|
||||
AND remediated_at > sla_deadline THEN 1 ELSE 0 END) as breached_remediated,
|
||||
SUM(CASE WHEN status = 'open'
|
||||
AND datetime('now') > sla_deadline THEN 1 ELSE 0 END) as currently_overdue
|
||||
FROM vulnerability_sla
|
||||
WHERE discovered_at >= ?
|
||||
GROUP BY severity
|
||||
ORDER BY CASE severity
|
||||
WHEN 'critical' THEN 1
|
||||
WHEN 'high' THEN 2
|
||||
WHEN 'medium' THEN 3
|
||||
WHEN 'low' THEN 4 END""",
|
||||
(cutoff,),
|
||||
)
|
||||
|
||||
report = {
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"period_days": period_days,
|
||||
"tiers": [],
|
||||
}
|
||||
total_findings = 0
|
||||
total_compliant = 0
|
||||
for row in cursor.fetchall():
|
||||
severity, total, within_sla, breached_remediated, currently_overdue = row
|
||||
compliance_rate = (within_sla / total * 100) if total > 0 else 0
|
||||
report["tiers"].append({
|
||||
"severity": severity,
|
||||
"total": total,
|
||||
"within_sla": within_sla,
|
||||
"breached_remediated": breached_remediated,
|
||||
"currently_overdue": currently_overdue,
|
||||
"compliance_rate": round(compliance_rate, 1),
|
||||
})
|
||||
total_findings += total
|
||||
total_compliant += within_sla
|
||||
|
||||
report["overall_compliance"] = round(
|
||||
(total_compliant / total_findings * 100) if total_findings > 0 else 0, 1
|
||||
)
|
||||
report["total_findings"] = total_findings
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[+] Compliance report written to {output_path}")
|
||||
print(f" Overall SLA Compliance: {report['overall_compliance']}%")
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Vulnerability SLA Breach Alerting System")
|
||||
parser.add_argument("--import-findings", help="Import findings from CSV")
|
||||
parser.add_argument("--check-sla", action="store_true", help="Check for SLA breaches")
|
||||
parser.add_argument("--report", action="store_true", help="Generate compliance report")
|
||||
parser.add_argument("--output", default="sla_compliance_report.json", help="Report output path")
|
||||
parser.add_argument("--period", type=int, default=30, help="Report period in days")
|
||||
parser.add_argument("--slack-webhook", help="Slack webhook URL for notifications")
|
||||
parser.add_argument("--db", default=DB_PATH, help="Database path")
|
||||
args = parser.parse_args()
|
||||
|
||||
conn = init_db(args.db)
|
||||
|
||||
if args.import_findings:
|
||||
import_findings(conn, args.import_findings)
|
||||
|
||||
if args.check_sla:
|
||||
results = check_sla_breaches(conn)
|
||||
print(f"\n[*] SLA Check Results:")
|
||||
print(f" Breached: {len(results['breached'])}")
|
||||
print(f" Approaching: {len(results['approaching'])}")
|
||||
print(f" Within SLA: {len(results['within_sla'])}")
|
||||
|
||||
if args.slack_webhook:
|
||||
if results["breached"]:
|
||||
send_slack_notification(args.slack_webhook, results["breached"], "breached")
|
||||
if results["approaching"]:
|
||||
send_slack_notification(args.slack_webhook, results["approaching"], "approaching")
|
||||
|
||||
if args.report:
|
||||
generate_compliance_report(conn, args.output, args.period)
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user