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

177 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""Software-Defined Perimeter (SDP) deployment audit agent."""
import json
import sys
import argparse
import socket
import ssl
from datetime import datetime
try:
import requests
except ImportError:
print("Install: pip install requests")
sys.exit(1)
def check_spa_port(host, port, timeout=5):
"""Check if a port responds to standard TCP connect (should be dark in SDP)."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return {"host": host, "port": port, "open": result == 0}
except socket.error as e:
return {"host": host, "port": port, "open": False, "error": str(e)}
def audit_dark_ports(host, ports):
"""Verify SDP ports are invisible to unauthorized scanners."""
results = []
for port in ports:
scan = check_spa_port(host, port)
if scan.get("open"):
scan["finding"] = "Port visible without SPA — SDP not enforced"
scan["severity"] = "HIGH"
else:
scan["finding"] = "Port dark — SDP enforced"
scan["severity"] = "INFO"
results.append(scan)
return results
def check_tls_mutual_auth(host, port, client_cert=None, client_key=None):
"""Verify mutual TLS authentication on SDP controller."""
result = {"host": host, "port": port}
try:
ctx = ssl.create_default_context()
if client_cert and client_key:
ctx.load_cert_chain(client_cert, client_key)
with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
s.settimeout(10)
s.connect((host, port))
cert = s.getpeercert()
result["tls_version"] = s.version()
result["cipher"] = s.cipher()[0]
result["server_cn"] = dict(x[0] for x in cert.get("subject", ()))
result["mtls_enforced"] = client_cert is not None
except ssl.SSLError as e:
result["error"] = str(e)
if "CERTIFICATE_REQUIRED" in str(e):
result["mtls_enforced"] = True
result["finding"] = "Server requires client certificate — mTLS enforced"
except Exception as e:
result["error"] = str(e)
return result
class SDPControllerClient:
"""Client for SDP controller REST API (e.g., Appgate SDP)."""
def __init__(self, controller_url, username, password):
self.base_url = controller_url.rstrip("/")
self.session = requests.Session()
self._authenticate(username, password)
def _authenticate(self, username, password):
resp = self.session.post(f"{self.base_url}/admin/login", json={
"providerName": "local",
"username": username,
"password": password,
}, timeout=15, verify=True)
resp.raise_for_status()
token = resp.json().get("token", "")
self.session.headers.update({"Authorization": f"Bearer {token}"})
def list_sites(self):
resp = self.session.get(f"{self.base_url}/admin/sites", timeout=15)
resp.raise_for_status()
return resp.json().get("data", [])
def list_policies(self):
resp = self.session.get(f"{self.base_url}/admin/policies", timeout=15)
resp.raise_for_status()
return resp.json().get("data", [])
def list_entitlements(self):
resp = self.session.get(f"{self.base_url}/admin/entitlements", timeout=15)
resp.raise_for_status()
return resp.json().get("data", [])
def list_appliances(self):
resp = self.session.get(f"{self.base_url}/admin/appliances", timeout=15)
resp.raise_for_status()
return resp.json().get("data", [])
def run_audit(args):
"""Execute SDP deployment audit."""
print(f"\n{'='*60}")
print(f" SOFTWARE-DEFINED PERIMETER AUDIT")
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
print(f"{'='*60}\n")
report = {}
if args.scan_host:
ports = [int(p) for p in args.ports.split(",")] if args.ports else [22, 443, 8443, 3389]
dark_results = audit_dark_ports(args.scan_host, ports)
report["dark_port_scan"] = dark_results
print(f"--- DARK PORT SCAN ({args.scan_host}) ---")
for r in dark_results:
status = "OPEN" if r["open"] else "DARK"
print(f" Port {r['port']}: {status}{r['finding']}")
if args.mtls_host:
mtls = check_tls_mutual_auth(args.mtls_host, args.mtls_port or 443,
args.client_cert, args.client_key)
report["mtls_check"] = mtls
print(f"\n--- MUTUAL TLS CHECK ---")
print(f" Host: {mtls['host']}:{mtls['port']}")
print(f" mTLS Enforced: {mtls.get('mtls_enforced', 'unknown')}")
if mtls.get("tls_version"):
print(f" TLS Version: {mtls['tls_version']}")
if args.controller_url and args.username and args.password:
client = SDPControllerClient(args.controller_url, args.username, args.password)
appliances = client.list_appliances()
report["appliances"] = len(appliances)
print(f"\n--- SDP APPLIANCES ({len(appliances)}) ---")
for a in appliances[:10]:
print(f" {a.get('name', '')}: {a.get('function', '')} ({a.get('state', '')})")
entitlements = client.list_entitlements()
report["entitlements"] = len(entitlements)
print(f"\n--- ENTITLEMENTS ({len(entitlements)}) ---")
for e in entitlements[:10]:
print(f" {e.get('name', '')}: {e.get('site', '')} -> {e.get('actions', [])}")
return report
def main():
parser = argparse.ArgumentParser(description="SDP Deployment Audit")
parser.add_argument("--scan-host", help="Host to scan for dark ports")
parser.add_argument("--ports", help="Comma-separated ports to scan (default: 22,443,8443,3389)")
parser.add_argument("--mtls-host", help="Host to check mutual TLS")
parser.add_argument("--mtls-port", type=int, default=443, help="mTLS port")
parser.add_argument("--client-cert", help="Client certificate for mTLS test")
parser.add_argument("--client-key", help="Client key for mTLS test")
parser.add_argument("--controller-url", help="SDP controller URL (Appgate)")
parser.add_argument("--username", help="Controller admin username")
parser.add_argument("--password", help="Controller admin password")
parser.add_argument("--output", help="Save report to JSON file")
args = parser.parse_args()
report = run_audit(args)
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[+] Report saved to {args.output}")
if __name__ == "__main__":
main()