CTFd icon indicating copy to clipboard operation
CTFd copied to clipboard

Feature request: Opt‑in programmatic init API for first‑boot setup

Open petems opened this issue 4 months ago • 1 comments

Summary

Automating first‑boot configuration for CTFd currently requires driving the CSRF‑protected HTML form at /setup (cookie jar + nonce scraping). This is brittle for infrastructure automation (ECS/Fargate, Helm, etc.).

I'm proposing an opt‑in, disabled‑by‑default JSON API endpoint to perform initial setup in a secure, idempotent, and well‑documented way.

Current behavior (what automation must do today)

Because /setup is an HTML form guarded by CSRF and session cookies, a headless initializer must:

  1. GET /setup without following redirects; capture cookies.
  2. Parse the CSRF nonce from the HTML (hidden input name="nonce").
  3. POST application/x-www-form-urlencoded back to /setup with fields including nonce, ctf_name, name, email, password.
  4. Treat a redirect away from /setup as success; otherwise retry.

Example (Python stdlib; trimmed):

import re, json, urllib.request, urllib.error

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, *args, **kwargs): return None
opener = urllib.request.build_opener(NoRedirect())

base = "https://ctfd.example.com"
r = opener.open(urllib.request.Request(base + "/setup", method="GET"), timeout=10)
html = r.read().decode("utf-8", "ignore")
m = re.search(r'name=["\']nonce["\']\s+value=["\']([^"\']+)', html)
nonce = m.group(1)

form = urllib.parse.urlencode({
  "nonce": nonce,
  "ctf_name": "My CTF",
  "name": "admin",
  "email": "[email protected]",
  "password": "StrongPass#1",
}).encode()

req = urllib.request.Request(base + "/setup", data=form, headers={
  "Content-Type": "application/x-www-form-urlencoded"
}, method="POST")
try:
    opener.open(req, timeout=15)  # 30x redirect indicates configured
except urllib.error.HTTPError as e:
    assert 300 <= e.code < 400

Pain points:

  • HTML structure parsing is brittle; CSRF implementation details may change.
  • Requires redirect control and cookie jar handling in every automation environment.
  • Hard to document a “supported” way to automate; infra code ends up re‑implementing the same flow.

Proposal: Add an opt‑in first‑boot JSON endpoint

  • Example Path: POST /api/v1/init/setup
  • Disabled by default. Enable with explicit server config/env (e.g., CTFD_INIT_API_ENABLED=true).
  • Require a boot‑strap token (e.g., CTFD_INIT_API_TOKEN) provided via header Authorization: Bearer <token>.
  • Only usable when the instance is not yet configured. After success, the endpoint returns 409 AlreadyConfigured.
  • Idempotent: repeating with the same payload after success returns 200 OK + a status object or 409.
  • Rate‑limited and single‑use token option (CTFD_INIT_API_TOKEN_SINGLE_USE=true) to reduce attack surface.
  • Clear audit logging on server: caller IP, outcome (created/skipped), redacted fields.

Request (JSON):

POST /api/v1/init/setup HTTP/1.1
Authorization: Bearer <one-time-token>
Content-Type: application/json

{
  "ctf_name": "My CTF",
  "admin": {
    "name": "admin",
    "email": "[email protected]",
    "password": "StrongPass#1"
  },
  "ctf_description": "Internal training",
  "theme": "core",
  "ctf_timezone": "UTC",
  "lang": "en",
  "user_mode": "teams"  // or "users"
}

Responses:

  • 201 Created – setup completed now; body returns minimal config summary (no secrets).
  • 200 OK – already configured with matching state (idempotent).
  • 401 Unauthorized – missing/invalid token.
  • 403 Forbidden – endpoint disabled by config.
  • 409 Conflict – already configured (generic case).
  • 400 Bad Request – validation errors.

Example response (success):

{
  "success": true,
  "configured": true,
  "ctf_name": "My CTF"
}

Security considerations:

  • Disabled by default; clearly documented risk if enabled on public endpoints.
  • Strong, random CTFD_INIT_API_TOKEN; optionally time‑limited (CTFD_INIT_API_TOKEN_TTL).
  • Option to restrict to private networks (CIDR allowlist) or loopback only.
  • Enforce password policy and input validation.
  • Immediately disable the endpoint after successful configuration if CTFD_INIT_API_DISABLE_ON_SUCCESS=true.

Operational guidance:

  • Document official init flows: (1) HTML/CSRF form; (2) Opt‑in JSON API for automation.
  • Provide a tiny example client (curl + token) to demonstrate usage.
  • Log a clear warning if the endpoint is enabled.

Minimal alternative (even smaller change)

If adding a new endpoint is undesirable, consider allowing a JSON variant on /setup when a trusted bootstrap token header is present, e.g.:

  • Header: X-CTFd-Init-Token: <token>
  • Content-Type: application/json
  • Bypass CSRF for first‑boot only when token matches, instance unconfigured.

This preserves the existing route while giving a supported path for infrastructure to initialize safely.

Why this helps

  • Makes cloud‑native deployment (Fargate/Kubernetes) sane without brittle HTML scraping.
  • Encourages a single, documented, testable init flow for maintainers and users.
  • Reduces support burden from ad‑hoc automation scripts.

Backwards compatibility

  • No change to existing /setup UI flow.
  • New API is opt‑in and off by default.

petems avatar Aug 23 '25 02:08 petems

Would this be more efficient than just having all configuration options in the env / config.ini?

daanbreur avatar Nov 08 '25 15:11 daanbreur