feat(demo_lab_network): new scenario — demo_lab + inter-bridge iptables isolation#48
feat(demo_lab_network): new scenario — demo_lab + inter-bridge iptables isolation#48t0kubetsu wants to merge 7 commits into
Conversation
…proxmox_vm.list Capture the output of proxmox_vm.list.to.jsons.sh into a variable, validate it with jq -e before processing, and abort with a clear error message if the output is not valid JSON. Previously, any non-JSON output (e.g. an API error when the token is stale or Proxmox is unreachable) caused jq to emit a parse error and produce no output. The stop and delete commands then received no VM IDs, silently skipping all deletions while still exiting 0. The subsequent deploy would fail with HTTP 500 "config file already exists". Also removes the misleading "non-fatal" comment in blank_scenario_2_subnets.delete_all.sh that incorrectly described this failure mode as harmless. Affects all 20 delete_all/delete_vms_only/reset.setup scripts across all scenarios: blank_scenario_2/4/6_subnets, debug_scenario_a/b, demo_lab, _init_lab. Fixes #46
…es isolation Implements the network isolation design from issue #20. Adds `scenarios/demo_lab_network/` — a self-contained scenario based on demo_lab that enforces zone separation between the admin (vmbr142) and CTF (vmbr144) bridges via iptables FORWARD chain rules on the Proxmox host. New: 05_network_isolation/stage_00/proxmox_forward_rules.yml - Installs iptables-persistent on pve01 - ACCEPT: admin → ctf (management access) - ACCEPT: ctf → wazuh :1514/:1515 (agent reporting) - DROP: ctf → admin (zone isolation) - DROP: ctf → vmbr0/WAN (air-gap) - Rules are idempotent and persist across reboots via netfilter-persistent All rules implemented as Ansible tasks (not Python API calls), per hyde-repo's recommendation in the issue discussion. Two-zone design (admin/ctf) chosen over three-zone; the dedicated management bridge alternative is documented in README.md as a future option. Closes #20
ansible_connection: local on the proxmox host targets the deployer machine, which doesn't have iptables. Switch to proxmox-cli (SSH to pve01 as root) where iptables is available.
…ress discovery warning
t0kubetsu
left a comment
There was a problem hiding this comment.
Test results — validated on hv-lab-01 ✅
Deployed and tested 05_network_isolation against the live Proxmox (hv-lab-01, Proxmox 8 / Debian 12). All checks pass.
Syntax check
ansible-playbook --syntax-check ./main.yml
→ playbook: ./main.yml (no errors)
Dry run (--check)
hv-lab-01-cli : ok=7 changed=7 unreachable=0 failed=0
All 7 tasks would change on a clean host — expected.
Real run
TASK [NETWORK ISOLATION - Install iptables-persistent] changed
TASK [NETWORK ISOLATION - Allow established/related connections (FORWARD)] changed
TASK [NETWORK ISOLATION - Allow Admin (vmbr142) → CTF (vmbr144)] changed
TASK [NETWORK ISOLATION - Allow CTF → Wazuh TCP 1514 (agent events)] changed
TASK [NETWORK ISOLATION - Allow CTF → Wazuh TCP 1515 (agent enrollment)] changed
TASK [NETWORK ISOLATION - Drop CTF (vmbr144) → Admin (vmbr142) — zone isolation] changed
TASK [NETWORK ISOLATION - Drop CTF (vmbr144) → WAN — air-gap] changed
RUNNING HANDLER [persist iptables rules] changed
hv-lab-01-cli : ok=8 changed=8 unreachable=0 failed=0
Idempotency (second run) ✅
hv-lab-01-cli : ok=7 changed=0 unreachable=0 failed=0
FORWARD chain on pve01 ✅
1 ACCEPT all * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED /* r42: allow established */
2 ACCEPT all * * 192.168.142.0/24 192.168.144.0/24 /* r42: admin to ctf */
3 ACCEPT tcp * * 192.168.144.0/24 192.168.142.100 tcp dpt:1514 /* r42: ctf wazuh events */
4 ACCEPT tcp * * 192.168.144.0/24 192.168.142.100 tcp dpt:1515 /* r42: ctf wazuh enrollment */
5 DROP all * * 192.168.144.0/24 192.168.142.0/24 /* r42: block ctf to admin */
6 DROP all vmbr144 vmbr0 0.0.0.0/0 0.0.0.0/0 /* r42: ctf air-gap */
Rules are persisted in /etc/iptables/rules.v4 — survive reboots. ✅
One bug caught and fixed during testing
The original playbook used hosts: proxmox (which has ansible_connection: local — Proxmox API). The ansible.builtin.iptables module needs the iptables binary, which lives on the Proxmox host itself, not on the deployer. Fixed in fa1c75f by switching to hosts: proxmox-cli (SSH root to pve01). Python interpreter also pinned in 8f506e0.
Ready to merge. Needs approval from @pparage or @hyde-repo.
t0kubetsu
left a comment
There was a problem hiding this comment.
Full network isolation test — live traffic validation ✅
Previous review confirmed rules were applied. This validates that they actually enforce the correct traffic flows using real kernel packet forwarding (network namespaces injecting traffic through the live FORWARD chain on hv-lab-01).
Test setup
Two network namespaces attached to the real Proxmox bridges:
test_ctf_ns→vmbr144@192.168.144.250(simulates a CTF/vuln VM)test_admin_ns→vmbr142@192.168.142.250(simulates an admin VM)
All traffic flows through the real FORWARD chain — same path as actual VMs.
Connectivity matrix
| # | From | To | Expected | Result |
|---|---|---|---|---|
| 1 | CTF .144.250 |
CTF .144.200 (same VLAN) |
✅ pass | PASS ✓ |
| 2 | CTF .144.250 |
Admin .142.250 (cross-VLAN) |
❌ blocked | FAIL ✗ (blocked as intended) |
| 3 | CTF .144.250 |
8.8.8.8 internet |
❌ blocked | FAIL ✗ (blocked as intended) |
| 4 | Admin .142.250 |
CTF .144.250 (cross-VLAN) |
✅ pass | PASS ✓ |
| 5 | Admin .142.250 |
Admin .142.100 (same VLAN, live VM) |
✅ pass | PASS ✓ |
| 6 | CTF .144.250 |
Wazuh .142.100 TCP 1514 |
✅ pass | PASS ✓ |
Packet counters after tests
ACCEPT all * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT all * * 192.168.142/24 192.168.144/24 /* r42: admin to ctf */ 1 pkt
ACCEPT tcp * * 192.168.144/24 192.168.142.100 dpt:1514 /* ctf wazuh */ 1 pkt
DROP all * * 192.168.144/24 192.168.142/24 /* r42: block ctf to admin */ 2 pkts
DROP all vmbr144 vmbr0 /* r42: ctf air-gap */ 522 pkts
Every rule hit exactly the expected traffic. The Wazuh port 1515 counter stays at 0 (no enrollment traffic during the test — expected).
All 6 scenarios validated. Ready to merge.
t0kubetsu
left a comment
There was a problem hiding this comment.
End-to-end SSH + isolation test — all pass ✅
Following up on the iptables rule validation. Tested full SSH connectivity from the deployer through the ProxyJump path, and verified the isolation policy from inside live VMs on both bridges.
Test setup
VMs used (blank_scenario_2_subnets, same bridges as demo_lab_network):
bs2-team-144-01@192.168.144.200— vmbr144 (CTF bridge)bs2-team-144-02@192.168.144.201— vmbr144 (CTF bridge, same-VLAN peer)bs2-admin-wazuh@192.168.142.100— vmbr142 (Admin bridge)
Connectivity tested using bash /dev/tcp (no ping/nc on minimal Ubuntu install).
Results
| # | Test | Expected | Result |
|---|---|---|---|
| 1 | Deployer → CTF VM SSH (via ProxyJump) | open | OK ✓ |
| 2 | Deployer → Admin VM SSH (via ProxyJump) | open | OK ✓ |
| 3 | CTF → CTF same-VLAN TCP 22 (144.201) | open | open ✓ |
| 4 | CTF → Admin TCP 22 (142.100) | blocked | blocked ✓ |
| 5 | CTF → Internet TCP 22 (1.1.1.1) | blocked | blocked ✓ |
| 6 | CTF → Internet HTTP (1.1.1.1:80) | blocked | blocked ✓ |
| 7 | Admin → CTF TCP 22 (144.200) | open | open ✓ |
| 8 | Admin → Internet HTTP (1.1.1.1:80) | open | open ✓ |
8/8. The deployer SSH path (ProxyJump via pve01) works for both bridges. The isolation rules are enforced correctly from live VM traffic, not just from the FORWARD chain counters.
Note: SSH from the deployer goes through jump_user on Proxmox and tunnels directly to the VM — this path uses Proxmox's OUTPUT chain (locally initiated), not the FORWARD chain. The iptables rules we added do not interfere with deployer management access to either zone.
…d packages can reach internet Before this fix, the CTF→WAN FORWARD DROP rule was already in place when the CTF VMs were deployed, blocking DNS and NTP during cloud-init and package installation. This caused the deployment to hang (30-60s DNS timeouts) and ultimately fail at the NTP sync step. New lift/restore pattern: 1. lift_ctf_airgap.yml removes the DROP rule before CTF infrastructure runs 2. 04_ctf_infrastructure/_main.yml runs with internet access 3. 05_network_isolation/_main.yml re-installs the DROP rule after setup Both main.yml and main_vms_only.yml updated. lift_ctf_airgap.yml handles both DROP and REJECT variants (idempotent, state=absent).
…, tcpdump) INSTALL_PACKAGES_UTILS_NETWORK was hardcoded to NO for the vuln-box stage, blocking installation of nmap, tcpdump, net-tools and iputils-ping even though iputils-ping was added to the role. Set to YES so all network diagnostic tools are available on vuln-boxes.
…ir-gap rules
netfilter-persistent can save rules with a newline embedded in the comment
("r42: ctf\n air-gap"), which ansible.builtin.iptables state=absent cannot
match. Add a second comment-based pass for duplicates, then a shell fallback
that loops iptables -D until no more DROP rules on vmbr144→vmbr0 remain.
Summary
Implements the
demo_lab_networkscenario proposed in #20.scenarios/demo_lab_network/— self-contained, same VM layout asdemo_lab(same IDs/IPs/bridges)05_network_isolation/— the only truly new piece: Ansible tasks that apply iptables FORWARD chain rules on the Proxmox host to enforce zone separationiptables-persistent/netfilter-persistentNetwork isolation rules (applied on Proxmox host)
Design choice: 2-zone
Two-zone design (admin/ctf) over three-zone because vmbr142/vmbr144 already pre-exist in
proxmox.init. Wazuh agent traffic is handled via iptables pinholes on 1514/1515. The three-zone alternative (dedicatedvmbr145management bridge) is documented in README.md as a future option.Inventory
Files
Test plan
1 · Deploy
PLAY RECAPshowsfailed=0on all hosts2 · Zone isolation — CTF cannot reach admin
192.168.142.100(r42.admin-wazuh): 100% packet loss — zone isolation confirmed ✅192.168.142.1(Proxmox bridge IP) responds — handled by INPUT chain, not FORWARD, expected behaviour3 · Wazuh agent registration
range42-context ssh admin-wazuh # inside r42.admin-wazuh: sudo /var/ossec/bin/agent_control -ladmin-deployer-api-gateway(ID 001): Active ✅admin-deployer-api-backend(ID 002): Active ✅admin-deployer-ui(ID 003): Active ✅4 · Admin can reach CTF
Permission denied (publickey)✅5 · CTF air-gap — no internet from vuln boxes
ping -c 3 8.8.8.8: 100% packet loss ✅curl -m 5 https://example.com: DNS resolution timed out ✅6 · Idempotency
PLAY RECAPshowsfailed=0on all hosts ✅hv-lab-01-cli: changed=4— air-gap lift/restore runs every deploy by designvuln-box-*: changed=10— first run after iputils-ping was added; a third run giveschanged=0Closes #20