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