Skip to content

Commit

Permalink
Code ready for release
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdoupe committed Feb 16, 2022
0 parents commit b10b80b
Show file tree
Hide file tree
Showing 140 changed files with 44,444 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.git
**/*.egg-info
**/*.pyc
**/__pycache__
test

**/cloned_repos

ooogame/database/frontend/build
ooogame/team_interface/frontend/build
**/node_modules
45 changes: 45 additions & 0 deletions .github/workflows/dockerimage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Test

on: [push]

jobs:

pytype:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Pip install ooogame itself
run: pip install -e .[mysql]

- name: Pip install other deps (koh, chalmanager)
run: pip install ipdb click boto3
- name: Install pytype
run: pip install pytype

- name: Check python files with pytype
run: find ./ooogame -name '*.py' | xargs pytype --keep-going


nose:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Pre-pulling docker images
run: |
docker pull registry
docker pull ubuntu:18.04
docker pull httpd:alpine
- name: Pip install ooogame itself
run: pip install -e .[mysql]

- name: Run nose tests
run: python3 -m "nose" -v --logging-clear-handlers
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/__pycache__/
**/*.pyc
**/*.egg-info
**/.idea
**/.DS_Store
3 changes: 3 additions & 0 deletions .ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.map
/ooogame/database/frontend/build/
yarn.lock
25 changes: 25 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
BSD 2-Clause License

Copyright (c) 2022, Order of the Overflow
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
174 changes: 174 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# dcf-game-infrastructure

[![](https://github.com/o-o-overflow/dcf-game-infrastructure/workflows/Test/badge.svg)](https://github.com/o-o-overflow/dcf-game-infrastructure/actions)

All the components necessary to run a game of the OOO DC CTF finals.

Authors: [adamd](https://adamdoupe.com), [hacopo](https://jacopo.cc), [Erik Trickel](https://trickel.com/), [Zardus](https://www.yancomm.net/), and [bboe](https://bryceboe.com/)

## Design Philosophy

This repo contains all the game components necessary to run an Attack-Defense CTF that OOO used from 2018--2021.

The design is based on adamd's experience building the [ictf-framework](https://github.com/shellphish/ictf-framework).

There are fundamental tenenats that we try to follow in the design of the system:

### Spoke component model

The communication design of the components in the system (which you can kind of think of as micro-services) is a "spoke" model, where every component talks to the database (through a RESTish API), and no component directly talks to any other.

In this way, each component can be updated separately and can also be _scaled_ independently using our k8s hosting.

This also made testing of each component easier, as the only dependence on a component is on the state of the database.

The only exception to this is the `patchbot` (the component that needs to test the patches submitted by the teams).

The database API puts the `patchbot` testing jobs into an [RQ (Redis Queue)](https://python-rq.org), which all the `patchbot` workers pull jobs from.

### Append-only database design

Fundamentally, a CTF database needs to calculate scores (that's essentially what the teams care about).

Prior design approaches that we've used would have a `points` or `score` column in the `team` table, and when they acquired or lost points, the app code would change this value.

However, many crazy things can happen during a CTF: recalculating scores or missed flags, even changing the scoring functions itself.

These can be difficult to handle depending on how the system is developed.

Therefore, we created a completely append-only database model, where no data in the DB is ever deleted or changed.

Even things like `service` status (the GOOD, OK, LOW, BAD that we used) is not a column in the `services` table.
Every change of status would created a new `StatusIndicator` row, and the `services` would pull the latest version from this table.

### Event model

Related to the append-only database design, everything in the database was represented by events.

The database would store all game events (in our game over the years was `SLA_SCRIPT`, `FLAG_STOLEN`, `SET_FLAG`, `KOH_SCORE_FETCH`, `KOH_RANKING`, `PCAP_CREATED`, `PCAP_RELEASED`, and `STEALTH`).

Then, the state of the game is based on these events.

An additional benefit is that these events could be shipped to the teams as part of the `game_state.json`.

### Separate k8s clusters

How we ran this is with _two_ [k8s](https://kubernetes.io) clusters: an admin cluster and a game cluster.

The `admin` cluster ran all of these components.

The `game` cluster ran all of the CTF challenges.

We used this design to do things like drop flags on the services.
The `flagbot` used `kubectl` to drop a flag onto a service running in the other cluster.

This also allowed us to lock down the `game` cluster so that the vulnerable services couldn't make external requests, could be scaled separately, etc.


## Install Requirements

This package is pip installable, and installs all dependencies. Do the following in a virtualenv:

~~~bash
$ pip install -e .
~~~

**NOTE:** If you want to connect to a mysql server (such as in prod or when deving against a mysql server), install the `mysqlclient` dependency like so:

~~~bash
$ pip install -e .[mysql]
~~~

## Testing

Make sure the tests pass before you commit, and add new test cases in [test](test) for new features.

Note the database API now checks that the timezone is in UTC, so you'll need to specify that to run the tests:

~~~bash
$ TZ=UTC nosetests -v
~~~

## Local Dev

If you're using tmux, I created a script [local_dev.sh](local_dev.sh)
that will run a database-api, database-api frontend, team-interface
backend, team-interface frontend, gamebot, and an ipython session with
a database client created.

Just run the following

~~~bash
$ ./local_dev.sh
~~~

## Deploy to prod

Build and `-p` push the image to production registry.

~~~bash
$ ./deploy.sh -p
~~~

Won't `-r` restart the running services, need to do:

~~~bash
$ ./deploy.sh -p -r
~~~


## database-api

This has the tables for the database, a REST API to access it, and a python client to access the REST API.

See [ooogame/database](ooogame/database) for details.

## flagbot

Responsible for putting new flags into all the services for every game tick.

See [ooogame/flagbot](ooogame/flagbot) for details.


## fresh-flagbot

Responsible for putting a new flags into a pod when it first comes up (from a team patching the service).

See [ooogame/fresh_flagbot](ooogame/fresh_flagbot) for details.

## gamebot

Responsible for incrementing the game's ticks.

See [ooogame/gamebot](ooogame/gamebot) for details.

## koh-scorebot

Responsible for extracting the King of the Hill (koh) scores from all
the koh pods every tick, and submitting them to the database.

See [ooogame/koh_scorebot](ooogame/koh_scorebot) for details.

## team-interface

Responsible for providing an interface to the teams so that they can
submit flags, get pcaps, upload patches, and get their patch status.
Split into a backend flask REST API, which essentially wraps the
database-api, and a React frontend.

See [ooogame/team_interface](ooogame/team_interface) for details.

## pcapbot

Responsible for picking up all the newly generated pcaps, anonymize
them, and if the service is releasing pcaps then release them.

See [ooogame/pcapbot](ooogame/pcapbot) for details.

## gamestatebot

Responsible for creating the game state at every new tick and storing them in the nfs, and release them publicly.

See [ooogame/gamestatebot](ooogame/gamestatebot) for details.

**This is also the component that pushes data to the public scoreboard**
88 changes: 88 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/bash -e

REGISTRY=registry.31337.ooo:5000

only_service=""
do_push=false
restart_pods=false

if [[ $# -ne 0 ]]; then

while [[ $# -gt 0 ]]
do
key="$1"

case $key in
-p|--push)
do_push=true
;;
-s|--service)
shift
only_service+=" ${1}"
;;
-r|--restart-pods)
restart_pods=true
;;
-h|--help)
printf $' This script builds frontends, builds dockers, and deploys\n'
printf $' -p, --push \tpushes to production\n -s, --service <service_name> \tproviding this arg limits built services'
printf $'to base and provided service names\n\t--service may be provided multiple times \n'
printf $' -r, --restart-pods \trestarts the k8s pods running this service \n'
printf $'example: ./deploy.sh --frontend --push --service team-interface \n\tBuilds frontend, pushes to production but only for team-interface'

exit 0
;;
*)
echo -e "${key} is unknown parameter"
;;
esac
shift # past argument or value
done

fi


echo "First build base"
docker build -f "dockerfiles/Dockerfile.game-infrastructure-base" -t "game-infrastructure-base" .
docker tag game-infrastructure-base:latest $REGISTRY/game-infrastructure-base:latest
if [[ ${do_push} = true ]]; then
docker push $REGISTRY/game-infrastructure-base:latest
fi

for DOCKERFILE in dockerfiles/Dockerfile.*
do
SERVICE_NAME="${DOCKERFILE##*.}"

if [ "$SERVICE_NAME" = "game-infrastructure-base" ]; then
continue
fi

if [[ -z "${only_service}" ]] || [[ ${only_service} =~ (^| )"${SERVICE_NAME}"($| ) ]]; then
echo "Building and deploying ${DOCKERFILE} for ${SERVICE_NAME}"
else
continue
fi


docker build -f "$DOCKERFILE" -t "$SERVICE_NAME" .
docker tag $SERVICE_NAME:latest $REGISTRY/$SERVICE_NAME:latest
if [[ ${do_push} = true ]]; then
docker push $REGISTRY/$SERVICE_NAME:latest
if [[ ${restart_pods} = true ]]; then
echo "Restarting ${SERVICE_NAME} pod"
pod_names=$(kubectl get pod -n default -o=custom-columns=NAME:.metadata.name | egrep ${SERVICE_NAME})
for pod_nm in ${pod_names}; do
echo kubectl delete pod -n default ${pod_nm}
kubectl delete pod -n default ${pod_nm}
done
fi
fi
done

sleep 2

if [[ ${restart_pods} = true ]]; then
kubectl get pod -n default -o=custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase
fi


33 changes: 33 additions & 0 deletions dockerfiles/Dockerfile.database-api
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Stage 0, build the app
FROM node:latest

# Copy all yarn dependencies over first so that they don't need to be continually installed
CMD mkdir -p /root/app/frontend
COPY ooogame/database/frontend/package.json /root/app/frontend/package.json
COPY ooogame/database/frontend/yarn.lock /root/app/frontend/yarn.lock

RUN yarn --cwd /root/app/frontend install --production

# Build the interface
COPY ooogame/database/frontend /root/app/frontend
RUN yarn --cwd /root/app/frontend run build

FROM game-infrastructure-base:latest

RUN rm -f /etc/nginx/conf.d/default.conf

COPY ooogame/database/deployment/nginx.conf /etc/nginx/
COPY ooogame/database/deployment/flask-site-nginx.conf /etc/nginx/conf.d/
COPY ooogame/database/deployment/uwsgi.ini /etc/uwsgi/
COPY ooogame/database/deployment/supervisord.conf /etc/supervisord.conf

WORKDIR /opt/ooogame

COPY --from=0 /root/app/frontend/build /frontend/
COPY ooogame/database/deployment/config.py /opt/ooogame/ooogame/database/config.py
RUN . /opt/ooogame/venv/bin/activate && pip3 install /opt/ooogame rq-dashboard 'click<8'
# XXX: click<8 due to https://github.com/Parallels/rq-dashboard/pull/383

RUN . /opt/ooogame/venv/bin/activate && pip check

CMD ["/usr/bin/supervisord"]
3 changes: 3 additions & 0 deletions dockerfiles/Dockerfile.flagbot
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM game-infrastructure-base:latest

CMD ["/opt/ooogame/venv/bin/python3", "-u", "-m", "ooogame.flagbot.flagbot"]
Loading

0 comments on commit b10b80b

Please sign in to comment.