challenge.yaml Config Reference
Challenge configuration is expected to be at <category>/<name>/challenge.yaml.
There are some examples available on the challenge quickstart guide.
Available fields:
* denotes required fields.
name *
- type:
string - no default
The name of the challenge, as shown to players in the frontend UI. Any characters are allowed.
name: notsh
name: cha-cha-cha
# name can have spaces:
name: Revenge of the FIPSauthor *
- type:
string - no default
Author or authors of the challenge, as shown to players in the frontend UI. If there are multiple authors, specify them as one string.
author: John Author
# multiple authors:
author: Alice, Bob, and othersdescription *
- type:
string - no default
Description and flavortext for the challenge, as shown to players in the frontend UI. Supports templating to include information about the challenge, such as the link or command to connect.
Most challenges only need {{ nc }} or {{ link }}.
| Available fields | Description |
|---|---|
domain | Full domain the challenge is exposed at, e.g. <subdomain>.chals.example.ctf |
port | Port the challenge is listening on |
nc | nc command to connect to TCP challenges, with Markdown backticks (equivalent to `nc {{domain}} {{port}}`) |
url | URL to the exposed web domain for web challenges, plus port if needed (equivalent to https://{{domain}}:{{port}}) |
link | Markdown link to url |
challenge | The full challenge.yaml config object for this challenge, with subfields |
description: |
Some example challenge. Blah blah blah flavor text.
In case you missed it, this was written by {{ challenge.author }}
and is called {{ challenge.name }}.
{{ nc }} # `nc somechal.chals.example.ctf 12345`
{{ link }} # [https://somechal.chals.example.ctf](https://somechal.chals.example.ctf)challenge_id *
- type:
string - no default
Persistent challenge identifier, used by the frontend scoreboard to track challenges if they are renamed.
This accepts any string but we recommend using a short random string, for example from pwgen 8.
Once set, this should not be renamed.
category
- type:
string - default: from folder structure
The category for the challenge, parsed from the directory structure.
DANGER
This is automatically set from the expected directory structure of <category>/<name>/challenge.yaml and should not be set in the file.
point_class
- type:
string - default: set in
rcds.yaml
WARNING
Not implemented yet, does nothing. Requires upcoming scoreboard integration.
The point class of the challenge, used to set point values. Values correspond to entries in the rcds.yaml point_class settings. The default value is set in the rcds.yaml defaults settings
point_class: standard
point_class: survey
# (given a challenge.yaml that looks like:)
# point_classes:
# - name: standard
# - name: surveyflag *
- type:
string|dict - no default
Where to find the flag for the challenge. The flag can be in a file, a regex, or a direct string.
# directly set
flag: ctf{example-flag}
# from a file in in the challenge directory
flag:
file: ./flag
# regex
flag:
regex: /ctf\{(foo|bar|ba[xyz])\}/INFO
Regex flags are not implemented yet and setting one does nothing
provide
- type: list of
string/dict - default:
[](no files)
List of files to provide to the players on the frontend UI. These files can be from the challenge directory or from a container image built for a challenge pod, and uploaded individually or zipped together.
If there are no files to upload for this challenge, this can be omitted or set to an empty array.
provide:
# files from the challenge folder in the repo
- somefile.txt
- otherfile.txt
# these are all equivalent
- foo.txt
- include: foo.txt
- include: [ foo.txt ]
# rename a really long name as something shorter for upload
- as: short.h
include: some_really_long_name.h
# multiple files from src/ in the challenge folder, zipped as together.zip
- as: together.zip
include:
- src/file1
- src/file2
- src/file3
# multiple files pulled from the container image for the `main` pod,
# uploaded individually as `notsh` and `libc.so.6`
- from: main
include:
- /chal/notsh
- /lib/x86_64-linux-gnu/libc.so.6
# single file pulled from the main container and renamed
- from: main
as: libc.so
include: /lib/x86_64-linux-gnu/libc.so.6
# multiple files pulled from the main container and zipped together
- from: main
as: notsh.zip
include:
- /chal/notsh
- /lib/x86_64-linux-gnu/libc.so.6
# if no files need to be provided:
provide: []
# or omit entirely.include
- type: list of
string - no default
File or list of files to upload individually, or include in a zip if as is set.
When uploading, only the basename is used and the path to the file is discarded.
If a provide item is specified as a single string, it is interpreted as an include:.
.as
- type:
string - no default
If .include is a single file, rename to this name while uploading.
If multiple files, zip them together into the given zip file.
.from
- type:
string - no default
Fetch these files from the corresponding challenge pod image.
pods
- type: list of
dict - default:
[](no pods)
Defines how to build and deploy any services needed for the challenge.
Challenge pods can be built from a local Dockerfile in the challenge folder or use an upstream image directly.
Pods can either define parameters for a deployment template or a path to a manifest file for custom deployment resources.
If there are no pods or images needed for this challenge, this can be omitted or set to an empty array.
pods:
- name: main
build: .
ports:
- internal: 1337 # expose a container listening on port 1337 ...
expose:
http: examplechal # as a web chal at https://examplechal.<chals_domain>
- name: db
image: postgres:alpine
architecture: arm64
env:
POSTGRES_USER: someuser
POSTGRES_PASSWORD: notsecure
- name: something-custom
manifest: custom/manifest.yaml
# if no containers or pods need to be deployed:
pods: []
# or omit entirely.name
- type:
string - no default
Name of the pod, used to refer to this container as a source for provide files and for generated resource names.
Cannot contain spaces or punctuation, only alphanumeric and -.
.build
- type:
string|dict - no default
Build the container image for this pod from a local Dockerfile. Supports a subset of the docker-compose build spec, either:
- a string path to the build context folder
- yaml with explicit
contextpath,dockerfilepath within context folder, andargsbuild args (onlycontext,dockerfile, andargsare supported)
The build context directory is relative to the challenge.yaml, and the dockerfile is relative to the context directory.
Conflicts with image.
# build a container from a Dockerfile in the challenge folder
build: .
# equivalent to the above but with explicit build context and Dockerfile name
build:
context: .
dockerfile: Dockerfile
# build from a subfolder with a custom Dockerfile and some build args
build:
context: src/
dockerfile: Containerfile.remote
args:
CC_OPTS: "-Osize".image
- type:
string - no default
Use an available container image for the pod instead of building one from source.
Conflicts with build.
.manifest
Path to a file containing custom Kubernetes resources. All resources from this manifest will be deployed into the cluster instead of the normal deployment template.
Image pull secrets will be added to any Namespace included in the manifest using the configured cluster credentials. All other resources will be deployed as-is with no modifications or checks.
Manifest pods can specify a build: config to have an image built and pushed to the registry like the normal pod template.
Conflicts with all pod parameters except name and build.
WARNING
This will not perform any templating or add additional resources outside of the pull credentials. If any services in the manifest use a built image or need to be exposed, that must be hardcoded in the manifest.
pods:
- name: my-custom-stuff
manifest: custom.yaml
- name: with-image
build:
context: src/
dockerfile: Dockerfile
manifest: custom-image.yaml # must hardcode the built image reference.env
- type:
dict - default:
{}(no envvars)
Any environment variables to set for the running pod. Specify as name: value.
env:
SOME_ENVVAR: foo bar.architecture
- type:
string - default:
"amd64"
Set the desired CPU architecture to run this pod on. Kubernetes uses GOARCH architecture names.
architecture: amd64
architecture: arm64.resources
- type:
dict - default: global default from
rcds.yaml
The CPU and memory resources that will be reserved for this pod. Kubernetes will make sure the requested amounts will be available for this pod to use, and will also restart the pod if it goes over these limits.
Uses the Kubernetes resource units.
If not set, the default set in rcds.yaml is used.
resources:
cpu: 1
memory: 512Mi.replicas
- type:
number - default:
2
How many instances of the pod to run. Traffic is load-balanced between instances.
Default is 2 and this is probably fine unless the challenge is very resource intensive.
replicas: 2 # the default.ports
- type: list of
dict - default:
[]
List of ports to expose to players.
.ports[].internal
- type:
number - no default
The port that the challenge container (i.e. xinetd/nginx/etc inside) is listening on.
.ports[].expose
- type:
dict - no default
How to expose the internal container port to players -- either as a TCP port or a subdirectory for web challenges. Must be one of the following:
.ports[].expose.tcp
- type:
number - no default
The port to expose the challenge over raw TCP at on the challenge subdomain. Must be unique across all other exposed TCP challenges.
pods:
- #...
ports:
- internal: 31337 # the port the container listens on
expose:
tcp: 30124 # exposed at <challenges-domain>:30124.ports[].expose.http
- type:
string - no default
The subdomain to expose the challenge at as a website (port 80/443). This is prepended to the global challenge subdomain. The cluster will provision an SSL certificate for the site.
Must be a valid DNS domain name (alphanumeric, _, -).
pods:
- name: main
build:
context: .
dockerfile: Dockerfile
replicas: 2
ports:
- internal: 31337 # the port the container listens on
expose:
http: my-chal # exposed at https://my-chal.<challenges-domain>.volume
- type:
string - no default
INFO
Not implemented yet, does nothing