Files
infra-automation/playbooks/audit_docker.yml
ansible da1da34d25 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>
2025-11-11 07:47:06 +01:00

326 lines
12 KiB
YAML

---
# ==============================================================================
# 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]