Appearance
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
| Container | Image | Profile (limits) | IP | Shell user in the terminal | What runs on it |
|---|---|---|---|---|---|
pod-<student>-kali | kali-base | default + pod-security-base (2 CPU, 2 GB, unprivileged) | 10.0.<50+id>.10 | student (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>-meta | meta-base | default + pod-target-base (1 CPU, 1 GB, nesting) | 10.0.<50+id>.20 | msfadmin (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>-dvwa | dvwa-base | default + pod-target-base | 10.0.<50+id>.30 | student | Docker 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-agent4.7.5 (held, to match the manager) andrsyslog; - routes
auth,authpriv.*to/var/log/auth.logand makes sureossec.confhas exactly one/var/log/auth.logsyslogcollection entry (the 4.7.5 package can write two<ossec_config>blocks, whichossec_localfile.pyhandles), with theMANAGER_IPplaceholder filled in at provisioning; - validates the staged image while its services run, publishes it as a candidate, and only then moves the
meta-basealias, 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
| Container | Networks | Role |
|---|---|---|
guacamole | lxdbr0 (10.115.77.12) and mgmt-net | HTTP gateway: nginx and native Keycloak. The name is historical; it does not run Apache Guacamole. |
wazuh-manager | mon-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
| Control | How it is implemented | Source |
|---|---|---|
| Separate network per lab | lxc 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 internet | pod_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 in | pod_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 guess | pod_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 restart | pod_net.py (ensure_manager_pod_route), manual/09-known-issues.md Issue 14 |
| No lab-to-lab path | Lab 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 other | pod_net.py, manual/02-gateway-and-wazuh.md |
| Limited peering privilege | Peers are created through /usr/local/sbin/cyberrange-lxc-peer, a root wrapper that allows only network peer create and network peer delete | pod_net.py, deploy/host/install_cyberrange_lxc_peer.sh |
| Unprivileged containers with limits | LXD containers are unprivileged by default (Kali's profile sets it explicitly); CPU and memory caps per profile | manual/02-gateway-and-wazuh.md |
| Host does not route into labs | The API and bridge use the LXD control plane (lxc exec, file API); code comments state the host has no route to the lab networks | ssh_verifier.py |
| DVWA not published | Each 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 contained | Students have sudo inside their own containers; the containers are unprivileged | provision.py |
| Wazuh API not reachable from labs | The 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 55000 | deploy/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:55000are 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.shis 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'sauthorized_keysin every lab container (provision.py), although scoring no longer uses SSH.
Concurrency and resource allocation
| Control | Value | Source |
|---|---|---|
| Concurrent labs | MAX_PODS: 1 on the oci_12gib profile, 3 on onprem | profiles.py, config.py |
| Address space | Six slots (pod_id 1 to 6), always the lowest free slot | pods_router.py |
| One lab per student | Checked inside BEGIN IMMEDIATE and enforced by a unique partial index | pods_router.py, schema/v2.sql |
| RAM gate | Free memory (MemAvailable) must be at least labs-to-add × POD_RAM_MB + RAM_BUFFER_MB; labs still PROVISIONING count as in-flight | capacity.py, pods_router.py |
| Storage gate | LXD pool free space must cover POD_STORAGE_MB; if LXD cannot report, falls back to database accounting against STORAGE_LIMIT_MB and logs a warning | pods_router.py, provision.py |
| Lifetime | Per scenario: 30 min (01), 45 (06, 09), 60 (11), checked by the reaper every 60 s | config.py SCENARIO_TTL_MINUTES, ttl.py, reaper.py |
| Per-lab CPU, memory and disk | Kali 2 vCPU / 2 GB, meta and dvwa 1 vCPU / 1 GB each; 7 GB disk reserved. Full breakdown: Resources per Student | LXD 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.confwhoseMANAGER_IPplaceholder 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 writesalerts.json. The bake now guarantees that meta collects/var/log/auth.logas 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 markedACTIVEuntil its agents reportactive(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-healthreports API, LXD, Keycloak and Wazuh status for the Admin dashboard;GET /capacityreports free RAM and slots. - Audit trail: every provisioning step and teardown writes an event to
audit_log(for exampleCLONE_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/*.logon the host (cleared on reboot); the scoring script logs to/var/log/cyberrange-scoring.loginside 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.