Appearance
Technical Defense Cheat Sheet
Likely panel questions, answered strictly from the implementation on main at commit 6971373. Each answer links to the page with the detail. Where the honest answer is "not in the repo" or "needs live verification", it says so rather than inventing a defense.
How to use this
Read the How the System Works page first for the narrative. This page is quick-recall for a viva: short, specific, and cross-referenced. If a panelist pushes for detail, follow the link.
Architecture
Q: Give the one-paragraph overview. A single OCI Ampere VM runs everything. Students reach a Next.js portal over a Cloudflare Tunnel; the portal's backend-for-frontend calls a FastAPI service that provisions, per student, an isolated OVN network with three LXD containers (Kali attacker, meta and DVWA targets). Keycloak handles identity; Wazuh is the SIEM; SQLite stores labs, scores and reviews. See System Architecture.
Q: Why one host, and what are the limits? It targets OCI's Always Free Ampere allowance (2 OCPU, 12 GiB, 200 GB). The oci_12gib profile therefore caps concurrent labs at 1. The single host is the main scalability and availability limit. See Technology Decisions and Oracle Cloud Setup.
Q: What are the trust boundaries? Internet to edge (Cloudflare), edge to application (nginx and the BFF), browser to data (session plus per-request introspection), host to lab zone (LXD control plane only, no IP route into labs), and lab to monitoring (one-way peering to Wazuh). See the Threat Model.
Frontend and backend choices
Q: Why Next.js with a backend-for-frontend? So the browser never holds a long-lived API credential path or calls the API cross-origin. proxyToApi runs server-side, attaches the token from the session, and only same-origin /api/* routes are exposed. The API has no CORS because only the BFF calls it. See Frontend.
Q: Why FastAPI? Typed request models (Pydantic) give input validation and 422s for free, dependency-injection makes the auth guards composable (require_app_role, require_role, require_owner), and async suits the background provisioning and polling tasks. See Backend.
Q: How does the terminal work without SSH? A Python websockets bridge runs lxc exec into the container and attaches a tmux session; the browser uses xterm.js. The host has no SSH into labs. See Terminal Access.
Database and "Oracle"
Q: Which database, and why SQLite? SQLite in WAL mode, one file, written only by the API. For a single-host, single-writer service it removes a whole moving part; concurrency is handled with BEGIN IMMEDIATE and busy_timeout. See Data Stores & ERD.
Q: The brief mentions Oracle. Where is it? There is no Oracle Database anywhere in the repository (no driver, no port 1521, no schema). "Oracle" here is Oracle Cloud Infrastructure, the hosting provider. The relational store is SQLite; identities are in Keycloak. See Oracle Cloud (OCI) Setup.
Q: How are relationships enforced if there is one foreign key? Only milestone_verification.pod_id -> pods.pod_id is a declared FK. Other relationships are logical, joined in code by student_id (the Keycloak preferred_username) and scenario/milestone ids. It is a deliberate simplicity trade-off, noted honestly. See Data Stores & ERD.
Q: Backups? There is no backup job in the repository for the database, evidence images or Keycloak data (F-none printed; open question 4). The suggested procedure is on the Database page. This is a known gap to raise.
Authentication vs authorization
Q: What is the difference here, concretely? Authentication is Keycloak proving who you are (OAuth2 authorization code + PKCE, password plus a required TOTP code, a 12-hour session). Authorization is what your role lets you do, enforced on every API request by token introspection plus route guards. Different mechanisms, different layers. See Authentication & RBAC and Authorization.
Q: How is a token validated on each request? By Keycloak introspection (RFC 7662), not local signature checking, so a disabled user or changed role takes effect within the cache window (at most 60 seconds, and the cache is invalidated on disable or role change). See Authorization.
Q: Where do roles come from? Realm roles student, instructor, admin, read from the token (union of realm_access and resource_access.portal). One app role per account. See Roles & RBAC.
Q: Is a password enough to get in? No. Since SEC-03 every account must enter a 6-digit authenticator code after the password; Keycloak forces enrolment by QR code at the first sign-in, and an enrolled user cannot fall back to password-only. Passwords follow a NIST 800-63B-style policy (8 to 128 characters, blocklist of 2,112 common passwords, not the username or email, last 3 not reused, no forced complexity or expiry), and 10 wrong passwords lock the account for 1 to 15 minutes. A lost phone is handled by an audited Admin Reset MFA. See Login, Sessions & Tokens.
Q: Does logout really end the session? Yes, since pull request 146: the portal keeps the ID token server-side and sends it to Keycloak as id_token_hint, which ends that exact Keycloak session and returns the user to the sign-in page. See Login, Sessions & Tokens.
Authorization and RBAC
Q: How do you stop a student reaching another student's lab or an admin route? Three layers: the edge middleware gates page routes by role; the router carries require_app_role; per-route require_role and require_owner enforce the rest. require_owner returns 404, not 403, so a lab's existence is not revealed. A test (test_sec01_rbac_matrix.py) classifies every route and fails if one is added unclassified. See Authorization and the Role Matrix.
Q: Can a student escalate by editing the request? Identity comes from the verified token's preferred_username, never from a query or body field, so a caller can only act as themselves. Inputs are Pydantic-validated; SQL is parameterized. See Authorization.
Q: Any authorization weaknesses you found? Yes, and they are documented: an admin token can open any lab's terminal (F7), and /capacity is public (F13). See Findings. Being upfront about these is part of the defense.
Networking and container isolation
Q: How are labs isolated from each other? Each lab is its own OVN network pod-N-net (10.0.(50+N).0/24), peered only to the monitoring network, never to another lab; OVN peering is not transitive. Peers are created through a restricted sudo wrapper that allows only network peer create/delete. See Containers & Networks.
Q: What stops a student attacking the host? Containers are unprivileged with CPU/RAM caps; the host reaches them only through the LXD control plane and has no IP route into lab networks; students have sudo only inside their own container. See Containers & Networks.
Q: Are the labs connected to the internet? Not by design, since pull requests 308 and 309. Lab networks still have ipv4.nat=true (LXD needs it to create the network), but each one now carries an egress ACL pod-N-egress with a default reject: traffic leaving the lab is allowed only inside its own /24 and to the Wazuh manager on TCP 1514/1515 (agent enrolment and events); DNS to the uplink resolver and the lab's own gateway address are explicitly rejected. Everything else, including the internet, host SSH and the Wazuh API on 55000, falls to the default reject; inbound traffic (consoles, the DVWA proxy, scoring) is still allowed. The ACL is attached when the network is created, so the network is never up without it, and the reaper's network reconciler re-creates or repairs it if it drifts (pod_acl_rules(), create_pod_network(), reconcile_pod_networks() in cyberrange/src/provisioning/pod_net.py). A live egress test is still the honest proof (D2, open question 2). See Open Questions.
Q: How is the vulnerable DVWA kept safe? It is never published; students reach it only through the session-gated portal proxy with a path allowlist, on an LXD proxy device bound to a private interface. See Scenario 2.
Scalability and concurrency
Q: How many students at once?MAX_PODS = 1 on the live profile (3 on onprem). The code comment says raising it needs a load test first. The single host is the ceiling. See Containers & Networks.
Q: How much does one student cost the host? Three containers capped at 4 vCPU and 4 GB RAM in total (Kali 2 vCPU / 2 GB, meta and dvwa 1 vCPU / 1 GB each), 7 GB of disk reserved (copy-on-write clones, so real disk use starts near zero), one /24 network and two Wazuh agents. Admission needs 4 GB plus a 1.5 GB buffer free; the 12 GiB host has about 8.9 GB free with no lab running, which is why it runs one lab at a time. See Resources per Student.
Q: How long can a lab run? As long as its scenario's time limit: 30 minutes for Scenario 1, 45 for Scenarios 2 and 3, 60 for Scenario 4, matching the catalog card. The reaper checks every 60 seconds. With short limits one slot can serve several students per class. See Provisioning.
Q: How do you prevent two requests taking the last slot or one student getting two labs? Admission runs inside a BEGIN IMMEDIATE transaction with a unique partial index (one active lab per student), and slow host probes (RAM, storage) happen before the lock so they cannot stall writers. See Provisioning.
Q: What if provisioning or teardown gets stuck? A reaper destroys labs past their scenario's time limit (checked every 60 seconds), and every 10 minutes rolls back or retries anything stuck in PROVISIONING/DESTROYING past a 30-minute grace. Failed builds always reach a terminal status so a slot cannot leak. See Provisioning.
Scoring
Q: How is a milestone scored? A poller runs a bash check script inside the scored container every 3 seconds; the check reads evidence (command history, a file, or live state) and returns PASS/FAIL. Scenario 2 also scores from DVWA responses through the proxy. Scenarios 1 and 2 end with a flag task the student submits. If auto-detect misses a task the student gets one Manual Check (Scenario 3's page offers instructor review directly instead); if that fails, the task goes to an instructor, whose approval credits the points. See Lifecycle & Scoring.
Q: Can a student fake a pass by pasting the guide's commands? The checks guard against it: they confirm the command actually targeted this lab (subnet derived from the container's own interface), require real state (a live socket for Scenario 1 M4, the HTTP code for Scenario 4 M3, the file's content for Scenario 4 M2), and ignore placeholder/echo lines. Scenario 3's files must name the student's own meta agent and rule 5710 and carry a UTC timestamp that matches a real failed SSH event in that lab's auth.log, so the guide's example or another student's values do not score. Scenario 1's final flag is a per-student token planted on that student's Kali. See the per-scenario pages, for example Scenario 1 and Scenario 3.
Q: How does an instructor know why a check failed? Optional, instructor-only scoring diagnostics: for Scenarios 1, 3 and 4 the check prints an allowlisted reason code (for example TIMESTAMP_NOT_MATCHED or OLD_CREDS_ACCEPTED) next to its result, and for Scenario 2 the DVWA proxy reports near-miss codes such as SECURITY_NOT_LOW. Only codes are stored, one row per student, lab and milestone, purged after 14 days by default; students still see a generic result, and the feature is off unless switched on per scenario. See Lifecycle & Scoring.
Q: Are flags hardcoded in the public repo? The rubric seed contains legacy fixed strings, but real scoring uses per-student HMAC flags (keyed by COHORT_FLAG_SECRET); the fixed strings are rejected when a student id is known. This documentation does not reproduce them. See Data Stores and Lifecycle & Scoring.
Q: What is the hybrid/conflict path?POST /progress/{scenario}/flag compares a submitted flag with lab state; if they disagree it escalates a staff-only SCORING_CONFLICT, which an instructor can approve to grant a milestone. The portal uses this route for the two flag-only final tasks (where a correct flag alone passes); hybrid submission for other milestones is API-only. See Instructor.
Q: Why only one Manual Check? So the check cannot be used as an oracle (retrying until something passes). The attempt is recorded per student, scenario and milestone, survives a new lab or sign-in, is never used up by the background poller or by an infrastructure error, and is cleared by the student's own scenario reset. See Lifecycle & Scoring.
Security and monitoring
Q: What are the main security strengths? Per-request introspection with a route matrix test, owner checks that return 404, fail-closed startup and capacity gates, one isolated network per lab with a default-reject egress ACL, unprivileged containers, secrets kept off the browser and out of the repo, required TOTP MFA with a password policy and lockout, portal security headers, a Wazuh API bound to loopback behind an internal CA, and a health-gated release with automatic rollback. See Security Architecture.
Q: How does code get to the live site? GitHub Actions builds the portal and packs the code; a maintainer promotes it as the ampere-live release; a timer on the host pulls it every 5 minutes, snapshots the current version, applies, health-checks and rolls back automatically on failure. The site is published through an outbound Cloudflare Tunnel to nginx, so the VM has no open web port. See How the Web App Is Deployed.
Q: What are the main weaknesses? Two to verify on the live host (Keycloak admin routes exposed through nginx; Keycloak in dev mode), plus token-in-WebSocket-URL, access token reachable by page scripts, passwordless Wazuh enrollment, admin-can-open-any-terminal, no test suite in CI (since pull request 269 CI runs a portal TypeScript type-check, .github/workflows/portal-typecheck.yml, but still no tests; at 6971373 the portal suite passes and the backend suite fails only Scenario 3 date tests on macOS, see Accuracy Notes), unbounded scoring-table growth, and a Next.js version with open security advisories (F19; the image optimizer is disabled as an interim measure). One earlier finding (logout without id_token_hint) is fixed and the portal response headers are partly fixed (F14). All are listed with fixes in Findings.
Q: How is the platform monitored? Wazuh agents in each lab (meta always; dvwa only if its image ships one, see D19) feed the manager (the students' SIEM in Scenario 3); GET /admin/infra-health reports API/LXD/Keycloak/Wazuh health; every provisioning and admin action writes to an append-only audit_log. See Containers & Networks and Admin.
Lab design
Q: Why these four scenarios? Two offensive (recon-to-exploit, web injection) then two defensive (SIEM triage, hardening), and the defensive hardening lab closes the exact weakness the first lab exploits, so students see both sides of one vulnerability. See Lab Scenarios.
Q: What did you change for usability? A set of lab-page aids, three of which name the System Usability Scale (SUS) items they target in their code comments (scenarioPrereqs.ts, scenarioHints.ts, TroubleshootingDrawer.tsx): a "Before you start" prerequisites box on each scenario page, progressive per-task hints (a nudge first, then the exact step), a first-run lab walkthrough replayable from a ? button, a Troubleshooting drawer, a Start here marker and a "Step complete" banner, DVWA that opens already signed in at Security Low, and a terminal that offers to reconnect after a drop. See Lab Page & Terminal UI.
Q: How is a scenario defined; could you add one? A scenario is a set of keyed pieces (catalog entry, guide, scoring target, check function, rubric, flags), not a single file. Adding one means touching each piece plus a golden-image change if new software is needed. See Lab Scenarios.
Q: What honesty is built into this documentation? Every non-obvious claim cites a source file; 17 documentation-vs-code discrepancies are tracked (D1 to D17, three of them since resolved); security weaknesses are named with severities; and unverifiable items are marked rather than guessed. See Open Questions.