Skip to content

Containers & Networks ​

Every student lab is a set of three LXD system containers on a dedicated OVN network. This page explains what the machines are, how they are connected and isolated, how capacity is controlled and how labs are monitored.

Lab network topology ​

Addresses come from manual/appendix-network-map.md and manual/02-gateway-and-wazuh.md; lab networks, their egress ACLs, peers and the manager's return routes are created by cyberrange/src/provisioning/pod_net.py.

The lab machines ​

ContainerImageProfile (limits)IPShell user in the terminalWhat runs on it
pod-<student>-kalikali-basedefault + pod-security-base (2 CPU, 2 GB, unprivileged)10.0.<50+id>.10student (passwordless sudo in this container)Kali headless tool set (Nmap, Metasploit and others); cloud-init writes /etc/lab.env with the student and scenario; $TARGET_META and $TARGET_DVWA exported
pod-<student>-metameta-basedefault + pod-target-base (1 CPU, 1 GB, nesting)10.0.<50+id>.20msfadmin (passwordless sudo in this container)Deliberately weak Ubuntu target: vsftpd, Apache, Tomcat Manager on 8180 with the default credential, password SSH, rsyslog, Wazuh agent
pod-<student>-dvwadvwa-basedefault + pod-target-base10.0.<50+id>.30studentDocker Compose with DVWA and MariaDB (security level Low); a Wazuh agent only if the image ships one (the builder bakes the agent into meta-base only)

Sources: cyberrange/src/provisioning/provision.py, cyberrange/deploy/oci_build_goldens_arm64.sh, cyberrange/deploy/golden/phase3/*.sh, cyberrange/bridge-src/bridge.py, profiles in manual/02-gateway-and-wazuh.md.

"meta" is not Metasploitable

Despite the account name msfadmin, meta is an Ubuntu container built by the project's golden-image script with a few deliberately weak services, sized for ARM64.

Golden images ​

Golden images are built once on the host by cyberrange/deploy/oci_build_goldens_arm64.sh and published as LXD image aliases kali-base, meta-base and dvwa-base. Since pull request 179 the builder runs cyberrange/deploy/golden/bake_wazuh_agent.sh meta-base automatically, unless the image description already says wazuh-agent 4.7.5 auth.log collection; it is no longer an optional step (manual 03, Step 2). The bake:

  • refuses to run while any lab container (pod-*-kali, -meta, -dvwa) exists;
  • checks the Wazuh signing key against a pinned fingerprint, installs wazuh-agent 4.7.5 (held, to match the manager) and rsyslog;
  • routes auth,authpriv.* to /var/log/auth.log and makes sure ossec.conf has exactly one /var/log/auth.log syslog collection entry (the 4.7.5 package can write two <ossec_config> blocks, which ossec_localfile.py handles), with the MANAGER_IP placeholder filled in at provisioning;
  • validates the staged image while its services run, publishes it as a candidate, and only then moves the meta-base alias, restoring the old alias if publishing fails;
  • keeps a unique, fingerprint-anchored rollback alias meta-base-prewazuh-<timestamp>-<pid> and prunes all but the two newest.

Every lab starts from these images, so each student gets a clean, identical environment.

Infrastructure containers ​

ContainerNetworksRole
guacamolelxdbr0 (10.115.77.12) and mgmt-netHTTP gateway: nginx and native Keycloak. The name is historical; it does not run Apache Guacamole.
wazuh-managermon-net (10.0.40.10) and lxdbr0 (10.115.77.144, for package downloads only)Wazuh manager, memory limit 3 GB, no indexer or dashboard

Source: manual/02-gateway-and-wazuh.md.

Isolation and security boundaries ​

ControlHow it is implementedSource
Separate network per lablxc network create pod-<id>-net --type=ovn network=vmbr1 ipv4.address=10.0.<50+id>.1/24 ipv4.nat=true ipv6.address=none security.acls=pod-<id>-egress security.acls.default.egress.action=reject security.acls.default.ingress.action=allow. NAT stays on only because LXD 5.21 needs the lab subnet in the uplink's routes otherwise; the default reject is what blocks the internetpod_net.py
Egress ACL (pull request 308)The ACL pod-<id>-egress is created before the network, so the network is never up without it. Allowed out: the lab's own /24, and the Wazuh manager's /32 on TCP 1514/1515. Rejected explicitly: DNS (UDP and TCP 53) to the uplink resolver, which LXD would otherwise allow, and the lab's own gateway .1. Everything else, including the internet, host SSH and the Wazuh API on 55000, falls to the default reject. The manager address must be a host inside 10.0.40.0/24. Ingress stays open so the terminal, the DVWA proxy and scoring can reach inpod_net.py (pod_acl_rules, create_pod_network)
ACL kept in line (pull request 309)Every reaper sweep compares each ACTIVE lab's ACL with the wanted rules on every field, removes stale rules, adds missing ones, sets security.acls to the lab's own ACL only (a foreign ACL would outrank the default reject) and resets the default actions. It skips the refresh if it cannot be sure of the manager address or the uplink resolver rather than guesspod_net.py (ensure_pod_acl, reconcile_pod_networks)
Return route on the manager (pull request 195)The manager's default route is its lxdbr0 NIC, so replies to a lab would be lost. Creating a lab network runs ip route replace 10.0.<50+id>.0/24 via <mon-net gateway> dev eth0 inside wazuh-manager; teardown deletes it, and the reaper re-adds it after a manager restartpod_net.py (ensure_manager_pod_route), manual/09-known-issues.md Issue 14
No lab-to-lab pathLab networks are peered only with mon-net (peers to-mon and from-pod-<id>); OVN peering is not transitive, so labs do not see each otherpod_net.py, manual/02-gateway-and-wazuh.md
Limited peering privilegePeers are created through /usr/local/sbin/cyberrange-lxc-peer, a root wrapper that allows only network peer create and network peer deletepod_net.py, deploy/host/install_cyberrange_lxc_peer.sh
Unprivileged containers with limitsLXD containers are unprivileged by default (Kali's profile sets it explicitly); CPU and memory caps per profilemanual/02-gateway-and-wazuh.md
Host does not route into labsThe API and bridge use the LXD control plane (lxc exec, file API); code comments state the host has no route to the lab networksssh_verifier.py
DVWA not publishedEach lab's DVWA is exposed through an LXD proxy device bound to LAB_PROXY_LISTEN (default 10.115.77.1) on port 18300+id, not the public NIC, and reached by students only through the session-protected portal proxy. A wildcard 0.0.0.0 bind happens only if LAB_PROXY_ALLOW_WILDCARD_FALLBACK=true, and a second device is added only if LAB_PUBLIC_HOST is set (keep it empty on OCI).lab_proxy.py, portal/src/app/lab/dvwa/[[...path]]/route.ts
Root is containedStudents have sudo inside their own containers; the containers are unprivilegedprovision.py
Wazuh API not reachable from labsThe manager's API is bound to 127.0.0.1 inside its container by harden_wazuh_api.sh (pull request 303) and published to the host loopback by an LXD proxy device; the lab ACL also rejects port 55000deploy/host/harden_wazuh_api.sh, manual/02-gateway-and-wazuh.md Step 4d

Needs verification

  • Internet egress from labs. Since pull request 308 the code creates the egress ACL and rejects everything not listed above. It is new: confirm from a disposable live lab that the internet, the uplink DNS resolver and 10.0.40.10:55000 are refused while agents still enrol. Labs that were already running when the change shipped get their ACL from the reaper's next sweep (every 10 minutes).
  • Shared services reachable from labs. Every lab can still reach the Wazuh manager on 1514/1515 (that is how agents report). Enrollment is passwordless by design (manual/06-scoring.md), so a student with root in Kali could register extra agents or send noise. See the threat model.
  • API loopback bind. harden_wazuh_api.sh is run by hand once per host; its evidence record (cyberrange/docs/evidence/WAZUH-API-286-loopback-and-rotation.md) is still Pending.
  • Host administrator key. Provisioning installs the host operator's SSH public key into root's authorized_keys in every lab container (provision.py), although scoring no longer uses SSH.

Concurrency and resource allocation ​

ControlValueSource
Concurrent labsMAX_PODS: 1 on the oci_12gib profile, 3 on onpremprofiles.py, config.py
Address spaceSix slots (pod_id 1 to 6), always the lowest free slotpods_router.py
One lab per studentChecked inside BEGIN IMMEDIATE and enforced by a unique partial indexpods_router.py, schema/v2.sql
RAM gateFree memory (MemAvailable) must be at least labs-to-add × POD_RAM_MB + RAM_BUFFER_MB; labs still PROVISIONING count as in-flightcapacity.py, pods_router.py
Storage gateLXD pool free space must cover POD_STORAGE_MB; if LXD cannot report, falls back to database accounting against STORAGE_LIMIT_MB and logs a warningpods_router.py, provision.py
LifetimePer scenario: 30 min (01), 45 (06, 09), 60 (11), checked by the reaper every 60 sconfig.py SCENARIO_TTL_MINUTES, ttl.py, reaper.py
Per-lab CPU, memory and diskKali 2 vCPU / 2 GB, meta and dvwa 1 vCPU / 1 GB each; 7 GB disk reserved. Full breakdown: Resources per StudentLXD profiles, profiles.py

A comment in config.py states that raising MAX_PODS above 3 needs a load test first (it points to Manual 09 section 2b).

Monitoring ​

  • Security events: the Wazuh agent (4.7.5) is baked into the meta image with a templated ossec.conf whose MANAGER_IP placeholder is filled in at provisioning; provisioning also starts an agent on dvwa when that image has one, and Kali has no agent (deploy/golden/bake_wazuh_agent.sh, provision.py). The agents send events to the manager, which writes alerts.json. The bake now guarantees that meta collects /var/log/auth.log as syslog (the source of Scenario 3's rule 5710), while web-log rules for DVWA are deferred until Apache log ingestion is configured (wazuh_rule_map.py). A lab is not marked ACTIVE until its agents report active (see Provisioning). Students see their lab's alerts in the portal; instructors see live and archived alerts per lab. Other agent modules are package defaults and are not described in the repository (Needs verification on the live images).
  • Platform health: GET /admin/infra-health reports API, LXD, Keycloak and Wazuh status for the Admin dashboard; GET /capacity reports free RAM and slots.
  • Audit trail: every provisioning step and teardown writes an event to audit_log (for example CLONE_OK, GUEST_NIC_UP_FAIL, WAZUH_ENROLL_OK, WAZUH_STALE_AGENT, WAZUH_CLEANUP_SKIPPED, LAB_PROXY_FAIL, PROVISION_SUCCESS, POD_TTL_REAP).
  • Logs: service logs in /tmp/*.log on the host (cleared on reboot); the scoring script logs to /var/log/cyberrange-scoring.log inside each container.

Sources

cyberrange/src/provisioning/provision.py, pod_net.py, lab_proxy.py, capacity.py, profiles.py, reaper.py, infra_health.py, ssh_verifier.py, wazuh_client.py, cyberrange/deploy/oci_build_goldens_arm64.sh, cyberrange/deploy/golden/bake_wazuh_agent.sh, ossec_localfile.py, cyberrange/deploy/host/harden_wazuh_api.sh, manual/02-gateway-and-wazuh.md, manual/03-golden-images-arm64.md, manual/appendix-network-map.md, manual/06-scoring.md, manual/09-known-issues.md.

Cyber Range capstone documentation (Capstone 2 · Group 2 · A4102). Documents main at commit 6971373.