CTFd / CTFd/CTFd

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

Open
#2,816 1 comment 1 reaction 0 assignees View on GitHub
Dominant language
Python
Stars
6.8k
Forks
2.8k
Avg merge
1d 6h
Merged PRs (30d)
5

Description

### 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):
```python
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": "admin@example.com",
"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 `.
- 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):
```http
POST /api/v1/init/setup HTTP/1.1
Authorization: Bearer
Content-Type: application/json

{
"ctf_name": "My CTF",
"admin": {
"name": "admin",
"email": "admin@example.com",
"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):
```json
{
"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: `
- 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.

Contributor guide

Open the contributing guide

Research direction

Start by reading the existing /setup flow and its first-boot behavior; no source files or tests are named in the issue. Compare the proposed /api/v1/init/setup entry point with project conventions for configuration, authentication, validation, and responses. Done would require an agreed, tested scope for the opt-in API or the smaller /setup alternative, plus the requested documentation and security behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
flask, python
Domain
api, backend, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.