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>
This commit is contained in:
parent
66a9a0af08
commit
b10a33f439
4 changed files with 28 additions and 1 deletions
|
|
@ -11,6 +11,14 @@ base__firewall_rollback_timeout: 45 # seconds before the auto-revert fires on a
|
|||
base__firewall_confirm_timeout: 20 # seconds to re-establish a fresh connection post-apply
|
||||
base__firewall_dropin_dir: /etc/nftables.d
|
||||
base__firewall_apply: true # set false to render+validate without applying (CI/Molecule)
|
||||
base__firewall_input_only: false # true → the forward chain is `policy accept` (host-local
|
||||
# INPUT filtering only). For hosts that forward/route
|
||||
# container or NAT traffic (the control node's Docker +
|
||||
# libvirt-NAT) where a forward default-deny would break
|
||||
# them. Real service hosts keep this false (forward drop).
|
||||
base__firewall_admin_addrs: [] # extra LAN source IPs allowed to SSH, besides wt0 +
|
||||
# ssh-from-control. For an operator workstation reaching
|
||||
# the host over the LAN (no mesh). Key-gated. (ADR-021)
|
||||
|
||||
# SSH hardening + fail2ban (ADR-002) — `hardening` concern.
|
||||
base__ssh_password_authentication: "no"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
vars:
|
||||
base__firewall_apply: false
|
||||
base__firewall_control_addr: 10.10.0.99 # test control-node LAN address
|
||||
base__firewall_admin_addrs:
|
||||
- "10.30.0.77" # fixture: an operator-workstation LAN source (admin-addr SSH allow)
|
||||
# Exercise the mesh concern's include path with the live actions gated off, so it
|
||||
# runs hermetically (no coordinator/key needed) and must be a clean no-op.
|
||||
base__mesh_enabled: true
|
||||
|
|
|
|||
|
|
@ -51,6 +51,20 @@
|
|||
- "'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
|
||||
|
|
|
|||
|
|
@ -12,13 +12,16 @@ table inet filter {
|
|||
{% if base__firewall_control_addr %}
|
||||
ip saddr {{ base__firewall_control_addr }} tcp dport {{ base__firewall_ssh_port }} accept
|
||||
{% endif %}
|
||||
{% for addr in base__firewall_admin_addrs %}
|
||||
ip saddr {{ addr }} tcp dport {{ base__firewall_ssh_port }} accept
|
||||
{% endfor %}
|
||||
ip protocol icmp accept
|
||||
ip6 nexthdr ipv6-icmp accept
|
||||
{% for r in base__firewall_resolved %}
|
||||
ip saddr { {{ r.sources | join(', ') }} } {{ r.proto }} dport {{ r.port }} accept
|
||||
{% endfor %}
|
||||
}
|
||||
chain forward { type filter hook forward priority 0; policy drop; }
|
||||
chain forward { type filter hook forward priority 0; policy {{ 'accept' if base__firewall_input_only | bool else 'drop' }}; }
|
||||
chain output { type filter hook output priority 0; policy accept; }
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue