CTF Pilot's CTFd Manager is a management API and orchestration tool for CTFd.
The tool contains a management API that allows for programmatic management of CTFd content, and a Kubernetes listener, that allows for continued deployment of CTFd content.
The tool listens for new content added as ConfigMaps in a designated Kubernetes namespace, and automatically uploads them to the connected CTFd instance. When ConfigMaps are updated, the changes are automatically reflected in CTFd. When the ConfigMaps are deleted, the content will be hidden from CTFd, instead of being permanently deleted, allowing for recovery if needed.
Note
Currently, initial setup of CTFd, and continued deployment of challenges and pages is supported.
Caution
The tool has full admin access to the connected CTFd instance, and can modify all content.
Ensure to run the tool in a secure environment, and protect access to the management API.
The tool is made to be non-destructive, but retains the needed permissions to change all content.
- Supported Versions
- How to run
- API Reference
- Operation guide
- Troubleshooting
- Security Considerations
- Architecture
- Contributing
- License
- Code of Conduct
This CTFd Manager is tested and compatible with:
- CTFd: 3.7.7
- Kubernetes: 1.32+
- Go: 1.21+ (for development)
While the manager may work with earlier and later versions, they are not officially supported or tested.
The following sections describe how to run the CTFd manager application.
The application makes use of restarting to try to fix errors.
Ensure to run the application with health checks and restart when appropriate automatically.
The application is built to run inside a Kubernetes cluster.
This is due to how it retrieves the Kubernetes connection configuration.
The application requires the following access through Kubernetes RBAC service account:
- Api groups:
"", resources:configmaps, verbs:get,list,watch,update,patch
In order for the application to run properly, the following ConfigMaps must be created in the same namespace as the application is running in, before the application is started:
ctfd-access-token: Will contain the CTFd API access token for the manager. The manager will automatically store the API access token inaccess_tokenwhen setting up CTFd for the first time.ctfd-challenges: Will store the uploaded challenges to CTFd. The manager will automatically create and update this ConfigMap when challenges are uploaded the service.ctfd-pages: Will store the uploaded pages to CTFd. The manager will automatically create and update this ConfigMap when pages are uploaded through the service.challenge-configmap-hashset: Will store a hashset of uploaded challenges and pages, in order to track changes. The manager will automatically create and update this ConfigMap when challenges or pages are uploaded through the service.mapping-map: Should store a mapping of category and difficulty slugs to category names. Will be used to dynamically change the "category" field in challenges. See the Category and Difficulty Mapping section for more information.
Note
Namespace Requirement:
All challenge and page ConfigMaps, as well as required configuration, must be located in the namespace specified by the NAMESPACE environment variable for the manager. The manager can run in a different namespace, but will only watch and manage resources in the namespace defined by NAMESPACE. Ensure your service account and RBAC permissions allow access to this namespace.
Tip
Included in the repository is a sample Kubernetes deployment manifest, k8s/example.yml, which can be used to deploy the application to a Kubernetes cluster.
Important
Currently, the application has not been built to run with multiple replicas.
Ensure to only run a single replica of the application to avoid race conditions and conflicts.
The repository automatically builds a Docker image for the application and pushes it to GitHub Container Registry: ghcr.io/ctfpilot/ctfd-manager:latest.
The image is versioned, so you can also use specific versions, for example: ghcr.io/ctfpilot/ctfd-manager:1.0.0.
For more information, see the Docker image package
Warning
Before configuring, please ensure permissions are created correctly through the service account, and the required ConfigMaps are created. See the Prerequisites section for more information.
Failure to setup these elements correctly may lead to the application failing to start or work correctly.
The application needs the following configuration through environment variables:
CTFD_URL: The URL to the CTFd instance to manage.
Example:https://ctfd.example.comPASSWORD: The password to use for API authorization, provided by the service.
This should be a strong password, as it will give admin access to the CTFd instance and all challenge information. Example:SuperSecretPassword123!NAMESPACE: The Kubernetes namespace the application is running in.
Example:ctfd-managerGITHUB_REPO: The GitHub repository to use for challenge files.
Example:ctfpilot/ctfd-challengesGITHUB_BRANCH: The GitHub branch to use for challenge files.
Example:mainordevelopGITHUB_USER: The GitHub username to use for accessing the challenge files repository.
Example:ctfpilotGITHUB_TOKEN: The GitHub access token to use for accessing the challenge files repository.
Example:ghp_XXXXXXXXXXXXXXXXXXXX
Important
Passwords should always be stored using secrets instead of cleartext environment variables.
To develop, build the docker image locally:
docker build -t ctfd-manager:dev .Then deploy the application within Kubernetes, using the local image.
To update the Kubernetes deployment file, update the deployment template in template/k8s.yml.
An updated k8s/k8s.yml will then be automatically generated on the next release.
The CTFd Manager exposes a REST API for programmatic management of challenges and CTFd content.
All API endpoints (except health checks) require Bearer token authentication using the password configured via the PASSWORD environment variable.
Include the authorization header in your requests:
Authorization: Bearer <your-password>Example curl command:
curl -H "Authorization: Bearer <password>" \
http://localhost:8080/api/challenges-
GET
/api/challenges: List all available challenges from Kubernetes ConfigMaps. Response format:{ "challenges": [ {"name": "web-challenge-1"}, {"name": "crypto-challenge-1"} ] } -
GET
/api/challenges/{id}: Get detailed configuration for a specific challenge. -
GET
/api/challenges/{id}/files: List files available for a challenge from the GitHub repository. -
GET
/api/challenges/{id}/files/{file}: Download a specific file for a challenge.
-
POST
/api/ctfd/setup: Initialize CTFd instance with initial configuration. This should be run once when first setting up CTFd.curl -X POST -H "Authorization: Bearer <password>" \ -H "Content-Type: application/json" \ -d @setup-params.json \ http://localhost:8080/api/ctfd/setup
Setup Parameters (click to expand)
The setup endpoint accepts a JSON body with the following parameters:
Parameter Type Description Example ctf_namestring Name of the CTF event "My CTF 2025"ctf_descriptionstring Description of the CTF "Annual cybersecurity competition"user_modestring Participation mode: "users"or"teams""teams"challenge_visibilitystring Who can view challenges: "public","private", or"admins""public"account_visibilitystring Who can view accounts: "public","private", or"admins""public"score_visibilitystring Who can view scores: "public","private","hidden", or"admins""public"registration_visibilitystring Who can register: "public","private", or"mlc"(MajorLeagueCyber)"public"verify_emailsboolean Whether to require email verification falsectf_themestring Theme name to use "core"namestring Admin user's name "Admin User"emailstring Admin user's email "admin@example.com"passwordstring Admin user's password "SecurePassword123!"Parameter Type Description Example startstring CTF start time (Unix timestamp) "1700000000"endstring CTF end time (Unix timestamp) "1700086400"Parameter Type Description Example team_sizeinteger Maximum team size (required if user_modeis"teams")4Parameter Type Description bracketsarray List of bracket objects (see below) Bracket Object Structure:
{ "name": "College Students", "description": "For college and university students", "type": "teams" }name(string, required): Bracket namedescription(string, optional): Bracket description (max 255 chars)type(string, optional): Bracket type - empty string,"users", or"teams"
Parameter Type Description Example ctf_logoobject CTF logo file {"name": "logo.png", "content": "<base64>"}ctf_bannerobject CTF banner image {"name": "banner.jpg", "content": "<base64>"}ctf_smalliconobject CTF small icon/favicon {"name": "icon.png", "content": "<base64>"}theme_colorstring Theme color (hex code) "#3b7dd6"File Object Structure:
{ "name": "logo.png", "content": "iVBORw0KGgoAAAANSUhEUgAAAAUA..." }name(string): Filename with extensioncontent(string): Base64-encoded file content
Parameter Type Description Example mail_serverstring SMTP server address "smtp.gmail.com"mail_portinteger SMTP server port (1-65535) 587mail_usernamestring SMTP username "noreply@example.com"mail_passwordstring SMTP password "smtp-password"mail_sslboolean Use SSL for SMTP falsemail_tlsboolean Use TLS for SMTP truemail_fromstring Sender email address "noreply@example.com"Parameter Type Description Example registration_codestring Code required for registration "SECRET2025"{ "ctf_name": "My CTF 2025", "ctf_description": "Annual cybersecurity competition", "start": "1700000000", "end": "1700086400", "user_mode": "teams", "challenge_visibility": "public", "account_visibility": "public", "score_visibility": "public", "registration_visibility": "public", "verify_emails": false, "team_size": 4, "brackets": [ { "name": "College Students", "description": "For college and university students", "type": "teams" }, { "name": "Professionals", "description": "For security professionals", "type": "teams" } ], "ctf_theme": "core", "theme_color": "#3b7dd6", "name": "Admin User", "email": "admin@example.com", "password": "SecurePassword123!", "mail_server": "smtp.gmail.com", "mail_port": 587, "mail_username": "noreply@example.com", "mail_password": "smtp-password", "mail_ssl": false, "mail_tls": true, "mail_from": "noreply@example.com", "registration_code": "SECRET2025" }{ "ctf_name": "Quick CTF", "ctf_description": "A simple CTF event", "user_mode": "users", "challenge_visibility": "public", "account_visibility": "public", "score_visibility": "public", "registration_visibility": "public", "verify_emails": false, "ctf_theme": "core", "name": "Admin", "email": "admin@ctf.local", "password": "ChangeMe123!" } -
POST
/api/ctfd/challenges/init: Upload all challenges to CTFd. Creates new challenges or updates existing ones. -
GET
/api/ctfd/challenges: List all challenges currently in CTFd. -
GET
/api/ctfd/challenges/uploaded: List challenges that have been uploaded by the manager with their CTFd IDs.
- GET
/api/version: Get the version information of the manager. - GET
/api/statusor GET/status: Health check endpoint. Returns200 OKwith{"status":"ok"}when healthy, or500with{"status":"error"}when unhealthy.
The following sections describes how to operate the CTFd manager.
Follow these steps for first-time deployment:
Before starting the manager, create the required ConfigMaps in your namespace:
# Create namespace
kubectl create namespace ctfd-manager
# Create empty ConfigMaps
kubectl create configmap ctfd-access-token -n ctfd-manager
kubectl create configmap ctfd-challenges -n ctfd-manager
kubectl create configmap ctfd-pages -n ctfd-manager
kubectl create configmap challenge-configmap-hashset -n ctfd-manager
# Create mapping ConfigMap with category/difficulty mappings
kubectl create configmap mapping-map -n ctfd-manager \
--from-literal=categories="{\"web\":\"Web Challenges\",\"crypto\":\"Cryptography\",\"pwn\":\"Binary Exploitation\"}" \
--from-literal=difficulties="{\"easy\":\"Easy\",\"medium\":\"Medium\",\"hard\":\"Hard\"}" \
--from-literal=difficulty-categories="{\"beginner\":\"Beginner Challenges\"}"Apply the RBAC configuration from the example manifest:
kubectl apply -f k8s/example.ymlOr create manually:
# Create service account
kubectl create serviceaccount ctfd-manager -n ctfd-manager
# Create role with ConfigMap permissions
kubectl create role ctfd-manager -n ctfd-manager \
--verb=get,list,watch,update,patch \
--resource=configmaps
# Create role binding
kubectl create rolebinding ctfd-manager -n ctfd-manager \
--role=ctfd-manager \
--serviceaccount=ctfd-manager:ctfd-managerStore sensitive information in Kubernetes secrets:
# Encode your values
echo -n "your-github-token" | base64
echo -n "your-strong-password" | base64
# Create secret
kubectl create secret generic ctfd-manager-secret -n ctfd-manager \
--from-literal=github-token=your-github-token \
--from-literal=password=your-strong-passwordUpdate the example manifest with your configuration and apply:
kubectl apply -f k8s/example.ymlOnce the manager is running, initialize your CTFd instance:
# Get the manager service endpoint
kubectl get svc ctfd-manager -n ctfd-manager
# Setup CTFd (run once)
curl -X POST -H "Authorization: Bearer <your-password>" \
-H "Content-Type: application/json" -d @setup-params.json \
http://<manager-endpoint>:8080/api/ctfd/setup
# Upload challenges (run once)
curl -X POST -H "Authorization: Bearer <your-password>" \
http://<manager-endpoint>:8080/api/ctfd/challenges/initChallenges added to Kubernetes, will now be automatically synchronized to CTFd.
CTFd Manager loads challenge and page definitions from Kubernetes ConfigMaps. These must follow strict formats based on the CTF Pilot schemas:
- Challenges: CTF Pilot's Challenge Schema (JSON)
- Pages: CTF Pilot's Page Schema (JSON)
CTFd Manager is not concerned with how the ConfigMaps are managed in Kubernetes, as long as they follow the required format.
The CTFd manager will automatically pick up changes to the ConfigMaps and update CTFd accordingly.
Note
Challenge and page ConfigMaps must be located in the NAMESPACE that is configured for the service.
| Type | Label Key | Label Value |
|---|---|---|
| Challenge | challenges.ctfpilot.com/configmap |
challenge-config |
| Page | challenges.ctfpilot.com/configmap |
page-config |
These labels are required for the manager to recognize and process the ConfigMap.
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique name for the challenge |
path |
Yes | Path to challenge files in the repository |
repository |
Yes | GitHub repository for challenge files |
challenge |
Yes | JSON object matching the Challenge Schema |
description |
Yes | Short description of the challenge |
generated_at |
Yes | ISO8601 timestamp when the config was generated |
| Field | Required | Description |
|---|---|---|
slug |
Yes | Unique slug for the page |
name |
Yes | Name of the page |
path |
Yes | Path to page files in the repository |
repository |
Yes | GitHub repository for page files |
page |
Yes | JSON object matching the Page Schema |
description |
Yes | Short description of the page |
generated_at |
Yes | ISO8601 timestamp when the config was generated |
apiVersion: v1
kind: ConfigMap
metadata:
name: web-challenge-1
namespace: ctfd-manager
labels:
challenges.ctfpilot.com/configmap: challenge-config
data:
name: "web-challenge-1"
path: "web/web-challenge-1"
repository: "ctfpilot/ctfd-challenges"
challenge: |
{ ...Challenge Schema JSON... }
description: "This is a web challenge focused on XSS vulnerabilities."
generated_at: "2025-11-19T12:00:00Z"apiVersion: v1
kind: ConfigMap
metadata:
name: rules-page
namespace: ctfd-manager
labels:
challenges.ctfpilot.com/configmap: page-config
data:
slug: "rules"
name: "Rules Page"
path: "pages/rules"
repository: "ctfpilot/ctfd-pages"
page: |
{ ...Page Schema JSON... }
description: "Competition rules and guidelines."
generated_at: "2025-11-19T12:00:00Z"In order to get proper categories and difficulties in CTFd, categories and difficulties can be mapped to specific names through the mapping-map ConfigMap.
Three mappings are available:
categories: A mapping of category slugs to category names.
Example:web: Web Challengesdifficulties: A mapping of difficulty slugs to difficulty names.
Example:easy: Easy Challengesdifficulty-categories: A mapping of difficulty slugs to category names.
Allows for mapping a difficulty to a specific category, such asbeginnerdifficulty challenges to a "Beginner Challenges" category. Example:easy: Easy Challenges
For category names, the service will first check the difficulty-categories mapping, then the categories mapping.
If no mapping is found, the original category name from the challenge file will be used.
If no category is found (empty string for challenge category), the challenge will be placed in the "Uncategorized" category in CTFd.
Currently, the difficulty is not uploaded to CTFd, as CTFd does not have a built-in difficulty field for challenges.
Example mapping-map ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: mapping-map
namespace: ctfd-manager
data:
categories: |
{
"web" : "Web",
"forensics" : "Forensics",
"rev" : "Reverse Engineering",
"crypto" : "Crypto",
"pwn" : "Pwn",
"boot2root" : "Boot2Root",
"osint" : "OSINT",
"misc" : "Misc",
"blockchain" : "Blockchain",
"mobile" : "Mobile"
}
difficulties: |
{
"beginner" : "Beginner",
"easy" : "Easy",
"easy-medium" : "Easy - Medium",
"medium" : "Medium",
"medium-hard" : "Medium - Hard",
"hard" : "Hard",
"very-hard" : "Very Hard",
"insane" : "Insane"
}
difficulty-categories: |
{
"beginner" : "Beginner"
}In order to attach the manager to an existing CTFd instance, you need to provide the manager with a valid CTFd API access token.
This can be done by adding the access token to the ctfd-access-token ConfigMap in the same namespace as the manager is running in.
The token must be added under the key access_token.
For existing challenges and pages, these can be added to their appropriate ConfigMaps (ctfd-challenges and ctfd-pages) in order for the manager to manage them.
They are added in the format of: <challenge-or-page-slug>: <ctfd-id>.
For example, to add an existing challenge with slug web-challenge-1 and CTFd ID 5, you would run:
kubectl patch configmap ctfd-challenges -n <namespace> \
--type merge -p '{"data":{"web-challenge-1":"5"}}'In order to ensure the application is running correctly, it exposes two health check endpoints, which provide the same information: status and /api/status.
They will return 200 OK when the application is running correctly, and 500 Internal Server Error when there is an issue.
The return body will contain a JSON object with a status field, which will be either ok or error.
Please set up Kubernetes to listen on these endpoints for liveness and readiness probes.
Example probe configuration:
livenessProbe:
httpGet:
path: /status
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /status
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Common issues and their solutions:
Symptom: Pod crashes or restarts repeatedly
Possible causes:
- Missing or incorrect ConfigMaps
- Insufficient RBAC permissions
- Invalid environment variables
Solutions:
- Check pod logs:
kubectl logs -n <namespace> deployment/ctfd-manager - Verify all required ConfigMaps exist:
kubectl get configmaps -n <namespace> - Verify service account has correct permissions:
kubectl describe role ctfd-manager -n <namespace> - Check environment variables are set correctly:
kubectl describe deployment ctfd-manager -n <namespace>
Symptom: /status endpoint returns 500 error
Possible causes:
- Kubernetes API connection issues
- ConfigMap access problems
- Background watcher errors
Solutions:
- Check manager logs for error messages
- Verify RBAC permissions for ConfigMaps
- Test ConfigMap access:
kubectl get configmap -n <namespace> - Restart the deployment:
kubectl rollout restart deployment/ctfd-manager -n <namespace>
Symptom: Cannot fetch challenge files or repository contents
Possible causes:
- Invalid GitHub token
- Token lacks required permissions
- Repository or branch doesn't exist
Solutions:
-
Verify GitHub token has
Contentsread access (for fine-grained tokens) orrepo/public_reposcope (for classic tokens) -
Test token manually:
curl -H "Authorization: token <github-token>" \ https://api.github.com/repos/<owner>/<repo>/contents
-
Verify
GITHUB_REPOandGITHUB_BRANCHenvironment variables are correct -
Update secret with new token if needed:
kubectl create secret generic ctfd-manager-secret -n <namespace> \ --from-literal=github-token=new-token \ --from-literal=password=your-password \ --dry-run=client -o yaml | kubectl apply -f -
Symptom: Cannot setup CTFd or upload challenges
Possible causes:
- CTFd instance not accessible
- Invalid CTFd API token
- CTFd already configured
Solutions:
-
Verify CTFd URL is accessible from the pod:
kubectl exec -n <namespace> deployment/ctfd-manager -- curl <ctfd-url>
-
Check CTFd access token in ConfigMap:
kubectl get configmap ctfd-access-token -n <namespace> -o yaml
-
For existing CTFd instances, manually add the API token to the ConfigMap:
kubectl patch configmap ctfd-access-token -n <namespace> \ --type merge -p '{"data":{"access_token":"your-ctfd-api-token"}}'
Symptom: Changes in GitHub repository not reflected in CTFd
Possible causes:
- Change not triggered, due to challenge config not being updated in the Kubernetes
- Background watcher not running
- ConfigMap hashset not updating
- Challenge already exists with same ID
Solutions:
-
Check if challenge ConfigMap has been updated in Kubernetes
-
Check if background watcher is running (check logs for "background challenge watcher" messages)
-
Manually trigger upload:
curl -X POST -H "Authorization: Bearer <password>" \ http://<manager-endpoint>:8080/api/ctfd/challenges/init
-
Clear challenge hashset to force re-upload:
kubectl delete configmap challenge-configmap-hashset -n <namespace> kubectl create configmap challenge-configmap-hashset -n <namespace> kubectl rollout restart deployment/ctfd-manager -n <namespace>
Symptom: "Forbidden" or "403" errors in logs
Possible causes:
- Service account not attached to deployment
- RBAC role missing required permissions
Solutions:
-
Verify service account is configured:
kubectl get deployment ctfd-manager -n <namespace> -o yaml | grep serviceAccountName
-
Add service account to deployment if missing:
kubectl patch deployment ctfd-manager -n <namespace> \ --type merge -p '{"spec":{"template":{"spec":{"serviceAccountName":"ctfd-manager"}}}}'
-
Verify role permissions include all required verbs:
kubectl get role ctfd-manager -n <namespace> -o yaml
- Always store the API password in Kubernetes Secrets, never in plain environment variables
- Use strong passwords with high entropy (at least 32 characters recommended)
- Rotate passwords periodically
The GitHub token should have minimal required permissions:
- Fine-grained tokens:
Contentsread access for the specific repository - Classic tokens:
reposcope for private repositories, orpublic_reposcope for public repositories
Never use personal access tokens with admin privileges or unnecessary scopes.
The CTFd Manager orchestrates synchronization between Kubernetes, GitHub and the CTFd application. Below is an architecture diagram reflecting namespaces, required ConfigMaps, watched labels, and data flows.
graph TB
subgraph k8s["Kubernetes Namespace (NAMESPACE)"]
direction TB
subgraph watched["Watched ConfigMaps"]
cm["Challenge ConfigMaps<br/><small>label: challenge-config</small>"]
pm["Page ConfigMaps<br/><small>label: page-config</small>"]
end
subgraph state["State ConfigMaps"]
at["ctfd-access-token<br/><small>stores API token</small>"]
hs["challenge-configmap-hashset<br/><small>tracks changes</small>"]
cc["ctfd-challenges<br/><small>CTFd challenge IDs</small>"]
cp["ctfd-pages<br/><small>CTFd page IDs</small>"]
end
mm["mapping-map<br/><small>category/difficulty mappings</small>"]
end
gh[("GitHub Repository<br/><small>GITHUB_REPO/GITHUB_BRANCH</small>")]
mgr["CTFd Manager Pod<br/><small>API Server + Watcher</small>"]
ctfd[("CTFd Instance<br/><small>CTFD_URL</small>")]
api["API Clients<br/><small>Bearer auth with PASSWORD</small>"]
cm -->|"watch events"| mgr
pm -->|"watch events"| mgr
mm -->|"read mappings"| mgr
hs <-->|"read/write hashes"| mgr
cc <-->|"read/write IDs"| mgr
cp <-->|"read/write IDs"| mgr
at <-->|"read/write token"| mgr
mgr -->|"fetch challenge files<br/>(GITHUB_TOKEN)"| gh
mgr -->|"create/update<br/>challenges & pages"| ctfd
mgr -->|"initial setup<br/>(one-time)"| ctfd
ctfd -.->|"return API token"| mgr
api -->|"POST /api/ctfd/setup<br/>POST /api/ctfd/challenges/init<br/>GET /api/*"| mgr
style k8s fill:#2d3748,stroke:#4299e1,stroke-width:2px
style watched fill:#1a365d,stroke:#3182ce,stroke-width:1px,stroke-dasharray: 5 5
style state fill:#1a202c,stroke:#718096,stroke-width:1px,stroke-dasharray: 5 5
style mgr fill:#744210,stroke:#f6ad55,stroke-width:3px
style gh fill:#553c9a,stroke:#9f7aea,stroke-width:2px
style ctfd fill:#276749,stroke:#48bb78,stroke-width:2px
style api fill:#742a2a,stroke:#fc8181,stroke-width:2px
style cm fill:#1a202c,stroke:#a0aec0,stroke-width:1px
style pm fill:#1a202c,stroke:#a0aec0,stroke-width:1px
style mm fill:#1a202c,stroke:#a0aec0,stroke-width:1px
style hs fill:#1a202c,stroke:#a0aec0,stroke-width:1px
style cc fill:#1a202c,stroke:#a0aec0,stroke-width:1px
style cp fill:#1a202c,stroke:#a0aec0,stroke-width:1px
style at fill:#1a202c,stroke:#a0aec0,stroke-width:1px
Key points:
- The manager watches only ConfigMaps in the namespace defined by the
NAMESPACEenvironment variable. - Only large files (handouts) are pulled from GitHub; metadata & schema JSON come from ConfigMaps.
challenge-configmap-hashsetprevents redundant uploads by tracking last applied hashes.mapping-mapdynamically rewrites category/difficulty presentation.- Access token is generated once at setup and persisted in
ctfd-access-token. - Pages are created/updated or deleted based on presence/removal of page ConfigMaps.
Components:
- GitHub Repository: Stores large challenge files (e.g., handouts) referenced by ConfigMaps.
- Kubernetes ConfigMaps: Provide structured metadata & schema JSON for challenges/pages plus auxiliary state (mappings, hashes, token).
- CTFd Instance: Receives created/updated challenges & pages via Manager API calls; issues access token during setup.
- CTFd Manager Pod: Watches labeled ConfigMaps, fetches GitHub file content, applies mappings, manages lifecycle & synchronization.
Data Flow:
- Watcher detects add/update/delete of labeled ConfigMaps (
challenge-config,page-config). - Manager reads schema JSON + metadata from ConfigMap; pulls referenced files from GitHub repo/branch using configured token.
- Applies category/difficulty mapping from
mapping-mapand determines effective category. - Creates or updates challenge/page in CTFd via authenticated REST calls (using stored access token).
- Stores resulting CTFd IDs in
ctfd-challenges/ctfd-pagesConfigMaps and updates hash inchallenge-configmap-hashset. - On ConfigMap deletion: challenge is disabled (not hard-deleted) or page removed in CTFd; hash cleared.
- Setup endpoint initializes platform, generates access token, writes it to
ctfd-access-token.
We welcome contributions of all kinds, from code and documentation to bug reports and feedback!
Please check the Contribution Guidelines (CONTRIBUTING.md) for detailed guidelines on how to contribute.
To maintain the ability to distribute contributions across all our licensing models, all code contributions require signing a Contributor License Agreement (CLA).
You can review the CLA here. CLA signing happens automatically when you create your first pull request.
To administrate the CLA signing process, we are using CLA assistant lite.
A copy of the CLA document is also included in this repository as CLA.md.
Signatures are stored in the cla repository.
This component and repository is licensed under the EUPL-1.2 License.
You can find the full license in the LICENSE file.
We encourage all modifications and contributions to be shared back with the community, for example through pull requests to this repository.
We also encourage all derivative works to be publicly available under the EUPL-1.2 License.
At all times must the license terms be followed.
For information regarding how to contribute, see the contributing section above.
CTF Pilot is owned and maintained by The0Mikkel.
Required Notice: Copyright Mikkel Albrechtsen (https://themikkel.dk)
We expect all contributors to adhere to our Code of Conduct to ensure a welcoming and inclusive environment for all.