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).
105 lines
3.3 KiB
YAML
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]
|