Add comprehensive Docker security audit playbook

Implement production-ready Docker security audit framework with
CIS Docker Benchmark and NIST SP 800-190 alignment.

Features:
- Comprehensive container security checks (privileges, network, resources)
- Daemon configuration audit
- Image and network analysis
- Security findings categorization (CRITICAL/HIGH/MEDIUM/LOW)
- Automated report generation (JSON + detailed text)
- Support for multiple hosts via dynamic inventory

Audit Checks:
- Privileged container detection (CRITICAL)
- Host network mode usage (HIGH)
- User namespace remapping status (MEDIUM)
- Resource limits enforcement (MEDIUM)
- Container capabilities audit
- Security profiles (AppArmor/SELinux)
- Image tag analysis (:latest usage)
- Exposed ports inventory

Report Outputs:
- Detailed text report with recommendations
- Machine-readable JSON report
- CIS Benchmark compliance mapping
- NIST SP 800-190 alignment
- Actionable remediation roadmap

Files:
- playbooks/audit_docker.yml (300+ lines)
- templates/docker_audit_report.j2 (comprehensive report template)

Usage:
  ansible-playbook playbooks/audit_docker.yml
  ansible-playbook playbooks/audit_docker.yml --limit hostname

Results: ./stats/docker_audits/{hostname}/docker_audit_{timestamp}.{txt,json}

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-11 07:47:06 +01:00
parent 3009f4ce1e
commit da1da34d25
2 changed files with 628 additions and 0 deletions

325
playbooks/audit_docker.yml Normal file
View File

@@ -0,0 +1,325 @@
---
# ==============================================================================
# Docker Security Audit Playbook
# ==============================================================================
# Comprehensive security audit for Docker installations
# Generates detailed security reports with findings and recommendations
# ==============================================================================
- name: Docker Security Audit
hosts: all
become: true
gather_facts: true
tags: [docker, security, audit]
vars:
audit_output_dir: "./stats/docker_audits"
audit_timestamp: "{{ ansible_date_time.epoch }}"
tasks:
- name: Display audit start information
ansible.builtin.debug:
msg:
- "=== Docker Security Audit ==="
- "Host: {{ inventory_hostname }}"
- "Date: {{ ansible_date_time.iso8601 }}"
tags: [always]
- name: Check if Docker is installed
ansible.builtin.command: docker --version
register: docker_version
failed_when: false
changed_when: false
tags: [always]
- name: Skip audit if Docker not installed
ansible.builtin.meta: end_host
when: docker_version.rc != 0
tags: [always]
- name: Create audit output directory on control node
ansible.builtin.file:
path: "{{ audit_output_dir }}/{{ inventory_hostname }}"
state: directory
mode: '0755'
delegate_to: localhost
become: false
tags: [always]
# ==========================================================================
# Docker Daemon Configuration Audit
# ==========================================================================
- name: Check if Docker daemon config exists
ansible.builtin.stat:
path: /etc/docker/daemon.json
register: daemon_config_stat
tags: [daemon]
- name: Read Docker daemon configuration
ansible.builtin.slurp:
src: /etc/docker/daemon.json
register: docker_daemon_config
failed_when: false
when: daemon_config_stat.stat.exists
tags: [daemon]
- name: Get Docker daemon info
ansible.builtin.command: docker info --format json
register: docker_info_json
changed_when: false
tags: [daemon]
- name: Parse Docker info
ansible.builtin.set_fact:
docker_info: "{{ docker_info_json.stdout | from_json }}"
tags: [daemon]
- name: Check Docker daemon security options
ansible.builtin.set_fact:
docker_security_options: "{{ docker_info.SecurityOptions | default([]) }}"
tags: [daemon]
# ==========================================================================
# Container Audit
# ==========================================================================
- name: List running containers
ansible.builtin.command: docker ps --format json
register: docker_containers_raw
changed_when: false
failed_when: false
tags: [containers]
- name: Parse container list
ansible.builtin.set_fact:
running_containers: "{{ docker_containers_raw.stdout_lines | map('from_json') | list }}"
when: docker_containers_raw.stdout_lines | length > 0
tags: [containers]
- name: Get all container IDs
ansible.builtin.command: docker ps -q
register: container_ids
changed_when: false
failed_when: false
tags: [containers]
- name: Audit container privileges
ansible.builtin.shell: |
set -o pipefail
docker inspect {{ container_ids.stdout_lines | join(' ') }} --format '{% raw %}{{.Name}}: Privileged={{.HostConfig.Privileged}}{% endraw %}' 2>/dev/null || echo "No containers"
args:
executable: /bin/bash
register: container_privileges
changed_when: false
when: container_ids.stdout_lines | length > 0
tags: [containers, privileges]
- name: Check user namespace remapping
ansible.builtin.shell: |
docker info --format '{% raw %}{{ .SecurityOptions }}{% endraw %}' | grep -i userns || echo "Not configured"
register: userns_check
changed_when: false
failed_when: false
tags: [containers, namespaces]
- name: Audit security profiles (AppArmor/SELinux)
ansible.builtin.shell: |
set -o pipefail
docker inspect {{ container_ids.stdout_lines | join(' ') }} --format '{% raw %}{{.Name}}: AppArmor={{.AppArmorProfile}} SELinux={{.HostConfig.SecurityOpt}}{% endraw %}' 2>/dev/null || echo "No containers"
args:
executable: /bin/bash
register: security_profiles
changed_when: false
when: container_ids.stdout_lines | length > 0
tags: [containers, profiles]
- name: Check network modes
ansible.builtin.shell: |
set -o pipefail
docker inspect {{ container_ids.stdout_lines | join(' ') }} --format '{% raw %}{{.Name}}: NetworkMode={{.HostConfig.NetworkMode}}{% endraw %}' 2>/dev/null || echo "No containers"
args:
executable: /bin/bash
register: network_modes
changed_when: false
when: container_ids.stdout_lines | length > 0
tags: [containers, network]
- name: Check resource limits
ansible.builtin.shell: |
set -o pipefail
docker inspect {{ container_ids.stdout_lines | join(' ') }} --format '{% raw %}{{.Name}}: Memory={{.HostConfig.Memory}} CPU={{.HostConfig.CpuShares}}{% endraw %}' 2>/dev/null || echo "No containers"
args:
executable: /bin/bash
register: resource_limits
changed_when: false
when: container_ids.stdout_lines | length > 0
tags: [containers, resources]
- name: Check for exposed ports
ansible.builtin.shell: |
docker ps --format "{% raw %}{{.Names}}: {{.Ports}}{% endraw %}"
register: exposed_ports
changed_when: false
tags: [containers, ports]
- name: Check container capabilities
ansible.builtin.shell: |
set -o pipefail
docker inspect {{ container_ids.stdout_lines | join(' ') }} --format '{% raw %}{{.Name}}: CapAdd={{.HostConfig.CapAdd}} CapDrop={{.HostConfig.CapDrop}}{% endraw %}' 2>/dev/null || echo "No containers"
args:
executable: /bin/bash
register: container_capabilities
changed_when: false
when: container_ids.stdout_lines | length > 0
tags: [containers, capabilities]
- name: Check container restart policies
ansible.builtin.shell: |
set -o pipefail
docker inspect {{ container_ids.stdout_lines | join(' ') }} --format '{% raw %}{{.Name}}: RestartPolicy={{.HostConfig.RestartPolicy.Name}}{% endraw %}' 2>/dev/null || echo "No containers"
args:
executable: /bin/bash
register: restart_policies
changed_when: false
when: container_ids.stdout_lines | length > 0
tags: [containers]
# ==========================================================================
# Image Audit
# ==========================================================================
- name: List all Docker images
ansible.builtin.command: docker images --format json
register: docker_images_raw
changed_when: false
tags: [images]
- name: Check for images with latest tag
ansible.builtin.shell: |
docker images --format "{% raw %}{{.Repository}}:{{.Tag}}{% endraw %}" | grep -c ":latest" || echo "0"
register: latest_tag_count
changed_when: false
tags: [images]
# ==========================================================================
# Network Audit
# ==========================================================================
- name: List Docker networks
ansible.builtin.command: docker network ls --format json
register: docker_networks_raw
changed_when: false
tags: [network]
- name: Check Docker storage driver
ansible.builtin.set_fact:
storage_driver: "{{ docker_info.Driver | default('unknown') }}"
tags: [storage]
# ==========================================================================
# Security Findings Analysis
# ==========================================================================
- name: Analyze security findings
ansible.builtin.set_fact:
security_findings:
critical: []
high: []
medium: []
low: []
tags: [analysis]
- name: Check for privileged containers (CRITICAL)
ansible.builtin.set_fact:
security_findings: "{{ security_findings | combine({'critical': security_findings.critical + ['Privileged containers detected']}) }}"
when:
- container_privileges.stdout is defined
- "'Privileged=true' in container_privileges.stdout"
tags: [analysis]
- name: Check for host network mode (HIGH)
ansible.builtin.set_fact:
security_findings: "{{ security_findings | combine({'high': security_findings.high + ['Containers using host network mode']}) }}"
when:
- network_modes.stdout is defined
- "'NetworkMode=host' in network_modes.stdout"
tags: [analysis]
- name: Check for missing user namespace remapping (MEDIUM)
ansible.builtin.set_fact:
security_findings: "{{ security_findings | combine({'medium': security_findings.medium + ['User namespace remapping not configured']}) }}"
when: "'userns' not in userns_check.stdout"
tags: [analysis]
- name: Check for unlimited resources (MEDIUM)
ansible.builtin.set_fact:
security_findings: "{{ security_findings | combine({'medium': security_findings.medium + ['Containers without resource limits']}) }}"
when:
- resource_limits.stdout is defined
- "'Memory=0' in resource_limits.stdout"
tags: [analysis]
- name: Check for latest image tags (LOW)
ansible.builtin.set_fact:
security_findings: "{{ security_findings | combine({'low': security_findings.low + ['Images using :latest tag (' + latest_tag_count.stdout + ')']}) }}"
when: latest_tag_count.stdout | int > 0
tags: [analysis]
# ==========================================================================
# Generate Audit Report
# ==========================================================================
- name: Generate audit report from template
ansible.builtin.template:
src: ../templates/docker_audit_report.j2
dest: "{{ audit_output_dir }}/{{ inventory_hostname }}/docker_audit_{{ audit_timestamp }}.txt"
mode: '0644'
delegate_to: localhost
become: false
tags: [report]
- name: Generate JSON report
ansible.builtin.copy:
content: |
{
"timestamp": "{{ ansible_date_time.iso8601 }}",
"host": "{{ inventory_hostname }}",
"docker_version": "{{ docker_version.stdout }}",
"security_options": {{ docker_security_options | to_json }},
"containers": {
"total": {{ container_ids.stdout_lines | length }},
"privileged": {{ (container_privileges.stdout | default('') | regex_findall('Privileged=true')) | length }},
"host_network": {{ (network_modes.stdout | default('') | regex_findall('NetworkMode=host')) | length }}
},
"findings": {{ security_findings | to_json }},
"storage_driver": "{{ storage_driver }}"
}
dest: "{{ audit_output_dir }}/{{ inventory_hostname }}/docker_audit_{{ audit_timestamp }}.json"
mode: '0644'
delegate_to: localhost
become: false
tags: [report]
# ==========================================================================
# Display Results
# ==========================================================================
- name: Display audit summary
ansible.builtin.debug:
msg:
- "=== Docker Security Audit Summary ==="
- "Host: {{ inventory_hostname }}"
- "Docker Version: {{ docker_version.stdout }}"
- "Running Containers: {{ container_ids.stdout_lines | length }}"
- "Security Options: {{ docker_security_options }}"
- "Storage Driver: {{ storage_driver }}"
- ""
- "Security Findings:"
- " CRITICAL: {{ security_findings.critical | length }}"
- " HIGH: {{ security_findings.high | length }}"
- " MEDIUM: {{ security_findings.medium | length }}"
- " LOW: {{ security_findings.low | length }}"
- ""
- "Report saved to: {{ audit_output_dir }}/{{ inventory_hostname }}/"
tags: [always]

View File

@@ -0,0 +1,303 @@
================================================================================
DOCKER SECURITY AUDIT REPORT
================================================================================
Host: {{ inventory_hostname }}
Date: {{ ansible_date_time.iso8601 }}
Auditor: Ansible Automation Platform
Report ID: {{ audit_timestamp }}
================================================================================
SYSTEM INFORMATION
----------------------------------------
Hostname: {{ ansible_hostname }}
FQDN: {{ ansible_fqdn | default('N/A') }}
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
Kernel: {{ ansible_kernel }}
Architecture: {{ ansible_architecture }}
DOCKER INFORMATION
----------------------------------------
Version: {{ docker_version.stdout }}
Storage Driver: {{ storage_driver }}
Security Options: {{ docker_security_options | join(', ') if docker_security_options else 'None configured' }}
Daemon Config File: {{ 'Exists' if daemon_config_stat.stat.exists else 'Not found' }}
{% if daemon_config_stat.stat.exists and docker_daemon_config.content is defined %}
Daemon Configuration:
{{ docker_daemon_config.content | b64decode | indent(2) }}
{% endif %}
CONTAINER INVENTORY
----------------------------------------
Running Containers: {{ container_ids.stdout_lines | length }}
{% if container_ids.stdout_lines | length > 0 %}
Container List:
{{ running_containers | map(attribute='Names') | join('\n') | indent(2) }}
{% else %}
No containers running
{% endif %}
SECURITY AUDIT RESULTS
========================================
PRIVILEGE AUDIT
----------------------------------------
{% if container_privileges.stdout is defined %}
{{ container_privileges.stdout }}
{% else %}
No containers to audit
{% endif %}
USER NAMESPACE REMAPPING
----------------------------------------
Status: {{ userns_check.stdout }}
SECURITY PROFILES (AppArmor/SELinux)
----------------------------------------
{% if security_profiles.stdout is defined %}
{{ security_profiles.stdout }}
{% else %}
No containers to audit
{% endif %}
NETWORK CONFIGURATION
----------------------------------------
{% if network_modes.stdout is defined %}
{{ network_modes.stdout }}
{% else %}
No containers to audit
{% endif %}
RESOURCE LIMITS
----------------------------------------
{% if resource_limits.stdout is defined %}
{{ resource_limits.stdout }}
{% else %}
No containers to audit
{% endif %}
CONTAINER CAPABILITIES
----------------------------------------
{% if container_capabilities.stdout is defined %}
{{ container_capabilities.stdout }}
{% else %}
No containers to audit
{% endif %}
RESTART POLICIES
----------------------------------------
{% if restart_policies.stdout is defined %}
{{ restart_policies.stdout }}
{% else %}
No containers to audit
{% endif %}
EXPOSED PORTS
----------------------------------------
{{ exposed_ports.stdout }}
IMAGE ANALYSIS
----------------------------------------
Total Images: {{ docker_images_raw.stdout_lines | length }}
Images using :latest tag: {{ latest_tag_count.stdout }}
WARNING: Using :latest tag is not recommended for production as it makes
deployments non-reproducible and can lead to unexpected updates.
NETWORK ANALYSIS
----------------------------------------
Networks: {{ docker_networks_raw.stdout_lines | length }}
SECURITY FINDINGS
========================================
{% if security_findings.critical | length > 0 %}
🔴 CRITICAL FINDINGS ({{ security_findings.critical | length }})
----------------------------------------
{% for finding in security_findings.critical %}
- {{ finding }}
{% endfor %}
{% endif %}
{% if security_findings.high | length > 0 %}
🟠 HIGH SEVERITY FINDINGS ({{ security_findings.high | length }})
----------------------------------------
{% for finding in security_findings.high %}
- {{ finding }}
{% endfor %}
{% endif %}
{% if security_findings.medium | length > 0 %}
🟡 MEDIUM SEVERITY FINDINGS ({{ security_findings.medium | length }})
----------------------------------------
{% for finding in security_findings.medium %}
- {{ finding }}
{% endfor %}
{% endif %}
{% if security_findings.low | length > 0 %}
🟢 LOW SEVERITY FINDINGS ({{ security_findings.low | length }})
----------------------------------------
{% for finding in security_findings.low %}
- {{ finding }}
{% endfor %}
{% endif %}
{% if security_findings.critical | length == 0 and security_findings.high | length == 0 and security_findings.medium | length == 0 and security_findings.low | length == 0 %}
✅ NO SECURITY FINDINGS
----------------------------------------
No significant security issues detected.
{% endif %}
RECOMMENDATIONS
========================================
CRITICAL PRIORITY
----------------------------------------
{% if container_privileges.stdout is defined and 'Privileged=true' in container_privileges.stdout %}
1. ⚠️ DISABLE PRIVILEGED MODE
- Privileged containers have full access to host resources
- Remove --privileged flag unless absolutely necessary
- Use specific capabilities (--cap-add) instead
- Document justification for any privileged containers
{% endif %}
{% if network_modes.stdout is defined and 'NetworkMode=host' in network_modes.stdout %}
2. ⚠️ AVOID HOST NETWORK MODE
- Host network mode bypasses Docker network isolation
- Use bridge mode and explicit port mappings
- Consider using macvlan for performance-critical applications
{% endif %}
HIGH PRIORITY
----------------------------------------
3. IMPLEMENT USER NAMESPACE REMAPPING
- Add to /etc/docker/daemon.json:
{
"userns-remap": "default"
}
- Restart Docker daemon after configuration change
- Note: Existing containers will need to be recreated
4. ENFORCE RESOURCE LIMITS
- Set memory limits: --memory="512m"
- Set CPU limits: --cpus="1.0"
- Prevents container resource exhaustion attacks
- Example:
docker run --memory="512m" --cpus="1.0" image:tag
5. USE SECURITY PROFILES
- Enable AppArmor (Debian/Ubuntu):
--security-opt apparmor=docker-default
- Enable SELinux (RHEL/CentOS):
--security-opt label=type:container_t
- Create custom profiles for sensitive containers
MEDIUM PRIORITY
----------------------------------------
6. DROP UNNECESSARY CAPABILITIES
- Drop all by default: --cap-drop=ALL
- Add only required capabilities:
--cap-add=NET_BIND_SERVICE (for ports < 1024)
--cap-add=CHOWN (for ownership changes)
- Never use --cap-add=ALL
7. USE SPECIFIC IMAGE TAGS
- Replace :latest with specific version tags
- Ensures reproducible deployments
- Facilitates rollback procedures
- Example: nginx:1.25.3-alpine instead of nginx:latest
8. MINIMIZE EXPOSED PORTS
- Only expose necessary ports
- Use internal networks for container-to-container communication
- Consider using reverse proxy (Traefik, nginx) for public access
9. IMPLEMENT READ-ONLY ROOT FILESYSTEMS
- Use --read-only flag when possible
- Mount tmpfs for writable directories:
--tmpfs /tmp --tmpfs /var/run
10. ENABLE DOCKER CONTENT TRUST
- Set environment variable:
export DOCKER_CONTENT_TRUST=1
- Ensures images are signed and verified
- Prevents use of tampered images
LOW PRIORITY
----------------------------------------
11. REGULAR IMAGE UPDATES
- Schedule regular image pulls and container recreation
- Subscribe to security advisories for base images
- Consider using automated tools: Watchtower, Renovate
12. IMPLEMENT LOGGING
- Configure centralized logging
- Use logging drivers: syslog, json-file, etc.
- Set log rotation limits to prevent disk exhaustion
13. NETWORK SEGMENTATION
- Create separate networks for different application tiers
- Use internal networks for backend services
- Implement network policies where supported
COMPLIANCE CHECKLIST
========================================
CIS Docker Benchmark Alignment:
[ ] 2.1 - Run daemon as non-root user (user namespace remapping)
[ ] 2.2 - Set default ulimit as appropriate
[ ] 2.13 - Enable user namespace support
[ ] 5.1 - Do not disable AppArmor/SELinux profile
[ ] 5.3 - Do not use privileged containers
[ ] 5.7 - Do not map privileged ports within containers
[ ] 5.12 - Mount container's root filesystem as read only
[ ] 5.15 - Do not share the host's network namespace
[ ] 5.25 - Restrict container from acquiring additional privileges
[ ] 5.28 - Use PIDs cgroup limit
NIST 800-190 Guidelines:
[ ] Image security and integrity
[ ] Registry security
[ ] Container runtime protection
[ ] Host OS and multi-tenancy
[ ] Network isolation and segmentation
NEXT STEPS
========================================
IMMEDIATE ACTIONS (This Week)
1. Review and address all CRITICAL findings
2. Document justification for any privileged containers
3. Implement resource limits on all production containers
SHORT TERM (This Month)
1. Enable user namespace remapping
2. Implement security profiles (AppArmor/SELinux)
3. Replace :latest tags with specific versions
4. Set up automated security scanning
LONG TERM (This Quarter)
1. Implement comprehensive container monitoring
2. Set up automated vulnerability scanning
3. Create hardened base images
4. Implement network segmentation policies
5. Regular security audits and penetration testing
REFERENCES
========================================
- CIS Docker Benchmark: https://www.cisecurity.org/benchmark/docker
- NIST SP 800-190: https://csrc.nist.gov/publications/detail/sp/800-190/final
- Docker Security Best Practices: https://docs.docker.com/engine/security/
- OWASP Docker Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html
================================================================================
END OF REPORT
================================================================================
Report generated: {{ ansible_date_time.iso8601 }}
Audit tool: Ansible {{ ansible_version.full }}
================================================================================