Feature request: Opt‑in programmatic init API for first‑boot setup
- 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
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