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:
@@ -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]
|
||||||
@@ -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 }}
|
||||||
|
================================================================================
|
||||||
Reference in New Issue
Block a user