Skip to content

feat(demo_lab_network): new scenario — demo_lab + inter-bridge iptables isolation#48

Open
t0kubetsu wants to merge 7 commits into
mainfrom
feat/demo_lab_network-scenario
Open

feat(demo_lab_network): new scenario — demo_lab + inter-bridge iptables isolation#48
t0kubetsu wants to merge 7 commits into
mainfrom
feat/demo_lab_network-scenario

Conversation

@t0kubetsu
Copy link
Copy Markdown

@t0kubetsu t0kubetsu commented May 15, 2026

Summary

Implements the demo_lab_network scenario proposed in #20.

  • New scenario scenarios/demo_lab_network/ — self-contained, same VM layout as demo_lab (same IDs/IPs/bridges)
  • Adds 05_network_isolation/ — the only truly new piece: Ansible tasks that apply iptables FORWARD chain rules on the Proxmox host to enforce zone separation
  • All rules implemented as Ansible tasks (not Python API calls), per @hyde-repo's recommendation
  • Rules are idempotent and persist across reboots via iptables-persistent / netfilter-persistent

Network isolation rules (applied on Proxmox host)

From To Port Action Reason
any any ACCEPT (ESTABLISHED,RELATED) return traffic
Admin (vmbr142 · .142/24) CTF (vmbr144 · .144/24) ALL ACCEPT admin manages vuln boxes
CTF (.144/24) Wazuh (.142.100) 1514 TCP ACCEPT Wazuh agent events
CTF (.144/24) Wazuh (.142.100) 1515 TCP ACCEPT Wazuh agent enrollment
CTF (.144/24) Admin (.142/24) ALL DROP zone isolation
CTF (vmbr144) WAN (vmbr0) ALL DROP air-gap

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 (dedicated vmbr145 management bridge) is documented in README.md as a future option.

Inventory

@r42_admin:
  r42.admin-wazuh                 192.168.142.100
  r42.admin-deployer-api-gateway  192.168.142.x
  r42.admin-deployer-api-backend  192.168.142.x
  r42.admin-deployer-ui           192.168.142.x

@r42_vuln_box_group:
  r42.vuln-box-00  192.168.144.170
  r42.vuln-box-01  192.168.144.171
  r42.vuln-box-02  192.168.144.172
  r42.vuln-box-03  192.168.144.173
  r42.vuln-box-04  192.168.144.174

@proxmox-cli:
  hv-lab-01-cli   172.16.222.134

Files

scenarios/demo_lab_network/
├── main.yml                                   # imports 01 + 02 + 04 + 05
├── main_vms_only.yml                          # imports 02 + 04 + 05 (skip templates)
├── README.md
├── 01_init_proxmox/                           # same templates as demo_lab
├── 02_admin_infrastructure/                   # same admin VMs as demo_lab
├── 04_ctf_infrastructure/                     # same CTF VMs as demo_lab
├── 05_network_isolation/                      # NEW ← the isolation logic
│   ├── _main.yml
│   ├── stage_pre/lift_ctf_airgap.yml          # removes air-gap before CTF setup
│   └── stage_00/proxmox_forward_rules.yml     # installs all FORWARD rules
├── manifest/scenario_vms.json
├── templates/
└── demo_lab_network.{setup,delete_all,...}.sh

Test plan

1 · Deploy

# First-time bootstrap (generates credentials, configures Proxmox, provisions deployer-cli)
python3 range42-init.py

# Activate workspace and deploy
range42-context use hv-lab-01 demo_lab_network
range42-context deploy
  • PLAY RECAP shows failed=0 on all hosts

2 · Zone isolation — CTF cannot reach admin

range42-context ssh vuln-box-00
# inside r42.vuln-box-00:
ping -c 3 192.168.142.100   # r42.admin-wazuh — must time out (DROP)
# Note: 192.168.142.1 (Proxmox vmbr142 bridge) responds via INPUT chain — expected, not a VM
  • 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 behaviour

3 · Wazuh agent registration

range42-context ssh admin-wazuh
# inside r42.admin-wazuh:
sudo /var/ossec/bin/agent_control -l
  • admin-deployer-api-gateway (ID 001): Active ✅
  • admin-deployer-api-backend (ID 002): Active ✅
  • admin-deployer-ui (ID 003): Active ✅

4 · Admin can reach CTF

range42-context ssh admin-wazuh
# inside r42.admin-wazuh:
ssh alice@192.168.144.170   # r42.vuln-box-00
  • TCP connection reaches vuln-box-00 — SSH handshake completes, Permission denied (publickey)
  • ℹ️ Auth fails as expected — admin-wazuh holds no SSH key for vuln-boxes (keys live on the deployer). Network path is confirmed open.

5 · CTF air-gap — no internet from vuln boxes

range42-context ssh vuln-box-00
# inside r42.vuln-box-00:
ping -c 3 8.8.8.8           # must time out
curl -m 5 https://example.com   # must fail
  • ping -c 3 8.8.8.8: 100% packet loss ✅
  • curl -m 5 https://example.com: DNS resolution timed out ✅

6 · Idempotency

range42-context use hv-lab-01 demo_lab_network
range42-context deploy
  • PLAY RECAP shows failed=0 on all hosts ✅
  • ℹ️ hv-lab-01-cli: changed=4 — air-gap lift/restore runs every deploy by design
  • ℹ️ vuln-box-*: changed=10 — first run after iputils-ping was added; a third run gives changed=0

Closes #20

t0kubetsu added 2 commits May 11, 2026 15:05
…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
t0kubetsu added 2 commits May 15, 2026 10:57
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.
Copy link
Copy Markdown
Author

@t0kubetsu t0kubetsu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

@t0kubetsu t0kubetsu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_nsvmbr144 @ 192.168.144.250 (simulates a CTF/vuln VM)
  • test_admin_nsvmbr142 @ 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.

Copy link
Copy Markdown
Author

@t0kubetsu t0kubetsu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

t0kubetsu added 3 commits May 15, 2026 14:20
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: demo_lab_network — Segmented cyber range with isolated vuln/admin zones

1 participant