Appearance
Technology Decisions
What each major technology does in this system, and why the architecture uses it. Reasons are taken from code comments, the operator manual and design documents in the repository. Where the repository states what is used but not why, the reason is marked as inferred.
Summary table
| Technology | Role in this system | Why it is used here | Evidence |
|---|---|---|---|
| Oracle Cloud Infrastructure (OCI), Ampere A1 | Hosts the entire platform on one ARM64 virtual machine (2 OCPU, 12 GiB RAM) | Fits the OCI Always Free allowance, so the capstone runs at no cloud cost. Everything else is sized for 12 GiB. | manual/README.md, manual/01-oci-host-and-network.md, cyberrange/src/provisioning/profiles.py (oci_12gib) |
| Ubuntu 24.04 (aarch64) | Host operating system | Always Free eligible image for the A1 shape | manual/01-oci-host-and-network.md |
| LXD system containers | Runs every lab machine (kali, meta, dvwa) and the infrastructure containers (gateway, Wazuh manager) | System containers run a full Linux userspace (systemd, SSH, tmux, Docker inside dvwa) at a fraction of a VM's memory, which is what makes labs possible on 12 GiB. Instances are created from published golden images through pylxd. | cyberrange/src/provisioning/provision.py, cyberrange/deploy/oci_build_goldens_arm64.sh |
| OVN (via LXD networks) | Gives each lab its own virtual network pod-<id>-net (10.0.<50+id>.0/24) and peers it to the monitoring network | One isolated layer-2 network per student lab, created and deleted by API calls. Peering lets Wazuh agents reach the manager without joining lab networks to each other. | cyberrange/src/provisioning/pod_net.py, manual/02-gateway-and-wazuh.md |
| FastAPI (Python) | The provisioning and scoring API | Async request handling with background tasks for slow LXD work, Pydantic validation of every request body, dependency-injected auth guards (Depends) | cyberrange/src/provisioning/provision_api_fastapi.py, cyberrange/src/provisioning/pods_router.py |
| SQLite | Stores labs, scores, reviews, audit events, lab sessions, evidence image metadata | Single-host deployment with low write volume. The code uses WAL mode, a 30 s busy timeout and BEGIN IMMEDIATE transactions to handle concurrent writers. No separate database server to run on 12 GiB. Rationale partly inferred. | cyberrange/src/provisioning/db.py, cyberrange/src/provisioning/migrate.py |
| Next.js 15 (App Router) + React 19 | The portal: all pages plus a server-side "backend for frontend" (BFF) API layer | Server-side route handlers keep the FastAPI service off the public path and attach the user's token server-side. Edge middleware enforces login and role redirects before a page renders. | cyberrange/portal/src/lib/apiProxy.ts, cyberrange/portal/src/middleware.ts |
| NextAuth v4 | OAuth/OIDC client and session management in the portal | Handles the Keycloak authorization code flow with PKCE, encrypted session cookies and token refresh | cyberrange/portal/src/lib/auth.ts |
| Keycloak | Identity provider: accounts, passwords, realm roles (student, instructor, admin) | Standards-based OIDC login, central role management, token introspection for the API, and an admin REST API used by the Admin "Users" page | cyberrange/deploy/keycloak/keycloak.service, cyberrange/src/provisioning/keycloak_admin.py |
| Tailwind CSS | Portal styling | Utility classes and shared design tokens | cyberrange/portal/tailwind.config.js |
| xterm.js | In-browser terminal | A full terminal emulator in the page, so students need no SSH client | cyberrange/portal/src/components/terminal/XtermView.tsx |
Python websockets + lxc exec | Terminal bridge from the browser to a container shell | Uses the LXD control plane instead of SSH, because the host has no network route into the per-lab OVN networks | cyberrange/bridge-src/bridge.py, comment in cyberrange/src/provisioning/ssh_verifier.py |
| tmux | Keeps each student shell alive in a named session lab | Reconnecting reattaches the same session, and Scenario 1's live Metasploit check reads that session | cyberrange/bridge-src/bridge.py, cyberrange/src/provisioning/scoring_checks.sh |
| Bash scoring script | Milestone checks run inside the lab containers | Checks need to execute on the target (read shell history, inspect files, test a login), which a read-only file API cannot do | cyberrange/src/provisioning/scoring_checks.sh, header of cyberrange/src/provisioning/ssh_verifier.py |
| Wazuh 4.7.5 (manager only) | SIEM: collects agent events from meta and dvwa, raises alerts used in Scenario 3 and in instructor views | On 12 GiB there is no room for the Wazuh indexer and dashboard, so only the manager runs; the portal reads alerts itself | manual/02-gateway-and-wazuh.md, manual/09-known-issues.md (Issue 13), cyberrange/src/provisioning/alerts_reader.py |
| DVWA + MariaDB (Docker Compose inside dvwa) | Deliberately vulnerable web application for Scenario 2 | A standard teaching target for SQL injection and XSS, pinned to security level Low | cyberrange/deploy/oci_build_goldens_arm64.sh, cyberrange/src/provisioning/dvwa_compose.py |
| nginx | Gateway reverse proxy in the guacamole container | Single origin for the tunnel: portal, /auth (Keycloak) and the terminal WebSocket share one hostname, so cookies stay same-site | cyberrange/deploy/guacamole/nginx-ampere.conf, comment in cyberrange/portal/src/lib/auth.ts |
| Cloudflare Tunnel (cloudflared) | Publishes the portal on HTTPS without any inbound firewall rule | The tunnel dials out to Cloudflare, so ports 80 and 443 stay closed on the VM | cyberrange/deploy/systemd/cloudflared.service, manual/01-oci-host-and-network.md |
| systemd user units | Run the API, portal, bridge and release puller | Restart on failure, start at boot, environment files per service | cyberrange/deploy/systemd/ |
| GitHub Actions + GitHub Releases | Builds the portal and packages the code tree; a "Promote" run publishes release ampere-live | The host cannot build the portal itself, so it pulls a prebuilt .next and applies it with health checks and automatic rollback | .github/workflows/ampere-release.yml, cyberrange/deploy/host/pull_release.sh |
Decisions worth explaining to a panel
Why containers, not virtual machines?
The whole platform must fit in 12 GiB of RAM. A lab needs three machines. LXD system containers share the host kernel, so each lab machine costs hundreds of megabytes instead of gigabytes, yet it still behaves like a normal Linux server with services, users and a shell. Profiles cap each lab container: 2 CPUs and 2 GB for Kali, 1 CPU and 1 GB for meta and dvwa (manual/02-gateway-and-wazuh.md).
Why a separate network per lab?
Each lab gets its own OVN network, so one student's machines cannot see another student's machines. Lab networks are peered only to the monitoring network (mon-net) so Wazuh agents can report to the manager (cyberrange/src/provisioning/pod_net.py).
Why token introspection instead of verifying JWTs locally?
The API asks Keycloak about each token (results are cached for at most 60 seconds). A disabled account or a changed role therefore takes effect within a minute, and the Admin page clears the cache for that user immediately (cyberrange/src/provisioning/auth.py, cyberrange/src/provisioning/introspect_cache.py).
Why a backend-for-frontend layer?
The browser only ever talks to the portal. The portal's route handlers read the session server-side and forward the request with the bearer token, allow-listing query parameters. FastAPI is bound to a private address and is never exposed publicly (cyberrange/portal/src/lib/apiProxy.ts, cyberrange/deploy/systemd/cyberrange-provision-api.service).
Why lxc exec for the terminal and for scoring?
A code comment records that the earlier design (SSH from the host into each lab) always timed out because the host has no route into the per-lab OVN networks. The LXD control plane reaches every container without any network path (cyberrange/src/provisioning/ssh_verifier.py).
Needs verification
The repository does not record a written comparison against alternatives (for example Docker-only labs, Proxmox VMs or PostgreSQL). Rationale above comes from code comments and the manual. If the panel asks "what else did you consider?", confirm the answer with the team.