"""Config-drift source: read-only Docker socket -> 0.0.0.0 binds, privileged, rw-socket mounts. The designated edge proxy (SOC_EDGE, default 'traefik') is SUPPOSED to own 0.0.0.0:80/443 — that's its whole job — so its public binds are reported as good, not flagged (false positive).""" import os from soc import finding from . import _docker # Containers that are the public edge: allowed to bind 0.0.0.0 (they front the estate). EDGE = {x.strip() for x in os.environ.get("SOC_EDGE", "traefik").replace(";", ",").split(",") if x.strip()} def run(): if not _docker.available(): return None # socket not mounted -> skip conts = _docker.get("/containers/json") or [] findings, edge_ok = [], [] for c in conts: name = (c.get("Names") or ["?"])[0].lstrip("/") cid = c.get("Id", "")[:12] try: d = _docker.get(f"/containers/{cid}/json") except Exception: continue hc = d.get("HostConfig", {}) or {} # Ports published on all interfaces (0.0.0.0 / ::) — i.e. exposed off-host. exposed = [] for port, binds in ((d.get("NetworkSettings", {}) or {}).get("Ports") or {}).items(): for b in (binds or []): if b.get("HostIp") in ("0.0.0.0", "::", ""): exposed.append(f"{b.get('HostIp') or '0.0.0.0'}:{b.get('HostPort')}->{port}") if exposed and name in EDGE: edge_ok.append(f"{name} ({'; '.join(exposed)})") # the edge — public by design elif exposed: findings.append(finding( f"drift-bind-{name}", "high", f"{name} publishes ports on all interfaces", host=name, evidence="; ".join(exposed), impact=["reachable from outside the host, bypassing the reverse proxy"], fix=["bind to 127.0.0.1, or drop host ports and route via the proxy"])) if hc.get("Privileged"): findings.append(finding( f"drift-priv-{name}", "high", f"{name} runs --privileged", host=name, evidence="Privileged=true", impact=["a container escape becomes full host root"], fix=["drop --privileged; grant only the specific capabilities needed"])) for m in (d.get("Mounts") or []): if m.get("Source") == "/var/run/docker.sock" and m.get("RW"): findings.append(finding( f"drift-sock-{name}", "high", f"{name} mounts the Docker socket read-write", host=name, evidence="/var/run/docker.sock (rw)", impact=["the container can control the host's Docker = host root"], fix=["mount it :ro, or put a scoped socket-proxy in front"])) good = [f"edge {e} binds 0.0.0.0:80/443 by design (allowed)" for e in edge_ok] if not findings and not good: good.append("No 0.0.0.0 binds, privileged, or rw-socket containers") return { "scope": f"{len(conts)} container(s)", "findings": findings, "good": good, }