Files
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

182 lines
6.7 KiB
Python

#!/usr/bin/env python3
"""Vulnerability dashboard builder using DefectDojo API.
Queries DefectDojo REST API v2 for findings, products, and engagements
to build vulnerability management dashboards and metrics.
"""
import sys
import json
import datetime
import os
import collections
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
class DefectDojoClient:
"""Client for DefectDojo REST API v2."""
def __init__(self, url=None, api_key=None):
self.url = (url or os.environ.get("DEFECTDOJO_URL", "http://localhost:8080")).rstrip("/")
self.api_key = api_key or os.environ.get("DEFECTDOJO_API_KEY", "")
self.headers = {
"Authorization": "Token " + self.api_key,
"Content-Type": "application/json",
}
def _get(self, endpoint, params=None):
if not HAS_REQUESTS or not self.api_key:
return {"error": "requests not available or no API key"}
try:
resp = requests.get(
self.url + "/api/v2/" + endpoint,
headers=self.headers, params=params, timeout=15
)
if resp.status_code == 200:
return resp.json()
return {"error": "HTTP {}".format(resp.status_code)}
except Exception as e:
return {"error": str(e)}
def get_findings(self, severity=None, active=True, limit=100):
params = {"active": active, "limit": limit}
if severity:
params["severity"] = severity
return self._get("findings/", params)
def get_products(self, limit=100):
return self._get("products/", {"limit": limit})
def get_engagements(self, product_id=None, limit=100):
params = {"limit": limit}
if product_id:
params["product"] = product_id
return self._get("engagements/", params)
def get_finding_count_by_severity(self):
result = {}
for sev in ["Critical", "High", "Medium", "Low", "Info"]:
data = self._get("findings/", {"severity": sev, "active": True, "limit": 1})
if isinstance(data, dict) and "count" in data:
result[sev] = data["count"]
return result
def import_scan(self, product_id, engagement_id, scan_type, file_path):
if not HAS_REQUESTS or not self.api_key:
return {"error": "requests not available or no API key"}
try:
with open(file_path, "rb") as f:
resp = requests.post(
self.url + "/api/v2/import-scan/",
headers={"Authorization": "Token " + self.api_key},
data={
"product": product_id,
"engagement": engagement_id,
"scan_type": scan_type,
"active": True,
"verified": False,
},
files={"file": f},
timeout=60,
)
if resp.status_code in (200, 201):
return resp.json()
return {"error": "HTTP {}".format(resp.status_code)}
except Exception as e:
return {"error": str(e)}
def build_dashboard_data(findings):
"""Build dashboard metrics from findings list."""
if not isinstance(findings, dict) or "results" not in findings:
return {"error": "Invalid findings data"}
results = findings["results"]
severity_counts = collections.Counter()
product_counts = collections.Counter()
age_sum = 0
overdue_count = 0
now = datetime.datetime.now(datetime.timezone.utc)
for f in results:
severity_counts[f.get("severity", "Unknown")] += 1
product_counts[f.get("test", {}).get("engagement", {}).get("product", {}).get("name", "Unknown")] += 1
if f.get("date"):
try:
created = datetime.datetime.fromisoformat(f["date"])
if created.tzinfo is None:
created = created.replace(tzinfo=datetime.timezone.utc)
age = (now - created).days
age_sum += age
sla = {"Critical": 7, "High": 30, "Medium": 90, "Low": 180}.get(f.get("severity", ""), 999)
if age > sla:
overdue_count += 1
except ValueError:
pass
total = len(results)
return {
"total_active_findings": total,
"by_severity": dict(severity_counts),
"by_product": dict(product_counts.most_common(10)),
"avg_age_days": round(age_sum / max(total, 1), 1),
"overdue_count": overdue_count,
"sla_compliance_pct": round((total - overdue_count) / max(total, 1) * 100, 1),
}
SUPPORTED_SCAN_TYPES = [
"Nessus Scan", "Qualys Scan", "Burp REST API",
"ZAP Scan", "Trivy Scan", "Snyk Scan",
"Semgrep JSON Report", "SARIF", "Generic Findings Import",
"Anchore Grype", "Nuclei Scan", "Checkov Scan",
]
if __name__ == "__main__":
print("=" * 60)
print("Vulnerability Dashboard with DefectDojo")
print("REST API v2 queries, severity metrics, SLA tracking")
print("=" * 60)
print(" requests available: {}".format(HAS_REQUESTS))
client = DefectDojoClient()
print("\n--- Supported Scan Types ---")
for st in SUPPORTED_SCAN_TYPES:
print(" - {}".format(st))
print("\n--- API Endpoints ---")
endpoints = [
("GET", "/api/v2/findings/", "List findings"),
("GET", "/api/v2/products/", "List products"),
("GET", "/api/v2/engagements/", "List engagements"),
("POST", "/api/v2/import-scan/", "Import scan results"),
("POST", "/api/v2/reimport-scan/", "Re-import scan results"),
]
for method, path, desc in endpoints:
print(" {} {:30s} {}".format(method, path, desc))
demo_findings = {
"count": 5,
"results": [
{"severity": "Critical", "title": "SQL Injection", "date": "2025-01-10", "test": {"engagement": {"product": {"name": "WebApp"}}}},
{"severity": "High", "title": "XSS", "date": "2025-01-15", "test": {"engagement": {"product": {"name": "WebApp"}}}},
{"severity": "Medium", "title": "Missing Headers", "date": "2024-12-01", "test": {"engagement": {"product": {"name": "API"}}}},
{"severity": "Low", "title": "Cookie flag", "date": "2025-02-01", "test": {"engagement": {"product": {"name": "API"}}}},
{"severity": "Critical", "title": "RCE", "date": "2025-02-20", "test": {"engagement": {"product": {"name": "WebApp"}}}},
],
}
dashboard = build_dashboard_data(demo_findings)
print("\n--- Dashboard ---")
for k, v in dashboard.items():
print(" {}: {}".format(k, v))
print("\n" + json.dumps({"findings_analyzed": demo_findings["count"]}, indent=2))