|
1 | 1 | # Challenges |
2 | 2 |
|
3 | | -## Supported Specification Formats |
| 3 | +## problem.md |
4 | 4 |
|
5 | | -As an evolution on the original `hacksport` library, we provide two formats for specifying a challenge: JSON and Markdown. |
6 | | - |
7 | | -### problem.json |
8 | | - |
9 | | -We attempt to support backwards compatability with the previous `hacksport` JSON format and will use the compatability mode for any `problem.json` file that has a `challenge.py` file in the same directory. However, these challenge definitions are unable to take full advantage of new challenge types and metadata control. So it is preferred to use the newer format if you want to use JSON for specifying the metadata. |
10 | | - |
11 | | -### problem.md |
12 | | - |
13 | | -In an effort to make it easier to develop content, we've implemented a Markdown specification that imposes some restraints on the document layout but should be far easier to read and maintain than the JSON format. For a detailed breakdown of the new format, see the [specification](markdown_challenges.md). |
14 | | - |
15 | | -## Schemas |
16 | | - |
17 | | -"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. Two equivalent schema specifications can be found [here](schemas/). It is worth noting that a `-1` for instance count specifies that instances are still 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). |
| 5 | +This file specifies a challenge's metadata (name, description, etc.) For a detailed breakdown of the format, see the [specification](specification.md). |
18 | 6 |
|
19 | 7 | ## Supported Challenge Types |
20 | 8 |
|
21 | | -One of the primary goals of _cmgr_ is to make it easier to implement new challenge types. One notable lack of support right now, however, is the inability to mount and manipulate block devices. This is currently a limitation of the underlying container system (i.e. Docker and containerd). Unless otherwise specified, challenge types that expose a port have the associated service running as a non-root user inside the container who does not have permission to read `/challenge` (and hence a known location of the flag). |
22 | | - |
23 | | -As a general rule, all challenge types (except "custom" and "hacksport") support two mechanisms for installing dependencies. First, they will look for a `packages.txt` file in the challenge's root directory. This file should have a single "apt" package per line (versioning not supported) and will be installed before any other code is run. Additionally, challenges that require/support Python (e.g. "flask") will also look for the standard `requirements.txt` file and install those dependencies with "pip". |
24 | | - |
25 | | -If you are interested in the underlying mechanics for a challenge type, all of the associated Dockerfiles can be found [here](../cmgr/dockerfiles/). |
26 | | - |
27 | 9 | ### custom |
28 | 10 |
|
29 | | -This is by far the most flexible of the challenge types, but also the one with the least amount of structured support. In addition to the challenge specification file (e.g. `problem.md`), the challenge author must supply a complete Dockerfile. The Dockerfile will be supplied with three build arguments when it is first invoked: `FLAG`, `SEED`, and `FLAG_FORMAT`. |
| 11 | +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`. |
30 | 12 |
|
31 | | -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. Finally, if the Dockerfile expects any ports to be exposed directly to end-users, then there must be a comment line of the form `# PUBLISH <port number> AS <port name>` in the Dockerfile. |
| 13 | +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`. |
32 | 14 |
|
33 | 15 | You can find an example [here](custom/). The ["multi"](multi/) challenge example demonstrates the full range of customization you can leverage by demonstrating multi-container challenges and custom per-build lookup values. |
34 | 16 |
|
35 | | -#### Added Behavior for Docker files |
36 | | - |
37 | | -In order to make challenge types as reusable as possible, `cmgr` adds some |
38 | | -additional concepts to a normal `Dockerfile` that need to be considered when |
39 | | -building the `Dockerfile` for a custom challenge. |
40 | | - |
41 | | -##### Build stage named 'builder' |
| 17 | +#### Publishing ports |
42 | 18 |
|
43 | | -If any stage in the Dockerfile is labeled `builder`, then that staged |
44 | | -(rather thand the last stage) must contain the `/challenge/metadata.json` |
45 | | -and, if applicable, `/challenge/artifacts.tar.gz` files. |
| 19 | +Docker has a distinction between "exposed" ports and "published" ports. `cmgr` detects which exposed |
| 20 | +ports should be published by requiring a comment of the form `# PUBLISH {port} AS {name}` (case |
| 21 | +sensitive) to occur in the Dockerfile after `EXPOSE` directives. This allows challenge authors |
| 22 | +to bring in base images that already expose ports in Docker (e.g., the PostgreSQL image) without |
| 23 | +neccessarily exposing those ports to competitors. |
46 | 24 |
|
47 | | -##### Publishing ports |
48 | | - |
49 | | -Docker has a distinction between "exposed" ports and "published" ports. To |
50 | | -avoid repetitive boilerplate in `problem.md` files, `cmgr` detects which |
51 | | -exposed ports should be published by requiring a comment of the form |
52 | | -`# PUBLISH {port} AS {name}` (case sensitive) to occur in the Dockerfile after |
53 | | -the `EXPOSE` directive. This allows challenge authors to bring in base images |
54 | | -that already expose ports in Docker (e.g., the PostgreSQL image) without |
55 | | -requiring that the port be directly exposed to the competitor. |
56 | | - |
57 | | -##### Launching more than one container |
| 25 | +#### Launching more than one container |
58 | 26 |
|
59 | 27 | In order to support challenges that launch multiple containers for a |
60 | 28 | challenge, `cmgr` introduces a comment of the form `# LAUNCH {build_stage} ...` |
61 | 29 | which will launch an instance of each listed stage with the stage name as |
62 | | -its Docker DNS name and place them on their own internal network. For a |
63 | | -specific example of this, see the [multi example](../multi). When using |
64 | | -multiple containers, it is important that each `PUBLISH` directive (above) |
| 30 | +its Docker DNS name and place them on the same overlay network. For a |
| 31 | +specific example of this, see the [multi example](./multi). When using |
| 32 | +multiple containers, it is important that each `# PUBLISH` comment (described above) |
65 | 33 | appears in the same build stage as the `EXPOSE` directive it is referencing. |
66 | 34 |
|
67 | | -### flask |
68 | | - |
69 | | -This is a simple wrapper around the "flask" web framework designed to require minimal adjustment from standard practice for challenge authors to create new content. An example with a more detailed readme can be found [here](flask/). |
70 | | - |
71 | | -### hacksport |
72 | | - |
73 | | -This is a shim around the legacy `hacksport` framework. It should "just work" for those challenges, but is also likely to be fragile on more complicated ones. In particular, the "docker" challenges are not supported (but should be easily portable to a new "custom" one) and calls to "mount" are not supported. |
74 | | - |
75 | | -### node |
76 | | - |
77 | | -This is a simple wrapper around the _Node.js_ framework using their published LTS base image. An example with more details can be found [here](node/). Additionally, these challenges do not need to use _Node.js_ to run the server itself. An example of using Puppeteer (uses _Node_) to enable XSS challenges with a Flask backend can be found [here](puppeteer/). |
78 | | - |
79 | | -### php |
80 | | - |
81 | | -This is a simple framework for launching a web server/application built using PHP. An example with more details can be found [here](php/). |
82 | | - |
83 | 35 | ### Compiled Challenges |
84 | 36 |
|
85 | | -Many challenges require compilation as part of their build. To allow maximum flexibility while trying to minimize outside requirements on the build process, we have two different "drivers" for compiled challenges as well as three different styles for how competitors will interact with them. |
86 | | - |
87 | | -#### make |
88 | | - |
89 | | -The make "driver" is the simplest of the drivers. 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. |
| 37 | +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. |
90 | 38 |
|
91 | | -#### pybuild |
| 39 | +#### remote-make |
92 | 40 |
|
93 | | -The `pybuild` driver is an attempt to provide the power of templating (similar to what was available in `hacksport`) while still getting out of the way for the most part. It allows authors to hook the build process at various points by creating a file named `build.py` which defines a class named `Builder`. This class will then be referenced during the build process for the challenge. |
| 41 | +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. |
94 | 42 |
|
95 | | -Probably the most powerful part of this driver is the use of `jinja2` to template the challenge directory into the build image. In particular, any attribute of the `Builder` is directly available for templating. If there are files that should not be templated, you can specify them as a list of strings (full filepath from the challenge root to the file) assigned to `self.dont_template`. Additionally, you can specify files to remove after executing the build (`self.remove`) as well as specify compiler flags for the default target. |
| 43 | +#### static-make |
96 | 44 |
|
97 | | -There are three functions that can be used to manipulate the build process: `prebuild(self)`, `build(self)`, and `postbuild(self)`. They run in that order and all are completely optional (if `build` is omitted the build step defaults to shelling out to `make {{program_name}}`). Of particular note, `prebuild` is called after `self.flag` and `self.flag_format` have been populated and `random` has been seeded with the appropriate seed for the build. In contrast, `postbuild` is called after `artifacts.tar.gz` has been assembled but before `metadata.json` has been created. |
| 45 | +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. |
98 | 46 |
|
99 | | -Pre-defined attributes (all over-ridable) |
100 | | -- `flag`: the auto-generated flag for the problem |
101 | | -- `flag_format`: the requested format for what the flag should look like |
102 | | -- `x86_64`: a boolean indicating whether this should be a 64-bit build (true, and the default) or a 32-bit build. |
103 | | -- `executable_stack`: a boolean indicating whether the stack is executable (default is false) |
104 | | -- `stack_guards`: a boolean indicating whether compiler-injected stack-guards should be used (default is true) |
105 | | -- `strip`: a boolean indicating whether the final binary should be stripped (default is false) |
106 | | -- `debug`: a boolean indicating whether DWARF information should be included with the final binary (default is false) |
107 | | -- `pie`: a boolean indicating whether the final binary should be built as a position-independent executable (default is false) |
108 | | -- `extra_flags`: a list of strings which will be appended to the `CFLAGS`, `CXXFLAGS`, and `ASFLAGS` environment variables (and hence override auto-generated flags) |
109 | | -- `dont_template`: a list of filepaths to skip when applying the templating logic |
110 | | -- `program_name`: **REQUIRED if "build" function not defined**: specifies the name of the binary to build. By default, it will try to use make's implicit build rules to build it by calling `make {{program_name}}` (defaults to "main"). |
111 | | -- `exec`: (remote/service only) the command that should be used as the entrypoint for the server (defaults to `./{{program_name}})` |
112 | | -- `artifacts`: The list of files (after "build" step) that should be packaged into `artifacts.tar.gz` (defaults to an empty list) |
113 | | -- `lookups`: a dictionary of string key-value pairs which will be made available to the front-end's templating engine |
114 | | -- `remove`: a list of files to remove prior to starting the server (useful for removing sensitive build files from the build directory) |
115 | | - |
116 | | -For debugging purposes, the Python script used to drive this logic is available [here](../support/pybuild.py). |
117 | | - |
118 | | -#### remote |
119 | | - |
120 | | -The "remote" set of challenge types 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. |
121 | | - |
122 | | -#### service |
123 | | - |
124 | | -The "service" set of challenge types will start the program exactly once and expect it to accept and handle TCP connections completely on its own. The program is expected to read the "PORT" environment variable to ensure it listens on the correct port (but should be safe to hardcode as 5000). |
125 | | - |
126 | | -#### static |
| 47 | +## Schemas |
127 | 48 |
|
128 | | -The "static" set of challenge types have no network component and should be solvable solely by using the `artifacts.tar.gz` and `metadata.json` created during the build process. |
| 49 | +"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](./schema.yaml). 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). |
0 commit comments