182 lines
7.1 KiB
YAML
182 lines
7.1 KiB
YAML
---
|
|
# playbooks/docker/heimdall_audit.yml
|
|
# Read-only OS and stack health audit for the Heimdall edge router.
|
|
# Safe to schedule. Makes no changes to any host.
|
|
#
|
|
# What this asserts:
|
|
# OS: kernel, distro, swap, swappiness, bridge netfilter, ip_forward
|
|
# Docker: log rotation configured
|
|
# Stack: traefik, redis, docker-socket-proxy containers are running
|
|
#
|
|
# Usage:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/docker/heimdall_audit.yml
|
|
#
|
|
# Output:
|
|
# outputs/heimdall_audit_<timestamp>.md (repo root)
|
|
|
|
- name: "Play 1: Gather Heimdall state"
|
|
hosts: heimdall
|
|
become: true
|
|
gather_facts: true
|
|
|
|
tasks:
|
|
- name: Read sysctl values
|
|
ansible.builtin.shell: "sysctl -n {{ item }} 2>/dev/null || echo 0"
|
|
register: sysctl_raw
|
|
loop:
|
|
- vm.swappiness
|
|
- net.bridge.bridge-nf-call-iptables
|
|
- net.bridge.bridge-nf-call-ip6tables
|
|
- net.ipv4.ip_forward
|
|
changed_when: false
|
|
check_mode: false
|
|
|
|
- name: Read Docker daemon.json
|
|
ansible.builtin.command: cat /etc/docker/daemon.json
|
|
register: daemon_json_content
|
|
changed_when: false
|
|
failed_when: false
|
|
check_mode: false
|
|
|
|
- name: Get running container names
|
|
ansible.builtin.command: >
|
|
docker ps --format '{{ '{{' }}.Names{{ '}}' }}'
|
|
register: running_containers
|
|
changed_when: false
|
|
failed_when: false
|
|
check_mode: false
|
|
|
|
- name: Stash audit facts
|
|
ansible.builtin.set_fact:
|
|
heimdall_audit:
|
|
kernel: "{{ ansible_kernel }}"
|
|
distro: "{{ ansible_distribution }}"
|
|
distro_version: "{{ ansible_distribution_version }}"
|
|
swap_mb: "{{ ansible_swaptotal_mb }}"
|
|
swappiness: "{{ (sysctl_raw.results | selectattr('item', 'equalto', 'vm.swappiness') | first).stdout | trim }}"
|
|
bridge_iptables: "{{ (sysctl_raw.results | selectattr('item', 'equalto', 'net.bridge.bridge-nf-call-iptables') | first).stdout | trim }}"
|
|
bridge_ip6tables: "{{ (sysctl_raw.results | selectattr('item', 'equalto', 'net.bridge.bridge-nf-call-ip6tables') | first).stdout | trim }}"
|
|
ip_forward: "{{ (sysctl_raw.results | selectattr('item', 'equalto', 'net.ipv4.ip_forward') | first).stdout | trim }}"
|
|
log_rotation_configured: "{{ 'max-size' in (daemon_json_content.stdout | default('{}')) }}"
|
|
running_containers: "{{ running_containers.stdout_lines | default([]) }}"
|
|
traefik_running: "{{ running_containers.stdout_lines | default([]) | select('search', 'traefik') | list | length > 0 }}"
|
|
redis_running: "{{ running_containers.stdout_lines | default([]) | select('search', 'redis') | list | length > 0 }}"
|
|
socket_proxy_running: "{{ running_containers.stdout_lines | default([]) | select('search', 'socket-proxy|socketproxy|docker-socket') | list | length > 0 }}"
|
|
|
|
|
|
- name: "Play 2: Assertions and drift report"
|
|
hosts: localhost
|
|
gather_facts: false
|
|
|
|
vars:
|
|
audit_timestamp: "{{ lookup('pipe', 'date +%Y%m%dT%H%M%S') }}"
|
|
report_path: "{{ playbook_dir }}/../../../outputs/heimdall_audit_{{ audit_timestamp }}.md"
|
|
h: "{{ hostvars['heimdall']['heimdall_audit'] }}"
|
|
|
|
tasks:
|
|
- name: Ensure outputs directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ playbook_dir }}/../../../outputs"
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: Write drift report
|
|
ansible.builtin.copy:
|
|
dest: "{{ report_path }}"
|
|
mode: '0644'
|
|
content: |
|
|
# Heimdall Edge Router Audit Report
|
|
|
|
Generated: {{ audit_timestamp }}
|
|
|
|
## System
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| Kernel | `{{ h.kernel }}` |
|
|
| Distro | {{ h.distro }} {{ h.distro_version }} |
|
|
| Swap | {{ h.swap_mb }}MB |
|
|
|
|
## Sysctl
|
|
|
|
| Parameter | Value | Expected |
|
|
|-----------|-------|----------|
|
|
| vm.swappiness | {{ h.swappiness }} | 0 |
|
|
| net.bridge.bridge-nf-call-iptables | {{ h.bridge_iptables }} | 1 |
|
|
| net.bridge.bridge-nf-call-ip6tables | {{ h.bridge_ip6tables }} | 1 |
|
|
| net.ipv4.ip_forward | {{ h.ip_forward }} | 1 |
|
|
|
|
## Docker
|
|
|
|
| Check | Status |
|
|
|-------|--------|
|
|
| Log rotation configured | {{ '✅' if h.log_rotation_configured | bool else '❌' }} |
|
|
|
|
## Stack Health
|
|
|
|
| Container | Status |
|
|
|-----------|--------|
|
|
| traefik | {{ '✅ running' if h.traefik_running | bool else '❌ not running' }} |
|
|
| redis | {{ '✅ running' if h.redis_running | bool else '❌ not running' }} |
|
|
| docker-socket-proxy | {{ '✅ running' if h.socket_proxy_running | bool else '❌ not running' }} |
|
|
|
|
## Running Containers
|
|
|
|
{% for c in h.running_containers %}
|
|
- {{ c }}
|
|
{% endfor %}
|
|
|
|
- name: Assert swap is disabled
|
|
ansible.builtin.assert:
|
|
that: h.swap_mb | int == 0
|
|
fail_msg: "❌ Swap enabled: {{ h.swap_mb }}MB — run heimdall_baseline.yml --tags storage"
|
|
success_msg: "✅ Heimdall: swap disabled"
|
|
|
|
- name: Assert vm.swappiness=0
|
|
ansible.builtin.assert:
|
|
that: h.swappiness | int == 0
|
|
fail_msg: "❌ vm.swappiness={{ h.swappiness }} — run heimdall_baseline.yml --tags sysctl"
|
|
success_msg: "✅ Heimdall: vm.swappiness=0"
|
|
|
|
- name: Assert bridge netfilter enabled
|
|
ansible.builtin.assert:
|
|
that:
|
|
- h.bridge_iptables | int == 1
|
|
- h.bridge_ip6tables | int == 1
|
|
fail_msg: >-
|
|
❌ Bridge netfilter not fully enabled:
|
|
bridge-nf-call-iptables={{ h.bridge_iptables }}
|
|
bridge-nf-call-ip6tables={{ h.bridge_ip6tables }}
|
|
Run heimdall_baseline.yml --tags sysctl.
|
|
success_msg: "✅ Heimdall: bridge netfilter enabled"
|
|
|
|
- name: Assert ip_forward enabled
|
|
ansible.builtin.assert:
|
|
that: h.ip_forward | int == 1
|
|
fail_msg: "❌ net.ipv4.ip_forward={{ h.ip_forward }} — run heimdall_baseline.yml --tags sysctl"
|
|
success_msg: "✅ Heimdall: ip_forward=1"
|
|
|
|
- name: Assert Docker log rotation configured
|
|
ansible.builtin.assert:
|
|
that: h.log_rotation_configured | bool
|
|
fail_msg: "❌ Docker log rotation not configured — run heimdall_baseline.yml --tags docker"
|
|
success_msg: "✅ Heimdall: Docker log rotation configured"
|
|
|
|
- name: Assert Traefik container is running
|
|
ansible.builtin.assert:
|
|
that: h.traefik_running | bool
|
|
fail_msg: "❌ Traefik container is not running — check: docker ps -a | grep traefik"
|
|
success_msg: "✅ Heimdall: Traefik running"
|
|
|
|
- name: Assert Redis container is running
|
|
ansible.builtin.assert:
|
|
that: h.redis_running | bool
|
|
fail_msg: "❌ Redis container is not running — check: docker ps -a | grep redis"
|
|
success_msg: "✅ Heimdall: Redis running"
|
|
|
|
- name: Assert docker-socket-proxy container is running
|
|
ansible.builtin.assert:
|
|
that: h.socket_proxy_running | bool
|
|
fail_msg: "❌ docker-socket-proxy container is not running — check: docker ps -a | grep socket"
|
|
success_msg: "✅ Heimdall: docker-socket-proxy running"
|