Skip to content

Commit

Permalink
Major rewrite of everything related to Checkers
Browse files Browse the repository at this point in the history
* New API and library for Checker Scripts
* Build foundation for Checker Scripts in other languages (IPC
  protocol)
* Checker Scripts can be executed locally (during development) without
  a dedicated Runner
* No more Checker Slaves
* Launch processes using Python multiprocessing instead of asyncio
* Scheduling of checks within a tick is different, should lead to more
  even distribution
* Run Checker Scripts as different users than the Checker Master
* Lots of test cases

Closes: #1
Closes: #20
Closes: #33
Closes: #38
Improves: #17
Improves: #27
Improves: #29
Improves: #32
Improves: #35
  • Loading branch information
F30 committed Jun 3, 2020
1 parent 7f5701b commit babf7a1
Show file tree
Hide file tree
Showing 42 changed files with 2,618 additions and 979 deletions.
4 changes: 3 additions & 1 deletion conf/checker/checkermaster.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ CTF_DBNAME="DUMMY"
CTF_DBUSER="DUMMY"
CTF_STATEDBNAME="DUMMY"
CTF_STATEDBUSER="DUMMY"
CTF_SECRET="DUMMY"

CTF_IPPATTERN="0.0.%s.2"
CTF_FLAGSECRET="RFVNTVlTRUNSRVQ="
30 changes: 19 additions & 11 deletions conf/checker/[email protected]
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
[Unit]
Description=CTF Checker Runner
After=network.target postgresql.service
Description=CTF Gameserver Checker Master
After=postgresql.service

[Service]
Type=notify
ExecStart=/usr/bin/ctf-checkermaster
ExecStop=/bin/kill -s usr1 $MAINPID
WatchdogSec=20
Restart=on-failure
TimeoutStopSec=65
User=ctf-checkermaster
EnvironmentFile=/etc/ctf-gameserver/checkermaster.env
EnvironmentFile=-/etc/ctf-gameserver/checker/%i.env
Environment=PYTHONPATH=/etc/ctf-gameserver/checker/modules/
User=nobody
Group=nogroup
SyslogIdentifier=ctf-checkermaster@%i
ExecStart=/usr/bin/ctf-checkermaster
# Allow waiting for Checker Scripts to finish
TimeoutStopSec=90
Restart=on-failure
RestartSec=5

# Security options, less strict to accommodate for Checker Script oddities
PrivateDevices=yes
PrivateTmp=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=strict
RestrictNamespaces=yes
RestrictRealtime=yes

[Install]
WantedBy=multi-user.target
1 change: 1 addition & 0 deletions debian/install
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
conf/checker/checkermaster.env etc/ctf-gameserver
conf/checker/[email protected] lib/systemd/system
examples/checker/sudoers.d/ctf-checker etc/sudoers.d

conf/controller/controller.env etc/ctf-gameserver
conf/controller/flagid.env etc/ctf-gameserver
Expand Down
7 changes: 7 additions & 0 deletions debian/postinst
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#!/bin/sh

if ! getent passwd ctf-checkermaster >/dev/null; then
adduser --system --group --home /var/lib/ctf-checkermaster --gecos 'CTF Gameserver Checker Master user,,,' ctf-checkermaster
fi
if ! getent passwd ctf-checkerrunner >/dev/null; then
adduser --system --group --home /var/lib/ctf-checkerrunner --gecos 'CTF Gameserver Checker Script user,,,' ctf-checkerrunner
fi

# No dh-systemd because we don't want to enable/start any services
if test -x /bin/systemctl; then
systemctl daemon-reload
Expand Down
2 changes: 1 addition & 1 deletion doc/source/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ should contain exactly one table:
team_id INTEGER,
service_id INTEGER,
identifier CHARACTER VARYING (128),
data BYTEA
data TEXT
) PRIMARY KEY (team_id, service_id, identifier);
Checker
Expand Down
5 changes: 5 additions & 0 deletions examples/checker/example_checker.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CTF_SERVICE="example_slug"
CTF_CHECKERSCRIPT="/path/to/example_checker.py"
CTF_MAXCHECKDURATION="90"
CTF_CHECKERCOUNT="1"
CTF_INTERVAL="10"
88 changes: 88 additions & 0 deletions examples/checker/example_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3

import logging
import socket

from ctf_gameserver import checkerlib


class ExampleChecker(checkerlib.BaseChecker):

def place_flag(self, tick):
conn = connect(self.ip)
flag = checkerlib.get_flag(tick)
conn.sendall('SET {} {}\n'.format(tick, flag).encode('utf-8'))
logging.info('Sent SET command')

try:
resp = recv_line(conn)
logging.info('Received response to SET command')
except UnicodeDecodeError:
logging.warning('Received non-UTF-8 data')
return checkerlib.CheckResult.FAULTY
if resp != 'OK':
logging.warning('Received wrong response to SET command')
return checkerlib.CheckResult.FAULTY

conn.close()
return checkerlib.CheckResult.OK

def check_service(self):
conn = connect(self.ip)
conn.sendall(b'XXX\n')
logging.info('Sent dummy command')

try:
recv_line(conn)
logging.info('Received response to dummy command')
except UnicodeDecodeError:
logging.warning('Received non-UTF-8 data')
return checkerlib.CheckResult.FAULTY

conn.close()
return checkerlib.CheckResult.OK

def check_flag(self, tick):
flag = checkerlib.get_flag(tick)

conn = connect(self.ip)
conn.sendall('GET {}\n'.format(tick).encode('utf-8'))
logging.info('Sent GET command')

try:
resp = recv_line(conn)
logging.info('Received response to GET command')
except UnicodeDecodeError:
logging.warning('Received non-UTF-8 data')
return checkerlib.CheckResult.FAULTY
if resp != flag:
logging.warning('Received wrong response to GET command')
return checkerlib.CheckResult.FLAG_NOT_FOUND

conn.close()
return checkerlib.CheckResult.OK


def connect(ip):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, 9999))
return sock


def recv_line(conn):

received = b''
while not received.endswith(b'\n'):
new = conn.recv(1024)
if len(new) == 0:
if not received.endswith(b'\n'):
raise EOFError('Unexpected EOF')
break
received += new
return received.decode('utf-8').rstrip()


if __name__ == '__main__':

checkerlib.run_check(ExampleChecker)
55 changes: 55 additions & 0 deletions examples/checker/example_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env python3

import socketserver


_STORE = {}


class RequestHandler(socketserver.BaseRequestHandler):

def handle(self):
client_data = self._recv_line()
print('Received:', client_data)

try:
operation, client_data = client_data.split(' ', 1)
except ValueError:
response = 'INVALID'
else:
if operation == 'GET':
key = client_data
try:
response = _STORE[key]
except KeyError:
response = 'NODATA'
elif operation == 'SET':
try:
key, value = client_data.split(' ', 1)
except ValueError:
response = 'INVALID'
else:
_STORE[key] = value
response = 'OK'
else:
response = 'INVALID'

print('Response:', response)
self.request.sendall(response.encode('utf-8') + b'\n')

def _recv_line(self):
received = b''
while not received.endswith(b'\n'):
new = self.request.recv(1024)
if len(new) == 0:
if not received.endswith(b'\n'):
raise EOFError('Unexpected EOF')
break
received += new
return received.decode('utf-8').rstrip()


if __name__ == "__main__":

with socketserver.TCPServer(('localhost', 9999), RequestHandler) as server:
server.serve_forever()
12 changes: 0 additions & 12 deletions examples/checker/examplechecker.conf

This file was deleted.

11 changes: 0 additions & 11 deletions examples/checker/examples/dummy.py

This file was deleted.

25 changes: 0 additions & 25 deletions examples/checker/faust2048.conf

This file was deleted.

68 changes: 0 additions & 68 deletions examples/checker/faust2048.py

This file was deleted.

6 changes: 6 additions & 0 deletions examples/checker/sudoers.d/ctf-checker
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Allow user "ctf-checkermaster" to use the `--close-from` argument when running sudo
Defaults:ctf-checkermaster closefrom_override

# Allow user "ctf-checkermaster" to run any command as user "ctf-checkerrunner" without being prompted for a
# password
ctf-checkermaster ALL = (ctf-checkerrunner) NOPASSWD: ALL
Loading

0 comments on commit babf7a1

Please sign in to comment.