mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
265 lines
9.5 KiB
Python
265 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Vulnerability SLA breach alerting agent.
|
|
|
|
Monitors vulnerability remediation timelines and generates alerts when
|
|
SLA breaches occur or are imminent. Supports webhook notifications
|
|
(Slack, Teams, PagerDuty), email alerts, and escalation workflows.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
requests = None
|
|
|
|
|
|
DEFAULT_SLA_DAYS = {"CRITICAL": 7, "HIGH": 30, "MEDIUM": 90, "LOW": 180}
|
|
|
|
|
|
def load_vulnerabilities(source_path):
|
|
"""Load vulnerability data from JSON file."""
|
|
with open(source_path, "r") as f:
|
|
data = json.load(f)
|
|
if isinstance(data, list):
|
|
return data
|
|
return data.get("vulnerabilities", data.get("findings", []))
|
|
|
|
|
|
def check_sla_breaches(vulns, sla_days=None, warn_days_before=7):
|
|
"""Check for SLA breaches and upcoming deadlines."""
|
|
if sla_days is None:
|
|
sla_days = DEFAULT_SLA_DAYS
|
|
|
|
now = datetime.now(timezone.utc)
|
|
breaches = []
|
|
warnings = []
|
|
|
|
for vuln in vulns:
|
|
status = (vuln.get("status") or vuln.get("state") or "open").lower()
|
|
if status not in ("open", "new", "active", "unresolved"):
|
|
continue
|
|
|
|
severity = (vuln.get("severity") or "MEDIUM").upper()
|
|
target_days = sla_days.get(severity, 90)
|
|
|
|
disc_str = (vuln.get("discovered_date") or vuln.get("first_found") or
|
|
vuln.get("discovered") or "")
|
|
try:
|
|
if "T" in disc_str:
|
|
discovered = datetime.fromisoformat(disc_str.replace("Z", "+00:00"))
|
|
elif disc_str:
|
|
discovered = datetime.strptime(disc_str[:10], "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
|
else:
|
|
continue
|
|
except (ValueError, TypeError):
|
|
continue
|
|
|
|
deadline = discovered + timedelta(days=target_days)
|
|
days_remaining = (deadline - now).days
|
|
|
|
vuln_id = vuln.get("id") or vuln.get("cve_id") or vuln.get("vulnerability_id") or "unknown"
|
|
asset = vuln.get("asset") or vuln.get("host") or vuln.get("ip") or "unknown"
|
|
title = vuln.get("title") or vuln.get("name") or "Unknown"
|
|
|
|
alert_entry = {
|
|
"id": vuln_id,
|
|
"severity": severity,
|
|
"asset": asset,
|
|
"title": title[:80],
|
|
"discovered": disc_str[:10],
|
|
"deadline": deadline.isoformat()[:10],
|
|
"days_remaining": days_remaining,
|
|
"sla_target_days": target_days,
|
|
}
|
|
|
|
if days_remaining < 0:
|
|
alert_entry["alert_type"] = "BREACH"
|
|
alert_entry["overdue_days"] = abs(days_remaining)
|
|
breaches.append(alert_entry)
|
|
elif days_remaining <= warn_days_before:
|
|
alert_entry["alert_type"] = "WARNING"
|
|
warnings.append(alert_entry)
|
|
|
|
breaches.sort(key=lambda x: -x.get("overdue_days", 0))
|
|
warnings.sort(key=lambda x: x.get("days_remaining", 999))
|
|
return breaches, warnings
|
|
|
|
|
|
def send_slack_alert(webhook_url, breaches, warnings):
|
|
"""Send SLA breach alert to Slack via webhook."""
|
|
if not requests:
|
|
print("[!] requests library required for Slack alerts", file=sys.stderr)
|
|
return False
|
|
|
|
blocks = [
|
|
{"type": "header", "text": {"type": "plain_text",
|
|
"text": f"Vulnerability SLA Alert - {len(breaches)} Breaches, {len(warnings)} Warnings"}},
|
|
]
|
|
|
|
if breaches:
|
|
breach_text = "*SLA BREACHES (Immediate Action Required):*\n"
|
|
for b in breaches[:10]:
|
|
breach_text += (f"- [{b['severity']}] `{b['id']}` on {b['asset']} - "
|
|
f"*{b['overdue_days']}d overdue*\n")
|
|
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": breach_text}})
|
|
|
|
if warnings:
|
|
warn_text = "*Approaching SLA Deadline:*\n"
|
|
for w in warnings[:10]:
|
|
warn_text += (f"- [{w['severity']}] `{w['id']}` on {w['asset']} - "
|
|
f"{w['days_remaining']}d remaining\n")
|
|
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": warn_text}})
|
|
|
|
payload = {"blocks": blocks}
|
|
resp = requests.post(webhook_url, json=payload, timeout=15)
|
|
if resp.status_code == 200:
|
|
print(f"[+] Slack alert sent successfully")
|
|
return True
|
|
else:
|
|
print(f"[!] Slack alert failed: {resp.status_code}", file=sys.stderr)
|
|
return False
|
|
|
|
|
|
def send_teams_alert(webhook_url, breaches, warnings):
|
|
"""Send SLA breach alert to Microsoft Teams via webhook."""
|
|
if not requests:
|
|
return False
|
|
|
|
facts = []
|
|
for b in breaches[:5]:
|
|
facts.append({"name": f"[BREACH] {b['id']}", "value": f"{b['severity']} - {b['overdue_days']}d overdue on {b['asset']}"})
|
|
for w in warnings[:5]:
|
|
facts.append({"name": f"[WARNING] {w['id']}", "value": f"{w['severity']} - {w['days_remaining']}d left on {w['asset']}"})
|
|
|
|
payload = {
|
|
"@type": "MessageCard",
|
|
"themeColor": "FF0000" if breaches else "FFA500",
|
|
"summary": f"SLA Alert: {len(breaches)} breaches, {len(warnings)} warnings",
|
|
"sections": [{
|
|
"activityTitle": "Vulnerability SLA Alert",
|
|
"facts": facts,
|
|
}],
|
|
}
|
|
resp = requests.post(webhook_url, json=payload, timeout=15)
|
|
return resp.status_code == 200
|
|
|
|
|
|
def send_pagerduty_alert(routing_key, breaches):
|
|
"""Send PagerDuty incident for critical SLA breaches."""
|
|
if not requests or not breaches:
|
|
return False
|
|
|
|
critical_breaches = [b for b in breaches if b["severity"] == "CRITICAL"]
|
|
if not critical_breaches:
|
|
return False
|
|
|
|
payload = {
|
|
"routing_key": routing_key,
|
|
"event_action": "trigger",
|
|
"payload": {
|
|
"summary": f"{len(critical_breaches)} CRITICAL vulnerability SLA breaches",
|
|
"severity": "critical",
|
|
"source": "vulnerability-sla-agent",
|
|
"custom_details": {
|
|
"breaches": critical_breaches[:5],
|
|
"total_critical_breaches": len(critical_breaches),
|
|
},
|
|
},
|
|
}
|
|
resp = requests.post(
|
|
"https://events.pagerduty.com/v2/enqueue",
|
|
json=payload, timeout=15,
|
|
)
|
|
if resp.status_code == 202:
|
|
print(f"[+] PagerDuty incident created")
|
|
return True
|
|
return False
|
|
|
|
|
|
def format_summary(breaches, warnings):
|
|
"""Print alert summary."""
|
|
print(f"\n{'='*60}")
|
|
print(f" Vulnerability SLA Breach Alert Report")
|
|
print(f"{'='*60}")
|
|
print(f" SLA Breaches : {len(breaches)}")
|
|
print(f" SLA Warnings : {len(warnings)}")
|
|
|
|
if breaches:
|
|
critical = sum(1 for b in breaches if b["severity"] == "CRITICAL")
|
|
high = sum(1 for b in breaches if b["severity"] == "HIGH")
|
|
print(f" Critical breaches: {critical}")
|
|
print(f" High breaches : {high}")
|
|
|
|
print(f"\n Breached Vulnerabilities:")
|
|
for b in breaches[:15]:
|
|
print(f" [{b['severity']:8s}] {b['id']:20s} | {b['asset']:20s} | "
|
|
f"{b['overdue_days']}d overdue (deadline: {b['deadline']})")
|
|
|
|
if warnings:
|
|
print(f"\n Approaching Deadline:")
|
|
for w in warnings[:10]:
|
|
print(f" [{w['severity']:8s}] {w['id']:20s} | {w['asset']:20s} | "
|
|
f"{w['days_remaining']}d remaining")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Vulnerability SLA breach alerting agent")
|
|
parser.add_argument("--source", required=True, help="Vulnerability data JSON file")
|
|
parser.add_argument("--sla-critical", type=int, default=7)
|
|
parser.add_argument("--sla-high", type=int, default=30)
|
|
parser.add_argument("--sla-medium", type=int, default=90)
|
|
parser.add_argument("--sla-low", type=int, default=180)
|
|
parser.add_argument("--warn-days", type=int, default=7, help="Warn N days before deadline")
|
|
parser.add_argument("--slack-webhook", help="Slack webhook URL for alerts")
|
|
parser.add_argument("--teams-webhook", help="Teams webhook URL for alerts")
|
|
parser.add_argument("--pagerduty-key", help="PagerDuty routing key for critical breaches")
|
|
parser.add_argument("--output", "-o", help="Output JSON report")
|
|
parser.add_argument("--verbose", "-v", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
sla_days = {
|
|
"CRITICAL": args.sla_critical, "HIGH": args.sla_high,
|
|
"MEDIUM": args.sla_medium, "LOW": args.sla_low,
|
|
}
|
|
|
|
vulns = load_vulnerabilities(args.source)
|
|
print(f"[*] Loaded {len(vulns)} vulnerabilities")
|
|
|
|
breaches, warnings = check_sla_breaches(vulns, sla_days, args.warn_days)
|
|
format_summary(breaches, warnings)
|
|
|
|
alerts_sent = []
|
|
if args.slack_webhook and (breaches or warnings):
|
|
if send_slack_alert(args.slack_webhook, breaches, warnings):
|
|
alerts_sent.append("slack")
|
|
if args.teams_webhook and (breaches or warnings):
|
|
if send_teams_alert(args.teams_webhook, breaches, warnings):
|
|
alerts_sent.append("teams")
|
|
if args.pagerduty_key and breaches:
|
|
if send_pagerduty_alert(args.pagerduty_key, breaches):
|
|
alerts_sent.append("pagerduty")
|
|
|
|
report = {
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"tool": "SLA Breach Alerting",
|
|
"sla_targets": sla_days,
|
|
"breaches": breaches,
|
|
"warnings": warnings,
|
|
"alerts_sent": alerts_sent,
|
|
}
|
|
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
elif args.verbose:
|
|
print(json.dumps(report, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|