From ab3bf865460e2fccdea4279ac9148db743024cf4 Mon Sep 17 00:00:00 2001 From: Sam Cogan Date: Mon, 13 Apr 2026 09:41:01 +0100 Subject: [PATCH] Add Docker Compose Lab Challenge with Flask TODO API and PostgreSQL - Implemented a sample Flask application for a TODO API that interacts with a PostgreSQL database. - Added requirements.txt for necessary dependencies including Flask and psycopg2. - Created a placeholder for solution documentation. - Introduced a new challenge for Infrastructure as Code (IaC) focusing on a multi-tier environment with detailed requirements and architecture. - Added requirements.json for the IaC challenge specifying networking and tier configurations. - Created placeholders for IaC solution documentation. - Developed Dockerfiles for an inventory scanner challenge with three types of servers (web, app, db) and a compliance baseline. - Implemented a README for the inventory scanner challenge detailing requirements and hints. - Added a compliance baseline JSON for the inventory scanner challenge defining expected states for each server role. - Created a Docker Compose file to orchestrate the inventory scanner's server fleet. - Developed a security hardening toolkit with a deliberately vulnerable Docker container and a baseline for security checks. - Added a README for the security hardening challenge outlining requirements and audit checks. - Created a baseline JSON for security checks based on CIS Docker Benchmark and Linux hardening best practices. --- challenges/README.md | 18 ++ challenges/dockercomposelab/README.md | 93 +++++++++ challenges/dockercomposelab/app.py | 194 ++++++++++++++++++ challenges/dockercomposelab/requirements.txt | 3 + .../dockercomposelab/solution/placeholder.md | 0 challenges/iac-multitier/README.md | 82 ++++++++ challenges/iac-multitier/requirements.json | 104 ++++++++++ .../iac-multitier/solution/placeholder.md | 0 .../inventoryscanner/Dockerfile.appserver | 34 +++ .../inventoryscanner/Dockerfile.dbserver | 33 +++ .../inventoryscanner/Dockerfile.webserver | 31 +++ challenges/inventoryscanner/README.md | 90 ++++++++ .../inventoryscanner/compliance-baseline.json | 132 ++++++++++++ .../inventoryscanner/docker-compose.yml | 34 +++ .../inventoryscanner/scripts/placeholder.md | 0 .../securityhardening/Dockerfile.vulnerable | 72 +++++++ challenges/securityhardening/README.md | 84 ++++++++ challenges/securityhardening/baseline.json | 167 +++++++++++++++ .../securityhardening/toolkit/placeholder.md | 0 19 files changed, 1171 insertions(+) create mode 100644 challenges/README.md create mode 100644 challenges/dockercomposelab/README.md create mode 100644 challenges/dockercomposelab/app.py create mode 100644 challenges/dockercomposelab/requirements.txt create mode 100644 challenges/dockercomposelab/solution/placeholder.md create mode 100644 challenges/iac-multitier/README.md create mode 100644 challenges/iac-multitier/requirements.json create mode 100644 challenges/iac-multitier/solution/placeholder.md create mode 100644 challenges/inventoryscanner/Dockerfile.appserver create mode 100644 challenges/inventoryscanner/Dockerfile.dbserver create mode 100644 challenges/inventoryscanner/Dockerfile.webserver create mode 100644 challenges/inventoryscanner/README.md create mode 100644 challenges/inventoryscanner/compliance-baseline.json create mode 100644 challenges/inventoryscanner/docker-compose.yml create mode 100644 challenges/inventoryscanner/scripts/placeholder.md create mode 100644 challenges/securityhardening/Dockerfile.vulnerable create mode 100644 challenges/securityhardening/README.md create mode 100644 challenges/securityhardening/baseline.json create mode 100644 challenges/securityhardening/toolkit/placeholder.md diff --git a/challenges/README.md b/challenges/README.md new file mode 100644 index 00000000..2d27a054 --- /dev/null +++ b/challenges/README.md @@ -0,0 +1,18 @@ +# GitHub Copilot Hackathon Challenges + +Pick a challenge below to get started. Each one is designed to be completed in around 45–75 minutes using GitHub Copilot to accelerate your work. + +--- + +| Challenge | Description | Link | +|-----------|-------------|------| +| **Memory Game** | Build a card-matching memory game with a grid of cards, flipping mechanics, and win detection. Choose your own tech stack and card theme (colours, Pokémon, Star Wars, etc.). | [memorygame](memorygame/memorygame.md) | +| **Shop Cart (E-Shop)** | Build an online shop cart for automobile parts with a REST API, product search, and add/remove cart functionality. | [eshop](eshop/eshop.md) | +| **Real-Time Chat (WebSockets)** | Build a real-time chat application using WebSockets where users can send and receive messages instantly. Optional image sharing and session persistence. | [chatwebsockets](chatwebsockets/chatwebsockets.md) | +| **Cryptocurrency Market Analysis** | Explore and analyse cryptocurrency market data in a Jupyter notebook — price trends, volatility, correlations, and predictions. | [cryptoanalisis](cryptoanalisis/crypto.md) | +| **Expense Tracker** | Build a full-stack expense tracker microservice with a REST API, frontend UI, category breakdowns, and spending visualisations. | [expensetracker](expensetracker/README.md) | +| **Behaviour Driven Development (BDD)** | Write BDD tests using Gherkin and Cucumber for an existing Author REST API. Focus on testing skills rather than building from scratch. | [bdd](bdd/README.md) | +| **Infrastructure as Code — Multi-Tier Environment** | Write Bicep, Terraform, or Pulumi code to define a multi-tier environment (web, app, database) with networking, security groups, and parameterised environment support. Validate locally — deploying is optional. | [iac-multitier](iac-multitier/README.md) | +| **Security Hardening Toolkit** | Build scripts that audit a deliberately misconfigured Docker container against a provided security baseline. Identify misconfigurations, report pass/fail results, and optionally auto-remediate. No cloud needed. | [securityhardening](securityhardening/README.md) | +| **Server Inventory & Compliance Scanner** | Build scripts that scan a fleet of Docker containers (simulating servers), collect inventory data (OS, packages, ports, users), and check compliance against a provided baseline. No cloud needed. | [inventoryscanner](inventoryscanner/README.md) | +| **Docker Compose Lab Environment** | Starting from a sample Flask API, build a full local production-like environment with Docker Compose — reverse proxy, PostgreSQL, Prometheus, Grafana, and proper network isolation. No cloud needed. | [dockercomposelab](dockercomposelab/README.md) | diff --git a/challenges/dockercomposelab/README.md b/challenges/dockercomposelab/README.md new file mode 100644 index 00000000..6413388c --- /dev/null +++ b/challenges/dockercomposelab/README.md @@ -0,0 +1,93 @@ +# Docker Compose Lab Environment + +## 1. Problem Description + +Build a complete **local production-like environment** using Docker Compose. Starting from a simple provided sample application, use GitHub Copilot to construct the full surrounding infrastructure: reverse proxy, database, monitoring, logging, and networking — all running locally with `docker compose up`. + +**No cloud subscription is needed.** Everything runs on your local machine with Docker. + +### Core Requirements: +- **Application**: Containerise the provided sample application (a simple Python Flask API) +- **Database**: Add a PostgreSQL database container with persistent storage +- **Reverse Proxy**: Add an Nginx or Traefik reverse proxy in front of the application +- **Monitoring**: Add Prometheus to collect metrics and Grafana for dashboards +- **Networking**: Use named Docker networks to separate frontend, backend, and monitoring traffic +- **Configuration**: Use environment variables and `.env` files for configuration, not hardcoded values + +### Architecture: +``` +[User] → [Nginx/Traefik :80] → [Flask App :5000] → [PostgreSQL :5432] + ↓ + [Prometheus :9090] → [Grafana :3000] +``` + +### What You Should Build: +1. A `Dockerfile` for the Flask sample application +2. A `docker-compose.yml` orchestrating all services +3. Nginx/Traefik configuration for reverse proxying +4. Prometheus configuration to scrape the app and other services +5. A Grafana provisioning setup with a pre-configured dashboard +6. Proper volume mounts for data persistence +7. Named networks for traffic isolation + +## 2. Hints + +### Technical Implementation Hints: +- **Flask App**: The provided `app.py` is a simple API — you just need to containerise it and connect it to PostgreSQL +- **Nginx**: A simple `nginx.conf` with `proxy_pass` to the Flask app container is enough to start +- **Prometheus**: Needs a `prometheus.yml` config file to scrape targets — Flask can expose a `/metrics` endpoint using the `prometheus_client` library +- **Grafana**: Can be pre-configured using provisioning files mounted into `/etc/grafana/provisioning/` +- **Networks**: Create `frontend` (Nginx + App), `backend` (App + Postgres), and `monitoring` (Prometheus + Grafana + App) networks +- **Volumes**: Use named volumes for PostgreSQL data and Grafana data to survive container restarts + +### Quick Start Tips: +- Start with just the Flask app + PostgreSQL in Docker Compose and get that working first +- Add Nginx next — it's the simplest addition +- Add Prometheus + Grafana last as they have more configuration +- Use Copilot to generate the Nginx and Prometheus config files — these are boilerplate-heavy +- Test with `docker compose up --build` after each addition + +### Agent Mode Note: +If you are using Agent mode, try: +```markdown +Work in the directory challenges/dockercomposelab. Containerise the Flask app from app.py, +then build out a full Docker Compose environment with Nginx, PostgreSQL, Prometheus, and Grafana. +``` + +## 3. Time Needed + +**Estimated Duration: 45-75 minutes** + +### Time Breakdown: +- **Flask Dockerfile** (5-10 minutes): Containerise the sample application +- **Docker Compose + PostgreSQL** (10-15 minutes): Compose file, database service, app-to-DB connection +- **Reverse Proxy** (10-15 minutes): Nginx or Traefik configuration and integration +- **Monitoring Stack** (15-20 minutes): Prometheus config, Grafana provisioning, metrics endpoint +- **Networks & Volumes** (5-10 minutes): Network isolation and persistent storage + +## 4. Extra Features (Bonus Challenges) + +### Easy Additions (5-10 minutes each): +- **Health Checks**: Add Docker health checks to all services +- **Resource Limits**: Add CPU and memory limits to each container +- **Restart Policies**: Configure appropriate restart policies for each service +- **.env File**: Move all configurable values (ports, passwords, versions) to a `.env` file + +### Medium Additions (10-15 minutes each): +- **Log Aggregation**: Add Loki + Promtail to collect and centralise logs from all containers +- **Redis Cache**: Add a Redis container as a caching layer between the app and database +- **SSL/TLS**: Configure the reverse proxy with a self-signed certificate for HTTPS +- **Database Initialisation**: Add an init script that creates tables and seeds sample data on first run + +### Advanced Additions (15-20 minutes each): +- **Scaling**: Configure the Flask app to run with multiple replicas behind the load balancer +- **Alerting**: Configure Prometheus alerting rules and add Alertmanager +- **Backup Script**: Create a script that backs up the PostgreSQL database volume + +### Success Criteria: +- ✅ `docker compose up` brings up all services without errors +- ✅ The sample API is accessible through the reverse proxy on port 80 +- ✅ Data persists in PostgreSQL across container restarts +- ✅ Prometheus is scraping metrics from the application +- ✅ Grafana is accessible and shows a dashboard with app metrics +- ✅ Services are isolated on appropriate networks diff --git a/challenges/dockercomposelab/app.py b/challenges/dockercomposelab/app.py new file mode 100644 index 00000000..3fc8bd0a --- /dev/null +++ b/challenges/dockercomposelab/app.py @@ -0,0 +1,194 @@ +""" +Sample Flask Application for Docker Compose Lab Challenge. +A simple TODO API that stores items in PostgreSQL. + +Participants should containerise this app and build the surrounding +infrastructure using Docker Compose. +""" + +import os +from flask import Flask, jsonify, request +from datetime import datetime + +app = Flask(__name__) + +# Database connection settings - should come from environment variables +DB_HOST = os.environ.get("DB_HOST", "localhost") +DB_PORT = os.environ.get("DB_PORT", "5432") +DB_NAME = os.environ.get("DB_NAME", "tododb") +DB_USER = os.environ.get("DB_USER", "todouser") +DB_PASSWORD = os.environ.get("DB_PASSWORD", "todopass") + +# In-memory fallback if no database is connected yet +todos = [] +use_db = False +db_connection = None + + +def get_db_connection(): + """Attempt to connect to PostgreSQL. Falls back to in-memory storage.""" + global use_db, db_connection + try: + import psycopg2 + conn = psycopg2.connect( + host=DB_HOST, + port=DB_PORT, + dbname=DB_NAME, + user=DB_USER, + password=DB_PASSWORD, + ) + conn.autocommit = True + use_db = True + db_connection = conn + + # Create table if it doesn't exist + with conn.cursor() as cur: + cur.execute( + """ + CREATE TABLE IF NOT EXISTS todos ( + id SERIAL PRIMARY KEY, + title VARCHAR(200) NOT NULL, + completed BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + return conn + except Exception as e: + print(f"Database connection failed: {e}") + print("Falling back to in-memory storage") + use_db = False + return None + + +@app.route("/health", methods=["GET"]) +def health(): + """Health check endpoint.""" + return jsonify({"status": "healthy", "timestamp": datetime.utcnow().isoformat()}) + + +@app.route("/api/todos", methods=["GET"]) +def get_todos(): + """Get all TODO items.""" + if use_db and db_connection: + with db_connection.cursor() as cur: + cur.execute("SELECT id, title, completed, created_at FROM todos ORDER BY id") + rows = cur.fetchall() + items = [ + { + "id": r[0], + "title": r[1], + "completed": r[2], + "created_at": r[3].isoformat(), + } + for r in rows + ] + return jsonify(items) + return jsonify(todos) + + +@app.route("/api/todos", methods=["POST"]) +def add_todo(): + """Add a new TODO item.""" + data = request.get_json() + if not data or "title" not in data: + return jsonify({"error": "title is required"}), 400 + + title = data["title"] + + if use_db and db_connection: + with db_connection.cursor() as cur: + cur.execute( + "INSERT INTO todos (title) VALUES (%s) RETURNING id, title, completed, created_at", + (title,), + ) + row = cur.fetchone() + item = { + "id": row[0], + "title": row[1], + "completed": row[2], + "created_at": row[3].isoformat(), + } + return jsonify(item), 201 + else: + item = { + "id": len(todos) + 1, + "title": title, + "completed": False, + "created_at": datetime.utcnow().isoformat(), + } + todos.append(item) + return jsonify(item), 201 + + +@app.route("/api/todos/", methods=["PUT"]) +def update_todo(todo_id): + """Toggle a TODO item's completed status.""" + if use_db and db_connection: + with db_connection.cursor() as cur: + cur.execute( + "UPDATE todos SET completed = NOT completed WHERE id = %s RETURNING id, title, completed, created_at", + (todo_id,), + ) + row = cur.fetchone() + if not row: + return jsonify({"error": "not found"}), 404 + item = { + "id": row[0], + "title": row[1], + "completed": row[2], + "created_at": row[3].isoformat(), + } + return jsonify(item) + else: + for item in todos: + if item["id"] == todo_id: + item["completed"] = not item["completed"] + return jsonify(item) + return jsonify({"error": "not found"}), 404 + + +@app.route("/api/todos/", methods=["DELETE"]) +def delete_todo(todo_id): + """Delete a TODO item.""" + if use_db and db_connection: + with db_connection.cursor() as cur: + cur.execute("DELETE FROM todos WHERE id = %s RETURNING id", (todo_id,)) + row = cur.fetchone() + if not row: + return jsonify({"error": "not found"}), 404 + return jsonify({"deleted": todo_id}) + else: + global todos + original_len = len(todos) + todos = [t for t in todos if t["id"] != todo_id] + if len(todos) == original_len: + return jsonify({"error": "not found"}), 404 + return jsonify({"deleted": todo_id}) + + +@app.route("/api/stats", methods=["GET"]) +def get_stats(): + """Get TODO statistics - useful for monitoring dashboards.""" + if use_db and db_connection: + with db_connection.cursor() as cur: + cur.execute("SELECT COUNT(*) FROM todos") + total = cur.fetchone()[0] + cur.execute("SELECT COUNT(*) FROM todos WHERE completed = TRUE") + completed = cur.fetchone()[0] + else: + total = len(todos) + completed = sum(1 for t in todos if t["completed"]) + + return jsonify( + { + "total": total, + "completed": completed, + "pending": total - completed, + } + ) + + +if __name__ == "__main__": + get_db_connection() + app.run(host="0.0.0.0", port=5000, debug=False) diff --git a/challenges/dockercomposelab/requirements.txt b/challenges/dockercomposelab/requirements.txt new file mode 100644 index 00000000..d14057af --- /dev/null +++ b/challenges/dockercomposelab/requirements.txt @@ -0,0 +1,3 @@ +flask>=3.0 +psycopg2-binary>=2.9 +prometheus-client>=0.20 diff --git a/challenges/dockercomposelab/solution/placeholder.md b/challenges/dockercomposelab/solution/placeholder.md new file mode 100644 index 00000000..e69de29b diff --git a/challenges/iac-multitier/README.md b/challenges/iac-multitier/README.md new file mode 100644 index 00000000..36eee875 --- /dev/null +++ b/challenges/iac-multitier/README.md @@ -0,0 +1,82 @@ +# Infrastructure as Code - Multi-Tier Environment + +## 1. Problem Description + +Build a complete **Infrastructure as Code** project that defines a multi-tier cloud environment. The environment should include a web tier, an application tier, and a database tier, each with appropriate networking, security rules, and configuration. + +The focus of this challenge is on **writing correct, well-structured, production-quality IaC code** — deploying to a real cloud subscription is optional. You should be able to validate your code locally using linting, dry-run, and plan commands. + +### Core Requirements: +- **IaC Tool**: Choose Bicep, Terraform, or Pulumi +- **Three Tiers**: Web (public-facing), Application (internal), Database (private) +- **Networking**: Virtual network with separate subnets for each tier +- **Security**: Network security groups / firewall rules controlling traffic flow between tiers +- **Parameterisation**: Support for multiple environments (dev, staging, production) via parameters or variables +- **Outputs**: Export key resource information (endpoints, resource IDs) as outputs + +### Architecture Requirements: +1. A virtual network with three subnets (web, app, database) +2. Web tier: Load balancer + VM scale set or App Service, accessible from the internet on ports 80/443 +3. Application tier: Compute resources accessible only from the web tier subnet +4. Database tier: Database server accessible only from the application tier subnet +5. Network security groups restricting traffic between tiers appropriately +6. A Key Vault or equivalent for storing secrets + +## 2. Hints + +### Technical Implementation Hints: +- **Bicep**: Use modules to separate each tier into its own `.bicep` file. Use a `main.bicep` to orchestrate +- **Terraform**: Use modules under a `modules/` directory. Use `terraform validate` and `terraform plan` to check correctness +- **Pulumi**: Use your preferred language (TypeScript, Python, C#, Go). Use `pulumi preview` to validate +- **Naming**: Use a consistent naming convention with environment prefix (e.g., `dev-web-nsg`, `prod-app-subnet`) +- **Validation**: All tools support local validation without deploying — use this to verify your code + +### Quick Start Tips: +- Start with the virtual network and subnets — everything else builds on top +- Use Copilot to generate the initial module/file structure +- Ask Copilot to explain the security group rules it generates to make sure they are correct +- Use `az bicep build`, `terraform validate`, or `pulumi preview` to catch errors early +- Refer to the provided `requirements.json` for the specific resource sizes and configurations + +### Agent Mode Note: +If you are using Agent mode, try prompting with the full architecture in one go: +```markdown +Work in the directory challenges/iac-multitier/solution. Create a complete Terraform/Bicep project +for a multi-tier environment based on the requirements in requirements.json. +``` + +## 3. Time Needed + +**Estimated Duration: 45-75 minutes** + +### Time Breakdown: +- **Setup & Structure** (5-10 minutes): Initialise project, create folder/module structure +- **Networking** (10-15 minutes): VNet, subnets, NSGs +- **Web Tier** (10-15 minutes): Load balancer, compute, public access +- **App Tier** (10-15 minutes): Internal compute, private access +- **Database Tier** (10-15 minutes): Database server, private subnet +- **Parameterisation & Outputs** (5-10 minutes): Environment support, outputs + +## 4. Extra Features (Bonus Challenges) + +### Easy Additions (5-10 minutes each): +- **Tagging Strategy**: Add a consistent tagging scheme (environment, owner, cost centre) to all resources +- **Diagnostic Settings**: Add logging/diagnostic settings to each resource +- **README Generator**: Ask Copilot to generate documentation for your module inputs/outputs + +### Medium Additions (10-15 minutes each): +- **Private Endpoints**: Replace public database access with private endpoints +- **Bastion Host**: Add a bastion/jumpbox for secure management access +- **CI Validation Pipeline**: Create a GitHub Actions workflow that runs `validate`/`plan` on pull requests +- **State Management**: Configure remote state storage (Terraform backend or Pulumi state) + +### Advanced Additions (15-20 minutes each): +- **Policy as Code**: Add Azure Policy definitions or Sentinel/OPA policies that enforce your security rules +- **Multi-Region**: Extend the template to deploy to a primary and secondary region + +### Success Criteria: +- ✅ Code is syntactically valid (`validate`/`build` passes) +- ✅ Three tiers are properly isolated in separate subnets +- ✅ Security rules only allow the correct traffic flow between tiers +- ✅ Parameters allow switching between dev/staging/prod configurations +- ✅ Code is modular and well-organised diff --git a/challenges/iac-multitier/requirements.json b/challenges/iac-multitier/requirements.json new file mode 100644 index 00000000..4c3395db --- /dev/null +++ b/challenges/iac-multitier/requirements.json @@ -0,0 +1,104 @@ +{ + "project": "Multi-Tier Web Application Environment", + "environments": ["dev", "staging", "production"], + "location": "East US 2", + "networking": { + "vnet": { + "addressSpace": "10.0.0.0/16" + }, + "subnets": { + "web": { + "addressPrefix": "10.0.1.0/24", + "purpose": "Public-facing web servers and load balancer" + }, + "app": { + "addressPrefix": "10.0.2.0/24", + "purpose": "Internal application logic and APIs" + }, + "database": { + "addressPrefix": "10.0.3.0/24", + "purpose": "Database servers, no direct external access" + }, + "management": { + "addressPrefix": "10.0.4.0/24", + "purpose": "Optional: Bastion host or jumpbox for admin access" + } + } + }, + "tiers": { + "web": { + "description": "Public-facing web tier with load balancer", + "compute": { + "type": "VM Scale Set or App Service", + "sizing": { + "dev": { "sku": "Standard_B1s", "instanceCount": 1 }, + "staging": { "sku": "Standard_B2s", "instanceCount": 2 }, + "production": { "sku": "Standard_D2s_v3", "instanceCount": 3 } + } + }, + "loadBalancer": { + "type": "Public", + "rules": [ + { "name": "http", "frontendPort": 80, "backendPort": 80, "protocol": "TCP" }, + { "name": "https", "frontendPort": 443, "backendPort": 443, "protocol": "TCP" } + ] + }, + "allowedInboundTraffic": ["Internet on ports 80, 443"], + "allowedOutboundTraffic": ["App tier subnet on port 8080"] + }, + "app": { + "description": "Internal application tier for business logic and APIs", + "compute": { + "type": "VM Scale Set or App Service", + "sizing": { + "dev": { "sku": "Standard_B1s", "instanceCount": 1 }, + "staging": { "sku": "Standard_B2s", "instanceCount": 2 }, + "production": { "sku": "Standard_D2s_v3", "instanceCount": 2 } + } + }, + "allowedInboundTraffic": ["Web tier subnet on port 8080"], + "allowedOutboundTraffic": ["Database tier subnet on port 5432"] + }, + "database": { + "description": "Private database tier with no direct external access", + "service": { + "type": "PostgreSQL Flexible Server", + "sizing": { + "dev": { "sku": "Standard_B1ms", "storageSizeGB": 32 }, + "staging": { "sku": "Standard_B2s", "storageSizeGB": 64 }, + "production": { "sku": "Standard_D2s_v3", "storageSizeGB": 128 } + } + }, + "allowedInboundTraffic": ["App tier subnet on port 5432"], + "allowedOutboundTraffic": ["None"] + } + }, + "security": { + "keyVault": { + "purpose": "Store database credentials, TLS certificates, and application secrets", + "accessPolicy": "Only app tier compute should have read access to secrets" + }, + "nsgRules": { + "principle": "Deny all by default, explicitly allow only required traffic between tiers", + "rules": [ + "Internet -> Web tier: Allow TCP 80, 443", + "Web tier -> App tier: Allow TCP 8080", + "App tier -> Database tier: Allow TCP 5432", + "All tiers -> Internet: Deny (except for required updates)", + "Database tier -> Any: Deny all outbound" + ] + } + }, + "tags": { + "environment": "{{env}}", + "project": "multi-tier-demo", + "owner": "hackathon-team", + "costCentre": "innovation" + }, + "outputs": [ + "Load balancer public IP / FQDN", + "Key Vault name and URI", + "VNet and subnet resource IDs", + "Database server FQDN (private)" + ] +} diff --git a/challenges/iac-multitier/solution/placeholder.md b/challenges/iac-multitier/solution/placeholder.md new file mode 100644 index 00000000..e69de29b diff --git a/challenges/inventoryscanner/Dockerfile.appserver b/challenges/inventoryscanner/Dockerfile.appserver new file mode 100644 index 00000000..078c1ea3 --- /dev/null +++ b/challenges/inventoryscanner/Dockerfile.appserver @@ -0,0 +1,34 @@ +# Application Server - generally compliant but with some issues +FROM ubuntu:22.04 + +RUN apt-get update && apt-get install -y \ + python3 \ + python3-pip \ + curl \ + wget \ + net-tools \ + iproute2 \ + procps \ + sudo \ + cron \ + && rm -rf /var/lib/apt/lists/* + +# Create expected users +RUN useradd -m -s /bin/bash appadmin && echo "appadmin:password" | chpasswd +RUN useradd -m -s /bin/bash deployer && echo "deployer:deploy123" | chpasswd + +# deployer has sudo (expected) +RUN usermod -aG sudo deployer + +# Create a simple app that listens on port 8080 +RUN mkdir -p /app +RUN echo '#!/usr/bin/env python3\nimport http.server\nimport socketserver\nHandler = http.server.SimpleHTTPRequestHandler\nwith socketserver.TCPServer(("", 8080), Handler) as httpd:\n httpd.serve_forever()' > /app/server.py + +# Also open an unexpected debug port +RUN echo '#!/usr/bin/env python3\nimport http.server\nimport socketserver\nHandler = http.server.SimpleHTTPRequestHandler\nwith socketserver.TCPServer(("", 9999), Handler) as httpd:\n httpd.serve_forever()' > /app/debug.py + +RUN echo "#!/bin/bash\npython3 /app/server.py &\npython3 /app/debug.py &\ntail -f /dev/null" > /start.sh && chmod +x /start.sh + +EXPOSE 8080 9999 + +CMD ["/start.sh"] diff --git a/challenges/inventoryscanner/Dockerfile.dbserver b/challenges/inventoryscanner/Dockerfile.dbserver new file mode 100644 index 00000000..276864df --- /dev/null +++ b/challenges/inventoryscanner/Dockerfile.dbserver @@ -0,0 +1,33 @@ +# Database Server - missing expected packages, has extra unexpected ones +FROM ubuntu:22.04 + +RUN apt-get update && apt-get install -y \ + postgresql \ + postgresql-client \ + curl \ + net-tools \ + iproute2 \ + procps \ + nmap \ + gcc \ + make \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +# Create expected user +RUN useradd -m -s /bin/bash dbadmin && echo "dbadmin:password" | chpasswd + +# Missing: no dedicated monitoring user that the baseline expects + +# PostgreSQL setup +USER postgres +RUN /etc/init.d/postgresql start && \ + psql --command "ALTER USER postgres PASSWORD 'postgres';" && \ + /etc/init.d/postgresql stop +USER root + +RUN echo "#!/bin/bash\nservice postgresql start\ntail -f /dev/null" > /start.sh && chmod +x /start.sh + +EXPOSE 5432 22 + +CMD ["/start.sh"] diff --git a/challenges/inventoryscanner/Dockerfile.webserver b/challenges/inventoryscanner/Dockerfile.webserver new file mode 100644 index 00000000..889e1b4a --- /dev/null +++ b/challenges/inventoryscanner/Dockerfile.webserver @@ -0,0 +1,31 @@ +# Web Server - has some correct config but also some drift +FROM ubuntu:22.04 + +RUN apt-get update && apt-get install -y \ + nginx \ + curl \ + wget \ + net-tools \ + iproute2 \ + procps \ + vim \ + telnet \ + openssh-server \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +# Create expected user +RUN useradd -m -s /bin/bash webadmin && echo "webadmin:password" | chpasswd +RUN usermod -aG sudo webadmin + +# Create an unexpected user (compliance drift) +RUN useradd -m -s /bin/bash testuser && echo "testuser:test123" | chpasswd +RUN usermod -aG sudo testuser + +# Start nginx (expected) and ssh (unexpected for a web server) +RUN mkdir -p /var/run/sshd +RUN echo "#!/bin/bash\nservice nginx start\nservice ssh start\ntail -f /dev/null" > /start.sh && chmod +x /start.sh + +EXPOSE 80 22 8080 + +CMD ["/start.sh"] diff --git a/challenges/inventoryscanner/README.md b/challenges/inventoryscanner/README.md new file mode 100644 index 00000000..5ad12e64 --- /dev/null +++ b/challenges/inventoryscanner/README.md @@ -0,0 +1,90 @@ +# Server Inventory & Compliance Scanner + +## 1. Problem Description + +Build a **Server Inventory & Compliance Scanner** — a set of scripts that scan target systems, collect detailed inventory information, and check compliance against a provided baseline. The targets are a set of **Docker containers** (provided via a Docker Compose file) that simulate a fleet of misconfigured servers. + +**No cloud subscription or existing servers needed.** You spin up the "fleet" locally with Docker Compose and scan it. + +### Core Requirements: +- **Scripting Language**: PowerShell, Bash, or Python +- **Targets**: The provided `docker-compose.yml` spins up 3 containers simulating different servers (web server, database server, application server) +- **Inventory Collection**: Gather OS info, installed packages, running services, open ports, users, and disk usage from each target +- **Compliance Checks**: Compare the gathered inventory against the provided `compliance-baseline.json` to identify drift +- **Report**: Produce a structured report (JSON, CSV, or formatted console output) showing inventory data and compliance status per server + +### What Your Scanner Should Collect: +1. **OS Information**: Distribution, version, kernel version, hostname +2. **Installed Packages**: List of installed packages with versions +3. **Running Services**: Active services/processes +4. **Open Ports**: Listening ports and associated processes +5. **User Accounts**: List of users, their groups, and sudo access +6. **Disk Usage**: Filesystem usage and mount points +7. **Network Configuration**: IP addresses, DNS settings + +### What Your Scanner Should Check for Compliance: +1. Are all required packages installed at the correct version? +2. Are any prohibited packages present? +3. Are the expected services running? +4. Are only the allowed ports open? +5. Are there any unexpected user accounts? +6. Is disk usage within acceptable thresholds? + +## 2. Hints + +### Technical Implementation Hints: +- **Docker Compose**: Run `docker compose up -d` to start the fleet. Each container is accessible by its service name +- **Scanning**: Use `docker exec ` to run commands inside each container +- **Inventory commands**: `cat /etc/os-release`, `dpkg -l` or `rpm -qa`, `ss -tlnp`, `ps aux`, `cat /etc/passwd`, `df -h` +- **Baseline**: The `compliance-baseline.json` defines per-server-role expectations. Parse this to drive your compliance checks +- **Structure**: Consider a main orchestrator script that loops through targets and calls collection/check functions + +### Quick Start Tips: +- Start the fleet: `docker compose up -d` +- Start by collecting inventory from a single container and formatting the output +- Add compliance checks one category at a time (packages first, then services, then ports, etc.) +- Use Copilot to generate the parsing logic for command outputs — these vary by distro +- Test with one container first, then generalise to scan all three + +### Agent Mode Note: +If you are using Agent mode, try: +```markdown +Work in the directory challenges/inventoryscanner. Start the fleet with docker compose up -d, +then build scripts that scan each container and check compliance against compliance-baseline.json. +``` + +## 3. Time Needed + +**Estimated Duration: 45-75 minutes** + +### Time Breakdown: +- **Setup** (5 minutes): `docker compose up -d` to start the fleet +- **Inventory Collection** (15-20 minutes): Scripts to gather OS, packages, services, ports, users, disk info +- **Compliance Checks** (15-20 minutes): Parse baseline, compare against inventory, flag drift +- **Report Generation** (10-15 minutes): Combine results into a structured, readable report +- **Multi-Target Support** (5-10 minutes): Loop across all containers with a single command + +## 4. Extra Features (Bonus Challenges) + +### Easy Additions (5-10 minutes each): +- **Colour-Coded Output**: Compliant in green, non-compliant in red, warnings in yellow +- **Summary Dashboard**: Show a quick overview (e.g., "Server 1: 12/15 checks passed") +- **CSV Export**: Export the full inventory to a CSV file for spreadsheet analysis +- **Timestamp & Versioning**: Add scan timestamps and save historical results + +### Medium Additions (10-15 minutes each): +- **HTML Report**: Generate a styled HTML report with per-server sections and a summary table +- **Remediation Script**: Auto-generate a fix script that installs missing packages, stops prohibited services, etc. +- **Diff Between Scans**: Run the scanner twice and show what changed between scans +- **Parallel Scanning**: Scan all containers concurrently to speed up the process + +### Advanced Additions (15-20 minutes each): +- **Add a New Server**: Add a 4th container to the Compose file and a matching baseline entry, then verify your scanner handles it automatically +- **Alerting**: If critical compliance checks fail, send a notification (write to a file, webhook, or Slack-style message) + +### Success Criteria: +- ✅ Fleet of 3 containers starts with `docker compose up -d` +- ✅ Scanner collects inventory from all containers +- ✅ Compliance checks run against the provided baseline +- ✅ Report clearly shows pass/fail per check, per server +- ✅ Scanner works across all 3 server roles without hardcoding diff --git a/challenges/inventoryscanner/compliance-baseline.json b/challenges/inventoryscanner/compliance-baseline.json new file mode 100644 index 00000000..9c601e7c --- /dev/null +++ b/challenges/inventoryscanner/compliance-baseline.json @@ -0,0 +1,132 @@ +{ + "name": "Server Fleet Compliance Baseline", + "version": "1.0", + "description": "Defines the expected state for each server role in the fleet. Scanners should compare collected inventory against these expectations.", + "servers": { + "webserver": { + "role": "Web Server", + "container": "fleet-webserver", + "os": { + "distribution": "Ubuntu", + "minVersion": "22.04" + }, + "requiredPackages": [ + "nginx", + "curl", + "wget", + "net-tools", + "iproute2", + "procps" + ], + "prohibitedPackages": [ + "telnet", + "nmap", + "gcc", + "make", + "openssh-server" + ], + "requiredServices": [ + "nginx" + ], + "prohibitedServices": [ + "sshd" + ], + "allowedPorts": [80, 443], + "expectedUsers": [ + { + "name": "webadmin", + "sudoAccess": true + } + ], + "prohibitedUsers": ["testuser", "guest"], + "maxDiskUsagePercent": 85 + }, + "dbserver": { + "role": "Database Server", + "container": "fleet-dbserver", + "os": { + "distribution": "Ubuntu", + "minVersion": "22.04" + }, + "requiredPackages": [ + "postgresql", + "postgresql-client", + "curl", + "net-tools", + "iproute2", + "procps" + ], + "prohibitedPackages": [ + "nmap", + "gcc", + "make", + "telnet", + "openssh-server" + ], + "requiredServices": [ + "postgresql" + ], + "prohibitedServices": [ + "sshd", + "nginx", + "apache2" + ], + "allowedPorts": [5432], + "expectedUsers": [ + { + "name": "dbadmin", + "sudoAccess": false + }, + { + "name": "monitoring", + "sudoAccess": false + } + ], + "prohibitedUsers": ["testuser", "guest"], + "maxDiskUsagePercent": 80 + }, + "appserver": { + "role": "Application Server", + "container": "fleet-appserver", + "os": { + "distribution": "Ubuntu", + "minVersion": "22.04" + }, + "requiredPackages": [ + "python3", + "curl", + "net-tools", + "iproute2", + "procps", + "cron" + ], + "prohibitedPackages": [ + "nmap", + "gcc", + "make", + "telnet", + "openssh-server" + ], + "requiredServices": [ + "python3" + ], + "prohibitedServices": [ + "sshd", + "nginx" + ], + "allowedPorts": [8080], + "expectedUsers": [ + { + "name": "appadmin", + "sudoAccess": false + }, + { + "name": "deployer", + "sudoAccess": true + } + ], + "prohibitedUsers": ["testuser", "guest"], + "maxDiskUsagePercent": 85 + } + } +} diff --git a/challenges/inventoryscanner/docker-compose.yml b/challenges/inventoryscanner/docker-compose.yml new file mode 100644 index 00000000..6ad3e2cc --- /dev/null +++ b/challenges/inventoryscanner/docker-compose.yml @@ -0,0 +1,34 @@ +services: + webserver: + build: + context: . + dockerfile: Dockerfile.webserver + container_name: fleet-webserver + hostname: webserver-01 + networks: + - fleet + command: ["tail", "-f", "/dev/null"] + + dbserver: + build: + context: . + dockerfile: Dockerfile.dbserver + container_name: fleet-dbserver + hostname: dbserver-01 + networks: + - fleet + command: ["tail", "-f", "/dev/null"] + + appserver: + build: + context: . + dockerfile: Dockerfile.appserver + container_name: fleet-appserver + hostname: appserver-01 + networks: + - fleet + command: ["tail", "-f", "/dev/null"] + +networks: + fleet: + driver: bridge diff --git a/challenges/inventoryscanner/scripts/placeholder.md b/challenges/inventoryscanner/scripts/placeholder.md new file mode 100644 index 00000000..e69de29b diff --git a/challenges/securityhardening/Dockerfile.vulnerable b/challenges/securityhardening/Dockerfile.vulnerable new file mode 100644 index 00000000..b54ae6ef --- /dev/null +++ b/challenges/securityhardening/Dockerfile.vulnerable @@ -0,0 +1,72 @@ +# Deliberately Vulnerable Container for Security Hardening Challenge +# DO NOT use this in production - it is intentionally insecure! + +FROM ubuntu:22.04 + +# Install packages including some unnecessary ones +RUN apt-get update && apt-get install -y \ + openssh-server \ + telnet \ + ftp \ + netcat-openbsd \ + curl \ + wget \ + vim \ + net-tools \ + iputils-ping \ + nmap \ + gcc \ + make \ + python3 \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +# ---- MISCONFIGURATION: Running as root (no non-root user created for the app) ---- + +# ---- MISCONFIGURATION: SSH allows root login with password ---- +RUN mkdir /var/run/sshd +RUN echo 'root:password123' | chpasswd +RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config +RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config +RUN sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config + +# ---- MISCONFIGURATION: World-writable sensitive files ---- +RUN chmod 777 /etc/passwd +RUN chmod 777 /etc/shadow +RUN chmod 777 /etc/ssh/sshd_config + +# ---- MISCONFIGURATION: World-writable directories ---- +RUN mkdir -p /app/data /app/logs /app/config +RUN chmod 777 /app/data +RUN chmod 777 /app/logs +RUN chmod 777 /app/config + +# ---- MISCONFIGURATION: Sensitive data in plain text files ---- +RUN echo "DB_PASSWORD=SuperSecret123!" > /app/config/database.conf +RUN echo "API_KEY=sk-1234567890abcdef" >> /app/config/database.conf +RUN chmod 644 /app/config/database.conf + +# ---- MISCONFIGURATION: SUID bit on unnecessary binaries ---- +RUN chmod u+s /usr/bin/find +RUN chmod u+s /usr/bin/vim.basic + +# ---- MISCONFIGURATION: No log rotation configured, logs world-writable ---- +RUN touch /var/log/app.log && chmod 666 /var/log/app.log +RUN touch /var/log/audit.log && chmod 666 /var/log/audit.log + +# ---- MISCONFIGURATION: Weak sudo configuration ---- +RUN echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# ---- MISCONFIGURATION: .bash_history not restricted ---- +RUN echo "mysql -u root -pSuperSecret123!" >> /root/.bash_history +RUN echo "curl -H 'Authorization: Bearer sk-1234567890abcdef' https://api.example.com" >> /root/.bash_history + +# ---- MISCONFIGURATION: Unnecessary ports exposed ---- +EXPOSE 22 23 80 8080 3306 5432 + +# ---- MISCONFIGURATION: No health check defined ---- + +# ---- MISCONFIGURATION: No resource limits or read-only filesystem ---- + +# Keep the container running +CMD ["tail", "-f", "/dev/null"] diff --git a/challenges/securityhardening/README.md b/challenges/securityhardening/README.md new file mode 100644 index 00000000..d1b83025 --- /dev/null +++ b/challenges/securityhardening/README.md @@ -0,0 +1,84 @@ +# Security Hardening Toolkit + +## 1. Problem Description + +Build a **Security Hardening Toolkit** — a collection of scripts that audit a system for common security misconfigurations and produce a clear pass/fail report. The target system is a deliberately **misconfigured Docker container** provided as part of this challenge. + +Your toolkit should check for common CIS benchmark-style issues, report what is wrong, and optionally apply fixes automatically. + +**No cloud subscription is needed.** Everything runs locally against the provided Docker container. + +### Core Requirements: +- **Scripting Language**: PowerShell, Bash, or Python +- **Target**: The provided `Dockerfile.vulnerable` which builds a deliberately insecure Linux container +- **Audit Checks**: Scan the running container for misconfigurations listed in the provided `baseline.json` +- **Report**: Produce a structured report (JSON, CSV, or formatted console output) showing pass/fail per check with details +- **Categories**: File permissions, running services, user configuration, network settings, package vulnerabilities + +### What Your Toolkit Should Check: +1. Is the container running as root? +2. Are there unnecessary packages installed? +3. Are there files with overly permissive permissions (world-writable)? +4. Are unnecessary services/ports exposed? +5. Are password-related configurations secure? +6. Are there known vulnerable packages that need updating? +7. Do log files exist and have correct permissions? +8. Is SSH configured securely (if present)? + +## 2. Hints + +### Technical Implementation Hints: +- **Docker**: Use `docker build` and `docker run` to spin up the vulnerable container, then `docker exec` to run your audit scripts inside it +- **Baseline**: The `baseline.json` file describes each check, what the expected state is, and the severity. Use this to drive your audit +- **Structure**: Consider one script per category (files, users, services, network) and a main orchestrator script +- **Output**: JSON output is easiest to parse; consider also a human-friendly summary with colour-coded pass/fail + +### Quick Start Tips: +- Start by building and running the vulnerable container: `docker build -f Dockerfile.vulnerable -t vulnerable-target .` +- Run the container: `docker run -d --name audit-target vulnerable-target` +- Use `docker exec audit-target ` to run checks inside the container +- Ask Copilot to read the `baseline.json` and generate the corresponding check scripts +- Test each check individually before combining into the full toolkit + +### Agent Mode Note: +If you are using Agent mode, try: +```markdown +Work in the directory challenges/securityhardening. Build the vulnerable container from +Dockerfile.vulnerable, then create audit scripts based on the checks in baseline.json. +``` + +## 3. Time Needed + +**Estimated Duration: 45-75 minutes** + +### Time Breakdown: +- **Setup** (5-10 minutes): Build and run the vulnerable container +- **File & Permission Checks** (10-15 minutes): World-writable files, sensitive file permissions +- **User & Auth Checks** (10-15 minutes): Root usage, password policies, sudo configuration +- **Service & Network Checks** (10-15 minutes): Unnecessary services, open ports, SSH config +- **Package Checks** (5-10 minutes): Outdated/vulnerable packages +- **Report Generation** (5-10 minutes): Combine results into a structured report + +## 4. Extra Features (Bonus Challenges) + +### Easy Additions (5-10 minutes each): +- **Colour-Coded Console Output**: Show PASS in green, FAIL in red, WARN in yellow +- **Severity Scoring**: Calculate an overall security score (0-100) based on check results and severity weights +- **Fix Mode**: Add a `--fix` flag that automatically remediates issues where possible + +### Medium Additions (10-15 minutes each): +- **HTML Report**: Generate a styled HTML report with a summary dashboard +- **Diff Report**: Run the audit before and after fixes, and show what changed +- **Custom Baseline**: Allow users to provide their own baseline JSON to check against +- **Container Comparison**: Audit the vulnerable container vs. the hardened one and show the differences + +### Advanced Additions (15-20 minutes each): +- **Dockerfile Linter**: Analyse the `Dockerfile.vulnerable` itself (without running it) and flag bad practices +- **Remediation Dockerfile**: Generate a new Dockerfile that applies all the fixes + +### Success Criteria: +- ✅ Vulnerable container builds and runs +- ✅ Toolkit scans the container and identifies misconfigurations +- ✅ Report clearly shows pass/fail per check with details +- ✅ At least 8 distinct checks are implemented +- ✅ Report includes severity levels for each finding diff --git a/challenges/securityhardening/baseline.json b/challenges/securityhardening/baseline.json new file mode 100644 index 00000000..6d032f7f --- /dev/null +++ b/challenges/securityhardening/baseline.json @@ -0,0 +1,167 @@ +{ + "name": "Container Security Baseline", + "version": "1.0", + "description": "Security checks based on CIS Docker Benchmark and Linux hardening best practices", + "checks": [ + { + "id": "USER-001", + "category": "User Configuration", + "name": "Container should not run as root", + "description": "The container process should run as a non-root user to limit the impact of a compromise", + "severity": "HIGH", + "check": "The process running as PID 1 should not be owned by UID 0", + "expected": "Non-root user (UID > 0)", + "remediation": "Add a non-root user and use USER directive in Dockerfile" + }, + { + "id": "USER-002", + "category": "User Configuration", + "name": "No accounts with empty passwords", + "description": "All user accounts must have a password set or be locked", + "severity": "CRITICAL", + "check": "Check /etc/shadow for accounts with empty password field", + "expected": "No accounts with empty password fields", + "remediation": "Lock accounts without passwords using passwd -l" + }, + { + "id": "USER-003", + "category": "User Configuration", + "name": "Sudo should require a password", + "description": "The sudoers file should not contain NOPASSWD entries for ALL users", + "severity": "HIGH", + "check": "Check /etc/sudoers and /etc/sudoers.d/ for NOPASSWD ALL entries", + "expected": "No wildcard NOPASSWD entries", + "remediation": "Remove overly permissive sudo rules" + }, + { + "id": "FILE-001", + "category": "File Permissions", + "name": "/etc/passwd should not be world-writable", + "description": "The passwd file controls user accounts and must be protected", + "severity": "CRITICAL", + "check": "Check permissions on /etc/passwd", + "expected": "Permissions should be 644 or more restrictive", + "remediation": "Run chmod 644 /etc/passwd" + }, + { + "id": "FILE-002", + "category": "File Permissions", + "name": "/etc/shadow should be restricted", + "description": "The shadow file contains password hashes and must be tightly controlled", + "severity": "CRITICAL", + "check": "Check permissions on /etc/shadow", + "expected": "Permissions should be 640 or more restrictive", + "remediation": "Run chmod 640 /etc/shadow" + }, + { + "id": "FILE-003", + "category": "File Permissions", + "name": "No world-writable files in sensitive directories", + "description": "Sensitive directories should not contain world-writable files", + "severity": "HIGH", + "check": "Search for world-writable files in /etc, /var/log, /app", + "expected": "No world-writable files found", + "remediation": "Remove world-writable permission from flagged files" + }, + { + "id": "FILE-004", + "category": "File Permissions", + "name": "No SUID binaries beyond standard set", + "description": "SUID binaries run with elevated privileges and should be minimised", + "severity": "MEDIUM", + "check": "List all files with SUID bit set and compare against a known-good list", + "expected": "Only standard SUID binaries (su, passwd, ping, etc.)", + "remediation": "Remove SUID bit from non-essential binaries with chmod u-s" + }, + { + "id": "FILE-005", + "category": "File Permissions", + "name": "No secrets in plain text config files", + "description": "Configuration files should not contain passwords, API keys, or tokens in plain text", + "severity": "HIGH", + "check": "Search for patterns like PASSWORD=, API_KEY=, SECRET=, TOKEN= in files under /app and /etc", + "expected": "No plain text secrets found", + "remediation": "Use environment variables or a secrets manager instead" + }, + { + "id": "SSH-001", + "category": "SSH Configuration", + "name": "SSH root login should be disabled", + "description": "Remote root login via SSH is a major attack vector", + "severity": "HIGH", + "check": "Check sshd_config for PermitRootLogin setting", + "expected": "PermitRootLogin set to 'no'", + "remediation": "Set PermitRootLogin no in /etc/ssh/sshd_config" + }, + { + "id": "SSH-002", + "category": "SSH Configuration", + "name": "SSH should not allow empty passwords", + "description": "Allowing empty passwords over SSH defeats authentication", + "severity": "CRITICAL", + "check": "Check sshd_config for PermitEmptyPasswords setting", + "expected": "PermitEmptyPasswords set to 'no'", + "remediation": "Set PermitEmptyPasswords no in /etc/ssh/sshd_config" + }, + { + "id": "SSH-003", + "category": "SSH Configuration", + "name": "SSH password authentication should be disabled", + "description": "Key-based authentication is more secure than password-based", + "severity": "MEDIUM", + "check": "Check sshd_config for PasswordAuthentication setting", + "expected": "PasswordAuthentication set to 'no'", + "remediation": "Set PasswordAuthentication no in /etc/ssh/sshd_config" + }, + { + "id": "SVC-001", + "category": "Services & Packages", + "name": "No unnecessary network services installed", + "description": "Services like telnet, ftp, and nmap should not be present in production containers", + "severity": "MEDIUM", + "check": "Check for presence of telnet, ftp, nmap, netcat binaries", + "expected": "None of these tools should be installed", + "remediation": "Remove unnecessary packages with apt-get remove" + }, + { + "id": "SVC-002", + "category": "Services & Packages", + "name": "No development tools in production container", + "description": "Compilers and build tools increase attack surface", + "severity": "LOW", + "check": "Check for presence of gcc, make, and similar build tools", + "expected": "No development tools installed", + "remediation": "Use multi-stage Docker builds to exclude build tools from final image" + }, + { + "id": "NET-001", + "category": "Network Configuration", + "name": "Only required ports should be exposed", + "description": "Minimise exposed ports to reduce attack surface", + "severity": "MEDIUM", + "check": "List exposed ports from the container configuration", + "expected": "Only ports required by the application (e.g., 80 or 443)", + "remediation": "Remove unnecessary EXPOSE directives and restrict port mappings" + }, + { + "id": "LOG-001", + "category": "Logging & Auditing", + "name": "Log files should have restrictive permissions", + "description": "Log files may contain sensitive information and should not be world-readable or writable", + "severity": "MEDIUM", + "check": "Check permissions on files in /var/log", + "expected": "Log files should be 640 or more restrictive", + "remediation": "Set correct permissions: chmod 640 /var/log/*.log" + }, + { + "id": "LOG-002", + "category": "Logging & Auditing", + "name": "Shell history should not contain secrets", + "description": "Command history files may contain passwords or tokens typed on the command line", + "severity": "HIGH", + "check": "Search .bash_history and similar files for password/key patterns", + "expected": "No secrets found in history files", + "remediation": "Clear history files and avoid typing secrets on the command line" + } + ] +} diff --git a/challenges/securityhardening/toolkit/placeholder.md b/challenges/securityhardening/toolkit/placeholder.md new file mode 100644 index 00000000..e69de29b