Getting Started

Installing the Scoreboard

As a Python WSGI application, it can be run in any environment that supports the WSGI protocol.

Warning

Irrespectable of which way you choose, you must make sure that the complete /admin path is protected by external measures (e.g. HTTP Basic Auth). The backend has no separate protection.

Warning

If not using Nginx, you have to make sure to manually set the Host header to a static value and not trust the client as Pyramid relies on it to generate URLs!

Quickstart

First of all: The scoreboard uses PostgreSQL (and has only been tested with it). It does explicitly not support MySQL as MySQL is crappy software. However, it does not rely on obscure database features so you may get it running with a different server (though not MySQL. Never MySQL.). So go ahead and install PostgreSQL. You can then prepare the database:

-- Choose a secure password here!
CREATE USER fluxscoreboard WITH PASSWORD 'mypass';
CREATE DATABASE scoreboard WITH OWNER fluxscoreboard;

Now let’s install! Adjust the paths to your liking. Also pay attention to the username and group for the web server, it depends on the server you are using and the distribution, it may for example be “www-data” or “http” or “www”. You should also use virtualenv instead of your global python installation to run the scoreboard under.

mkdir -p /var/www/ctf
cd /var/www/ctf
chown http:http .
virtualenv .
. bin/activate
git clone git@github.com:Javex/fluxscoreboard.git
cd fluxscoreboard/
./run install production

Now create a valid configuration by opening cfg/production.ini and filling in all relevant values (everything is documented there). The run tool has already performed some of the heavy lifting.

Webserver Nginx + gunicorn

This is an example configuration file that can be used with Nginx. For this server you additionally need a reverse proxy that handles the WSGI protocol.

upstream fluxscoreboard {
    server 127.0.0.1:6875;
}

server {
    listen 80;
    server_name mydomain.com;
    rewrite ^ https://$server_name$request_uri? permanent;  # enforce https
}

server {
    listen 443 ssl;
    server_name mydomain.com;

    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private_key.key;

    access_log /var/log/nginx/fluxscoreboard_access.log;
    error_log /var/log/nginx/fluxscoreboard_error.log;

    # This is not needed if the page itself should be public
    #auth_basic "Restricted";
    #auth_basic_user_file /var/www/ctf/fluxscoreboard/.htpasswd;

    # Security headers
    add_header X-Frame-Options DENY;
    add_header Content-Security-Policy "default-src 'none'; connect-src 'self'; font-src 'self'; img-src 'self' www.google.com; script-src 'self' www.google.com 'sha256-dtX3Yk6nskFEtsDm1THZkJ4mIIohKJf5grz4nY6HxI8='; style-src 'self';";
    add_header X-XSS-Protection 0;
    add_header Strict-Transport-Security max-age=31536000;


    location / {
        # This file must be available under a sensible name and is reference
        # relative to /etc/nginx (or wherever nginx.conf lies)
        include nginx_proxy.conf;
    }

    location /admin {
        # This file must be available under a sensible name and is reference
        # relative to /etc/nginx (or wherever nginx.conf lies)
        include nginx_proxy.conf;

        # This MUST be active to protect the admin backend. It may NOT be
        # deactivated as it exposes a lot of features including adding a lot of
        # data and sending emails.
        auth_basic "Restricted";
        auth_basic_user_file /var/www/ctf/fluxscoreboard/.htpasswd_admin;
    }

    location /static {
        # A path to the root, i.e. it will have /static appended (+ the
        # searched file). This circumvents the application server as these are
        # static anyway.
        root                    /var/www/ctf/fluxscoreboard/fluxscoreboard;
        expires                 30d;
        add_header              Cache-Control public;
        access_log              off;
    }
}

This defines the base application. It is configured for SSL only access and automatically redirects any HTTP requests. There is not much to change here except that you might want a different path for your application. However, there is a second file that contains the actual options:

    # Don't trust the client on this one!
    proxy_set_header        Host $server_name;
    proxy_set_header        X-Real-IP $remote_addr;
    # This is set to assure the Client-Addr is trustworthy
    proxy_set_header        X-Forwarded-For $remote_addr;
    proxy_set_header        X-Forwarded-Proto $scheme;

    client_max_body_size    10m;
    client_body_buffer_size 128k;
    proxy_connect_timeout   60s;
    proxy_send_timeout      90s;
    proxy_read_timeout      90s;
    proxy_buffering         off;
    proxy_temp_file_write_size 64k;
    proxy_redirect          off;
    proxy_pass              http://fluxscoreboard;

This file has to be saved somewhere as-is and the path in the main configuration has to be adjusted in such a way that it points to it relative to the Nginx main configuration directory (see comments in file). Restart Nginx.

Note

The sample configuration sets the ‘Host’ header statically to whatever server_name setting you chose. Do not trust the $host header of the client: It may be spoofed but Pyramid relies on it so we have to make sure it is a trusted value!

After Nginx is configured this way you don’t have to do much for gunicorn: It already has a valid configuration in the default configuration file (see below).

Todo

Configure gunicorn with proper logging.

Protecting the Admin Backend

Finally, you should protect your backend with HTTP Auth:

htpasswd -c -b /var/www/ctf/fluxscoreboard/.htpasswd_admin fluxadmin secretpassword

This will protect your backend from unauthorized access.

Cron!

Since calculating points is a heavy task, you should not do it on every request from a user. Instead, we provide a convenience function that regularly updates the points of teams and challenges:

./run update_points

Make sure to put that in a cron. Run it e.g. every five minutes.

Test it!

This should be it. You should start the server as a test:

gunicorn_paster production.ini

Try out the server by visiting the domain configured for Nginx. Fix any errors that appear, then enable the daemon = True option for gunicorn in production.ini. You are now good to go. A simple setup is to just run it and close the shell. However, with this way you have no service, no monitoring and no restarting.

Todo

Add some more details here.