159 lines
5.8 KiB
Markdown
159 lines
5.8 KiB
Markdown
# CTF challenge status tracker
|
|
|
|
Tracks the progress of the h4tum CTF challenges. It reads every repo in the
|
|
GitLab challenges group, works out each challenge's status, category and
|
|
difficulty from its `README.md`, and checks that challenges marked as ready
|
|
really are. The result goes to:
|
|
|
|
- **a HedgeDoc note**: an overview of the challenges included in the CTF,
|
|
refreshed every 30 minutes
|
|
- **`challs.txt` in the deployment repo**: the clone URLs of every challenge
|
|
included in the CTF and ready for infra testing, updated daily
|
|
- **the terminal**: the same overview, plus the repos left out of the CTF
|
|
|
|
## Requirements
|
|
|
|
- Python 3.9+
|
|
- [`requests`](https://pypi.org/project/requests/) and [`PyYAML`](https://pypi.org/project/PyYAML/)
|
|
(`python-requests python-yaml` on Arch, `python3-requests python3-yaml` on Debian)
|
|
|
|
## Configuration
|
|
|
|
```sh
|
|
cp .env.example .env && chmod 600 .env
|
|
```
|
|
|
|
| Key | Required | Meaning |
|
|
|---|---|---|
|
|
| `GITLAB_GROUP_URL` | yes | GitLab group that holds one repo per challenge |
|
|
| `GITLAB_TOKEN` | yes | Personal access token, see below |
|
|
| `HEDGEDOC_NOTE` | for `--update` | URL of an existing note; its permission must be **freely** |
|
|
| `HEDGEDOC_TAGS` | no | Tags written into the note's front matter |
|
|
| `CHALLS_REPO_URL` | for `--challs` | Deployment repo that holds `challs.txt` |
|
|
| `CHALLS_PATH` | no | Path of the file in that repo (default `2026/challs.txt`) |
|
|
| `CHALLS_BRANCH` | no | Branch to commit to (default: the repo's default branch) |
|
|
|
|
The token needs **Project: Read** and **Repository: Read** on the challenges
|
|
group. For `--challs` it also needs **Repository: Read + Write** on the deployment
|
|
repo, and permission to push to the branch if it is protected.
|
|
|
|
Environment variables override the values in `.env`.
|
|
|
|
## Choosing what is in the CTF
|
|
|
|
[`challenges.yml`](challenges.yml) maps each repo path to its state:
|
|
|
|
```yaml
|
|
challenges:
|
|
baby_rsa: included # part of this CTF
|
|
static_antivirus: abandoned # dropped for good
|
|
some_chall: postponed # left to be finished for another CTF
|
|
|
|
skip_checks:
|
|
whoami: [has_solve_script] # OSINT: there is nothing to script
|
|
```
|
|
|
|
- Only **included** challenges appear in the HedgeDoc note and the counts, and
|
|
only they can end up in `challs.txt`.
|
|
- **Abandoned** and **postponed** challenges are only listed in the terminal.
|
|
- A repo that is **not listed** is also only listed in the terminal, in its own
|
|
section, and is not tracked or deployed until somebody decides. The timers run
|
|
quietly, so run `./ctf_status.py` by hand to see new repos.
|
|
- **`skip_checks`** exempts a repo from individual repo checks, named after their
|
|
functions in [`tracker/checks.py`](tracker/checks.py).
|
|
|
|
Entries for repos that do not exist are flagged under *Needs attention*. If the
|
|
file is missing or invalid, the run stops rather than committing an empty
|
|
`challs.txt`. The server reads the file from its checkout, so changes must be
|
|
pulled there.
|
|
|
|
## Usage
|
|
|
|
```sh
|
|
./ctf_status.py # print the overview
|
|
./ctf_status.py -o overview.md # also write the note's markdown to a file
|
|
./ctf_status.py --update # overwrite the HedgeDoc note
|
|
./ctf_status.py --challs # commit challs.txt, if it changed
|
|
./ctf_status.py -q --challs-out - # preview challs.txt without committing
|
|
```
|
|
|
|
`./ctf_status.py --help` lists all options. With `--quiet`, a run prints nothing
|
|
unless something changed or failed.
|
|
|
|
## Deployment
|
|
|
|
On the server, with `.env` in place:
|
|
|
|
```sh
|
|
./install-timer.sh
|
|
```
|
|
|
|
This installs and starts two systemd user timers:
|
|
|
|
| Timer | Runs | Does |
|
|
|---|---|---|
|
|
| `ctf-status.timer` | every 30 minutes | `--update`: refresh the HedgeDoc note |
|
|
| `ctf-challs.timer` | daily at 04:00 | `--challs`: commit `challs.txt` |
|
|
|
|
Re-running the script is safe. To check on the timers:
|
|
|
|
```sh
|
|
systemctl --user list-timers 'ctf-*'
|
|
journalctl --user -u ctf-status.service -n 50
|
|
journalctl --user -u ctf-challs.service -n 50
|
|
```
|
|
|
|
## How challenges are evaluated
|
|
|
|
Each challenge repo starts from a template whose README begins with:
|
|
|
|
```
|
|
### Challenge Name
|
|
- Category: crypto
|
|
- Difficulty: medium
|
|
- Event: ...
|
|
- Author: ...
|
|
- Tester: ...
|
|
- Status: Testing Ready
|
|
```
|
|
|
|
The status is one of **Planning → PoC Draft → Testing Ready → Tested → Ready**.
|
|
Loosely written values are normalised. If a status names several stages, the
|
|
least advanced one counts.
|
|
|
|
Two kinds of finding are reported:
|
|
|
|
- **Problems** are missing or unclear README fields, such as no category, an
|
|
unreadable status, or a Testing Ready challenge with no tester. They are
|
|
listed under *Needs attention* and change nothing else.
|
|
- **Blockers** are signs that a challenge claiming Testing Ready or beyond is not
|
|
actually finished: `description.yml` is missing or still the template, its
|
|
category or flag is wrong, a `deploy_dir` or its `Dockerfile` is missing, or
|
|
there is no `solve.py`. A blocked challenge (⛔) is kept out of `challs.txt`.
|
|
`skip_checks` in `challenges.yml` can exempt a challenge from a check.
|
|
|
|
Passing every check does not prove that a challenge builds or solves. That is
|
|
what testing is for.
|
|
|
|
All rules are in [`tracker/checks.py`](tracker/checks.py), one function each.
|
|
|
|
## Project layout
|
|
|
|
```
|
|
ctf_status.py command line entry point
|
|
challenges.yml what is part of this CTF, and check exceptions
|
|
tracker/
|
|
checks.py the rules: README problems and repo blockers
|
|
challenge.py reads a README header into a Challenge
|
|
gitlab.py GitLab API access
|
|
selection.py reads challenges.yml
|
|
overview.py runs parsing and checks over all repos
|
|
terminal.py terminal output
|
|
markdown.py HedgeDoc note
|
|
challs.py challs.txt
|
|
hedgedoc.py writes the note to HedgeDoc
|
|
config.py reads the configuration
|
|
install-timer.sh installs the systemd units below
|
|
ctf-*.service/.timer systemd unit templates
|
|
```
|