Files
Anthropic-Cybersecurity-Skills/skills/deploying-software-defined-perimeter/scripts/process.py
T

284 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Software-Defined Perimeter Deployment Validator
Validates SDP deployment readiness, tests SPA mechanisms,
verifies gateway invisibility, and generates deployment reports.
"""
import json
import socket
import hashlib
import hmac
import struct
import time
import ssl
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional
def check_gateway_invisibility(host: str, port_range: tuple = (1, 1024), timeout: float = 0.5) -> dict:
"""Scan gateway ports to verify SDP invisibility (all ports should appear closed/filtered)."""
result = {
"host": host,
"scanned_range": f"{port_range[0]}-{port_range[1]}",
"open_ports": [],
"closed_ports": 0,
"filtered_ports": 0,
"invisible": True,
"timestamp": datetime.now().isoformat(),
}
for port in range(port_range[0], port_range[1] + 1):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
conn_result = sock.connect_ex((host, port))
if conn_result == 0:
result["open_ports"].append(port)
result["invisible"] = False
else:
result["filtered_ports"] += 1
sock.close()
except socket.timeout:
result["filtered_ports"] += 1
except OSError:
result["closed_ports"] += 1
return result
def generate_spa_packet(
source_ip: str,
destination_service: str,
shared_key: str,
timestamp: Optional[float] = None
) -> bytes:
"""Generate a Single Packet Authorization payload (demonstration)."""
if timestamp is None:
timestamp = time.time()
payload = json.dumps({
"version": 2,
"source_ip": source_ip,
"service": destination_service,
"timestamp": timestamp,
"nonce": hashlib.sha256(f"{time.time()}{source_ip}".encode()).hexdigest()[:16],
}).encode()
mac = hmac.new(shared_key.encode(), payload, hashlib.sha256).digest()
packet = struct.pack("!I", len(payload)) + payload + mac
return packet
def validate_spa_packet(packet: bytes, shared_key: str, max_age_seconds: int = 60) -> dict:
"""Validate a received SPA packet."""
result = {"valid": False, "errors": [], "payload": None}
try:
payload_len = struct.unpack("!I", packet[:4])[0]
payload = packet[4:4 + payload_len]
received_mac = packet[4 + payload_len:]
expected_mac = hmac.new(shared_key.encode(), payload, hashlib.sha256).digest()
if not hmac.compare_digest(received_mac, expected_mac):
result["errors"].append("HMAC verification failed")
return result
data = json.loads(payload.decode())
result["payload"] = data
age = time.time() - data.get("timestamp", 0)
if age > max_age_seconds:
result["errors"].append(f"Packet expired ({age:.0f}s old, max {max_age_seconds}s)")
return result
if age < -5:
result["errors"].append("Packet timestamp is in the future")
return result
result["valid"] = True
except (struct.error, json.JSONDecodeError, KeyError) as e:
result["errors"].append(f"Packet parse error: {str(e)}")
return result
def validate_mtls_certificate(host: str, port: int, ca_cert_path: Optional[str] = None) -> dict:
"""Validate mTLS certificate configuration on SDP gateway."""
result = {
"host": host,
"port": port,
"tls_configured": False,
"certificate": None,
"errors": [],
}
try:
context = ssl.create_default_context()
if ca_cert_path:
context.load_verify_locations(ca_cert_path)
with socket.create_connection((host, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=host) as ssock:
cert = ssock.getpeercert()
result["tls_configured"] = True
result["certificate"] = {
"subject": dict(x[0] for x in cert.get("subject", [])),
"issuer": dict(x[0] for x in cert.get("issuer", [])),
"version": cert.get("version"),
"not_before": cert.get("notBefore"),
"not_after": cert.get("notAfter"),
"serial": cert.get("serialNumber"),
}
not_after = cert.get("notAfter", "")
if not_after:
expiry = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
days_remaining = (expiry - datetime.utcnow()).days
result["certificate"]["days_remaining"] = days_remaining
if days_remaining < 30:
result["errors"].append(f"Certificate expires in {days_remaining} days")
except ssl.SSLError as e:
result["errors"].append(f"SSL error: {str(e)}")
except Exception as e:
result["errors"].append(f"Connection error: {str(e)}")
return result
def validate_sdp_config(config: dict) -> dict:
"""Validate SDP deployment configuration."""
findings = []
score = 100
controller = config.get("controller", {})
if not controller.get("ha_enabled"):
findings.append({"severity": "high", "finding": "Controller HA not enabled"})
score -= 15
if not controller.get("idp_integration"):
findings.append({"severity": "critical", "finding": "No IdP integration configured"})
score -= 25
if not controller.get("audit_logging"):
findings.append({"severity": "high", "finding": "Audit logging not enabled on controller"})
score -= 10
gateways = config.get("gateways", [])
if not gateways:
findings.append({"severity": "critical", "finding": "No SDP gateways deployed"})
score -= 25
for gw in gateways:
if not gw.get("default_drop"):
findings.append({"severity": "critical", "finding": f"Gateway {gw.get('name')}: default-drop not enabled"})
score -= 20
if not gw.get("spa_enabled"):
findings.append({"severity": "critical", "finding": f"Gateway {gw.get('name')}: SPA not enabled"})
score -= 15
if not gw.get("mtls_enabled"):
findings.append({"severity": "high", "finding": f"Gateway {gw.get('name')}: mTLS not configured"})
score -= 10
pki = config.get("pki", {})
cert_lifetime_hours = pki.get("client_cert_lifetime_hours", 8760)
if cert_lifetime_hours > 72:
findings.append({
"severity": "warning",
"finding": f"Client certificate lifetime is {cert_lifetime_hours}h (recommend <=72h for zero trust)"
})
score -= 5
if not pki.get("ocsp_enabled") and not pki.get("crl_enabled"):
findings.append({"severity": "high", "finding": "No certificate revocation checking enabled"})
score -= 10
monitoring = config.get("monitoring", {})
if not monitoring.get("siem_integration"):
findings.append({"severity": "warning", "finding": "No SIEM integration for SDP events"})
score -= 5
return {
"score": max(score, 0),
"findings": findings,
"status": "ready" if score >= 80 else "needs_work" if score >= 50 else "not_ready",
"timestamp": datetime.now().isoformat(),
}
def generate_sdp_deployment_report(config: dict) -> dict:
"""Generate comprehensive SDP deployment report."""
validation = validate_sdp_config(config)
applications = config.get("applications", [])
users = config.get("authorized_users", [])
return {
"generated": datetime.now().isoformat(),
"deployment_status": validation["status"],
"security_score": validation["score"],
"findings": validation["findings"],
"summary": {
"controller_ha": config.get("controller", {}).get("ha_enabled", False),
"gateways_deployed": len(config.get("gateways", [])),
"applications_protected": len(applications),
"authorized_users": len(users),
"spa_enabled": all(g.get("spa_enabled") for g in config.get("gateways", [])),
"mtls_enabled": all(g.get("mtls_enabled") for g in config.get("gateways", [])),
},
"recommendations": [f["finding"] for f in validation["findings"] if f["severity"] in ("critical", "high")],
}
def main():
import argparse
parser = argparse.ArgumentParser(description="SDP Deployment Validator")
parser.add_argument("--config", type=str, help="Path to SDP configuration JSON")
parser.add_argument("--scan", type=str, help="Gateway host to scan for invisibility")
parser.add_argument("--scan-ports", type=str, default="1-1024", help="Port range to scan")
parser.add_argument("--check-tls", type=str, help="Host:port to check TLS certificate")
parser.add_argument("--output", type=str, default="sdp_report.json")
args = parser.parse_args()
if args.config:
with open(args.config) as f:
config = json.load(f)
report = generate_sdp_deployment_report(config)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"SDP Status: {report['deployment_status']} (Score: {report['security_score']})")
for r in report["recommendations"]:
print(f" - {r}")
elif args.scan:
start, end = args.scan_ports.split("-")
result = check_gateway_invisibility(args.scan, (int(start), int(end)))
with open(args.output, "w") as f:
json.dump(result, f, indent=2)
status = "INVISIBLE" if result["invisible"] else "EXPOSED"
print(f"Gateway {args.scan}: {status}")
if result["open_ports"]:
print(f" Open ports: {result['open_ports']}")
elif args.check_tls:
parts = args.check_tls.split(":")
host = parts[0]
port = int(parts[1]) if len(parts) > 1 else 443
result = validate_mtls_certificate(host, port)
with open(args.output, "w") as f:
json.dump(result, f, indent=2)
print(f"TLS on {host}:{port}: {'configured' if result['tls_configured'] else 'not configured'}")
else:
parser.print_help()
if __name__ == "__main__":
main()