diff --git a/tests/integration/overrides/askari_inputonly.yml b/tests/integration/overrides/askari_inputonly.yml new file mode 100644 index 0000000..e2fe904 --- /dev/null +++ b/tests/integration/overrides/askari_inputonly.yml @@ -0,0 +1,17 @@ +--- +# Integration overlay (ADR-025) — the askari mesh-hardening REDESIGN (2026-06-19). +# Validates INPUT-only default-deny on a Docker host: input policy drop, forward policy +# accept (Docker-safe), SSH via the admin-addr break-glass, reboot-survivable. +integration_profile: askari_inputonly +base__firewall_apply: true +base__firewall_input_only: true +# No sshd ListenAddress change — never wt0-only in a throwaway VM. +base__ssh_listen_mesh_only: false +# Isolated VM: never touch the real mesh. +base__mesh_enabled: false +# The non-mesh SSH break-glass = the admin-addr path the real design uses. Point it at the +# VM's libvirt-NAT gateway (where the harness connects from), by source IP so it is +# interface-independent and the default-deny + reboot don't lock out the driver. This +# mirrors askari's real base__firewall_admin_addrs (ubongo's WAN) in the test topology. +base__firewall_admin_addrs: + - 192.168.150.1 diff --git a/tests/integration/profiles/askari_inputonly.json b/tests/integration/profiles/askari_inputonly.json new file mode 100644 index 0000000..d571d1d --- /dev/null +++ b/tests/integration/profiles/askari_inputonly.json @@ -0,0 +1,10 @@ +{ + "groups": ["offsite_hosts"], + "applies": [ + {"playbook": "site.yml", "tags": ["base"]}, + {"playbook": "offsite.yml", "tags": ["docker_host", "reverse_proxy"]} + ], + "extra_vars_files": ["overrides/askari_inputonly.yml"], + "mem_mib": 3072, + "vcpus": 2 +} diff --git a/tests/integration/verify.yml b/tests/integration/verify.yml index 1f460f5..cf9f61c 100644 --- a/tests/integration/verify.yml +++ b/tests/integration/verify.yml @@ -11,8 +11,8 @@ ansible.builtin.assert: that: - integration_profile is defined - - integration_profile in ['askari', 'ubongo'] - fail_msg: "integration_profile must be set in the profile overlay (askari|ubongo)" + - integration_profile in ['askari', 'askari_inputonly', 'ubongo'] + fail_msg: "integration_profile must be set in the profile overlay (askari|askari_inputonly|ubongo)" # ── askari profile — Docker host: published-port forwarding survives the reboot ── # The load-bearing check probes the VM's published :80 FROM the controller (ubongo) — if @@ -83,3 +83,47 @@ ubongo profile: expected input policy drop, forward policy accept (input-only), the ssh-from-control lifeline (192.168.150.1), and both admin-addr (192.168.150.98/99) SSH allows in the live ruleset. + + # ── askari_inputonly profile — the mesh-hardening REDESIGN (2026-06-19) ── + # INPUT-only default-deny on a Docker host: input policy drop, forward policy ACCEPT + # (Docker-safe), SSH via the admin-addr break-glass, published-port DNAT survives reboot. + - name: (askari_inputonly) Read the live nftables ruleset + when: integration_profile == 'askari_inputonly' + ansible.builtin.command: nft list ruleset + register: _nft_io + changed_when: false + + - name: (askari_inputonly) INPUT default-deny, forward permissive, admin-addr break-glass + when: integration_profile == 'askari_inputonly' + ansible.builtin.assert: + that: + - "'hook input priority filter; policy drop;' in _nft_io.stdout" + - "'hook forward priority filter; policy accept;' in _nft_io.stdout" + - "'ip saddr 192.168.150.1 tcp dport 22 accept' in _nft_io.stdout" + fail_msg: >- + askari_inputonly: expected input policy drop, forward policy accept (input-only), + and the admin-addr break-glass (192.168.150.1) SSH allow in the live ruleset. + + - name: (askari_inputonly) Gather service facts + when: integration_profile == 'askari_inputonly' + ansible.builtin.service_facts: + + - name: (askari_inputonly) Docker daemon is active + when: integration_profile == 'askari_inputonly' + ansible.builtin.assert: + that: "ansible_facts.services['docker.service'].state == 'running'" + fail_msg: "docker.service is not running" + + - name: (askari_inputonly) Published port answers from the controller (DNAT + forward alive) + when: integration_profile == 'askari_inputonly' + delegate_to: localhost + become: false + ansible.builtin.uri: + url: "http://{{ ansible_host }}/" + follow_redirects: none + status_code: [200, 301, 308, 404, 502, 503] + timeout: 10 + register: _probe_io + retries: 5 + delay: 6 + until: _probe_io is succeeded