7336b17f79
Build and push soc-collector / build (push) Successful in 31s
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.
55 lines
2.3 KiB
Python
55 lines
2.3 KiB
Python
"""Config-drift source: read-only Docker socket -> 0.0.0.0 binds, privileged, rw-socket mounts."""
|
|
from soc import finding
|
|
|
|
from . import _docker
|
|
|
|
|
|
def run():
|
|
if not _docker.available():
|
|
return None # socket not mounted -> skip
|
|
conts = _docker.get("/containers/json") or []
|
|
|
|
findings = []
|
|
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:
|
|
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"]))
|
|
|
|
return {
|
|
"scope": f"{len(conts)} container(s)",
|
|
"findings": findings,
|
|
"good": [] if findings else ["No 0.0.0.0 binds, privileged, or rw-socket containers"],
|
|
}
|