Appearance
Request & Data Flows
How data moves from the browser, through the portal and API, into the infrastructure and back.
Frontend to backend to infrastructure
Flow 1: Loading a protected page
- The browser requests
/dashboard.middleware.tsdecrypts the NextAuth cookie withNEXTAUTH_SECRET. - No cookie: redirect to
/login?callbackUrl=/dashboard. No application role: redirect to/no-role. Role does not match the path (for example a student opening/admin): redirect to/dashboard. - The page renders and its hooks call BFF routes under
/api/*.
Source: cyberrange/portal/src/middleware.ts, cyberrange/portal/src/lib/routeRoles.ts.
Flow 2: Calling the API through the BFF
- A hook calls, for example,
GET /api/pods/5/status. - The route handler calls
proxyToApi(), which reads the server-side session. No session: 401. - It forwards to
http://10.115.77.1:5000/pods/5/statuswithAuthorization: Bearer <access token>, forwarding only allow-listed query parameters. - FastAPI introspects the token (cached up to 60 seconds), checks the role and ownership, and answers.
- If FastAPI is unreachable, the proxy logs the error and returns a degraded answer (an empty list for
GET /pods, otherwise 503).
Source: cyberrange/portal/src/lib/apiProxy.ts, cyberrange/src/provisioning/auth.py.
Flow 3: Starting a lab
POST /api/pods/provision → POST /pods/provision (202) → background perform_provisioning(). The portal polls GET /pods/{id}/status until the status is ACTIVE. The build creates the lab's egress ACL with its network, and a lab only becomes ACTIVE once its Wazuh agents are connected (when Wazuh scoring is configured; the provisioning page lists the exceptions) and, for Scenario 1, its flags are planted; otherwise it ends in FAILED_ROLLBACK_COMPLETE. Full sequence: Provisioning & Teardown.
Flow 4: Terminal session
Browser WebSocket wss://<host>/api/ssh-websocket?token=...&pod_id=...&pod_type=kali → nginx → bridge.py → Keycloak introspection → GET /pods/{id}/status (ownership) → lxc exec ... tmux attach -t lab. Browser resizes are also pushed into the container's tmux client, and a dropped connection is retried automatically with backoff. Full sequence: Terminal Access.
Flow 5: Automatic and manual scoring
- Automatic:
score_poller.pyevery 3 seconds callsscoring.verify_milestone()for each unpassed milestone of each active lab. - Manual Check:
POST /pods/{id}/verify/{scenario}/{milestone}from the Tasks panel. - Both run
scoring_checks.shin the container vialxc exec, optionally look up Wazuh detection evidence, and insert a row inmilestone_verificationtagged with itssource(autoormanual). For scenarios with diagnostics switched on, a failed check's reason code is stored inscoring_diagnosticsfor instructors (GET /instructor/students/{id}/diagnostics). - The Tasks panel polls
GET /pods/{id}/milestonesevery 3 seconds (TerminalView.tsx).
Details: Lifecycle & Scoring.
Flow 6: Scenario 2 browser scoring
- The student opens DVWA through the portal (
/lab/dvwa/...), which requires a portal session. - The proxy forwards the request to the lab's DVWA proxy port and inspects the response.
- When
detectBrowserMilestone()recognizes a successful attack (for example aUNION SELECTthat returns 32-character hashes), the portal callsPOST /internal/browser-scorewithX-Browser-Score-Secret. - The API checks the secret in constant time, checks that the lab is active, owned by that student and on Scenario 2, and records a
PASSwithdetection_datasuch asbrowser:sqli-m3andsource='browser'. - When an attempt looks like a near miss (for example the security level is not Low, or a
UNIONreturns no hashes), the proxy posts only an allow-listed reason code toPOST /internal/browser-diagnosticwith the same secret. It is stored only if Scenario 2 diagnostics are switched on and the milestone has not already passed on that lab.
Source: cyberrange/portal/src/lib/dvwaScoreSignals.ts, cyberrange/src/provisioning/pods_router.py.
Flow 7: SIEM alerts
- Student (Scenario 3):
GET /pods/{id}/alertsreturns up to 50 recent alerts for that lab's agents, with only six allow-listed fields (timestamp, agent id and name, rule id, description, level). - Instructor, live:
GET /instructor/pods/{id}/alerts. - Instructor, ended labs:
GET /instructor/students/{id}/labslists lab sessions;GET .../labs/{pod}/alerts?started_at=...reads the manager's daily archive files for that time window. - Frozen with a report:
GET /instructor/reviews/{id}/alert-snapshot.
Source: cyberrange/src/provisioning/alerts_endpoint.py, alerts_reader.py, lab_history.py.
Flow 8: Review submission and resolution
- Student
POST /reviews/submitcreates areview_casesrow with statusPENDING; a background task freezes the lab's alerts inreview_alert_snapshots. - The student can attach up to 5 screenshots (
POST /reviews/{id}/images). - An instructor opens the case and resolves it:
APPROVED,REJECTEDorRETRY, with a score from 0 to 100 and feedback. - A
RETRYcase can be resubmitted by its owner, which returns it toPENDINGand takes a fresh snapshot.
Source: cyberrange/src/provisioning/pods_router.py.
Data written where
| Data | Written by | Stored in |
|---|---|---|
| Lab rows and status | pods_router.py, provision.py, reaper.py | SQLite pods |
| Milestone results | scoring.py (every check), browser-score, flag capture, instructor approval; each row tagged with its source (schema v15) | SQLite milestone_verification |
| Failure reason codes (instructor-only) | scoring_diagnostics.py, browser-diagnostic callback | SQLite scoring_diagnostics (one row per student, lab, scenario and milestone; purged after 14 days by default) |
| Research exclusions | Admin → Users (users_router.py) | SQLite research_exclusions |
| First-login orientation | onboarding_router.py | SQLite user_onboarding |
| Audit events | db.log_event() from provisioning, scoring, admin actions | SQLite audit_log |
| Lab time windows | lab_history.py | SQLite lab_sessions |
| Reviews and snapshots | pods_router.py, lab_history.py | SQLite review_cases, review_alert_snapshots |
| Evidence screenshots | evidence_images.py | Files in EVIDENCE_IMAGE_DIR, metadata in review_evidence_images |
| Accounts, passwords, roles | Keycloak (Admin page via users_router.py) | Keycloak's database |
| Shell history, triage files | The student, inside the lab | Lab container filesystem (deleted with the lab) |
| Security alerts | Wazuh manager | /var/ossec/logs/alerts/ in the wazuh-manager container |
| Test-session bookings | Booking Apps Script | A Google Sheet, outside the platform |