Skip to content

API Reference ​

The provisioning API is a FastAPI service (cyberrange/src/provisioning/provision_api_fastapi.py, version 1.2.0) bound to 10.115.77.1:5000. The browser never calls it directly; the portal's BFF proxies every request and injects the access token server-side. The terminal bridge calls it to check lab ownership.

Authorization tiers ​

Every route is classified in ROUTE_POLICY in test_sec01_rbac_matrix.py, and a test fails if a route ships unclassified. There are four tiers.

TierWho passesEnforced by
PublicAnyone, including unauthenticatedno auth dependency (browser-score uses a shared secret)
Any app rolestudent, instructor or admin, scoped to their own datarequire_app_role on the router, plus require_owner per pod
Instructorinstructor or adminrequire_role(["instructor","admin"])
Adminadmin onlyrequire_role(["admin"])

"Allowed" means the caller passed authorization; a 404, 409 or 422 after that is business logic. require_owner returns 404 (not 403) so a lab's existence is not revealed to non-owners.

Public ​

MethodPathPurposeNotes
GET/healthLivenessReturns {"status":"ok"}
GET/capacityFree RAM, active pods, limits, whether a lab can be provisionedReturns available_mb, active_pods, max_pods, pod_ram_mb, ram_buffer_mb, profile, ram_required_mb, can_provision. See F13 (public info).
POST/internal/browser-scoreDVWA browser scoring callback (Scenario 2 only)Requires the X-Browser-Score-Secret header (constant-time compare); 503 if unset. Checks pod exists, ACTIVE, owner match, scenario 06. The body is validated strictly (unknown fields refused, student_id pattern, bounded IDs and label).
POST/internal/browser-diagnosticScenario 2 near-miss reason from the DVWA proxy, stored for instructorsSame secret and pod checks. Accepts only an allowlisted reason code; returns {"recorded": false} when Scenario 2 diagnostics are off or the milestone already passed on that lab.

Any app role (self-scoped) ​

Provided by pods_router.router, which carries Depends(require_app_role). Pod routes additionally check ownership.

MethodPathPurpose
POST/pods/provisionStart a lab (identity from the token; scenario_id in the body). Returns 202.
GET/podsList the caller's labs (admins get all, with an optional student_id filter)
GET/pods/{id}/statusOne lab's status (owner or admin), including ttl_minutes and remaining_seconds; updates the heartbeat when ACTIVE
GET/pods/{id}/guac-tokenLegacy placeholder (native-xterm-ws); owner only
GET/pods/{id}/lab-urlsDVWA and SIEM URLs for the lab; owner only
DELETE/pods/{id}/destroyEnd the caller's own lab
POST/pods/{id}/verify/{scenario}/{milestone}Manual Check; owner only, lab must be ACTIVE. Once per milestone: a definitive PASS or FAIL is recorded in manual_check_attempts; a later call answers status: "REVIEW" without running the check
GET/pods/{id}/milestonesMilestone history for the lab plus manual_check_locked (milestone IDs locked to instructor review); owner only
GET/pods/{id}/alertsThe lab's Wazuh alerts; owner only
GET/progressThe caller's milestone history across labs
DELETE/progress/{scenario}Reset (delete) the caller's own rows and used Manual Check attempts for one scenario
POST/progress/{scenario}/flagSubmit a flag. Flag-only tasks (1,5) and (6,5) pass on a correct flag alone; other milestones use hybrid flag + state scoring. Called by the portal's FlagSubmission box.
GET/progress/{scenario}/rubricsRubric criteria for a scenario
GET / POST/users/me/orientationRead ({"completed": bool}) or record the caller's first-login orientation, keyed by the token's sub; 401 if sub is missing (onboarding_router.py, mounted with require_app_role)
POST/reviews/submitSubmit a written report or manual-review request
GET/reviews/{id}Read a review (owner or staff)
POST/reviews/{id}/resubmitResubmit a review sent back as RETRY (owner only)
POST / GET/reviews/{id}/imagesUpload / list evidence screenshots (owner)
GET / DELETE/reviews/{id}/images/{image_id}View (owner or staff) / delete (owner) an image

Instructor (instructor or admin) ​

MethodPathPurpose
GET/instructor/podsAll live labs with progress and ttl_minutes (sensitive fields stripped)
GET/instructor/pods/{id}/alertsLive SIEM view for a lab
GET/instructor/students/{sid}/labsA student's lab history
GET/instructor/students/{sid}/labs/{pod}/alertsArchived alerts for an ended lab (started_at required)
GET/instructor/reviews/{id}/alert-snapshotAlerts frozen with a report
GET/instructor/studentsStudents derived from labs, milestones and reviews (not Keycloak)
GET/instructor/students/{sid}One student's detail; each milestone row includes source (auto, manual, browser, flag, review, or null for rows before v15)
GET/instructor/students/{sid}/diagnosticsCheck-failure reason codes for one student (optional pod_id, pod_created_at), plus enabled, enabled_scenarios and retention_days
GET/instructor/reviewsReview queue (filter by status)
GET/instructor/reviews/{id}Review detail
POST/instructor/reviews/{id}/resolveApprove, reject or request retry (score 0 to 100, feedback). Approving a milestone-scoped case writes the milestone PASS; changing it to reject or retry removes that instructor-granted PASS
GET/instructor/export/knowledge-gainTelemetry export (JSON or CSV, scenario_id, status_filter). Anonymized by default; anonymize=false and include_excluded=true are admin only (403 for instructors). Admin-marked test accounts and Keycloak staff accounts are left out; the JSON summary and the X-Excluded-Accounts / X-Staff-Exclusion headers report it. Every call writes KNOWLEDGE_GAIN_EXPORTED

Admin only ​

MethodPathPurpose
DELETE/admin/pods/{id}/force-destroyDestroy any lab (ACTIVE or FAILED_ROLLBACK_COMPLETE); audited
GET/admin/infra-healthAPI, LXD, Keycloak and Wazuh health
GET / POST/admin/usersList / create Keycloak users; each user carries research_excluded and research_exclusion_reason
PATCH/admin/users/{id}/enabledEnable or disable a user
PUT/admin/users/{id}/roleChange a user's single app role
PUT/admin/users/{id}/passwordReset a password (temporary by default; Keycloak's policy can answer 422)
DELETE/admin/users/{id}/mfaReset MFA: delete the user's OTP credentials so they re-enrol; 409 on your own account; audited ADMIN_USER_MFA_RESET
PUT/admin/users/{id}/research-exclusionBody {"excluded": bool, "reason": ...} with reason one of test_account, team_member, other (required when excluding; 422 otherwise). Leaves the account out of, or puts it back into, the knowledge-gain export; allowed on any account, including your own; 503 if the database write fails; audited ADMIN_USER_RESEARCH_EXCLUDE / ADMIN_USER_RESEARCH_INCLUDE
GET/admin/audit-logRead the append-only audit log: event_type or comma-separated event_types (at most 64), student_id, result (OK and FAILED match outcome groups), before_id, limit (keyset paging, up to 500)

User-management writes are audited with the actor, block self-targeting and removing the last admin, and revoke the target's cached tokens.

Breaking rename in pull request 167

Pod responses return ttl_minutes instead of ttl_hours. The API and portal must be deployed together.

Conventions ​

  • Auth: Authorization: Bearer <token>; the API validates it by Keycloak introspection every request (cache at most 60 s). Missing header is 401; introspection failure is 503.
  • Identity: taken from the token's preferred_username, never from a query or body parameter, so a caller can only act as themselves. (When AUTH_ENABLED=false, a dev-only bypass, student_id may come from the request.)
  • Validation: request bodies are Pydantic models (malformed input is 422); user-management, flag submission and the internal browser routes reject unknown fields. For POST /admin/users and PUT /admin/users/{id}/password the 422 body is rewritten to fixed messages (Value is too short, Value has an invalid format, ...) so a rejected password is never echoed back (sanitized_validation_error() in provision_api_fastapi.py).
  • Errors: JSON {"detail": ...}; owner checks use 404, capacity uses 503 with a machine-readable code (POD_CAP_REACHED, RAM_FULL, STORAGE_FULL, ALREADY_PROVISIONED).
  • No CORS: the API has no CORS middleware because only the server-side BFF calls it.

Older docs undercount the API

The network appendix lists only /pods, /capacity, /health, /alerts. The real surface is the 40-plus routes above (D7). The authoritative inventory is ROUTE_POLICY in test_sec01_rbac_matrix.py.

Sources

cyberrange/src/provisioning/provision_api_fastapi.py, pods_router.py, users_router.py, onboarding_router.py, research_exclusions.py, scoring_diagnostics.py, keycloak_admin.py, hybrid_scoring.py, ttl.py, models.py, infra_health.py, audit_router.py, alerts_endpoint.py, auth.py, test_sec01_rbac_matrix.py.

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