Skip to content

jonscafe/whaley

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐳 Whaley

Dedicated Docker Instancer for CTF Competitions

Features β€’ Quick Start β€’ Documentation β€’ Screenshots β€’ Contributing


πŸ“‹ Overview

Whaley is a production-ready Docker instance manager designed specifically for Capture The Flag (CTF) competitions. Deploy it on a dedicated server to provide each participant with their own isolated challenge environmentβ€”complete with automatic port allocation, resource limits, and seamless CTFd integration.

Why Whaley?

Problem Whaley's Solution
Shared challenge instances cause interference πŸ”’ Isolated containers per user/team
Manual port management is error-prone 🎯 Automatic port allocation with collision prevention
No visibility into player resource usage πŸ“Š Real-time monitoring dashboard
Difficult to detect flag sharing πŸ” Suspicious submission detection
Need packet-level evidence during incidents πŸ“‘ Native packet capture with flow search and raw PCAP download
Complex setup for dynamic flags πŸ’‰ Simple CTFd integration with auto flag injection
Need external observability πŸ“ˆ Prometheus /metrics endpoint protected by a secret

✨ Features

πŸš€ Core Functionality

  • Dynamic Instance Spawning β€” Isolated Docker containers per user/team
  • Automatic Port Allocation β€” Smart port management (10000-60000 range)
  • Multi-Port Challenges β€” Support for complex multi-service challenges
  • Auto-Cleanup β€” Instances automatically terminated after timeout
  • Startup Orphan Cleanup β€” Removes stale compose projects, networks, volumes, and per-spawn images
  • Docker Compose Support β€” Standard .yaml and .yml formats
  • Enforced Resource Limits β€” Global memory, CPU & PID caps enforced on all containers
  • Per-Challenge Resource Overrides β€” Fine-tune limits per challenge from admin panel

πŸ” Authentication & Security

  • CTFd Integration β€” Validate users and admin RBAC via CTFd access tokens
  • No-Auth Mode β€” IP-based identification for open events
  • Network Isolation β€” Each instance in its own Docker network
  • Host Firewall Rate Limits β€” Per-instance connlimit/hashlimit rules on published challenge ports
  • Distributed Locking β€” Redis-based locks for multi-worker safety
  • Fork Bomb Protection β€” PID limits per container

🚩 Dynamic Flags

  • Unique Flags β€” Each user/team gets unique flag per challenge
  • Auto Flag Injection β€” Inject flags into challenge files at build time
  • CTFd Registration β€” Automatically register flags with CTFd
  • Submission Monitoring β€” Detect and log flag sharing attempts
  • Team Mode Support β€” Shared flags per team when enabled
  • Database Persistence β€” Flag mappings and suspicious submissions survive restarts with duplicate protection

πŸ“Š Monitoring & Admin

  • Host-First Monitoring β€” Fast host load/memory/disk snapshot that stays responsive at high instance counts
  • On-Demand Page Sampling β€” Sample Docker CPU/RAM only for the visible monitoring page when you need detail
  • Prometheus Metrics β€” Protected /metrics endpoint for external scraping
  • Instance Forensics β€” Capture logs on termination or on-demand
  • Native Packet Capture β€” Per-instance tcpdump sidecar, searchable flows, payload viewer, lazy PCAP indexing, and raw PCAP download
  • Admin Dashboard β€” Web UI for monitoring and management, including manual spawn/destroy, firewall status, and paginated high-volume views
  • Per-Instance Logs & Metrics β€” Inspect Docker logs and CPU/RAM/network/block I/O from the dashboard
  • Challenge Manager β€” Upload & edit challenges without SSH
  • Challenge Toggle β€” Activate/deactivate challenges from admin panel
  • Admin Settings UI β€” Configure all Whaley settings via the web UI (no env/compose edits needed)
  • Event Logging β€” Comprehensive audit trail with Docker errors

πŸš€ Quick Start

Prerequisites

  • Docker Engine 24.0+ with Docker Compose v2
  • 4+ CPU cores, 8GB+ RAM (see capacity planning)
  • Linux server (Ubuntu 22.04+ or Debian 12+ recommended)

Installation

# Clone the repository
git clone https://github.com/jonscafe/whaley.git
cd whaley

# Configure environment
cp .env.example .env
nano .env  # Edit with your settings

# Start Whaley
docker compose up -d

Access Points

Interface URL Description
User Dashboard http://your-server:8000/ Challenge spawning interface
Admin Panel http://your-server:8000/admin Monitoring & management
API Docs http://your-server:8000/docs Swagger API documentation
Prometheus Metrics http://your-server:8000/metrics Protected metrics export when METRICS_SECRET is set

βš™οΈ Configuration

Essential Settings

# Authentication
AUTH_MODE=ctfd                    # "ctfd" or "none"
CTFD_URL=https://your-ctfd.com    # Your CTFd instance URL
CTFD_API_KEY=ctfd_xxx...          # CTFd admin API key for dynamic flags/sync

# Network
PUBLIC_HOST=auto                  # VPS IP ("auto" for detection)
PORT_RANGE_START=20000
PORT_RANGE_END=50000

# Admin
ADMIN_KEY=your-secure-key         # Local admin key when AUTH_MODE=none
METRICS_SECRET=change-me          # Enables protected /metrics endpoint

# Dynamic Flags
DYNAMIC_FLAGS_ENABLED=true
FLAG_PREFIX=FLAG                  # e.g., FLAG{...}

# Packet Capture
PCAP_ENABLED=true
PCAP_MODE=all                    # "all", "selected", or "none"
PCAP_MAX_SIZE_MB=25
PCAP_RETENTION_HOURS=24
PCAP_SNAP_LEN=1024
PCAP_BPF_FILTER=not (host 127.0.0.11 and port 53)

# Team Mode
TEAM_MODE=auto                    # "auto", "enabled", or "disabled"

Production Settings (Optional)

# PostgreSQL for high availability
DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/whaley

# Redis for distributed locking (required for multiple workers)
REDIS_URL=redis://redis:6379/0

# Network Isolation (recommended)
NETWORK_ISOLATION_ENABLED=true
NETWORK_ICC_DISABLED=true
NETWORK_SUBNET_BASE=10.240.0.0/16
NETWORK_SUBNET_PREFIX=28

# Host Firewall Rate Limits (recommended for public events)
FIREWALL_RATE_LIMIT_ENABLED=true
FIREWALL_CHAIN=DOCKER-USER
FIREWALL_CONN_LIMIT_PER_IP=60
FIREWALL_RATE_PER_MINUTE=120
FIREWALL_RATE_BURST=240
FIREWALL_REJECT_MODE=reject

TRUSTED_PROXIES=127.0.0.1,::1     # Only these proxies may set client IP headers

# Resource Limits (enforced on all containers)
CONTAINER_MAX_MEMORY=384m         # Max memory per container
CONTAINER_MAX_CPU=0.5             # Max CPU cores per container
CONTAINER_PIDS_LIMIT=256          # Max PIDs per container (fork bomb protection)

πŸ’‘ Tip: Most settings (including Authentication & CTFd integration) can be configured instantly via the Admin Panel β†’ βš™οΈ Settings tab.

πŸ” Admin access: In AUTH_MODE=ctfd, Whaley validates the submitted CTFd access token with CTFd's /api/v1/users/me, then fetches /api/v1/users/{id} and only enables /admin when that detailed user record has type: "admin". In AUTH_MODE=none, admin APIs use the local ADMIN_KEY fallback.

🌐 Subnet pool: Whaley uses NETWORK_SUBNET_BASE / NETWORK_SUBNET_PREFIX for both its per-instance isolation network and compose-defined challenge networks. This keeps multi-network challenges from exhausting Docker's default address pools during large events.

πŸ›‘οΈ Host rate limits: Enable FIREWALL_RATE_LIMIT_ENABLED=true to install per-instance connlimit + hashlimit rules on Docker published ports via DOCKER-USER. If Whaley itself runs inside a container, set FIREWALL_USE_NSENTER=true or provide equivalent host firewall access.

πŸ“ˆ Prometheus metrics: Set METRICS_SECRET to enable /metrics. Scrape with either Authorization: Bearer <secret> or X-Metrics-Secret: <secret>.

πŸ“– Full configuration guide: See DOCUMENTATION.md


πŸ“ Challenge Structure

Create challenges in the challenges/ directory:

challenges/
└── my-web-challenge/
    β”œβ”€β”€ challenge.yaml        # Challenge metadata
    β”œβ”€β”€ docker-compose.yaml   # Container definition
    β”œβ”€β”€ Dockerfile
    β”œβ”€β”€ flag.txt              # Flag file (auto-injected)
    └── src/
        └── app.py

challenge.yaml

id: my-web-challenge
name: "SQL Injection Lab"
category: web
description: "Can you bypass the login?"
ports:
  - 80
timeout: 3600  # 1 hour

docker-compose.yaml

services:
  web:
    build: .
    ports:
      - "${PORT_80}:80"
    environment:
      - FLAG=${FLAG}
    mem_limit: 256m
    cpus: 0.5

⚠️ Resource enforcement: Even if a challenge sets mem_limit: 2g, Whaley will cap it to the global CONTAINER_MAX_MEMORY (default 384m). You can set per-challenge overrides via the admin panel.

πŸ›‘οΈ Compose hardening: Whaley prepares every spawn from a per-instance copy, attaches the instance network automatically, and rejects dangerous compose options such as privileged, network_mode, host/container namespace sharing, added capabilities/devices, unsafe security options, Docker socket mounts, external networks/volumes, unsafe build/env file paths, symlinks, and bind mounts that escape the challenge directory. The hardening-safe security_opt: ["no-new-privileges:true"] option is allowed.

πŸ“– More examples: See DOCUMENTATION.md


πŸ‘₯ Team Mode

Whaley supports CTFd Team Mode where instances and flags are shared per-team:

Feature User Mode Team Mode
Instance Ownership Per user Per team
Dynamic Flags Unique per user Shared per team
Instance Control Only spawner Any team member
Suspicious Detection User vs User Team vs Team

Enable with TEAM_MODE=auto to automatically detect from CTFd settings.


πŸ“Š Capacity Planning

Estimation Formula

Hard Cap = Teams Γ— MAX_INSTANCES_PER_TEAM  (default: 2)
Peak Instances = Hard Cap Γ— Concurrency Factor (0.5 – 0.8)
RAM Required = 200 MB (infra) + Peak Instances Γ— 264 MB
Storage Required = Docker Images + (PCAP Instances Γ— PCAP Rate Γ— Hours)
Ports Required = Peak Instances Γ— Ports per Challenge

Per-Instance Resource Cost

Component RAM CPU Disk/hr Notes
Challenge containers (avg) 256 MB 0.5 cores β€” Capped by CONTAINER_MAX_MEMORY
tcpdump sidecar ~5 MB 0.02 cores 5–25 MB When PCAP_ENABLED=true
Isolated network ~1 MB negligible β€” Per-instance bridge + iptables
Forensics log (on terminate) β€” β€” ~30 KB Compressed gzip
Total per instance ~264 MB ~0.52 cores 5–25 MB Sidecar included

Server Recommendations

Event Size CPU RAM Storage Example
Small (≀50 teams) 4 cores 16 GB 60 GB SSD Local CTFs
Medium (50-150 teams) 8 cores 32 GB 150 GB NVMe University CTFs
Large (150-300 teams) 16 cores 64 GB 300 GB NVMe National CTFs

Example Calculations

Scenario A β€” University CTF: 100 teams, 8 challenges, 10-hour event

  • Hard cap: 100 Γ— 2 = 200 instances max
  • Peak instances: 200 Γ— 0.7 = 140 instances
  • RAM: 200 MB + 140 Γ— 264 MB = ~37 GB
  • PCAP storage: 140 Γ— 10 MB/hr Γ— 10 hr = ~14 GB
  • Ports: 140 Γ— 1.5 avg = 210 ports

Scenario B β€” National CTF: 200 teams, 10 challenges, 12-hour event

  • Hard cap: 200 Γ— 2 = 400 instances max
  • Peak instances: 400 Γ— 0.7 = 280 instances
  • RAM: 200 MB + 280 Γ— 264 MB = ~74 GB
  • PCAP storage: 280 Γ— 10 MB/hr Γ— 12 hr = ~34 GB
  • Ports: 280 Γ— 1.5 avg = 420 ports

πŸ§ͺ Stress Testing

Whaley includes a reusable stress harness at scripts/stress_test.py. It discovers active challenges from /challenges, spawns synthetic team-owned instances through the admin API, generates mixed HTTP/TCP traffic, samples admin and PCAP status, and can optionally clean up the instances it created.

Quick smoke test:

pip install -r requirements.txt

WHALEY_BASE_URL=http://your-server:8000 \
WHALEY_ADMIN_KEY=your-admin-key \
python3 scripts/stress_test.py \
  --team-count 10 \
  --instances-per-team 2 \
  --traffic-seconds 120 \
  --traffic-workers 16 \
  --team-prefix smoke \
  --cleanup

Larger rehearsal:

WHALEY_BASE_URL=http://your-server:8000 \
WHALEY_ADMIN_KEY=your-admin-key \
python3 scripts/stress_test.py \
  --team-count 160 \
  --instances-per-team 2 \
  --traffic-seconds 900 \
  --traffic-workers 64 \
  --spawn-concurrency 8 \
  --admin-qps 2.0 \
  --team-prefix fullrun \
  --state-file /tmp/whaley-stress.json

Cleanup later from saved state:

WHALEY_BASE_URL=http://your-server:8000 \
WHALEY_ADMIN_KEY=your-admin-key \
python3 scripts/stress_test.py \
  --cleanup-from-state /tmp/whaley-stress.json

The full runbook, caveats for AUTH_MODE=none, and tuning guidance live in DOCUMENTATION.md.


πŸ“Έ Screenshots

User Dashboard
User Dashboard
Admin Dashboard
Admin Dashboard & Controls
Event Logs
Event Logs & Audit Trail
Challenge Manager
Challenge Manager
Dynamic Flags
Dynamic Flags & Suspicious Submissions
CTFd Sync
CTFd Sync Wizard

βš™οΈ Admin Settings & Challenge Management

Live Settings (No Restart Required)

All key Whaley settings can be changed at runtime via the Admin Panel β†’ βš™οΈ Settings tab:

Category Settings
Instance Timeout, max instances per user/team
Resources Container max memory, CPU cores, PID limit
Network Port range, isolation, subnet pool, public host
Features Dynamic flags, flag prefix
Authentication Auth mode (CTFd/None), CTFd URL, CTFd API key, local Admin Key fallback, metrics secret
Forensics Auto capture, retention period
Packet Capture Mode (all/selected/none), selected challenges, max file size, retention, snap length, BPF filter

Changes persist to the database and survive container restartsβ€”no need to edit docker-compose.yaml or .env files.

Challenge Active/Inactive Toggle

Control which challenges are visible and spawnable from the Challenge Manager tab:

  • 🟒 Active β€” Visible on the user dashboard, can be spawned
  • πŸ”΄ Inactive β€” Hidden from users, spawn requests rejected (HTTP 403)

Use this during competitions to stage challenges for later rounds, or to quickly disable a broken challenge without deleting it.

Challenge uploads reject path traversal, absolute paths, and symlinks. Runtime challenge trees are also rejected if they contain symlinks. The browser editor only writes text files up to 2 MB, and Whaley blocks deleting a challenge while active instances are still using it.

Admin Instance Operations

The admin dashboard can manually spawn instances for a chosen user/team owner, force-destroy any active instance, inspect live per-instance Docker logs, and sample detailed resource metrics on demand. The Monitoring tab now uses a cheap host-level snapshot by default, so it stays usable even when hundreds of instances are alive, and it also shows firewall/rate-limit status plus per-instance rule details. The Packet Capture tab adds per-instance flow summaries, payload search, raw PCAP downloads, and retention cleanup controls. Failed spawn/stop actions return the backend error message directly in the UI, which makes broken compose files, resource exhaustion, firewall misconfiguration, and Docker cleanup issues easier to diagnose during an event.

Enforced Resource Limits

Whaley enforces maximum resource limits on every container, regardless of what the challenge's docker-compose.yaml specifies:

CONTAINER_MAX_MEMORY=384m    # Caps mem_limit in compose files
CONTAINER_MAX_CPU=0.5        # Caps cpus in compose files
CONTAINER_PIDS_LIMIT=256     # Injects pids_limit (fork bomb protection)

Per-challenge overrides can be set via the admin API if certain challenges need more resources.


πŸ“– Documentation

For comprehensive documentation, see DOCUMENTATION.md:


🀝 Contributing

Contributions are welcome! Here's how you can help:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Ideas for Contribution

  • πŸ› Bug fixes and improvements
  • πŸ“ Documentation enhancements
  • 🎨 UI/UX improvements
  • πŸ§ͺ Test coverage
  • 🌐 Internationalization

πŸ“„ License

This project is licensed under the MIT License β€” see the LICENSE file for details.


Acknowledgments

Some infrastructure ideas and hardening lessons for Whaley were inspired by the MCTF 5.0 post-mortem and follow-up work from MCTF Behind the Scenes: The Infra We Built and the Chaos We Caused, by Younes Ferradji (Ynxfdj) and Abderrahmane Yahiaoui (COn4n).

The packet-capture workflow and PCAP sidecar direction were also inspired by Tulip.


πŸ‘¨β€πŸ’» Author

keii
keii

Creator & Maintainer

Built with ❀️ for the CTF community

If you find Whaley useful, please consider giving it a ⭐

GitHub stars

About

🐳 Whaley - a dedicated docker instancer for ctf competitions

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors