Skip to content

About

Score Engine, Bank, and CTF

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Competition System

An integrated rebuild of the ScoreTrak engine, simulated banking, and competition store. The workspace began as a README-only repository; the application here is a clean implementation based on the workflows and public repository references, not a copy of their source files.

Current platform shape

  • Flask/Python application: one web app, rendered with server-side templates, separated into Engine, Bank, Store, and Admin areas.
  • Shared team login: one organizer-created account per team, shared across the sites. Administrators have full access; organizers manage competitions and the store; bank operators manage the bank and store; team accounts access their own bank and store activity.
  • PostgreSQL: persistent application and transaction data. New installs start empty.
  • PostgreSQL-backed scoring queue: competition schedules and queued rounds are stored in the application database; Redis and a separate message broker are not used.
  • Containerized scoring workers: workers poll PostgreSQL for due competitions and queued rounds. Database row locks prevent multiple workers from claiming the same round, and worker containers can be scaled with Docker Compose. Each worker processes one round at a time and runs up to SCORING_THREADS service probes concurrently. Supported checks include HTTP/HTTPS, DNS, ICMP PING, SSH and WinRM authentication, LDAPS search, MySQL health, and read-only FTPS. Credentials are encrypted at rest with a dedicated CHECK_CREDENTIAL_KEY.
  • Caddy: optional public reverse proxy and automatic TLS for engine, bank, store, and admin subdomains.
  • Docker Compose: the web app, scoring workers, PostgreSQL, and optional proxy run as separate containers.

Scoring workers are managed as Docker Compose services, not dynamically created per service or scoring round. The web and scoring containers do not have Docker socket access. Team challenge-container provisioning is not configured: the image names, network isolation, resource limits, and lifecycle rules are still unspecified. Once those rules are defined, challenge containers should be managed by a separately constrained Engine capability rather than granting the web container unrestricted Docker daemon access. Arbitrary custom check plugins are also not implemented yet.

Safe preview on a clean Ubuntu VM

The setup script targets Ubuntu Server 24.04 and checks for Docker, Docker Compose v2, and OpenSSL. It installs only missing host prerequisites, starts and verifies the Docker daemon, secures .env with mode 600, validates the credential-encryption key and Compose configuration, and checks web, database, and scoring-worker readiness after startup. It does not install Python or application packages on the VM; those are built into the application container.

  1. Clone this repository to the VM.

  2. Run:

    sudo bash scripts/setup.sh
  3. Enter the base domain when prompted. The script generates .env secrets, starts a private preview on 127.0.0.1:8080, and prompts for the initial administrator account.

  4. To view it from another machine, create an SSH tunnel:

    ssh -L 8080:127.0.0.1:8080 <your-vm>

    Then open http://127.0.0.1:8080.

The default preview deliberately avoids public ports 80/443 so it can be evaluated on the same VM without displacing the current system. .env contains database, signing, and check-credential encryption secrets; keep it private and back it up securely. The installer refuses to start if the credential-encryption key is not exactly 64 hexadecimal characters or if Docker Compose cannot resolve its required settings.

For a reverse proxy running on another host, bind the preview to the VM's IPv4 address that the proxy can reach. Run the same setup command:

sudo bash scripts/setup.sh

When prompted for the web app bind IPv4 address, enter the VM interface address reachable from the proxy; press Enter to keep the current value. The installer saves the answer in .env, recreates the web container, and prints the matching proxy upstream URL. The default remains 127.0.0.1; no network address is hard-coded, so use the appropriate interface address for each deployment. The proxy must have a route to the VM, and this plain-HTTP application port must be restricted to the proxy by a firewall or network security rule. Docker-published ports may bypass UFW rules; verify that your firewall filters Docker traffic.

For a full clean reinstall on a VM, first back up anything you need, then run:

sudo env CLEAN_DOCKER_INSTALL=true bash scripts/setup.sh

The script requires you to type WIPE ALL DOCKER DATA before proceeding. This removes every Docker container, image, network, volume, Docker package, and the rootful Docker data/configuration directories from that VM, including data belonging to other Docker applications. It then installs missing Docker prerequisites and continues with a fresh CompetitionSystem setup. The checkout and its .env file are not deleted, so the existing domain and application secrets are reused; back up or manually replace .env if you want new settings/secrets. Do not use this on a shared Docker host or if you need to preserve any Docker data. This reset targets standard rootful Docker installations; it does not migrate or preserve existing application data.

To migrate an existing installation, securely copy its .env and database backup to the VM first. Run sudo env DATABASE_IMPORT_ONLY=true bash scripts/setup.sh to install prerequisites, stop the web app and scoring worker, and start PostgreSQL for the import. Restore the database, then run sudo bash scripts/setup.sh; the existing administrator account will be detected and preserved.

Import the local competition database to a VM

On Windows, run scripts/import_competition.ps1 from a configured local checkout. The script starts the local Compose database service if needed, verifies that the source has competition and team data, then exports the complete PostgreSQL database, including every competition, account, host/check, encrypted credential, scoring round/result, bank record, and store/order record. It securely transfers the export and the source SECRET_KEY and CHECK_CREDENTIAL_KEY over SSH; target database and domain settings in the VM's .env are preserved.

The target VM must already have this project installed, a configured .env, a running SSH service, and a user with sudo access. The script uses strict SSH host-key checking, so verify a reinstalled VM's host-key fingerprint and update your local known_hosts entry before connecting. It saves a private backup of the existing VM database and .env, then requires you to type REPLACE <user>@<host> before changing anything. It replaces the VM database, marks interrupted running rounds as failed, rebuilds the web and scoring-worker services, and checks application readiness. Database and environment backups remain under ~/competition-backups/imports; do not commit or share them.

Example:

.\scripts\import_competition.ps1 -TargetHost 192.168.8.200 -TargetUser compadmin -SshKeyPath "$env:USERPROFILE\.ssh\competition-vm"

Public deployment and cutover

Before public access, make sure:

  • engine.<base-domain>, bank.<base-domain>, store.<base-domain>, and admin.<base-domain> resolve to this VM.
  • Inbound TCP ports 80 and 443 are available to Caddy.
  • The current site has been cut over or stopped, so this stack can bind those ports.

Update .env:

COOKIE_DOMAIN=.your-base-domain
SESSION_COOKIE_SECURE=true

Confirm all four *_DOMAIN values use the intended domain, then run:

sudo docker compose --profile public up -d --build --remove-orphans

Caddy obtains and renews TLS certificates for the configured site hostnames. The root domain itself is not served by this stack. When upgrading an existing deployment, --remove-orphans stops and removes containers for services removed from Compose, including the former Redis and Celery containers; it leaves their data volumes untouched. To preview changes later, rebuild with sudo docker compose up -d --build --remove-orphans; to stop the stack, use sudo docker compose down (persistent named volumes remain).

First use

  1. Sign in at https://admin.<base-domain>.
  2. Create team accounts and staff accounts. Team accounts are provisioned by an administrator or organizer; enter the team's display name when creating its login. The account directory can be sorted by username, role, team, or status. There is no public registration or email dependency.
  3. Create a scoreboard through the guided builder: enter its details, create device groups and select their supported check types, configure each group's IPv4 {index} pattern and ending octet, enroll teams to generate one host per group, configure a score-check template for every selected check type in each group, then review the setup. The score-check step shows per-group readiness; missing, disabled, paused, or unexpected checks need attention before review.
  4. Activate the competition. Scoring workers schedule rounds at the configured interval, claim queued rounds from PostgreSQL, execute checks concurrently, and deposit whole-dollar payouts. If workers are stopped, scoring pauses; after restart, workers resume from the competition's due schedule. A round already claimed by a worker that exits unexpectedly is not automatically reclaimed.
  5. Add and publish store items. Team purchases use simulated US dollars, produce ledger transactions, and remain pending until an administrator, bank operator, or organizer completes or refunds the order.

All bank and store amounts are whole US dollars; cents are not accepted. Bank operators and administrators can add or remove funds for one team or all teams, set balances, or reset balances to zero. They can also configure global whole-dollar uptime rewards for each check type; each passing team/service result earns its reward once per scoring round, in addition to the competition's dollars-per-point payout. Failed checks earn no uptime reward. Set/reset actions are recorded as balancing transactions so the ledger history remains intact. Bank accounts, transactions, store items, and orders include search/filter controls; transaction details show staff attribution and related store or scoring information where available. Bank operators can manage bank and store workflows, organizers can manage competitions and the store, and administrators can access all areas.

Administrators and organizers can rename accounts from Accounts and teams. Removing a team account permanently deletes its linked team, host assignments, check settings, scoring results, bank transactions, and store orders; staff audit references to a removed staff account are cleared. Scoreboards can be archived from the scoreboard list. Archiving pauses an active scoreboard, hides it from other menus, and retains its configuration and history; archived boards can only be viewed and restored under Management Controls → Archived boards. Restoring an active board resumes scoring.

The first administrator can also be created later with:

sudo docker compose exec web flask --app app:create_app bootstrap-admin

Password resets are performed by an administrator or organizer in the Admin area. No reset email is sent.

Operations

sudo docker compose ps
sudo docker compose logs -f web scoring-worker
sudo docker compose logs -f proxy

SCORING_THREADS controls simultaneous service probes per scoring worker (1–64; default 64). SCORING_TICK_SECONDS controls how often a worker polls for due rounds (5–300 seconds; default 5). Workers process at most one round per competition at a time; missed intervals do not build a backlog, and rounds abandoned by a stopped worker are marked failed after progress heartbeats expire. Workers can process different competitions in parallel; scale the number of worker containers to increase that parallelism:

sudo docker compose up -d --scale scoring-worker=3 scoring-worker

Set the scale to the desired total number of worker containers; Compose starts or removes containers to match. Keep the replica count and SCORING_THREADS within the VM's resource limits. Competition intervals are constrained to 15–3,600 seconds. The former Redis/Celery settings (REDIS_URL and CELERY_CONCURRENCY) are no longer used and can be removed from an existing .env.

Service check catalog

The supplied legacy exports store check settings as service properties. This implementation keeps each supported service's typed settings in its own check_config object and records a check_type; it does not import the exports or their credentials.

Legacy check Properties seen in the exports Current status
HTTP/HTTPS Scheme, Subdomain, Path, Port, ExpectedOutput Supported; checks status and optional response text
DNS Lookup, ExpectedOutput Supported; A, AAAA, CNAME, MX, and TXT answers with a bounded resolver timeout
PING Protocol, Attempts Supported; 1–4 ICMP echo requests, with a bounded process timeout
SSH Port, Username, Password, optional host-key verification Authentication-only; host-key pinning is recommended, but can be explicitly skipped with a man-in-the-middle credential risk warning. Never opens a shell
WinRM Scheme, ClientType, Port, Username, Password, Command Supported for HTTPS authentication only; opens and closes a temporary shell without running Command
LDAP ApplicationProtocol, TransportProtocol, Domain, BaseDN, Filter, Attributes, Port, Username, Password Supported as bounded, read-only LDAPS search with certificate validation
SQL DBType, DBName, Port, Username, Password, Command, MinExpectedRows, MaxExpectedRows Supported for MySQL TLS authentication using fixed SELECT 1; user SQL is never executed
FTP Port, Username, Password, ReadFilename, WriteFilename, Text, ExpectedOutput Supported as read-only FTPS over verified TLS; remote writes are not supported

Service scoring metadata maps weight to base points and pointsBoost to a flat bonus added when a check passes. A paused service is skipped and earns no points; hiding a service does not remove its points from team totals. The public scoreboard keeps the standard Competition System page layout and displays a compact grid with solid pass/fail cells. It updates in place using ETag-conditional polling, without a full-page refresh. Its active-competition selector is hidden by default and can be enabled from Admin → Management controls. roundUnits and roundDelay are not implemented because their scheduling behavior still needs to be specified. The files include password and password-hash fields. Do not commit or import them, and rotate their credentials if they are still valid.

Usernames and passwords for SSH, WinRM, LDAPS, MySQL, and FTPS are encrypted before being saved in the service configuration. The setup script generates a dedicated CHECK_CREDENTIAL_KEY; back up and protect it separately from SECRET_KEY. Do not change or lose it without re-entering affected check credentials. Existing credentials encrypted by an earlier release can still be decrypted with the unchanged SECRET_KEY; re-save those checks after upgrading to encrypt them with the dedicated key. SSH checks pin the configured SHA256 host-key fingerprint by default; administrators can explicitly skip verification, which makes the check vulnerable to man-in-the-middle servers capturing credentials. WinRM requires HTTPS and certificate validation; supported authentication transports are NTLM and Basic (over HTTPS). TLS checks validate certificates; configure trusted certificates on the Docker image/host as appropriate. Arbitrary WinRM/SSH commands, user-supplied SQL statements, and FTP writes are deliberately not run.

The current Engine UI separates service metadata from check properties. This platform maps Weight, Points Boost, and pause/hide flags. Existing service-group metadata is retained for compatibility; it is organizational only and does not deploy workers. The scoreboard builder creates device groups before IP templates; each group selects supported check types, then receives its own IPv4 {index} pattern, ending octet, and team index range. The IP-template step uses a two-column layout and saves all group templates together; navigating to another builder step with unsaved edits prompts to save them. Every device group gets one generated host address per enrolled team. The score-check step opens a configuration form for every selected group/check-type combination that does not yet have an enabled template; the same template runs for every enrolled team's matching host. Team indexes are assigned in ascending team-ID order, and generated addresses can be edited per team. Admins can optionally allow a team to edit only its own host address, restricted to configured CIDR ranges or exact hostnames. The team-check page shows the latest result and failure details with non-secret check information; management controls can enable high-level recommended hints for failures. A team can update only its own service-check password, not its UBNETDEF account password. Username edits remain organizer-controlled unless explicitly unlocked. Credential overrides are encrypted at rest and used only when scoring that team's host. Routes that accept resource IDs enforce role and owning-team authorization server-side; changing an ID in a URL does not grant access to another team's data. Organizers and admins can select any active competition team, while banker accounts are denied access. Changing a group's template regenerates addresses for its hosts. DNS check forms can fill the expected answer from the current selected DNS record; if left blank on save, the form resolves the answer server-side with a bounded timeout. Legacy SMB, IMAP, and CalDav records are retained for compatibility but are not offered as check types in the UI. Per-service Round Units/Round Delay still need explicit scheduling requirements. By default SQL uses fixed SELECT 1 and WinRM/SSH check authentication only.

Opt-in legacy compatibility modes

Each SSH, WinRM, LDAP, SQL, FTP, and DNS check form has a collapsed Legacy compatibility box (legacy_mode) that reproduces the old Engine behavior so a scoreboard can match it exactly: SSH/WinRM run a configured command (for example whoami) and require the expected text in its output; WinRM can use plain http; LDAP can use plain LDAP with user@domain; MySQL can run one read-only SELECT (no ;) and check a min/max row count without TLS; FTP can use plain FTP and write a file before reading it; DNS can query the target host directly and default the expected answer to {address} (the host's own IP). These modes send credentials without TLS and execute administrator-configured commands, so enable them only on isolated competition networks.

Mirroring the live Engine scoreboard

A mirror of the live Engine scoreboard ("UBNetDef Engine Mirror": 15 teams, 7 device groups, 105 hosts as 10.{team}.1.{octet}, 14 check templates) is active. Scoring requires network access to those hosts. Team accounts and credentials were not copied from the live Engine, and where live per-team passwords differed (Dev Server SQL, WebApp FTP), the majority password was used.

Existing mirrors created with the earlier 10.1.{team}.{octet} layout can be corrected by running sudo bash scripts/update_mirror_ip_layout.sh from the project directory. The script backs up PostgreSQL under /var/backups/competition-ip-layout, pauses the scoring worker, verifies the active mirror's expected host and template counts, updates its generated addresses, and restarts the worker.

To edit a configured check, use Edit in the competition's service-check table. Its type is fixed, while supported settings, points, timeout, boost, pause/hide status, and group assignment can be updated. Credential fields stay blank; leave both blank to retain the encrypted credentials, or enter both to replace them. Legacy TCP checks can be edited but cannot be newly created.

Development

The app's dependencies are in requirements.txt; run it locally with a virtual environment and a local PostgreSQL service, or use Compose. Compose requires a .env file containing the database password and app secrets. For a local setup, copy .env.example to .env, replace the placeholder values for SECRET_KEY, CHECK_CREDENTIAL_KEY, and POSTGRES_PASSWORD with unique secret values, and adjust the domains if needed. The setup script generates these values automatically on a clean Ubuntu VM. The Compose preview binds only to localhost:

docker compose up --build

The first run applies database migrations. Create an administrator with docker compose exec web flask --app app:create_app bootstrap-admin. No test data or default users are seeded. Redis is not required.

On an installed Git checkout, apply updates with sudo bash scripts/update.sh. The updater requires a clean working tree, an untracked .env, and a fast-forwardable branch; it preserves .env and all Docker volumes, backs up PostgreSQL before app migrations, and checks web and worker readiness. Backups are retained under ~/competition-backups/updates for the checkout owner. It does not run docker compose down, remove volumes, or remove orphaned containers.

Run the platform tests in the application image (the repository is mounted only to provide the test files):

docker compose build web
docker compose run --rm --no-deps -v "$PWD:/workspace:ro" --entrypoint python web \
  -m unittest discover -s /workspace/tests -p "test_platform.py"

The setup-script integration test runs in a disposable Ubuntu 24.04 container. It mocks package installation and Docker commands, and verifies fresh-install setup, missing Docker prerequisites, database-import mode, generated secret formats/permissions, invalid-key rejection, and the guarded full-reset/cancellation paths without changing the host:

docker run --rm -v "$PWD:/workspace:ro" ubuntu:24.04 \
  bash /workspace/tests/test_setup.sh

The update-script integration test also runs in a disposable Ubuntu 24.04 container. It mocks Git and Docker, verifies database backup and service-readiness checks, ensures .env remains unchanged and destructive volume commands are not used, and covers dirty-checkout and active-proxy handling:

docker run --rm -v "$PWD:/workspace:ro" ubuntu:24.04 \
  bash /workspace/tests/test_update.sh

About

Score Engine, Bank, and CTF

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages