From cfe87002c55ccd9123b3e9f8709709b646c69971 Mon Sep 17 00:00:00 2001 From: Ruakij Date: Wed, 7 Oct 2026 07:29:29 +0000 Subject: [PATCH] Replace the nftables role with host_firewall, one atomic inet host input table with zones, geo sets and lockout rollback --- host_firewall/defaults/main.yml | 36 ++++ host_firewall/files/nft-host-geo.service | 8 + host_firewall/files/nft-host-geo.timer | 10 ++ host_firewall/files/nft-host-rollback | 9 + host_firewall/files/nft-host.service | 22 +++ host_firewall/tasks/main.yml | 168 ++++++++++++++++++ host_firewall/templates/host.nft.j2 | 118 ++++++++++++ host_firewall/templates/nft-host-geo.j2 | 46 +++++ nftables/defaults/nftables.yml | 29 --- nftables/tasks/apply-files.yml | 8 - nftables/tasks/deploy-rules/main.yml | 11 -- .../deploy-rules/per-group-template-file.yml | 51 ------ .../tasks/deploy-rules/per-group-template.yml | 48 ----- nftables/tasks/install-package.yml | 4 - nftables/tasks/main.yml | 7 - nftables/tasks/prerequisites.yml | 13 -- nftables/tasks/remove-files.yml | 21 --- nftables/tasks/remove-per-group.yml | 20 --- nftables/tasks/setup-packages.yml | 15 -- 19 files changed, 417 insertions(+), 227 deletions(-) create mode 100644 host_firewall/defaults/main.yml create mode 100644 host_firewall/files/nft-host-geo.service create mode 100644 host_firewall/files/nft-host-geo.timer create mode 100644 host_firewall/files/nft-host-rollback create mode 100644 host_firewall/files/nft-host.service create mode 100644 host_firewall/tasks/main.yml create mode 100644 host_firewall/templates/host.nft.j2 create mode 100644 host_firewall/templates/nft-host-geo.j2 delete mode 100644 nftables/defaults/nftables.yml delete mode 100644 nftables/tasks/apply-files.yml delete mode 100644 nftables/tasks/deploy-rules/main.yml delete mode 100644 nftables/tasks/deploy-rules/per-group-template-file.yml delete mode 100644 nftables/tasks/deploy-rules/per-group-template.yml delete mode 100644 nftables/tasks/install-package.yml delete mode 100644 nftables/tasks/main.yml delete mode 100644 nftables/tasks/prerequisites.yml delete mode 100644 nftables/tasks/remove-files.yml delete mode 100644 nftables/tasks/remove-per-group.yml delete mode 100644 nftables/tasks/setup-packages.yml diff --git a/host_firewall/defaults/main.yml b/host_firewall/defaults/main.yml new file mode 100644 index 0000000..5f396e5 --- /dev/null +++ b/host_firewall/defaults/main.yml @@ -0,0 +1,36 @@ +--- +# Port and address maps are name -> value so group_vars and host_vars levels +# merge into one map (hash_behaviour = merge); a null value drops an entry. +# Ports are numbers or "first-last" ranges. +firewall: + public: + tcp: {} + udp: {} + # Accepted only from the geo sets or the admin set. + geo: + tcp: {} + # Accepted on wt0 before the public zone is evaluated. + vpn: + tcp: {} + udp: {} + admin: + v4: {} + v6: {} + # Interface names (nft wildcards allowed) whose traffic is trusted. + internal_ifaces: {} + # Guest bridges (Proxmox vmbr*): DHCP, router solicitations and the public + # zone only, never the geo ports. + guest_ifaces: {} + # Kubernetes ring: pod interfaces, and node NetBird IPs / pod CIDRs on wt0. + cluster: + ifaces: {} + v4: {} + v6: {} + ssh_rate_limit: "10/minute burst 5 packets" + # EU-27 plus CH, NO, GB, IS, LI (ipdeny country codes). Empty leaves the geo + # ports to the admin set; a load peaks at about 60 MB of kernel memory, too much for a 1 GB host. + geo_countries: [at, be, bg, hr, cy, cz, dk, ee, fi, fr, de, gr, hu, ie, it, lv, lt, lu, mt, nl, pl, pt, ro, sk, si, es, se, ch, "no", gb, is, li] + rollback_seconds: 120 + # name -> shell command that has to exit 0 through the new rule set before the + # rollback is disarmed; retried for a quarter of the rollback window. + confirm: {} diff --git a/host_firewall/files/nft-host-geo.service b/host_firewall/files/nft-host-geo.service new file mode 100644 index 0000000..4122a92 --- /dev/null +++ b/host_firewall/files/nft-host-geo.service @@ -0,0 +1,8 @@ +[Unit] +Description=Refresh the geo sets of the host firewall +Wants=network-online.target +After=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/nft-host-geo diff --git a/host_firewall/files/nft-host-geo.timer b/host_firewall/files/nft-host-geo.timer new file mode 100644 index 0000000..f5a7854 --- /dev/null +++ b/host_firewall/files/nft-host-geo.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Daily refresh of the host firewall geo sets + +[Timer] +OnCalendar=daily +RandomizedDelaySec=6h +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/host_firewall/files/nft-host-rollback b/host_firewall/files/nft-host-rollback new file mode 100644 index 0000000..b3dd627 --- /dev/null +++ b/host_firewall/files/nft-host-rollback @@ -0,0 +1,9 @@ +#!/bin/sh +# Started by a transient timer before a rule set load; Ansible stops the timer +# once the new rule set is confirmed. +cd /etc/nftables.d || exit 1 +mv -f host.nft host.nft.rolled-back +if [ -s host.nft.good ]; then + cp host.nft.good host.nft && nft -f host.nft && exit 0 +fi +nft delete table inet host diff --git a/host_firewall/files/nft-host.service b/host_firewall/files/nft-host.service new file mode 100644 index 0000000..6d63ef2 --- /dev/null +++ b/host_firewall/files/nft-host.service @@ -0,0 +1,22 @@ +[Unit] +Description=Host input firewall (nftables table inet host) +# Same early-boot placement as nftables.service. Its "flush ruleset" on start, +# reload and stop would wipe this table, so load after it and follow all three. +DefaultDependencies=no +Wants=network-pre.target +Before=network-pre.target shutdown.target +After=nftables.service +PartOf=nftables.service +ReloadPropagatedFrom=nftables.service +Conflicts=shutdown.target +ConditionPathExists=/etc/nftables.d/host.nft + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/sbin/nft -f /etc/nftables.d/host.nft +ExecReload=/usr/sbin/nft -f /etc/nftables.d/host.nft +ExecStop=-/usr/sbin/nft delete table inet host + +[Install] +WantedBy=sysinit.target diff --git a/host_firewall/tasks/main.yml b/host_firewall/tasks/main.yml new file mode 100644 index 0000000..42bd69f --- /dev/null +++ b/host_firewall/tasks/main.yml @@ -0,0 +1,168 @@ +--- +- name: Install nftables and curl + ansible.builtin.apt: + name: [nftables, curl] + state: present + +- name: Check that this node is part of its own cluster ring + ansible.builtin.assert: + that: ansible_wt0.ipv4.address in (firewall.cluster.v4 | dict2items | map(attribute='value') | list) + fail_msg: "wt0 address {{ ansible_wt0.ipv4.address }} is missing from firewall.cluster.v4" + when: firewall.cluster.v4 | default({}, true) | length > 0 and ansible_wt0.ipv4.address is defined + +- name: Remove the include block of the old nftables role + ansible.builtin.blockinfile: + path: /etc/nftables.conf + marker: "# {mark} ANSIBLE MANAGED BLOCK - nftables" + state: absent + +- name: Remove the rule files of the old nftables role + ansible.builtin.file: + path: /etc/nftables/ansible-managed + state: absent + +- name: Create /etc/nftables.d + ansible.builtin.file: + path: /etc/nftables.d + state: directory + mode: "0755" + +- name: Install the systemd units + ansible.builtin.copy: + src: "{{ item }}" + dest: "/etc/systemd/system/{{ item }}" + mode: "0644" + loop: [nft-host.service, nft-host-geo.service, nft-host-geo.timer] + register: firewall_units + +- name: Install the rollback script + ansible.builtin.copy: + src: nft-host-rollback + dest: /usr/local/sbin/nft-host-rollback + mode: "0755" + +- name: Geo sets + when: firewall.geo_countries | length > 0 + block: + - name: Install the geo set updater + ansible.builtin.template: + src: nft-host-geo.j2 + dest: /usr/local/sbin/nft-host-geo + mode: "0755" + + - name: Build the geo sets on first run + ansible.builtin.command: /usr/local/sbin/nft-host-geo + args: + creates: /etc/nftables.d/host-geo.nft + + - name: Read the geo sets file + ansible.builtin.stat: + path: /etc/nftables.d/host-geo.nft + register: firewall_geo + + # Without geo sets the public SSH port would only answer NetBird and the admin set. + - name: Require non-empty geo sets + ansible.builtin.assert: + that: firewall_geo.stat.exists and firewall_geo.stat.size > 0 + fail_msg: /etc/nftables.d/host-geo.nft is missing or empty, refusing to apply the firewall + # Check mode neither installs the updater nor builds the sets. + when: not ansible_check_mode + + - name: Enable the geo set timer + ansible.builtin.systemd: + name: nft-host-geo.timer + enabled: true + state: started + daemon_reload: "{{ firewall_units is changed }}" + # Check mode does not install the unit. + ignore_errors: "{{ ansible_check_mode }}" + +- name: Stop the geo set refresh + when: firewall.geo_countries | length == 0 + block: + - name: Disable the geo set timer + ansible.builtin.systemd: + name: nft-host-geo.timer + enabled: false + state: stopped + daemon_reload: "{{ firewall_units is changed }}" + + - name: Remove the geo sets file + ansible.builtin.file: + path: /etc/nftables.d/host-geo.nft + state: absent + +- name: Render the rule set + ansible.builtin.template: + src: host.nft.j2 + dest: /etc/nftables.d/host.nft + mode: "0644" + validate: nft -c -f %s + register: firewall_rules + +- name: Read the firewall unit state + ansible.builtin.command: systemctl is-active nft-host.service + register: firewall_active + changed_when: false + failed_when: false + check_mode: false + +- name: Apply the rule set with a rollback timer + when: + - firewall_rules is changed or firewall_units is changed or firewall_active.stdout != 'active' + - not ansible_check_mode + block: + # A failed run of the transient unit keeps its name taken for systemd-run. + - name: Clear a leftover rollback timer + ansible.builtin.shell: >- + systemctl stop nft-host-rollback.timer; + systemctl reset-failed nft-host-rollback.timer nft-host-rollback.service; + true + changed_when: false + + # Unless disarmed below, this moves the rejected rule file aside so a reboot + # does not load it again, and reloads the last confirmed one; without one + # it drops the table. + - name: Arm the rollback + ansible.builtin.command: systemd-run --on-active={{ firewall.rollback_seconds }} --unit=nft-host-rollback /usr/local/sbin/nft-host-rollback + changed_when: false + + - name: Load the rule set + ansible.builtin.systemd: + name: nft-host.service + enabled: true + state: reloaded + daemon_reload: true + + # A reused SSH control socket would hide a lockout, so test a fresh connection. + - name: Drop the SSH connection + ansible.builtin.meta: reset_connection + + - name: Reconnect through the new rule set + ansible.builtin.wait_for_connection: + timeout: "{{ (firewall.rollback_seconds / 2) | int }}" + + - name: Check the services behind the new rule set + ansible.builtin.shell: "{{ item.value }}" + loop: "{{ firewall.confirm | default({}, true) | dict2items | rejectattr('value', 'none') }}" + loop_control: + label: "{{ item.key }}" + register: firewall_confirm + until: firewall_confirm.rc == 0 + retries: "{{ (firewall.rollback_seconds / 20) | int }}" + delay: 5 + changed_when: false + + # Fails when the timer already fired, so a slow check cannot pass for a rolled-back host. + - name: Disarm the rollback + ansible.builtin.shell: >- + test "$(systemctl show --property=SubState --value nft-host-rollback.timer)" = waiting && + systemctl stop nft-host-rollback.timer + changed_when: true + + - name: Keep the rule set as the last confirmed one + ansible.builtin.copy: + src: /etc/nftables.d/host.nft + dest: /etc/nftables.d/host.nft.good + remote_src: true + mode: "0644" diff --git a/host_firewall/templates/host.nft.j2 b/host_firewall/templates/host.nft.j2 new file mode 100644 index 0000000..1e43a4d --- /dev/null +++ b/host_firewall/templates/host.nft.j2 @@ -0,0 +1,118 @@ +#!/usr/sbin/nft -f +# {{ ansible_managed }} +{% macro list(map) -%} +{{ map | default({}, true) | dict2items | rejectattr('value', 'none') | map(attribute='value') | map('string') | unique | join(', ') }} +{%- endmacro %} +{% macro addr_set(name, type, map) -%} +set {{ name }} { + type {{ type }}; flags interval; auto-merge; +{% if list(map) %} + elements = { {{ list(map) }} } +{% endif %} + } +{%- endmacro %} +{% set cluster = firewall.cluster | default({}, true) %} +{% set cluster_ifaces = cluster.ifaces | default({}, true) | dict2items | rejectattr('value', 'none') | map(attribute='value') | unique | list %} +{% set internal_ifaces = firewall.internal_ifaces | default({}, true) | dict2items | rejectattr('value', 'none') | map(attribute='value') | unique | list %} +{% set guest_ifaces = firewall.guest_ifaces | default({}, true) | dict2items | rejectattr('value', 'none') | map(attribute='value') | unique | list %} +{% set guest_match = 'iifname { "' ~ (guest_ifaces | join('", "')) ~ '" }' %} + +# Declare-then-delete makes every load one atomic transaction that replaces +# only this table; Cilium, Docker, NetBird and CrowdSec tables stay untouched. +table inet host {} +delete table inet host + +{% if firewall.geo_countries %} +include "/etc/nftables.d/host-geo.nft" +{% endif %} + +table inet host { + {{ addr_set('admin_v4', 'ipv4_addr', firewall.admin.v4) }} + {{ addr_set('admin_v6', 'ipv6_addr', firewall.admin.v6) }} + {{ addr_set('cluster_v4', 'ipv4_addr', cluster.v4) }} + {{ addr_set('cluster_v6', 'ipv6_addr', cluster.v6) }} + set ssh_meter_v4 { + type ipv4_addr; flags dynamic; timeout 10m; size 65535; + } + set ssh_meter_v6 { + type ipv6_addr; flags dynamic; timeout 10m; size 65535; + } + + chain input { + type filter hook input priority filter; policy drop; + + ct state established,related accept + ct state invalid drop + iif lo accept + + icmp type { echo-request, destination-unreachable, time-exceeded, parameter-problem } accept + icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, mld-listener-query } accept + udp sport 67 udp dport 68 accept + meta nfproto ipv6 udp sport 547 udp dport 546 accept +{% if cluster_ifaces or list(cluster.v4) or list(cluster.v6) %} + + # ponytail: the cluster ring trusts every port between nodes and pods; + # per-port lists (6443, 10250, 2379-2380, 4240, 8472, ...) are the upgrade path. +{% for iface in cluster_ifaces %} + iifname "{{ iface }}" accept +{% endfor %} + iifname "wt0" ip saddr @cluster_v4 accept + iifname "wt0" ip6 saddr @cluster_v6 accept +{% endif %} +{% if internal_ifaces %} + + # ponytail: internal bridges are trusted on every port; per-port lists are the upgrade path. +{% for iface in internal_ifaces %} + iifname "{{ iface }}" accept +{% endfor %} +{% endif %} +{% if guest_ifaces %} + + # Pods on k3s guests egress through the node IP, so guest bridges get + # only DHCP and router solicitations beyond the public zone. + {{ guest_match }} udp dport 67 accept + {{ guest_match }} icmpv6 type nd-router-solicit accept +{% endif %} + + # VPN peers get the vpn ports, then everything public. + iifname "wt0" jump vpn + jump public + } + + chain vpn { +{% if list(firewall.vpn.tcp) %} + tcp dport { {{ list(firewall.vpn.tcp) }} } accept +{% endif %} +{% if list(firewall.vpn.udp) %} + udp dport { {{ list(firewall.vpn.udp) }} } accept +{% endif %} + } + + chain public { +{% if list(firewall.public.tcp) %} + tcp dport { {{ list(firewall.public.tcp) }} } accept +{% endif %} +{% if list(firewall.public.udp) %} + udp dport { {{ list(firewall.public.udp) }} } accept +{% endif %} +{% if list(firewall.geo.tcp) %} + tcp dport { {{ list(firewall.geo.tcp) }} } jump geo +{% endif %} + } + + chain geo { +{% if guest_ifaces %} + # Guest IPv6 addresses sit inside the geo sets. + {{ guest_match }} return +{% endif %} + ip saddr @admin_v4 accept + ip6 saddr @admin_v6 accept + tcp dport 22 ct state new update @ssh_meter_v4 { ip saddr limit rate over {{ firewall.ssh_rate_limit }} } drop + # One IPv6 client usually holds a whole /64. + tcp dport 22 ct state new update @ssh_meter_v6 { ip6 saddr & ffff:ffff:ffff:ffff:: limit rate over {{ firewall.ssh_rate_limit }} } drop +{% if firewall.geo_countries %} + ip saddr @geo_v4 accept + ip6 saddr @geo_v6 accept +{% endif %} + } +} diff --git a/host_firewall/templates/nft-host-geo.j2 b/host_firewall/templates/nft-host-geo.j2 new file mode 100644 index 0000000..ec8b832 --- /dev/null +++ b/host_firewall/templates/nft-host-geo.j2 @@ -0,0 +1,46 @@ +#!/bin/sh +# {{ ansible_managed }} +# Rebuilds the geo sets of table inet host from ipdeny. Any failed download or +# check exits before the swap, so the previous file stays in place. +set -eu + +out=/etc/nftables.d/host-geo.nft +countries="{{ firewall.geo_countries | join(' ') }}" +prefix='^[0-9a-f.:]+/[0-9]+$' + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +fetch() { + curl -fsS --retry 3 --max-time 120 -o "$2" "$1" + grep -Eq "$prefix" "$2" || { echo "no prefixes in $1" >&2; exit 1; } +} + +for cc in $countries; do + fetch "https://www.ipdeny.com/ipblocks/data/aggregated/$cc-aggregated.zone" "$tmp/$cc.v4" + fetch "https://www.ipdeny.com/ipv6/ipaddresses/aggregated/$cc-aggregated.zone" "$tmp/$cc.v6" +done + +elements() { + cat "$@" | grep -E "$prefix" | sed 's/$/,/' +} + +{ + echo "# Generated by $0, do not edit." + echo 'table inet host {' + echo ' set geo_v4 { type ipv4_addr; flags interval; auto-merge; elements = {' + elements "$tmp"/*.v4 + echo ' } }' + echo ' set geo_v6 { type ipv6_addr; flags interval; auto-merge; elements = {' + elements "$tmp"/*.v6 + echo ' } }' + echo '}' +} >"$tmp/host-geo.nft" + +nft -c -f "$tmp/host-geo.nft" +install -m 0644 "$tmp/host-geo.nft" "$out.new" +mv -f "$out.new" "$out" + +if systemctl -q is-active nft-host.service; then + systemctl reload nft-host.service +fi diff --git a/nftables/defaults/nftables.yml b/nftables/defaults/nftables.yml deleted file mode 100644 index ad4c51e..0000000 --- a/nftables/defaults/nftables.yml +++ /dev/null @@ -1,29 +0,0 @@ -nftables: - # Rules to add - # Handled as templates - # Creates separate files for each entry. - # The identifier is necessary for ansible to be able to merge the keys (when 'hash_behaviour = merge') - # rule-ids have to be unique across files and raw - rules: - # Files with Rules to add - files: - #'': '' - #'': - # main: - # '': '' - - # Rules to add - raw: - #'': '' - #'': - # main: - # '': '' - - # Decides if /etc/nftables.conf is applied or separate files which have changed - # Separate changes require the files to be self-tyding to not end up with duplicate rules - # e.g. - # table ip mytable - # flush table ip mytable - # delete table ip mytable - # table ip mytable {} ... - apply_global: false diff --git a/nftables/tasks/apply-files.yml b/nftables/tasks/apply-files.yml deleted file mode 100644 index b23e4f0..0000000 --- a/nftables/tasks/apply-files.yml +++ /dev/null @@ -1,8 +0,0 @@ -- name: Load group rules - command: "nft -f /etc/nftables/ansible-managed/{{ item }}.nft" - loop: "{{ combined_rules | list }}" - when: not nftables.apply_global - -- name: Load global rule file - command: "nft -f /etc/nftables.nft" - when: nftables.apply_global diff --git a/nftables/tasks/deploy-rules/main.yml b/nftables/tasks/deploy-rules/main.yml deleted file mode 100644 index 350bd4e..0000000 --- a/nftables/tasks/deploy-rules/main.yml +++ /dev/null @@ -1,11 +0,0 @@ -- name: Deploying group files - include_tasks: ./per-group-template-file.yml - with_items: - - "{{ nftables.rules.files | list }}" - -- name: Deploying group raw-files - include_tasks: ./per-group-template.yml - with_items: - - "{{ nftables.rules.raw | list }}" - -- include_tasks: ./remove-files.yml diff --git a/nftables/tasks/deploy-rules/per-group-template-file.yml b/nftables/tasks/deploy-rules/per-group-template-file.yml deleted file mode 100644 index 26e0da8..0000000 --- a/nftables/tasks/deploy-rules/per-group-template-file.yml +++ /dev/null @@ -1,51 +0,0 @@ -- set_fact: - group_identifier: "{{ item }}" - value: "{{ nftables.rules.files[item] }}" - when: "item is defined" - - #'': '' -- block: - - name: Create main rule file - template: - src: "{{ value }}" - dest: "/etc/nftables/ansible-managed/{{ group_identifier }}.nft" - when: value is string - - #'': - # main: - # '': '' -- block: - - set_fact: - items: "{{ nftables.rules.files[item] }}" - - - block: - - name: Create main rule file - template: - src: "{{ items['main'] }}" - dest: "/etc/nftables/ansible-managed/{{ group_identifier }}.nft" - - - name: Include rule files - lineinfile: - path: "/etc/nftables/ansible-managed/{{ group_identifier }}.nft" - regexp: "include\\s+(\"|')\\/etc\\/nftables\\/ansible-managed\\/{{ group_identifier }}\\/.*$" - line: 'include "/etc/nftables/ansible-managed/{{ group_identifier }}/*.nft"' - when: items['main'] is defined - - - name: Create group folder - file: - path: "/etc/nftables/ansible-managed/{{ group_identifier }}/" - state: directory - when: items|length > 0 - - - set_fact: - test: "{{ items | dict2items | selectattr('key', 'ne', 'main') }}" - - - name: Create included rule files - template: - src: "{{ fileItem.value }}" - dest: "/etc/nftables/ansible-managed/{{ group_identifier }}/{{ fileItem.key }}.nft" - loop: "{{ items | dict2items | selectattr('key', 'ne', 'main') }}" - loop_control: - loop_var: fileItem - - when: value is mapping diff --git a/nftables/tasks/deploy-rules/per-group-template.yml b/nftables/tasks/deploy-rules/per-group-template.yml deleted file mode 100644 index 4d2f6ea..0000000 --- a/nftables/tasks/deploy-rules/per-group-template.yml +++ /dev/null @@ -1,48 +0,0 @@ -- set_fact: - group_identifier: "{{ item }}" - value: "{{ nftables.rules.raw[item] }}" - when: "item is defined" - - #'': '' -- block: - - name: Create main rule file - copy: - content: "{{ value }}" - dest: "/etc/nftables/ansible-managed/{{ group_identifier }}.nft" - when: value is string - - #'': - # main: - # '': '' -- block: - - set_fact: - items: "{{ nftables.rules.raw[item] }}" - - - block: - - name: Create main rule file - copy: - content: "{{ items['main'] }}" - dest: "/etc/nftables/ansible-managed/{{ group_identifier }}.nft" - - - name: Include rule files - lineinfile: - path: "/etc/nftables/ansible-managed/{{ group_identifier }}.nft" - regexp: "include\\s+(\"|')\\/etc\\/nftables\\/ansible-managed\\/{{ group_identifier }}\\/.*$" - line: 'include "/etc/nftables/ansible-managed/{{ group_identifier }}/*.nft"' - when: items['main'] is defined - - - name: Create group folder - file: - path: "/etc/nftables/ansible-managed/{{ group_identifier }}/" - state: directory - when: items|length > 0 - - - name: Create included rule files - copy: - content: "{{ included_item.value }}" - dest: "/etc/nftables/ansible-managed/{{ group_identifier }}/{{ included_item.key }}.nft" - loop: "{{ items | dict2items | selectattr('key', 'ne', 'main') }}" - loop_control: - loop_var: included_item - - when: value is mapping diff --git a/nftables/tasks/install-package.yml b/nftables/tasks/install-package.yml deleted file mode 100644 index fc088e4..0000000 --- a/nftables/tasks/install-package.yml +++ /dev/null @@ -1,4 +0,0 @@ -- name: Install Packages - package: - name: - - nftables diff --git a/nftables/tasks/main.yml b/nftables/tasks/main.yml deleted file mode 100644 index 506f4c0..0000000 --- a/nftables/tasks/main.yml +++ /dev/null @@ -1,7 +0,0 @@ -- import_tasks: ./prerequisites.yml - -- import_tasks: ./setup-packages.yml - -- import_tasks: ./deploy-rules/main.yml - -- import_tasks: ./apply-files.yml diff --git a/nftables/tasks/prerequisites.yml b/nftables/tasks/prerequisites.yml deleted file mode 100644 index 83bd342..0000000 --- a/nftables/tasks/prerequisites.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Defaults if missing -- name: Set defaults if missing - set_fact: - nftables: - rules: - files: "{{ nftables.rules.files | default({}) | combine({}) }}" - raw: "{{ nftables.rules.raw | default({}) | combine({}) }}" - combined_rules: "{{ nftables.rules.raw | combine(nftables.rules.files, recursive=true) }}" - -#- name: Check items for consistency -# assert: -# that: "{{ nftables.rules.files.values() | length }} + {{ nftables.rules.raw.values() | length }} == {{ combined_rules.values() | length }}" -# fail_msg: "files and raw rules share the same identifier" diff --git a/nftables/tasks/remove-files.yml b/nftables/tasks/remove-files.yml deleted file mode 100644 index 97b3266..0000000 --- a/nftables/tasks/remove-files.yml +++ /dev/null @@ -1,21 +0,0 @@ -- name: Handle removed group files - block: - - find: - paths: /etc/nftables/ansible-managed/ - file_type: 'any' - excludes: '{% for item in combined_rules %}{{ item }},{{ item }}.nft,{% endfor %}' - depth: 1 - register: removeFiles - - - file: - path: "{{ fileItem.path }}" - state: absent - loop: "{{ removeFiles.files }}" - loop_control: - label: "{{ fileItem.path }}" - loop_var: fileItem - -- name: Handle removed included files per group - include_tasks: ./remove-per-group.yml - with_items: - - "{{ combined_rules | list }}" diff --git a/nftables/tasks/remove-per-group.yml b/nftables/tasks/remove-per-group.yml deleted file mode 100644 index fcd3aac..0000000 --- a/nftables/tasks/remove-per-group.yml +++ /dev/null @@ -1,20 +0,0 @@ -- set_fact: - group_identifier: "{{ item }}" - group_items: "{{ combined_rules[item] }}" - -- block: - - find: - paths: "/etc/nftables/ansible-managed/{{ group_identifier }}/" - file_type: 'any' - excludes: '{% for item in group_items %}{{ item }}.nft,{% endfor %}' - register: removeFiles - - - file: - path: "{{ fileItem.path }}" - state: absent - loop: "{{ removeFiles.files }}" - loop_control: - label: "{{ fileItem.path }}" - loop_var: fileItem - - when: group_items is mapping diff --git a/nftables/tasks/setup-packages.yml b/nftables/tasks/setup-packages.yml deleted file mode 100644 index caa80d2..0000000 --- a/nftables/tasks/setup-packages.yml +++ /dev/null @@ -1,15 +0,0 @@ -- name: Install nftables - package: - name: - - nftables - -- name: Create /etc/nftables/ansible-managed - file: - path: /etc/nftables/ansible-managed - state: directory - -- name: Include files in /etc/nftables/ansible-managed/ from /etc/nftables.conf - blockinfile: - path: /etc/nftables.conf - marker: "# {mark} ANSIBLE MANAGED BLOCK - nftables" - content: 'include "/etc/nftables/ansible-managed/*.nft"'