150 lines
3.5 KiB
Python
Executable File
150 lines
3.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Docker system audit: collect container diagnostics and return as JSON."""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
|
|
|
|
def cmd(args, parse_json=False):
|
|
try:
|
|
result = subprocess.run(
|
|
args,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30,
|
|
)
|
|
out = (result.stdout + result.stderr).strip()
|
|
|
|
if parse_json:
|
|
try:
|
|
return json.loads(out) if out else None
|
|
except json.JSONDecodeError:
|
|
return out
|
|
|
|
return out
|
|
|
|
except Exception as e:
|
|
return f"ERROR: {e}"
|
|
|
|
|
|
def read_file(path):
|
|
try:
|
|
with open(path) as f:
|
|
return f.read().strip().splitlines()
|
|
except FileNotFoundError:
|
|
return None
|
|
|
|
|
|
def main():
|
|
audit = {}
|
|
|
|
# Container identity
|
|
audit["container_id"] = os.environ.get("HOSTNAME")
|
|
|
|
# OS information inside container
|
|
audit["os_release"] = read_file("/etc/os-release")
|
|
audit["uname"] = cmd(["uname", "-a"])
|
|
|
|
# Installed packages (works for Debian/Ubuntu, Alpine, etc.)
|
|
audit["packages"] = {}
|
|
|
|
if os.path.exists("/etc/alpine-release"):
|
|
audit["packages"]["manager"] = "apk"
|
|
audit["packages"]["installed"] = cmd(["apk", "info", "-v"]).splitlines()
|
|
audit["packages"]["upgradeable"] = cmd(
|
|
["apk", "version", "-l", "<"]
|
|
).splitlines()
|
|
|
|
elif os.path.exists("/etc/debian_version"):
|
|
audit["packages"]["manager"] = "dpkg"
|
|
audit["packages"]["installed"] = cmd(
|
|
["dpkg-query", "-W", "-f=${Package}-${Version}\n"]
|
|
).splitlines()
|
|
|
|
elif os.path.exists("/etc/redhat-release"):
|
|
audit["packages"]["manager"] = "rpm"
|
|
audit["packages"]["installed"] = cmd(["rpm", "-qa"]).splitlines()
|
|
|
|
# Docker engine information
|
|
audit["docker_version"] = cmd(
|
|
["docker", "version", "--format", "{{json .}}"],
|
|
parse_json=True,
|
|
)
|
|
|
|
audit["docker_info"] = cmd(
|
|
["docker", "info", "--format", "{{json .}}"],
|
|
parse_json=True,
|
|
)
|
|
|
|
# Containers
|
|
containers = cmd(
|
|
["docker", "ps", "-a", "--format", "{{json .}}"],
|
|
parse_json=True,
|
|
)
|
|
|
|
if isinstance(containers, list):
|
|
audit["docker_containers"] = containers
|
|
else:
|
|
audit["docker_containers"] = []
|
|
|
|
# Images
|
|
audit["docker_images"] = cmd(
|
|
["docker", "images", "--format", "{{json .}}"],
|
|
parse_json=True,
|
|
)
|
|
|
|
# Runtime stats
|
|
audit["docker_stats"] = cmd(
|
|
[
|
|
"docker",
|
|
"stats",
|
|
"--no-stream",
|
|
"--format",
|
|
"table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}",
|
|
]
|
|
)
|
|
|
|
# Container logs
|
|
logs = {}
|
|
|
|
for c in audit["docker_containers"]:
|
|
name = c.get("Names") or c.get("ID")
|
|
|
|
if not name:
|
|
continue
|
|
|
|
logs[name] = cmd(
|
|
[
|
|
"docker",
|
|
"logs",
|
|
"--tail",
|
|
"100",
|
|
name,
|
|
]
|
|
)
|
|
|
|
audit["docker_logs"] = logs
|
|
|
|
# SSH config if present
|
|
audit["sshd_config"] = cmd(["sshd", "-T"]).splitlines()
|
|
|
|
# Users
|
|
audit["passwd"] = read_file("/etc/passwd")
|
|
|
|
# Runtime info
|
|
audit["uptime"] = cmd(["uptime"])
|
|
audit["top"] = cmd(["top", "-b", "-n", "1"]).splitlines()
|
|
|
|
# cgroups (useful in Docker)
|
|
audit["cgroup"] = read_file("/proc/self/cgroup")
|
|
|
|
# Mounted filesystems
|
|
audit["mounts"] = cmd(["mount"]).splitlines()
|
|
|
|
print(json.dumps(audit, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|