npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
The Docker daemon (dockerd) runs with root privileges and controls all container operations. Hardening its configuration through /etc/docker/daemon.json, TLS certificates, user namespace remapping, and network restrictions is essential to prevent privilege escalation, lateral movement, and container breakout attacks.
When to Use
- When deploying or configuring hardening docker daemon configuration capabilities in your environment
- When establishing security controls aligned to compliance requirements
- When building or improving security architecture for this domain
- When conducting security assessments that require this implementation
Prerequisites
- Docker Engine 24.0+ installed
- Root or sudo access to the Docker host
- OpenSSL for TLS certificate generation
- Understanding of Linux namespaces and cgroups
Core Hardened daemon.json
{
"icc": false,
"userns-remap": "default",
"no-new-privileges": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
},
"storage-driver": "overlay2",
"live-restore": true,
"userland-proxy": false,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 32768
},
"nproc": {
"Name": "nproc",
"Hard": 4096,
"Soft": 2048
}
},
"seccomp-profile": "/etc/docker/seccomp/default.json",
"default-address-pools": [
{
"base": "172.17.0.0/16",
"size": 24
}
],
"iptables": true,
"ip-forward": true,
"ip-masq": true,
"experimental": false,
"metrics-addr": "127.0.0.1:9323",
"max-concurrent-downloads": 3,
"max-concurrent-uploads": 5,
"default-runtime": "runc",
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc",
"runtimeArgs": ["--platform=ptrace"]
}
}
}Setting-by-Setting Explanation
Disable Inter-Container Communication (ICC)
{
"icc": false
}Prevents containers on the default bridge network from communicating. Each container must use explicit --link or user-defined networks with published ports.
Enable User Namespace Remapping
{
"userns-remap": "default"
}Maps container root (UID 0) to a high unprivileged UID on the host. This prevents a container breakout from gaining root on the host.
# Verify userns-remap is active
cat /etc/subuid
# Output: dockremap:100000:65536
cat /etc/subgid
# Output: dockremap:100000:65536
# Verify container UID mapping
docker run --rm alpine id
# uid=0(root) gid=0(root) -- but host UID is 100000+Disable New Privilege Escalation
{
"no-new-privileges": true
}Prevents container processes from gaining additional privileges via setuid/setgid binaries or capability escalation.
Enable Live Restore
{
"live-restore": true
}Keeps containers running during daemon downtime, enabling daemon upgrades without container restart.
Disable Userland Proxy
{
"userland-proxy": false
}Uses iptables rules instead of docker-proxy for port forwarding, reducing attack surface and improving performance.
TLS Configuration for Remote Docker API
Generate CA and Server Certificates
# Create CA
openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem \
-subj "/CN=Docker CA"
# Create server key and CSR
openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=docker-host" -sha256 -new -key server-key.pem -out server.csr
# Create extfile with SANs
echo "subjectAltName = DNS:docker-host,IP:10.0.0.5,IP:127.0.0.1" > extfile.cnf
echo "extendedKeyUsage = serverAuth" >> extfile.cnf
# Sign server certificate
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out server-cert.pem -extfile extfile.cnf
# Create client key and certificate
openssl genrsa -out key.pem 4096
openssl req -subj "/CN=client" -new -key key.pem -out client.csr
echo "extendedKeyUsage = clientAuth" > extfile-client.cnf
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out cert.pem -extfile extfile-client.cnf
# Set permissions
chmod 0400 ca-key.pem key.pem server-key.pem
chmod 0444 ca.pem server-cert.pem cert.pem
# Move to Docker TLS directory
sudo mkdir -p /etc/docker/tls
sudo cp ca.pem server-cert.pem server-key.pem /etc/docker/tls/Configure daemon.json for TLS
{
"tls": true,
"tlsverify": true,
"tlscacert": "/etc/docker/tls/ca.pem",
"tlscert": "/etc/docker/tls/server-cert.pem",
"tlskey": "/etc/docker/tls/server-key.pem",
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"]
}Client Connection
docker --tlsverify \
--tlscacert=ca.pem \
--tlscert=cert.pem \
--tlskey=key.pem \
-H=tcp://docker-host:2376 versionDocker Socket Protection
# Restrict socket ownership
sudo chown root:docker /var/run/docker.sock
sudo chmod 660 /var/run/docker.sock
# Audit Docker socket access
sudo auditctl -w /var/run/docker.sock -k docker-socket
# Never mount Docker socket into containers
# BAD: docker run -v /var/run/docker.sock:/var/run/docker.sock ...Rootless Docker
# Install rootless Docker
curl -fsSL https://get.docker.com/rootless | sh
# Configure environment
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
# Start rootless daemon
systemctl --user start docker
systemctl --user enable docker
# Verify rootless mode
docker info | grep -i rootless
# Rootless: trueContent Trust (Image Signing)
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# Pull only signed images
docker pull library/alpine:3.18
# Will fail if image is not signed
# Sign and push image
docker trust sign myregistry/myapp:1.0Seccomp Profile
# View default seccomp profile
docker info --format '{{.SecurityOptions}}'
# Use custom seccomp profile
docker run --security-opt seccomp=/etc/docker/seccomp/custom.json alpine
# Verify seccomp is enabled
docker inspect --format='{{.HostConfig.SecurityOpt}}' container_nameAppArmor Profile
# Check AppArmor status
sudo aa-status
# Use custom AppArmor profile
docker run --security-opt apparmor=docker-custom alpine
# Load custom profile
sudo apparmor_parser -r /etc/apparmor.d/docker-customVerification Commands
# Check daemon configuration
docker info
# Verify userns-remap
docker info --format '{{.SecurityOptions}}'
# Check ICC setting
docker network inspect bridge --format '{{.Options}}'
# Audit with Docker Bench
docker run --rm --net host --pid host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc:/etc:ro \
docker/docker-bench-securityBest Practices
- Never expose Docker daemon without TLS - Always use
--tlsverifyfor remote access - Enable user namespace remapping - Map container root to unprivileged host UID
- Disable ICC - Prevent default bridge network container-to-container communication
- Use rootless mode - Run Docker daemon as non-root where possible
- Enable content trust - Only pull signed images
- Configure log rotation - Prevent log files from filling disk
- Use seccomp profiles - Restrict syscalls available to containers
- Audit Docker socket - Monitor access to /var/run/docker.sock
- Run Docker Bench regularly - Automate CIS benchmark checks
- Keep Docker updated - Apply security patches promptly
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md1.9 KB
API Reference: Docker Daemon Configuration Hardening
daemon.json Location
- Linux:
/etc/docker/daemon.json - Windows:
C:\ProgramData\docker\config\daemon.json
Recommended daemon.json
{
"icc": false,
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"userns-remap": "default",
"log-driver": "json-file",
"log-opts": {"max-size": "10m", "max-file": "3"},
"tls": true,
"tlsverify": true,
"tlscacert": "/etc/docker/ca.pem",
"tlscert": "/etc/docker/server-cert.pem",
"tlskey": "/etc/docker/server-key.pem"
}CIS Docker Benchmark — Daemon Settings
| CIS # | Setting | Recommendation |
|---|---|---|
| 2.1 | icc |
Set to false |
| 2.2 | live-restore |
Set to true |
| 2.3 | userland-proxy |
Set to false |
| 2.4 | no-new-privileges |
Set to true |
| 2.6 | TLS | Enable with certificates |
| 2.8 | userns-remap |
Set to default |
| 2.12 | Logging | Configure centralized logging |
File Permission Checks
| File | Permissions |
|---|---|
/etc/docker/daemon.json |
644 |
/var/run/docker.sock |
660 |
/etc/docker/certs.d/ |
444 |
| Docker service files | 644 |
Docker Socket Security
Check permissions
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker 0 ... /var/run/docker.sockRestrict group access
chmod 660 /var/run/docker.sock
chown root:docker /var/run/docker.sockContent Trust (Image Signing)
Enable globally
export DOCKER_CONTENT_TRUST=1In daemon.json
{"content-trust": {"mode": "enforced"}}Docker Info Command
docker info --format '{{json .}}'Key Fields
| Field | Description |
|---|---|
SecurityOptions |
seccomp, apparmor, userns |
LiveRestoreEnabled |
Live restore status |
RegistryConfig.InsecureRegistryCIDRs |
Insecure registries |
ServerVersion |
Docker version |
standards.md2.4 KB
Standards and References - Docker Daemon Hardening
CIS Docker Benchmark v1.6
Section 2: Docker Daemon Configuration
| Rule | Description | Status |
|---|---|---|
| 2.1 | Run the Docker daemon as a non-root user | Rootless mode |
| 2.2 | Ensure network traffic is restricted between containers | icc: false |
| 2.3 | Ensure the logging level is set to info | log-level: info |
| 2.4 | Ensure Docker is allowed to make changes to iptables | iptables: true |
| 2.5 | Ensure insecure registries are not used | No --insecure-registry |
| 2.6 | Ensure aufs storage driver is not used | overlay2 driver |
| 2.7 | Ensure TLS authentication for Docker daemon is configured | tlsverify: true |
| 2.8 | Ensure the default ulimit is configured appropriately | default-ulimits set |
| 2.9 | Enable user namespace support | userns-remap: default |
| 2.10 | Ensure the default cgroup usage has been confirmed | cgroup-parent |
| 2.11 | Ensure base device size is not changed until needed | Default 10G |
| 2.12 | Ensure that authorization for Docker client commands is enabled | AuthZ plugin |
| 2.13 | Ensure centralized and remote logging is configured | log-driver |
| 2.14 | Ensure containers are restricted from acquiring new privileges | no-new-privileges |
| 2.15 | Ensure live restore is enabled | live-restore: true |
| 2.16 | Ensure Userland Proxy is disabled | userland-proxy: false |
| 2.17 | Ensure daemon-wide custom seccomp profile is applied | seccomp-profile |
NIST SP 800-190
- Section 4.1.4: Configuration defects in container images
- Section 5.1: Image security - Content trust enforcement
- Section 5.3: Daemon hardening recommendations
OWASP Docker Security Cheat Sheet
- Rule 0: Keep host and Docker up to date
- Rule 1: Do not expose the Docker daemon socket
- Rule 2: Set a user
- Rule 3: Limit capabilities
- Rule 4: Add no-new-privileges flag
- Rule 5: Disable inter-container communication
- Rule 6: Use Linux Security Module
- Rule 7: Limit resources
- Rule 8: Set filesystem and volumes to read-only
- Rule 9: Use static analysis tools
- Rule 10: Set log level to info
Compliance Mappings
PCI DSS v4.0
- Req 2.2: Develop configuration standards for all system components
- Req 2.2.1: System hardening procedures
SOC 2
- CC6.1: Logical and physical access controls
- CC8.1: Change management
FedRAMP
- CM-6: Configuration Settings
- CM-7: Least Functionality
workflows.md2.6 KB
Workflow - Hardening Docker Daemon Configuration
Phase 1: Baseline Assessment
# Check current Docker daemon configuration
docker info
docker system info --format '{{json .SecurityOptions}}'
# Check existing daemon.json
cat /etc/docker/daemon.json 2>/dev/null || echo "No daemon.json found"
# Run Docker Bench Security for baseline
docker run --rm --net host --pid host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc:/etc:ro \
docker/docker-bench-security 2>&1 | tee docker-bench-baseline.txtPhase 2: Apply Hardened Configuration
Step 1 - Backup Current Config
sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak 2>/dev/nullStep 2 - Deploy Hardened daemon.json
sudo tee /etc/docker/daemon.json <<'EOF'
{
"icc": false,
"userns-remap": "default",
"no-new-privileges": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
},
"storage-driver": "overlay2",
"live-restore": true,
"userland-proxy": false,
"default-ulimits": {
"nofile": { "Name": "nofile", "Hard": 65536, "Soft": 32768 },
"nproc": { "Name": "nproc", "Hard": 4096, "Soft": 2048 }
},
"experimental": false,
"metrics-addr": "127.0.0.1:9323"
}
EOFStep 3 - Restart Docker Daemon
sudo systemctl restart docker
sudo systemctl status dockerStep 4 - Verify Settings
docker info | grep -E "(Remap|ICC|Live Restore|Security)"Phase 3: TLS Configuration
# Generate certificates (see SKILL.md for full commands)
# Deploy to /etc/docker/tls/
# Add TLS to daemon.json
sudo jq '. + {
"tls": true,
"tlsverify": true,
"tlscacert": "/etc/docker/tls/ca.pem",
"tlscert": "/etc/docker/tls/server-cert.pem",
"tlskey": "/etc/docker/tls/server-key.pem",
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"]
}' /etc/docker/daemon.json | sudo tee /etc/docker/daemon.json.new
sudo mv /etc/docker/daemon.json.new /etc/docker/daemon.json
sudo systemctl restart dockerPhase 4: Post-Hardening Validation
# Run Docker Bench again
docker run --rm --net host --pid host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc:/etc:ro \
docker/docker-bench-security 2>&1 | tee docker-bench-hardened.txt
# Compare results
diff docker-bench-baseline.txt docker-bench-hardened.txtPhase 5: Ongoing Monitoring
# Setup auditd rules for Docker
sudo auditctl -w /var/run/docker.sock -k docker
sudo auditctl -w /etc/docker -p wa -k docker-config
sudo auditctl -w /usr/bin/docker -k docker-binary
sudo auditctl -w /var/lib/docker -k docker-data
# Monitor Docker metrics
curl -s http://127.0.0.1:9323/metrics | head -20Scripts 2
agent.py4.7 KB
#!/usr/bin/env python3
"""Agent for auditing and hardening Docker daemon configuration."""
import argparse
import json
import os
from datetime import datetime, timezone
DAEMON_JSON_PATH = os.environ.get("DOCKER_DAEMON_JSON", "/etc/docker/daemon.json")
RECOMMENDED_SETTINGS = {
"icc": False,
"live-restore": True,
"userland-proxy": False,
"no-new-privileges": True,
"userns-remap": "default",
"log-driver": "json-file",
"log-opts": {"max-size": "10m", "max-file": "3"},
}
def read_daemon_config():
"""Read Docker daemon.json configuration."""
if not os.path.isfile(DAEMON_JSON_PATH):
return {"error": f"{DAEMON_JSON_PATH} not found"}
try:
with open(DAEMON_JSON_PATH, "r") as f:
return json.load(f)
except (json.JSONDecodeError, PermissionError) as e:
return {"error": str(e)}
def audit_daemon_config(config):
"""Audit daemon.json against CIS benchmarks."""
findings = []
if "error" in config:
findings.append({"check": "daemon.json", "issue": config["error"], "severity": "HIGH"})
return findings
if config.get("icc", True):
findings.append({"check": "CIS 2.1", "issue": "Inter-container communication enabled", "severity": "MEDIUM"})
if not config.get("live-restore"):
findings.append({"check": "CIS 2.2", "issue": "Live restore not enabled", "severity": "LOW"})
if config.get("userland-proxy", True):
findings.append({"check": "CIS 2.3", "issue": "Userland proxy enabled (use iptables)", "severity": "LOW"})
if not config.get("no-new-privileges"):
findings.append({"check": "CIS 2.4", "issue": "no-new-privileges not set", "severity": "MEDIUM"})
if "userns-remap" not in config:
findings.append({"check": "CIS 2.8", "issue": "User namespace remapping not configured", "severity": "MEDIUM"})
if not config.get("tls"):
if not config.get("tlsverify"):
findings.append({"check": "CIS 2.6", "issue": "TLS not configured for Docker daemon", "severity": "HIGH"})
log_driver = config.get("log-driver", "")
if not log_driver:
findings.append({"check": "CIS 2.12", "issue": "No log driver configured", "severity": "MEDIUM"})
if config.get("insecure-registries"):
findings.append({"check": "CIS 2.4", "issue": f"Insecure registries: {config['insecure-registries']}", "severity": "HIGH"})
return findings
def check_docker_socket():
"""Check Docker socket permissions."""
findings = []
socket_path = "/var/run/docker.sock"
if os.path.exists(socket_path):
stat = os.stat(socket_path)
mode = oct(stat.st_mode)[-3:]
if mode != "660":
findings.append({
"check": "CIS 3.3",
"issue": f"Docker socket permissions: {mode} (should be 660)",
"severity": "HIGH",
})
return findings
def check_docker_files():
"""Audit Docker configuration file permissions."""
findings = []
files_to_check = {
"/etc/docker/daemon.json": "644",
"/etc/default/docker": "644",
"/etc/docker/certs.d": "444",
}
for fpath, expected in files_to_check.items():
if os.path.exists(fpath):
stat = os.stat(fpath)
mode = oct(stat.st_mode)[-3:]
if mode > expected:
findings.append({
"check": "CIS 3.x",
"issue": f"{fpath}: permissions {mode} (should be {expected})",
"severity": "MEDIUM",
})
return findings
def main():
global DAEMON_JSON_PATH
parser = argparse.ArgumentParser(
description="Audit Docker daemon configuration against CIS benchmarks"
)
parser.add_argument("--config", default=DAEMON_JSON_PATH, help="Path to daemon.json")
parser.add_argument("--output", "-o", help="Output JSON report")
args = parser.parse_args()
print("[*] Docker Daemon Configuration Audit Agent")
DAEMON_JSON_PATH = args.config
config = read_daemon_config()
findings = audit_daemon_config(config)
findings.extend(check_docker_socket())
findings.extend(check_docker_files())
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"daemon_config": config if "error" not in config else {},
"findings": findings,
"finding_count": len(findings),
}
critical = sum(1 for f in findings if f["severity"] == "HIGH")
print(f"[*] Findings: {len(findings)} (HIGH: {critical})")
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[*] Report saved to {args.output}")
else:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
process.py8.4 KB
#!/usr/bin/env python3
"""
Docker Daemon Hardening Auditor - Check Docker daemon configuration
against CIS Docker Benchmark recommendations and generate remediation.
"""
import json
import subprocess
import sys
import argparse
from pathlib import Path
HARDENING_CHECKS = {
"icc_disabled": {
"description": "Inter-container communication disabled (CIS 2.2)",
"check_key": "icc",
"expected": False,
"severity": "HIGH",
},
"userns_remap": {
"description": "User namespace remapping enabled (CIS 2.9)",
"check_key": "userns-remap",
"expected_not_empty": True,
"severity": "HIGH",
},
"no_new_privileges": {
"description": "No new privileges flag set (CIS 2.14)",
"check_key": "no-new-privileges",
"expected": True,
"severity": "HIGH",
},
"live_restore": {
"description": "Live restore enabled (CIS 2.15)",
"check_key": "live-restore",
"expected": True,
"severity": "MEDIUM",
},
"userland_proxy_disabled": {
"description": "Userland proxy disabled (CIS 2.16)",
"check_key": "userland-proxy",
"expected": False,
"severity": "MEDIUM",
},
"log_driver": {
"description": "Logging driver configured (CIS 2.13)",
"check_key": "log-driver",
"expected_not_empty": True,
"severity": "MEDIUM",
},
"storage_driver": {
"description": "Storage driver set to overlay2 (CIS 2.6)",
"check_key": "storage-driver",
"expected_value": "overlay2",
"severity": "LOW",
},
"experimental_disabled": {
"description": "Experimental features disabled",
"check_key": "experimental",
"expected": False,
"severity": "LOW",
},
}
RECOMMENDED_CONFIG = {
"icc": False,
"userns-remap": "default",
"no-new-privileges": True,
"log-driver": "json-file",
"log-opts": {"max-size": "10m", "max-file": "5"},
"storage-driver": "overlay2",
"live-restore": True,
"userland-proxy": False,
"default-ulimits": {
"nofile": {"Name": "nofile", "Hard": 65536, "Soft": 32768},
"nproc": {"Name": "nproc", "Hard": 4096, "Soft": 2048},
},
"experimental": False,
"metrics-addr": "127.0.0.1:9323",
}
def load_daemon_config(config_path: str = "/etc/docker/daemon.json") -> dict:
"""Load Docker daemon.json configuration."""
path = Path(config_path)
if not path.exists():
return {}
try:
return json.loads(path.read_text())
except json.JSONDecodeError as e:
print(f"Error parsing {config_path}: {e}", file=sys.stderr)
return {}
def get_docker_info() -> dict:
"""Get Docker system info."""
result = subprocess.run(["docker", "info", "--format", "{{json .}}"],
capture_output=True, text=True)
if result.returncode != 0:
print(f"Error running docker info: {result.stderr}", file=sys.stderr)
return {}
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return {}
def audit_config(config: dict) -> list:
"""Audit daemon.json against hardening checks."""
results = []
for check_id, check in HARDENING_CHECKS.items():
key = check["check_key"]
value = config.get(key)
passed = False
actual = value
if "expected" in check:
passed = value == check["expected"]
elif "expected_not_empty" in check:
passed = value is not None and value != ""
elif "expected_value" in check:
passed = value == check["expected_value"]
results.append({
"id": check_id,
"description": check["description"],
"severity": check["severity"],
"passed": passed,
"expected": check.get("expected", check.get("expected_value", "non-empty")),
"actual": actual,
})
return results
def check_tls_config(config: dict) -> dict:
"""Check TLS configuration."""
tls_enabled = config.get("tls", False)
tls_verify = config.get("tlsverify", False)
has_ca = bool(config.get("tlscacert"))
has_cert = bool(config.get("tlscert"))
has_key = bool(config.get("tlskey"))
return {
"tls_enabled": tls_enabled,
"tls_verify": tls_verify,
"has_ca_cert": has_ca,
"has_server_cert": has_cert,
"has_server_key": has_key,
"fully_configured": all([tls_enabled, tls_verify, has_ca, has_cert, has_key]),
}
def check_socket_permissions() -> dict:
"""Check Docker socket file permissions."""
import os
import stat
socket_path = "/var/run/docker.sock"
if not os.path.exists(socket_path):
return {"exists": False}
st = os.stat(socket_path)
mode = stat.filemode(st.st_mode)
owner_uid = st.st_uid
group_gid = st.st_gid
world_readable = bool(st.st_mode & stat.S_IROTH)
world_writable = bool(st.st_mode & stat.S_IWOTH)
return {
"exists": True,
"permissions": mode,
"owner_uid": owner_uid,
"group_gid": group_gid,
"world_readable": world_readable,
"world_writable": world_writable,
"secure": not world_readable and not world_writable,
}
def generate_report(audit_results: list, tls_info: dict, config_path: str) -> str:
"""Generate markdown audit report."""
passed = sum(1 for r in audit_results if r["passed"])
total = len(audit_results)
score = (passed / total * 100) if total > 0 else 0
report = f"""# Docker Daemon Hardening Audit Report
**Config File:** `{config_path}`
**Score:** {passed}/{total} checks passed ({score:.0f}%)
## Audit Results
| Status | Severity | Check | Expected | Actual |
|--------|----------|-------|----------|--------|
"""
for r in sorted(audit_results, key=lambda x: (0 if not x["passed"] else 1, x["severity"])):
status = "PASS" if r["passed"] else "FAIL"
report += f"| {status} | {r['severity']} | {r['description']} | {r['expected']} | {r['actual']} |\n"
report += f"""
## TLS Configuration
| Setting | Value |
|---------|-------|
| TLS Enabled | {tls_info.get('tls_enabled', False)} |
| TLS Verify | {tls_info.get('tls_verify', False)} |
| CA Certificate | {tls_info.get('has_ca_cert', False)} |
| Server Certificate | {tls_info.get('has_server_cert', False)} |
| Server Key | {tls_info.get('has_server_key', False)} |
| Fully Configured | {tls_info.get('fully_configured', False)} |
## Remediation
Failed checks require updating `/etc/docker/daemon.json` and restarting the Docker daemon:
```bash
sudo systemctl restart docker
```
"""
return report
def generate_hardened_config(existing: dict) -> dict:
"""Merge recommended settings with existing config."""
merged = existing.copy()
merged.update(RECOMMENDED_CONFIG)
return merged
def main():
parser = argparse.ArgumentParser(description="Docker Daemon Hardening Auditor")
parser.add_argument("--config", default="/etc/docker/daemon.json",
help="Path to daemon.json")
parser.add_argument("--audit", action="store_true", help="Run hardening audit")
parser.add_argument("--generate", action="store_true",
help="Generate hardened daemon.json")
parser.add_argument("--report", help="Save audit report to file")
parser.add_argument("--output", help="Output path for generated config")
args = parser.parse_args()
if args.generate:
existing = load_daemon_config(args.config)
hardened = generate_hardened_config(existing)
output = json.dumps(hardened, indent=2)
if args.output:
Path(args.output).write_text(output)
print(f"Hardened config written to {args.output}")
else:
print(output)
return
if args.audit:
config = load_daemon_config(args.config)
if not config:
print(f"Warning: No config found at {args.config}, auditing empty config")
audit_results = audit_config(config)
tls_info = check_tls_config(config)
report = generate_report(audit_results, tls_info, args.config)
if args.report:
Path(args.report).write_text(report)
print(f"Report written to {args.report}")
else:
print(report)
failed = sum(1 for r in audit_results if not r["passed"])
sys.exit(1 if failed > 0 else 0)
parser.print_help()
if __name__ == "__main__":
main()