Skip to content

Quick Start

Hosting your own CTF can be quite tricky, but we want to change that!

Below you will find all the necessary steps to get your own instance of CTF-Citadel up and running in no time!

Web-App Initial Setup

We can start off by cloning the main WebApp Repository

Terminal window
# clone the repo
git clone https://github.com/CTF-Citadel/webapp
# change into the examples folder
cd webapp/examples/

Great, now we have everything we need to get started!

The examples subdirectory contains config files for NGINX, as well as a dedicated subfolder for SSL-Certificates, which is nginx/ssl/.

You may put two files in here named server.crt and server.key respectably, which is you Public Certificate and Private Key.

Self-signed certificates are not recommended, you should of course have some valid domain to register officially signed SSL-Certificates. Although when deploying the platform locally, inaccessible to the outside world, self-signed Certificates may be sufficient.

Deploy Using Docker

We made hosting your own instance of CTF-Citadel really easy to accoplish using the power of Docker.

Fresh Images of our WebApp are built on every push to master and can be pulled from here:

Terminal window
docker pull ghcr.io/ctf-citadel/webapp:main

An way easier method is to use our docker-compose.yaml file already present in the repo.

This automatically pulls prebuilt images from their respective container registries and should get you up and running in no time.

Before that, it may be a good thing to customize some things for your needs, which is described below.

What to change

The file might look a bit overwhelming at first so here is a walkthrough on what you will need to change.

Bind Mounts

  • ./nginx/nginx.conf:/etc/nginx/nginx.conf: This is a bind mount to the configuration files for the NGINX reverse proxy. This path should not need changing as long as you do not change the file location of the docker-compose.yaml. You can navigate to proxy/ from the repo root and examine the provided config file. If desired you can use a different proxy or integrate it into an already exisiting one.
  • ./nginx/ssl:/etc/ssl/private: This is the bind mount to your SSL Certificates that NGINX will use to secure your traffic over HTTPS. If you examined the config file you might have noticed that it will always look for server.crt and server.key. You should either name your certfiles accordingly or modify the paths in the config file mentioned above.
  • ./data/postgres:/var/lib/mysql: This is where your database stores data, which is user data, challenges, ongoing events and more. Choose this path wisely as it is where your data will reside!

General Variables

  • DB_*: These are database parameters and should match the ones that you define for the PostgreSQL container itself. We won’t got into more detail on each of them as they are pretty self-explanatory
  • CONFIG_DIR: The folder where the configuration file config.yaml resides in.

Configuration File

The newest version of CTF-Citadel is configured via a unified configuration file. An example configuration is provided in the examples/ folder of the Repository which can be modified according to your needs. Every configuration entry is documented right in the file itself and most are self explanatory.

[webapp]
# Name of the instance
name = 'testCTF'
# Flag prefix
prefix = 'CTD'
# Dynamic scoring values
dynamic_k = 24
dynamic_p = 1.2
# Absolute path to the platform data storage
data = '/data'
# Official Discord server URL
discord = ''
[email]
# Email service name
service = 'hotmail'
# Username and password for the email account
username = 'someone@example.com'
password = 'CHANGEME'
# Disable email verification requirement on Signup
disable_verify = false
# Enable certificate mailing integration
enable_certificate_mailing = false
# Enforce a specific email domain
enable_enforce_domain = false
enforce_domain = ''
[infra]
enable = false
# Hostname or IP of the inegration
host = 'remote.example.com'
# Access port of the inegration
port = 443
# PSK of the service
psk = 'CHANGEME'
[m0n1t0r]
enable = false
# Hostname or IP of the inegration
host = 'm0n1t0r'
# Access port of the inegration
port = 9999
# PSK of the service
psk = 'CHANGEME'
[f1rstbl00d]
enable = false
# Hostname or IP of the inegration
host = 'f1rstbl00d'
# Access port of the inegration
port = 80
# PSK of the service
psk = 'CHANGEME'

Starting the Containers

Once you are happy with your changes, there is only one thing left to do:

Terminal window
docker-compose --profile full up -d

This will pull and start all containers and if you go to https://localhost:443/, you should see the Login Page show up.

Understanding Profiles

The docker-compose.yaml file in examples is structured and managed using docker profiles. This allows you to omit certain components of the whole Citadel stack, if desired.

There are currently three profiles available.

  • full: Deploy the stack using all components, including Anti-Cheat, Discord Bot and WebApp
  • standalone: Deploy only the WebApp component, excluding Anti-Cheat and Discord Bot
  • anticheat: Deploy the WebApp, including only the Anti-Cheat Component
  • firstblood: Deploy the WebApp, including only the Discord Bot Component

To start a different profile, just switch the command already shown above, for example to use standalone mode you can run the following.

Terminal window
docker-compose --profile standalone up -d

By utilizing different profiles, different environment variables will come into effect, so be sure you configured them correctly.

Manual Building of the WebApp

If you want to test the latest bleeding-edge changes directly from main or maybe incorporate some changes of your own, you will need to build a new Docker images locally.

Doing that is pretty easy and can be done by modifying the docker-compose.yaml in the examples folder.

version: '3'
services:
# ...
# You can leave the nginx and postgres containers identical
# ...
webapp:
build: # <- changing from image to build
context: ${PWD}/../ # <- specify our project root
dockerfile: Dockerfile # <- name of the Dockerfile to build from
# ...
# Rest is the same
# ...

If you did the above correctly you should now be able to build your own image, telling Docker to do so.

Terminal window
docker-compose up -d --build

This should build the Astro WebApp directly from source and start the other containers automatically.

Infra-Backend Setup

Start by copying the tophack-stack.yml file from the sample folder in the infra-middleware Repository to your server.

Setup Docker Swarm Production Environment

This setup requires a Linux machine with Docker installed.

Initialize Cluster:

Terminal window
docker swarm init --advertise-addr <MANAGER-IP>

This will return something like:

docker swarm init --advertise-addr 192.168.99.100
Swarm initialized: current node (dxn1zf6l61qsb1josjja83ngz) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \
192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

As the output suggests, you need to run the docker swarm join --token <TOKEN> command on each node.

Setup Traefik in Newly Created Swarm

Terminal window
docker network create --driver=overlay --subnet=10.1.0.0/16 traefik-public
Terminal window
export NODE_ID=$(docker info -f '{{.Swarm.NodeID}}')
Terminal window
docker node update --label-add traefik-public.traefik-public-certificates=true $NODE_ID
Terminal window
# Environment variables for the entire CTF Citadel backend, excluding the webapp
export EMAIL=admin@tophack.org # Email for certbot
export DOMAIN=traefik.cluster.tophack.org # Domain for Traefik
export CONTROLLER_DOMAIN=cluster.tophack.org # Domain for controller containers
export USERNAME=admin # Username for Portainer
export PASSWORD=super_secret_portainer_password # Password for Portainer
export HASHED_PASSWORD=$(openssl passwd -apr1 $PASSWORD) # Hashed password for Portainer
export CF_API_EMAIL=mymail@cloudflare.com # Email of Cloudflare account owning the domain for DNS challenge
export CF_API_KEY=my-cf-api-key # API key of Cloudflare account owning the domain for DNS challenge
export GITHUB_USERNAME=ctf-citadel # Username of the GitHub account owning the challenge repository
export GITHUB_TOKEN=ghp_supersecret # Personal access token of the GitHub account owning the challenge repository
export DOCKERHUB_USERNAME=dockerhubuser # Username for Dockerhub, used to push images during the event
export DOCKERHUB_TOKEN=dckr_pat_supersecret # Personal access token for Dockerhub, used to push images during the event
export PSK=super_secret_key # Pre-shared key to authenticate web-backend
export CONTROLLER_REPLICACOUNT=1 # Count of replicas for controller container. This also specifies the number of images that can be built simultaneously.
Terminal window
docker login -u $GITHUB_USERNAME -p $GITHUB_TOKEN ghcr.io

Login to ghcr.io for the controller image:

Terminal window
docker stack deploy -c tophack-stack.yml tophack-stack

This deploys Traefik and the infra-middleware controller to the Swarm cluster with your environment variables.


Authors: Fabian T. & Felix S.