admin / Synapse-Sonar
publicAttack Surface Simulation
Synapse-Sonar / synapse-sonar / public / agents / netscan-connector / sonar-netscan-connector.py
2718 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | #!/usr/bin/env python3 """ NetscanXi -> Synapse Sonar vulnerability connector. Reads a JSON file of findings (Synapse Sonar's documented format) and POSTs them to /api/ingest/vulnerabilities. Use it to test the feed, run it on a schedule (cron) against a NetscanXi export, or adapt the `load_findings` function to call NetscanXi v13 directly once its API is available. Pure Python 3 standard library. Usage: SONAR_URL=https://sonar.corp.internal:3000 \ SONAR_INGEST_KEY=nsx_sensor_xxxx \ python3 sonar-netscan-connector.py findings.json findings.json: { "findings": [ { "ip": "10.0.3.30", "cveId": "CVE-2024-1234", "cvssScore": 9.8, "title": "..." } ] } """ import json import os import ssl import sys import urllib.error import urllib.request SONAR_URL = os.environ.get("SONAR_URL", "").rstrip("/") INGEST_KEY = os.environ.get("SONAR_INGEST_KEY", "") VERIFY_TLS = os.environ.get("SONAR_VERIFY_TLS", "true").lower() != "false" def load_findings(path: str): """Load findings from a JSON file. Adapt this to pull from NetscanXi v13.""" with open(path, "r", encoding="utf-8") as fh: data = json.load(fh) findings = data.get("findings", data) if isinstance(data, dict) else data if not isinstance(findings, list): raise ValueError("Expected a list of findings or {\"findings\": [...]}") return findings def push(findings): body = json.dumps({"findings": findings[:1000]}).encode() req = urllib.request.Request( f"{SONAR_URL}/api/ingest/vulnerabilities", data=body, method="POST", headers={ "Content-Type": "application/json", "Authorization": f"Bearer {INGEST_KEY}", }, ) ctx = None if SONAR_URL.startswith("https") and not VERIFY_TLS: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE with urllib.request.urlopen(req, timeout=20, context=ctx) as resp: print(f"[netscan-connector] HTTP {resp.status}: {resp.read().decode()}") def main(): if len(sys.argv) < 2: print("usage: sonar-netscan-connector.py <findings.json>", file=sys.stderr) sys.exit(2) if not SONAR_URL or not INGEST_KEY: print("SONAR_URL and SONAR_INGEST_KEY must be set", file=sys.stderr) sys.exit(1) findings = load_findings(sys.argv[1]) if not findings: print("[netscan-connector] no findings to send") return try: push(findings) except urllib.error.HTTPError as e: print(f"[netscan-connector] error HTTP {e.code}: {e.read().decode(errors='ignore')}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() |