Skip to content

On-demand, per-team containerised challenge instances #266

Description

@tbcsec

Closed 2026-08-28. Tracking issue for on-demand, per-team containerised challenge instances. The Docker MVP (Phases 0–1) and the unique per-instance flags slice (Phase 2a) have shipped to main; the remaining phased work is split into dedicated issues (below). This issue is now the record of what landed. Full design lives in ADR-0036 (docs/adr/0036-challenge-instancing-provisioners.md).

What this delivered

The optional Challenge Instances module (ADR-0036) — off by default, toggled per competition, inert until an operator configures a provisioner. It hands each team (or user, in individual mode) an isolated, running instance of a challenge with live connection details, on a pluggable provisioner backend.

Phase 0 — Foundations

  • Provisioner kind registry + backend-agnostic contract (create/status/endpoints/destroy/list) — a new backend is a new kind, not a fork.
  • Data model + migrations for challenge_deployment and challenge_instance (no impact on existing challenge queries).

Phase 1 — Docker MVP (verified live against a real Docker daemon: launch → connect → extend → destroy)

  • Hardened DockerProvisioner over a least-privilege socket proxy (CapDrop ALL, no-new-privileges, ReadonlyRootfs, tmpfs-only writable).
  • Background-lane instance lifecycle (requested → provisioning → running → expiring → destroyed, terminal failed) with a TTL reaper + orphan GC on the existing scheduler — no new process.
  • TCP exposure + host-port allocation (configurable range; exhaustion is a clean, evented refusal).
  • Competitor launch / extend / destroy API + WebSocket status push; competitor challenge-modal UI (launch, expiry countdown, connection info, extend).
  • Staff ops view (running instances, health, force-kill, audited).
  • Site infra settings + a staged Test connection validator; RBAC (instance_launch / instance_manage / instance_view, and manage_instance_infra for site config).
  • Egress-deny is a host-firewall responsibility, not a Docker internal network (ADR-0036 amended after live testing — an internal network can't publish TCP ports).

Phase 2a — Unique per-instance flags (PR #318, ADR-0036 §3)

  • flag_mode: unique_per_instance — a fresh flag rendered from a template (<random> → random token) at provision time, injected into the container once, with only the salted hash stored (plaintext never persisted).
  • Grading against the submitting subject's live instance(s); static / regex / MCQ paths and first-blood / decay unchanged.
  • challenge.flag_shared_detected — a wrong submission carrying another subject's live flag is signalled to staff + automation as provable sharing (no automatic penalty).

Docs: docs/CHALLENGE_INSTANCES.md (infra + authoring guide) and ADR-0036.

Delivered checklist

  • ADR + provisioner interface / backend contract
  • Data model + migrations (challenge_deployment, challenge_instance)
  • DockerProvisioner (single host) via a least-privilege socket proxy
  • Instance lifecycle service + TTL reaper + orphan GC
  • TCP exposure + port allocation
  • Competitor launch API + WebSocket status push
  • Competitor challenge UI (launch, timer, connection info, extend)
  • Staff ops view (running instances, health, force-kill, audited)
  • RBAC: instance_launch / instance_manage / instance_view / manage_instance_infra
  • Per-instance unique flags + grading (+ flag-sharing detection)
  • Instance lifecycle events + automation actions
  • Docs: instanced-challenge authoring + infra deployment guide

Follow-up work — split into dedicated issues

Delivered across ADR-0036 and PRs #303#315 (Docker MVP) and #318 (unique flags).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions