This file specifies a challenge's metadata (name, description, etc.) For a detailed breakdown of the format, see the specification.
In this challenge type, the author must supply a complete Dockerfile. The Dockerfile will be supplied with three build arguments when the challenge is built: FLAG, SEED, and FLAG_FORMAT.
The Dockerfile is responsible for using these inputs to build the templated challenge and format the image appropriately for cmgr to retrieve the artifacts and build metadata. In particular, any artifacts competitors should see must be in a GZIP-ed tar archive located at /challenge/artifacts.tar.gz. Additionally, there must be a /challenge/metadata.json file that has a field for the flag (named flag) as well as any other lookup values the challenge references in its details and hints. These files must be present in either the final build stage, or in a stage explicitly named builder.
You can find an example here. The "multi" challenge example demonstrates the full range of customization you can leverage by demonstrating multi-container challenges and custom per-build lookup values.
Docker has a distinction between "exposed" ports and "published" ports. cmgr detects which exposed
ports should be published by requiring a comment of the form # PUBLISH {port} AS {name} (case
sensitive) to occur in the Dockerfile after EXPOSE directives. This allows challenge authors
to bring in base images that already expose ports in Docker (e.g., the PostgreSQL image) without
neccessarily exposing those ports to competitors.
In order to support challenges that launch multiple containers for a
challenge, cmgr introduces a comment of the form # LAUNCH {build_stage} ...
which will launch an instance of each listed stage with the stage name as
its Docker DNS name and place them on the same overlay network. For a
specific example of this, see the multi example. When using
multiple containers, it is important that each # PUBLISH comment (described above)
appears in the same build stage as the EXPOSE directive it is referencing.
In the remote-make and static-make challenge types, the build process will call make main, make artifacts.tar.gz, and make metadata.json in that order to build the challenge and necessary components. Additionally, challenges with a network component will have make run called to start as the entrypoint.
The remote-make challenge type will take a program that uses stdin/stdout to communicate and connect it to a port so that every new TCP connection gets forked into a new process with stdin/stdout piped to the network.
The static-make challenge type has no network component and should be solvable solely by using the artifacts.tar.gz and metadata.json created during the build process.
The flag-only challenge type is for challenges that consist of nothing but a submission prompt: no service and no downloads (e.g. knowledge-check questions, or a second flag exported by a sibling challenge). The build's only product is metadata.json. A Makefile with a metadata.json target is optional — without one, the flag templated by cmgr is used as-is. Providing a Makefile allows a static flag (such as the expected answer to a question) or extra lookup values referenced from the challenge text; since the SEED build argument is available, option lists can be shuffled per build and a sibling challenge built with the same seed can derive a matching flag. See the flag-only example for a multiple-choice question. Solve scripts do not apply to this type (the flag is the answer), so cmgr test --require-solve does not demand one.
Independent of the challenge type above (which controls how a challenge is built), cmgr derives a delivery_type for every challenge that describes what competitors actually receive:
service— the challenge publishes at least one port (via# PUBLISH); a running container serves competitors. Whether it runs as a shared persistent instance or on-demand per user is decided by the schema'sinstance_count, not by the challenge.artifact_only— the challenge publishes no ports; theartifacts.tar.gzproduced at build time is the entire challenge and no running container is needed. Allstatic-makechallenges are artifact-only, as is anycustomchallenge without a# PUBLISHdirective.flag_only— the challenge is a bare submission prompt (no ports, no artifacts); declared with theflag-onlychallenge type described above.
This value is derived from the Dockerfile — authors never write it, and it is reported through the cmgrd API so front-ends can distinguish these cases explicitly.
Two things follow from this that challenge authors should know:
- A challenge that publishes no ports should produce a non-empty
artifacts.tar.gz; if it produces neither, the build logs a warning since this usually indicates a forgotten# PUBLISHdirective — intentional cases should use theflag-onlychallenge type instead, which is exempt from the warning.cmgr updatealso tags each non-service challenge with its delivery type and prints a summary, so an unexpectedly artifact-only challenge is visible before deployment. - Solvers for artifact-only challenges run against the build's artifacts directly (on Docker's default network — no
challengehost, outbound access preserved) rather than alongside a running instance, andcmgr testskips the start/stop steps for them. - cmgr never launches instances for non-service challenges, so custom artifact-only Dockerfiles do not need an entrypoint. A leftover placeholder (e.g.
CMD tail -f /dev/null) from older challenge content is harmless — it is simply never run.
"Schemas" are a mechanism for declaratively specifying the desired state for a set of builds and instances. Builds and the associated instances that are created by a schema are locked out from manual control and should be the preferred way to manage a large number of builds and instances for events. However, they are still event agnostic and can be used for managing other groupings of resources as appropriate. An example schema can be found here. It is worth noting that a -1 for instance count specifies that instances are manually controlled and allows the CLI or cmgrd to dynamically increase or decrease the number of running instances (useful for mapping instances uniquely to end-users without having a large number of unused containers). instance_count is only meaningful for challenges with a service delivery type; artifact-only challenges listed in a schema get their builds (one per seed, with artifacts and flags) and no instances.