mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-06 10:50:19 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
---
|
||||
name: hardening-docker-daemon-configuration
|
||||
description: Harden the Docker daemon by configuring daemon.json with user namespace remapping, TLS authentication, rootless mode, and CIS benchmark controls.
|
||||
domain: cybersecurity
|
||||
subdomain: container-security
|
||||
tags: [docker, daemon-hardening, container-security, cis-benchmark, rootless, userns-remap]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Hardening Docker Daemon Configuration
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
|
||||
```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)
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```json
|
||||
{
|
||||
"no-new-privileges": true
|
||||
}
|
||||
```
|
||||
|
||||
Prevents container processes from gaining additional privileges via setuid/setgid binaries or capability escalation.
|
||||
|
||||
### Enable Live Restore
|
||||
|
||||
```json
|
||||
{
|
||||
"live-restore": true
|
||||
}
|
||||
```
|
||||
|
||||
Keeps containers running during daemon downtime, enabling daemon upgrades without container restart.
|
||||
|
||||
### Disable Userland Proxy
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```bash
|
||||
docker --tlsverify \
|
||||
--tlscacert=ca.pem \
|
||||
--tlscert=cert.pem \
|
||||
--tlskey=key.pem \
|
||||
-H=tcp://docker-host:2376 version
|
||||
```
|
||||
|
||||
## Docker Socket Protection
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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: true
|
||||
```
|
||||
|
||||
## Content Trust (Image Signing)
|
||||
|
||||
```bash
|
||||
# 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.0
|
||||
```
|
||||
|
||||
## Seccomp Profile
|
||||
|
||||
```bash
|
||||
# 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_name
|
||||
```
|
||||
|
||||
## AppArmor Profile
|
||||
|
||||
```bash
|
||||
# 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-custom
|
||||
```
|
||||
|
||||
## Verification Commands
|
||||
|
||||
```bash
|
||||
# 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-security
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Never expose Docker daemon without TLS** - Always use `--tlsverify` for remote access
|
||||
2. **Enable user namespace remapping** - Map container root to unprivileged host UID
|
||||
3. **Disable ICC** - Prevent default bridge network container-to-container communication
|
||||
4. **Use rootless mode** - Run Docker daemon as non-root where possible
|
||||
5. **Enable content trust** - Only pull signed images
|
||||
6. **Configure log rotation** - Prevent log files from filling disk
|
||||
7. **Use seccomp profiles** - Restrict syscalls available to containers
|
||||
8. **Audit Docker socket** - Monitor access to /var/run/docker.sock
|
||||
9. **Run Docker Bench regularly** - Automate CIS benchmark checks
|
||||
10. **Keep Docker updated** - Apply security patches promptly
|
||||
@@ -0,0 +1,61 @@
|
||||
# Docker Daemon Hardening Checklist
|
||||
|
||||
## Pre-Hardening
|
||||
|
||||
- [ ] Document current daemon.json configuration
|
||||
- [ ] Run Docker Bench Security baseline
|
||||
- [ ] Identify running containers that may be affected
|
||||
- [ ] Schedule maintenance window
|
||||
- [ ] Backup existing /etc/docker/daemon.json
|
||||
|
||||
## CIS Docker Benchmark v1.6 - Daemon Checks
|
||||
|
||||
### Critical
|
||||
- [ ] 2.2 - Disable inter-container communication (`"icc": false`)
|
||||
- [ ] 2.9 - Enable user namespace remapping (`"userns-remap": "default"`)
|
||||
- [ ] 2.14 - Restrict new privileges (`"no-new-privileges": true`)
|
||||
- [ ] 2.7 - Configure TLS authentication (if remote access needed)
|
||||
|
||||
### High
|
||||
- [ ] 2.6 - Use overlay2 storage driver
|
||||
- [ ] 2.16 - Disable userland proxy
|
||||
- [ ] 2.13 - Configure centralized logging
|
||||
- [ ] 2.8 - Set default ulimits
|
||||
- [ ] 2.17 - Apply custom seccomp profile
|
||||
|
||||
### Medium
|
||||
- [ ] 2.15 - Enable live restore
|
||||
- [ ] 2.1 - Consider rootless mode
|
||||
- [ ] Docker socket permissions set to 660
|
||||
|
||||
## Post-Hardening Verification
|
||||
|
||||
- [ ] Docker daemon restarts successfully
|
||||
- [ ] All containers start correctly
|
||||
- [ ] Docker Bench shows improved score
|
||||
- [ ] TLS connection works (if configured)
|
||||
- [ ] Monitoring endpoints accessible
|
||||
- [ ] Log rotation working
|
||||
|
||||
## Recommended daemon.json
|
||||
|
||||
```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,
|
||||
"experimental": false
|
||||
}
|
||||
```
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
1. Stop Docker daemon: `sudo systemctl stop docker`
|
||||
2. Restore backup: `sudo cp /etc/docker/daemon.json.bak /etc/docker/daemon.json`
|
||||
3. Start Docker daemon: `sudo systemctl start docker`
|
||||
4. Verify containers: `docker ps`
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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
|
||||
@@ -0,0 +1,107 @@
|
||||
# Workflow - Hardening Docker Daemon Configuration
|
||||
|
||||
## Phase 1: Baseline Assessment
|
||||
|
||||
```bash
|
||||
# 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.txt
|
||||
```
|
||||
|
||||
## Phase 2: Apply Hardened Configuration
|
||||
|
||||
### Step 1 - Backup Current Config
|
||||
```bash
|
||||
sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak 2>/dev/null
|
||||
```
|
||||
|
||||
### Step 2 - Deploy Hardened daemon.json
|
||||
```bash
|
||||
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"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Step 3 - Restart Docker Daemon
|
||||
```bash
|
||||
sudo systemctl restart docker
|
||||
sudo systemctl status docker
|
||||
```
|
||||
|
||||
### Step 4 - Verify Settings
|
||||
```bash
|
||||
docker info | grep -E "(Remap|ICC|Live Restore|Security)"
|
||||
```
|
||||
|
||||
## Phase 3: TLS Configuration
|
||||
|
||||
```bash
|
||||
# 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 docker
|
||||
```
|
||||
|
||||
## Phase 4: Post-Hardening Validation
|
||||
|
||||
```bash
|
||||
# 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.txt
|
||||
```
|
||||
|
||||
## Phase 5: Ongoing Monitoring
|
||||
|
||||
```bash
|
||||
# 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 -20
|
||||
```
|
||||
@@ -0,0 +1,273 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user