diff --git a/docs/backends/index.rst b/docs/backends/index.rst new file mode 100644 index 0000000..a9d6a4f --- /dev/null +++ b/docs/backends/index.rst @@ -0,0 +1,43 @@ +Deployment Backends +=================== + +rCDS uses a pluggable backend model for the task of actually deploying +challenges to infrastructure. rCDS contains a few built-in backends, and +third-party backends may be loaded by specifying their module name. + +Backends are specified in the top-level configuration :file:`rcds.yaml`: + +.. code-block:: yaml + + backends: + - resolve: name + options: + key: value + +The top-level key ``backends`` is an array of backend objects, which consist of +their name (``resolve``) and the options for the backend (``options``). +``resolve`` first attempts to load a built-in backend of the corresponding name, +and, if it does not exist, then interprets the name as a package name and loads +from it. + +Each backend may also modify the ``challenge.yaml`` schema---be sure to read +the docs for the backends you are using to understand challenge options specific +to that backend. + +Scoreboard Backends +------------------- + +These are responsible for displaying the challenge to competitors; they handle +uploading the challenge's metadata (description, flags, point value, etc) and +any assets that are served to competitors. + +- `rCTF `_ + +Container Runtime Backends +-------------------------- + +These are responsible for running the built challenge containers. By design, +none of the built-in backends will start containers on the machine that rCDS is +being run from. + +- `Kubernetes `_ diff --git a/docs/backends/k8s/index.rst b/docs/backends/k8s/index.rst new file mode 100644 index 0000000..5b5683d --- /dev/null +++ b/docs/backends/k8s/index.rst @@ -0,0 +1,77 @@ +``k8s`` --- Kubernetes +====================== + +This backend deploys challenges to a Kubernetes cluster. Each challenge is +deployed under its own namespace, and exposed via either a NodePort service or +an Ingress object, depending on the protocol specified by the challenge. No +accommodations are currently being made in case of NodePort conflicts---it is +recommended that challenges are deployed to an isolated cluster (you should be +doing this anyways since Kubernetes currently does not have hard multi-tenancy). +A NetworkPolicy is also created to prevent network traffic from outside a +challenge's namespace reaching any containers which are not explicitly exposed. + +Configuration +------------- + +The only required option is ``domain``---NodePort services must be reachable on +this domain, and the cluster's ingress controller must be reachable on its +subdomains. For example, if ``domain`` is set as ``example.com``, then +``example.com`` must accept incoming TCP connections to NodePort services, and +``chall.example.com`` must be routed through the cluster's ingress controller. +It is your responsibility to set up the ingress controller. + +Additional annotations on ingress and service objects can be specified through +the ``annotations`` key, and affinity and tolerations on pods can be set through +``affinity`` and ``tolerations``, respectively. + +See the `Options Reference`_ for more details. + +Recommended Cluster Configuration +--------------------------------- + +RBAC +~~~~ + +As always, we recommend running rCDS from a CI service; thus, rCDS will need to +authorize with your Kubernetes cluster. We have provided a ClusterRole which +grants the minimum privileges required by the Kubernetes backend (also +accessible here__): + +.. __: https://github.com/redpwn/rcds/blob/master/docs/content/backends/k8s/cluster-role.yaml + +.. literalinclude:: ./cluster-role.yaml + :language: yaml + +Cluster Operator +~~~~~~~~~~~~~~~~ + +We recommend `Google Kubernetes Engine`_, because it supports restricting of the +metadata server by Kubernetes service account, a feature called `Workload +Identity`_. This prevents SSRF from escalating into takeover of compute +resources. + +.. _Google Kubernetes Engine: https://cloud.google.com/kubernetes-engine +.. _Workload Identity: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity + +Ingress +~~~~~~~ + +Traefik_ is recommended as an ingress controller, since it is very configurable +and supports JSON access logging for easy visibility into your challenges with +your cluster operator's logging solution. Consider manually issuing a wildcard +LetsEncrypt certificate and setting it as the default. Then, set annotations on +ingresses to use TLS, and configure Traefik to upgrade HTTP to HTTPS for full +HTTPS on challenges. + +.. note:: + + By default, Traefik will attempt to auto-detect Content-Type; apply the + ``contentType`` middleware to disable this behavior if it breaks your + challenges. + +.. _Traefik: https://traefik.io/ + +Options Reference +----------------- + +.. jsonschema:: ../../../rcds/backends/k8s/options.schema.yaml diff --git a/docs/backends/rctf/index.rst b/docs/backends/rctf/index.rst new file mode 100644 index 0000000..401a8dc --- /dev/null +++ b/docs/backends/rctf/index.rst @@ -0,0 +1,36 @@ +``rctf`` --- rCTF +================= + +This backend deploys challenges to rCTF_. The options ``url`` and ``token`` +specify the URL of the rCTF instance and the token of the admin account to use, +respectively. Both of these will be set from the environment variables +``RCDS_RCTF_URL`` and ``RCDS_RCTF_TOKEN`` respectively, if they exist. +Challenges with a ``value`` set are assumed to be statically-scored; all other +challenges are dynamically-scored according to the global ``scoring`` config +(between ``scoring.minPoints`` and ``scoring.maxPoints``). rCTF does not support +regex flags. + +.. _rCTF: https://rctf.redpwn.net/ + +The ``sortOrder`` option allows you to automatically set the ``sortWeight`` +fields on challenges based on an ordering provided in this key. Listed +challenges are assigned a ``sortWeight`` equal to its index in the array +multiplied by -1. This means that if all the challenges have the same score and +solve count, they will be displayed with the first element of the array at the +top. + +Additional Challenge Properties +------------------------------- + +``author`` and ``category`` are required. + +``tiebreakEligible`` (bool): whether or not this challenge factors into time-based +tiebreakers. Defaults to ``true``. + +``sortWeight`` (number): rCTF sort weight parameter. Ignored if the challenge is +listed in the global ``sortOrder`` option. Defaults to ``0``. + +Options Reference +----------------- + +.. jsonschema:: ../../../rcds/backends/rctf/options.schema.yaml diff --git a/docs/challenge.rst b/docs/challenge.rst new file mode 100644 index 0000000..3342e4e --- /dev/null +++ b/docs/challenge.rst @@ -0,0 +1,177 @@ +``challenge.yaml`` --- Challenge Config +======================================= + +The file ``challenge.yaml`` defines the configuration for a challenge within an +rCDS project. ``.yml`` and ``.json`` files are also supported. + +Basics +------ + +``id`` --- the identifier for this challenge. Must be unique project wide. This +key is set automatically from the name of the directory the challenge is in; +unless you have a very good reason to, don't set this in ``challenge.yaml``. + +``author`` -- a string or list of strings containing the authors for this +challenge. + +``description`` -- self-explanatory. It is in Markdown format and will be +processed with Jinja_. See Templating_ for more details. + +``category`` -- self-explanatory. If the challenge directory is exactly two +directories deep (for example, ``/pwn/chall``, where ``/`` is the project root), +this is set from the parent directory of the challenge's directory ("pwn" in the +previous example). We recommend organizing your challenges in a +:samp:`{category}/{chall}` structure. + +``flag`` --- the flag for the challenge. If it is a string, then the flag is set +to the string verbatim. Otherwise, if ``flag.file`` is set, the flag is loaded +from the specified file (relative to the challenge root), and stripped of +leading and trailing whitespace. If ``flag.regex`` is set, the flag is anything +matching the given regex. A warning is emitted if the flag contains multiple +lines (usually this is from an improperly configured flag file). + +``provide`` --- an array of files to provide to competitors as downloads. The +files can either be a string, in which case they are interpreted as the path to +the file, or an object with the ``file`` and ``as`` properties; these properties +define the path and the displayed name of the file, respectively. + +``value`` --- point value of this challenge. Meaning is defined by the +scoreboard backend. + +``visible`` --- if set to ``false``, the scoreboard backend will act as if this +challenge does not exist. + +.. warning:: + + Most scoreboard backends will delete any challenges that were created by + rCDS but now no longer exist---switching ``visible`` to ``false`` after the + challenge has already been deployed may cause solves to be lost. + +Deployment +---------- + +In rCDS, you define first define all of the `containers <#containers>`_ that +your challenge needs to run, and then declare how you want them `exposed +<#expose>`_ to the world. + +``deployed`` --- whether or not this challenge's containers should be deployed. +Defaults to ``true``. + +Containers +~~~~~~~~~~ + +The ``containers`` key is an object whose keys are the names of the containers +this challenge creates. These containers can either use an existing image, or +specify a path to a Dockerfile to build from. Each container must declare all +ports that need to be connected to, both from other containers and by +competitors; which ports are exposed to competitors are specified `separately +<#expose>`_. Containers from the same challenge can connect to each other via a +DNS lookup of their names; for example, if a container ``app`` is defined, +another container can connect to any of ``app``'s declared ports by looking up +the name ``app``. + +``image`` --- the tag of an existing image to run + +``build`` --- settings for building this container. If it is a string, then it +is the path to the Docker build context (the directory where a Dockerfile is). +It can also be an object for advanced configuration: + +``build.context`` --- path to the Docker build context. + +``build.dockerfile`` --- path to the Dockerfile, relative to the build context +root. + +``build.args`` --- Docker build args to set when building the container. +Key-value object. + +``ports`` --- a list of integers of the port numbers this container listens on. +If anything needs to connect to a port on the container, list it here. + +``replicas`` --- number of replicas of this container to run (on backends that +support it). Defaults to 1. Leave at 1 for stateful containers. + +``environment`` --- key-value object of environment variables to set. + +``resources`` --- resource limits on the container. See `Kubernetes's +documentation`__ on the format of this value (only ``cpu`` and ``memory`` are +implemented). + +.. __: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + +Expose +~~~~~~ + +The top-level ``expose`` key defines all of the ports on `containers +<#containers>`_ that should be exposed to competitors. It is an object whose +keys correspond to the names of defined containers, and whose values are arrays +of port objects. These objects each describe how one port should be exposed. + +``target`` --- the port on the container that this rule is targeting. + +``tcp`` --- if specified, this port should be treated as TCP. The value is the +port at which it is exposed on, on the challenge host. + +``http`` --- if specified, this port should be treated as HTTP, and will be +reverse proxied with TLS termination. The value is a string, the subdomain name +on which the challenge will be hosted. Alternatively, it can be an object with a +``raw`` key, in which case ``http.raw`` contains the FQDN that the challenge +will be served on. When using ``http.raw``, rCDS will handle the virtual +hosting, however as a challenge author, you will need to coordinate with your +infrastructure admin on setting up TLS and DNS records. + +Templating +---------- + +Challenge descriptions are rendered using Jinja_. The contents of the +challenge's config is available on the ``challenge`` object in the Jinja +environment. Some fields are altered with more concrete versions of their +contents---for example, the ``http`` key on ``expose`` port objects will contain +the fully-qualified domain name, instead of just the prefix. Container backends +will also add a ``host`` key to a TCP ``expose`` port, which contains the host at +which that port will be accessible. + +.. note:: + + An example configuration: + + .. code-block:: yaml + + # challenge.yaml + ... + description: | + 1: {{ challenge.expose.main[0].http }} + + 2: {{ challenge.expose.main[1].host }}:{{ challenge.expose.main[1].tcp }} + containers: + main: + ports: [1337, 1338] + expose: + main: + - target: 1337 + http: leet + - target: 1338 + tcp: 31253 + + Assuming the container backend is hosted on example.com, the description + would render as: + + 1: leet.example.com + + 2: example.com:31253 + +There are also shortcuts available for the most common use-case: a single +exposed port. ``host`` is the hostname under which the port is accessible. +``link`` will automatically create a Markdown link to the exposed port, and +``url`` will create just the URL without the accompanying Markdown. This works +for both HTTP and TCP ports, since you may want to expose a challenge which +breaks behind a reverse proxy as TCP. For TCP ports, there is also ``port``, +which is the exposed port number of the port, and ``nc``, which +will create a ``nc`` command to connect to the challenge---it is equivalent to +``nc {{ host }} {{ port }}``. + +.. _Jinja: https://jinja.palletsprojects.com + +Reference +--------- + +.. jsonschema:: ../rcds/challenge/challenge.schema.yaml diff --git a/docs/index.rst b/docs/index.rst index ad2aff4..2a34aa2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,8 +10,18 @@ rCDS - A CTF Challenge Deployment Tool :maxdepth: 1 :caption: Contents + project + challenge + backends/index contributing +.. toctree:: + :maxdepth: 1 + :caption: Backends + + backends/rctf/index + backends/k8s/index + .. toctree:: :maxdepth: 2 :caption: API Reference diff --git a/docs/project.rst b/docs/project.rst new file mode 100644 index 0000000..58fdb25 --- /dev/null +++ b/docs/project.rst @@ -0,0 +1,54 @@ +``rcds.yaml`` --- Project Config +================================ + +The file ``rcds.yaml`` defines the configuration for the current project, and +its location also defines the root of the project. ``.yml`` and ``.json`` files +are also supported. Challenges will be searched for in subdirectories of the +project root. This file contains various global configuration options, including +for the backends_ and `Docker containers`_ + +.. _backends: backends/index.html +.. _Docker containers: #docker + +Docker +------ + +``docker.image.prefix`` *(required)* --- the prefix for generated Docker image +tags. This should contain the registry and "directory" --- e.g. +``gcr.io/redpwn/challs``. + +``docker.image.template`` --- the Jinja template to create image tags with; it +is joined with ``docker.image.prefix``. Defaults to ``rcds-{{ challenge.id }}-{{ +container.name }}``. + +Misc +---- + +``defaults`` --- default options to set on challenges. This key takes an object of +the same shape as ``challenge.yaml``. Setting defaults on keys like ``expose`` +and ``containers`` will apply the defaults to all exposed ports and containers, +respectively. + +.. note:: + + An example of setting default resource limits on all containers which don't + otherwise specify limits: + + .. code-block:: yaml + + defaults: + containers: + resources: + limits: + cpu: 100m + memory: 150Mi + requests: + cpu: 10m + memory: 30Mi + +``flagFormat`` --- a regex to test all (static) flags against. + +Reference +--------- + +.. jsonschema:: ../rcds/project/rcds.schema.yaml