"""TLS & web-exposure source: cert expiry, weak TLS, missing security headers per domain.""" import datetime import http.client import socket import ssl from soc import finding SECURITY_HEADERS = ["strict-transport-security", "content-security-policy", "x-content-type-options", "x-frame-options"] def _peer(host, port=443): ctx = ssl.create_default_context() with socket.create_connection((host, port), timeout=15) as sock: with ctx.wrap_socket(sock, server_hostname=host) as ss: return ss.getpeercert(), ss.version() def run(domains): if not domains: return None findings, good = [], [] for host in domains: host = host.strip() if not host: continue try: cert, ver = _peer(host) except Exception as e: findings.append(finding( f"tls-conn-{host}", "med", f"TLS connection to {host} failed", host=host, evidence=str(e), impact=["the site may be down or its TLS misconfigured"], fix=["check the service and its certificate"])) continue not_after = cert.get("notAfter") try: exp = datetime.datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z") days = (exp - datetime.datetime.utcnow()).days sev = "crit" if days < 7 else "high" if days < 21 else "med" if days < 40 else None if sev: findings.append(finding( f"tls-exp-{host}", sev, f"Certificate for {host} expires in {days} days", host=host, evidence=f"notAfter={not_after}", impact=["an outage the moment it expires"], fix=["renew it / verify auto-renewal (e.g. Traefik 'le' resolver)"])) else: good.append(f"{host}: certificate valid for {days} more days") except (ValueError, TypeError): pass if ver in ("TLSv1", "TLSv1.1"): findings.append(finding( f"tls-weak-{host}", "med", f"{host} negotiated deprecated {ver}", host=host, evidence=ver, impact=["deprecated, downgradeable TLS"], fix=["disable TLS < 1.2 at the edge"])) try: conn = http.client.HTTPSConnection(host, timeout=15) conn.request("HEAD", "/") resp = conn.getresponse() present = {k.lower() for k, _ in resp.getheaders()} conn.close() missing = [h for h in SECURITY_HEADERS if h not in present] if missing: findings.append(finding( f"hdr-{host}", "low", f"{host} is missing {len(missing)} security header(s)", host=host, evidence=", ".join(missing), impact=["weaker browser-side protections"], fix=["add at the edge/app: " + ", ".join(missing)])) except Exception: pass return {"scope": ", ".join(domains), "findings": findings, "good": good}