boma/roles/base/molecule/default/verify.yml
sjat b10a33f439 feat(base): input-only forward policy + admin-addr SSH allow
base__firewall_input_only renders the forward chain policy accept (host-local
INPUT filtering only) for hosts that forward container/NAT traffic; defaults
false so real service hosts keep the forward default-deny. base__firewall_admin_addrs
adds operator-workstation LAN sources to the SSH allow-list alongside wt0 +
ssh-from-control. Molecule locks the secure default + the admin rule.
Mesh-hardening 2/3 (ADR-020/021).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:37:06 +02:00

105 lines
4.1 KiB
YAML

---
- name: Verify
hosts: all
become: true
gather_facts: false
tasks:
- name: Read the rendered ruleset
ansible.builtin.slurp:
src: /etc/nftables.conf
register: ruleset
- name: Decode it
ansible.builtin.set_fact:
nft: "{{ ruleset.content | b64decode }}"
- name: Assert default-deny input policy and management plane
ansible.builtin.assert:
that:
- "'type filter hook input priority 0; policy drop;' in nft"
- "'ct state established,related accept' in nft"
- "'iifname \"wt0\" tcp dport 22 accept' in nft"
- "'ip saddr 10.10.0.99 tcp dport 22 accept' in nft"
fail_msg: >-
input chain is missing default-deny, the wt0 SSH allow,
or the ssh-from-control management-plane rule
- name: Assert the lan->reverse_proxy:443 ingress rule (zone source)
ansible.builtin.assert:
that:
- "'10.30.0.0/24' in nft"
- "'tcp dport 443 accept' in nft"
fail_msg: "missing lan->443 rule for reverse_proxy"
- name: Assert the srv->photoprism:2342 ingress rule (zone source)
ansible.builtin.assert:
that:
- "'10.20.0.0/24' in nft"
- "'tcp dport 2342 accept' in nft"
fail_msg: "missing srv->2342 rule for photoprism"
- name: Assert the public->stun:3478/udp ingress rule (0.0.0.0/0 source)
ansible.builtin.assert:
that:
- "'0.0.0.0/0' in nft"
- "'udp dport 3478 accept' in nft"
fail_msg: "missing public->3478/udp rule for netbird_stun"
- name: Assert the docker_host extension hook is present
ansible.builtin.assert:
that:
- "'include \"/etc/nftables.d/*.nft\"' in nft"
fail_msg: "missing drop-in include hook"
- name: Assert the forward chain defaults to policy drop (input_only off)
ansible.builtin.assert:
that:
- "'hook forward priority 0; policy drop;' in nft"
fail_msg: >-
forward chain must default to policy drop when base__firewall_input_only is
false (container isolation stays the norm on real service hosts)
- name: Assert the admin-addr SSH allow rule (operator workstation on the LAN)
ansible.builtin.assert:
that:
- "'ip saddr 10.30.0.77 tcp dport 22 accept' in nft"
fail_msg: "missing admin-addr SSH allow rule from base__firewall_admin_addrs"
- name: Syntax-check the rendered ruleset (no apply)
ansible.builtin.command: nft -c -f /etc/nftables.conf
changed_when: false
- name: Sshd drop-in present and config valid
ansible.builtin.command: sshd -t
changed_when: false
- name: PasswordAuthentication is disabled
ansible.builtin.command: grep -q '^PasswordAuthentication no' /etc/ssh/sshd_config.d/10-boma.conf
changed_when: false
- name: Fail2ban sshd jail configured
ansible.builtin.command: grep -q '^\[sshd\]' /etc/fail2ban/jail.d/sshd.local
changed_when: false
- name: ListenAddress bound to the fixture mesh IP (mesh-only mode)
ansible.builtin.command: grep -q '^ListenAddress 100.99.0.1$' /etc/ssh/sshd_config.d/10-boma.conf
changed_when: false
- name: Sysctl drop-in for ip_nonlocal_bind is present
ansible.builtin.command: grep -q '^net.ipv4.ip_nonlocal_bind=1' /etc/sysctl.d/60-boma-nonlocal-bind.conf
changed_when: false
- name: Kernel ip_nonlocal_bind is live in this netns
ansible.builtin.command: sysctl -n net.ipv4.ip_nonlocal_bind
register: _nonlocal
changed_when: false
failed_when: _nonlocal.stdout | trim != '1'
# mesh concern: enabled but manage=false must be a clean no-op (no install/enrol)
- name: Check whether netbird got installed
ansible.builtin.command: which netbird
register: _nb
changed_when: false
failed_when: false
- name: Assert mesh manage=false installed nothing
ansible.builtin.assert:
that:
- _nb.rc != 0
fail_msg: "netbird must not be installed when base__mesh_manage is false"
success_msg: "mesh concern is a clean no-op under manage=false"