Files
Steffen Skui 7336b17f79
Build and push soc-collector / build (push) Successful in 31s
soc-collector: run-anywhere scanner that pushes reports to SOC Center
A single Docker image, configured entirely by env vars (SOC_URL + SOC_TOKEN +
targets). Runs once and exits; schedule with cron. Four self-skipping sources:
  - surface     nmap open-port/service scan (SOC_TARGETS)
  - cve         Trivy HIGH/CRITICAL scan of running images (Docker socket)
  - tls         cert-expiry / weak-TLS / missing-headers (SOC_DOMAINS)
  - config      0.0.0.0 binds, --privileged, rw docker-socket mounts (Docker socket)
Stable slugs per source so SOC Center versions/diffs/alerts. Pure stdlib;
image adds nmap + trivy. MIT, intended as a public Ethica repo.
2026-06-24 18:17:39 +02:00

77 lines
3.0 KiB
Python

"""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}