boma/roles/base/tasks/firewall.yml
sjat 402913efb3 fix(base): make rollback snapshot restorable (flush-prefixed)
Bare 'nft list ruleset' has no leading flush, so the timer's 'nft -f rollback'
was a no-op on first apply (empty file) and errored ('table exists') on later
applies — the auto-rollback silently did nothing, defeating the askari lockout
safeguard. Prepend 'flush ruleset' so the revert is atomic + self-contained.
Verified the snapshot->lockout->revert round-trip in an isolated netns.
Also fix stale STATUS prose (base is partially built, not absent).
2026-06-06 19:15:38 +02:00

105 lines
3.3 KiB
YAML

---
- name: Install nftables
ansible.builtin.apt:
name: nftables
state: present
tags: [firewall]
- name: Ensure nftables drop-in dir exists
ansible.builtin.file:
path: "{{ base__firewall_dropin_dir }}"
state: directory
mode: "0755"
tags: [firewall]
- name: Resolve firewall ingress rules for this host
ansible.builtin.set_fact:
base__firewall_resolved: >-
{{ firewall_catalog | default({})
| resolve_firewall_rules(firewall_zones | default({}),
inventory_hostname, hostvars, groups) }}
tags: [firewall]
- name: Render nftables ruleset (syntax-checked before install)
ansible.builtin.template:
src: nftables.conf.j2
dest: /etc/nftables.conf
mode: "0644"
validate: "nft -c -f %s"
register: base__firewall_render
tags: [firewall]
- name: Apply firewall ruleset safely (with auto-rollback)
when:
- base__firewall_apply | bool
- base__firewall_render is changed
tags: [firewall]
block:
- name: Snapshot the current ruleset as the rollback point
# Prepend `flush ruleset` so the revert is atomic and self-contained: on a
# first-ever apply the snapshot is just `flush ruleset` (reverts to an empty,
# kernel-default-accept state → reachable); on later applies it avoids
# "table exists" errors when replayed. Without the flush the rollback is a no-op.
ansible.builtin.shell: "{ echo 'flush ruleset'; nft list ruleset; } > /etc/nftables.rollback"
changed_when: false
- name: Stop stale rollback timer unit
ansible.builtin.systemd:
name: "{{ item }}"
state: stopped
loop:
- nft-rollback.timer
- nft-rollback.service
failed_when: false
changed_when: false
- name: Reset failed state on stale rollback units
ansible.builtin.command: systemctl reset-failed {{ item }}
loop:
- nft-rollback.timer
- nft-rollback.service
failed_when: false
changed_when: false
- name: Arm the auto-rollback timer
ansible.builtin.command:
cmd: >-
systemd-run --on-active={{ base__firewall_rollback_timeout }}
--unit=nft-rollback /usr/sbin/nft -f /etc/nftables.rollback
changed_when: true
- name: Apply the new ruleset
ansible.builtin.command: nft -f /etc/nftables.conf
changed_when: true
- name: Drop the persistent connection so the confirm uses a fresh one
ansible.builtin.meta: reset_connection
- name: Confirm a NEW connection survives the applied ruleset
ansible.builtin.wait_for_connection:
timeout: "{{ base__firewall_confirm_timeout }}"
- name: Stop the rollback timer after connectivity confirmed
ansible.builtin.systemd:
name: "{{ item }}"
state: stopped
loop:
- nft-rollback.timer
- nft-rollback.service
failed_when: false
changed_when: false
- name: Reset failed state on rollback units after disarm
ansible.builtin.command: systemctl reset-failed {{ item }}
loop:
- nft-rollback.timer
- nft-rollback.service
failed_when: false
changed_when: false
- name: Enable nftables.service so the ruleset persists across reboot
ansible.builtin.systemd:
name: nftables
enabled: true
when: base__firewall_apply | bool
tags: [firewall]