From 06dfca41b39819a91f2971d0314dfa9f9a188cd3 Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Mon, 5 May 2025 14:31:55 +0330 Subject: [PATCH 1/8] Initial Commit --- AlirezaMirzaei/Problem1_PostgreSql/README.md | 0 AlirezaMirzaei/Problem2_Redis/README.md | 0 AlirezaMirzaei/Problem3_Celery/README.md | 0 AlirezaMirzaei/Problem4_WebAPI/README.md | 0 AlirezaMirzaei/Problem5_NGINX/README.md | 0 DockerAssignemnt.pdf => DockerAssignment.pdf | Bin 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 AlirezaMirzaei/Problem1_PostgreSql/README.md create mode 100644 AlirezaMirzaei/Problem2_Redis/README.md create mode 100644 AlirezaMirzaei/Problem3_Celery/README.md create mode 100644 AlirezaMirzaei/Problem4_WebAPI/README.md create mode 100644 AlirezaMirzaei/Problem5_NGINX/README.md rename DockerAssignemnt.pdf => DockerAssignment.pdf (100%) diff --git a/AlirezaMirzaei/Problem1_PostgreSql/README.md b/AlirezaMirzaei/Problem1_PostgreSql/README.md new file mode 100644 index 00000000..e69de29b diff --git a/AlirezaMirzaei/Problem2_Redis/README.md b/AlirezaMirzaei/Problem2_Redis/README.md new file mode 100644 index 00000000..e69de29b diff --git a/AlirezaMirzaei/Problem3_Celery/README.md b/AlirezaMirzaei/Problem3_Celery/README.md new file mode 100644 index 00000000..e69de29b diff --git a/AlirezaMirzaei/Problem4_WebAPI/README.md b/AlirezaMirzaei/Problem4_WebAPI/README.md new file mode 100644 index 00000000..e69de29b diff --git a/AlirezaMirzaei/Problem5_NGINX/README.md b/AlirezaMirzaei/Problem5_NGINX/README.md new file mode 100644 index 00000000..e69de29b diff --git a/DockerAssignemnt.pdf b/DockerAssignment.pdf similarity index 100% rename from DockerAssignemnt.pdf rename to DockerAssignment.pdf From 850816eedc39de6b546b9c9b1ec3fa1332c0feb6 Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Sat, 10 May 2025 23:06:59 +0330 Subject: [PATCH 2/8] First 3 Problems [v0.1] --- .gitignore | 3 + AlirezaMirzaei/Problem1_PostgreSql/README.md | 42 ++++ AlirezaMirzaei/Problem1_PostgreSql/initdb.sql | 39 +++ .../Problem1_PostgreSql/setup_postgres.sh | 22 ++ AlirezaMirzaei/Problem2_Redis/README.md | 58 +++++ .../Problem2_Redis/redis-consumer.py | 74 ++++++ .../Problem2_Redis/redis-producer.py | 60 +++++ AlirezaMirzaei/Problem2_Redis/run-demo.sh | 29 +++ AlirezaMirzaei/Problem2_Redis/setup-redis.sh | 37 +++ .../Problem2_Redis/setup-redisinsight.sh | 24 ++ AlirezaMirzaei/Problem3_Celery/README.md | 121 +++++++++ .../Problem3_Celery/celery_app/__init__.py | 3 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 278 bytes .../__pycache__/celery_app.cpython-313.pyc | Bin 0 -> 641 bytes .../__pycache__/celery_config.cpython-313.pyc | Bin 0 -> 768 bytes .../__pycache__/tasks.cpython-313.pyc | Bin 0 -> 8689 bytes .../Problem3_Celery/celery_app/celery_app.py | 18 ++ .../celery_app/celery_config.py | 29 +++ .../Problem3_Celery/celery_app/tasks.py | 234 ++++++++++++++++++ .../Problem3_Celery/run_celery_test.sh | 16 ++ .../Problem3_Celery/run_celery_worker.sh | 10 + .../Problem3_Celery/setup_celery.sh | 13 + .../Problem3_Celery/test_celery_tasks.py | 76 ++++++ .../Problem6_DockerCompose/README.md | 145 +++++++++++ 24 files changed, 1053 insertions(+) create mode 100644 .gitignore create mode 100644 AlirezaMirzaei/Problem1_PostgreSql/initdb.sql create mode 100755 AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh create mode 100755 AlirezaMirzaei/Problem2_Redis/redis-consumer.py create mode 100755 AlirezaMirzaei/Problem2_Redis/redis-producer.py create mode 100755 AlirezaMirzaei/Problem2_Redis/run-demo.sh create mode 100755 AlirezaMirzaei/Problem2_Redis/setup-redis.sh create mode 100755 AlirezaMirzaei/Problem2_Redis/setup-redisinsight.sh create mode 100644 AlirezaMirzaei/Problem3_Celery/celery_app/__init__.py create mode 100644 AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/__init__.cpython-313.pyc create mode 100644 AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/celery_app.cpython-313.pyc create mode 100644 AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/celery_config.cpython-313.pyc create mode 100644 AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc create mode 100755 AlirezaMirzaei/Problem3_Celery/celery_app/celery_app.py create mode 100755 AlirezaMirzaei/Problem3_Celery/celery_app/celery_config.py create mode 100755 AlirezaMirzaei/Problem3_Celery/celery_app/tasks.py create mode 100755 AlirezaMirzaei/Problem3_Celery/run_celery_test.sh create mode 100755 AlirezaMirzaei/Problem3_Celery/run_celery_worker.sh create mode 100755 AlirezaMirzaei/Problem3_Celery/setup_celery.sh create mode 100755 AlirezaMirzaei/Problem3_Celery/test_celery_tasks.py create mode 100644 AlirezaMirzaei/Problem6_DockerCompose/README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2f5cee40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +AlirezaMirzaei/Problem2_Redis/redis.conf +AlirezaMirzaei/Problem2_Redis/redis-data +AlirezaMirzaei/Problem2_Redis/consumer.log diff --git a/AlirezaMirzaei/Problem1_PostgreSql/README.md b/AlirezaMirzaei/Problem1_PostgreSql/README.md index e69de29b..58f173c9 100644 --- a/AlirezaMirzaei/Problem1_PostgreSql/README.md +++ b/AlirezaMirzaei/Problem1_PostgreSql/README.md @@ -0,0 +1,42 @@ +# My PostgreSQL Container Setup + +This here is a quick way to spin up a PostgreSQL database with my schema and sample data already loaded, so here’s exactly how I did it for this assignment: + +1. **Write the initialization script** + + In my project root I created an `init-db.sql` file containing everything I wanted PostgreSQL to run on first boot: + + - Create the database and initialize it. + - Create necessary tables (teams). + - Insert value into the tables and initialize them with the data necessary. + - Some changes to the database and confirmations that are in the container log after running the container. + +2. **Create my startup script** + + I wrote `setup-postgres.sh` so I could reproduce this on any machine: + + - The script runs the docker container using the username and credentials provided plainly, this could be parameterized or given as a docker secret but seeing as this is a local installation with no need to access the internet i made it more simple. + - Then the script initializes, fills and updates the database using the file `init-db.sql`. + - Then the container status is checked via the command `pg_isready` in a loop. + +3. **Verify the result** + + After running `chmod +x setup-postgres.sh` and `./setup-postgres.sh`, I double-checked the container to be running: + + ``` + docker exec -it postgres_ctf \ + psql -U postgres -d ctf_db \ + -c "SELECT * FROM teams;" + ``` + + This should output the final data in the database after any change we make to the `teams` table + +4. **Tearing down for a fresh start** + + Whenever I want to start over, I simply do: + + ``` + docker stop postgres_ctf && docker rm postgres_ctf + docker volume rm postgres_data + ./setup-postgres.sh + ``` diff --git a/AlirezaMirzaei/Problem1_PostgreSql/initdb.sql b/AlirezaMirzaei/Problem1_PostgreSql/initdb.sql new file mode 100644 index 00000000..f0c4bfc3 --- /dev/null +++ b/AlirezaMirzaei/Problem1_PostgreSql/initdb.sql @@ -0,0 +1,39 @@ +-- Create a new database +CREATE DATABASE ctf_db; + +-- Connect to the newly created database +\connect ctf_db; + +-- Create a table to store team information +CREATE TABLE teams ( + id SERIAL PRIMARY KEY, + team_name VARCHAR(100) NOT NULL, + challenge_assigned BOOLEAN DEFAULT FALSE +); + +-- Insert sample data into the table +INSERT INTO teams (team_name, challenge_assigned) +VALUES + ('Red Team', true), + ('Blue Team', false), + ('Green Team', false); + +-- This is just to verify the data was inserted +-- The output will show in the container logs +SELECT * FROM teams; + +-- Update a team's challenge assignment status +UPDATE teams +SET challenge_assigned = true +WHERE team_name = 'Blue Team'; + +-- Delete a team from the table +DELETE FROM teams +WHERE team_name = 'Red Team'; + +-- Insert an additional row as requested +INSERT INTO teams (team_name, challenge_assigned) +VALUES ('Yellow Team', true); + +-- Final state of the table +SELECT * FROM teams; \ No newline at end of file diff --git a/AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh b/AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh new file mode 100755 index 00000000..af4e5dfa --- /dev/null +++ b/AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Create volume for PostgreSQL data persistence +docker volume create postgres_data + +# Run PostgreSQL container with initialization +docker run --name postgres_ctf \ + -e POSTGRES_USERNAME=ctfadmin \ + -e POSTGRES_PASSWORD=s#cr#tpasssswithasalt \ + -d \ + -p 5432:5432 \ + -v postgres_data:/var/lib/postgresql/data \ + -v $(pwd)/initdb.sql:/docker-entrypoint-initdb.d/init-db.sql \ + postgres:14-alpine + +# 3. Wait until Postgres is actually ready +echo "Waiting for PostgreSQL to initialize…" +until docker exec postgres_ctf pg_isready -U postgres >/dev/null 2>&1; do + sleep 1 +done + +echo "PostgreSQL container initialized with our database schema and data!" diff --git a/AlirezaMirzaei/Problem2_Redis/README.md b/AlirezaMirzaei/Problem2_Redis/README.md index e69de29b..81ddc1c9 100644 --- a/AlirezaMirzaei/Problem2_Redis/README.md +++ b/AlirezaMirzaei/Problem2_Redis/README.md @@ -0,0 +1,58 @@ +# My Redis Server & IPC Demo + +We also need a lightweight pub/sub demo alongside Redis key/value storage, so here’s how I put that together for this assignment: + +1. **Spinning up Redis** + In `setup-redis.sh` I: + - I made a folder for redis data (volume) to be mounted in. + - I configured redis for listening address and mode of data access in `redis.conf`. + - I start the container with image `6-alpine` with the given settings and options. + - Check if redis is running successfuly using ping and pong command and output. +2. **Preparing my Python environment** + I set up a virtualenv and installed `redis`: + + ```bash + python -m venv .venv + source .venv/bin/activate + pip install redis + ``` + +3. **Writing the producer** (`redis-producer.py`) + + - Connect to Redis + - Set a few string keys and a hash + - Publish a “notification” every second, incrementing a counter + +4. **Writing the consumer** (`redis-consumer.py`) + + - Connect to Redis + - Dump all existing keys/hashes on start + - Subscribe to the `notifications` channel and print each new message + - The output is printed to stdout and can easily be inspected. + +5. **Wrapping it all together** + In `run-demo.sh` I made sure Redis is running, then: + + - I run the consumer file in background and set it's output to `consumer.log`. + - I run the producer file in foreground and stop the consumer when the producer is stopped. + - The logs can be viewed in `consumer.log`. + +6. **Monitoring with RedisInsight** + I used `setup-redisinsight.sh` to spin up RedisInsight so I could watch keys, hashes, and pub/sub traffic through a GUI. + +7. **Wrap Up** + The commands for using this module are run in this order: + + - `./setup-redis.sh` to run and install redis. + - `./setup-redisinsight.sh` to run and install redis insight. + - Install requirements as mentioned in step 2 (don't forget). + - Run the demo file: `./run-demo.sh`. + - Inspect the outputs and redis insight logs. + +8. **To Restart**: + - Run `docker stop redis-server` and `docker rm redis-server`. + - Run the commands above again. + +--- + +That’s exactly how I went step-by-step through each setup for this assignment—hope it’s clear and easy to follow from my perspective! diff --git a/AlirezaMirzaei/Problem2_Redis/redis-consumer.py b/AlirezaMirzaei/Problem2_Redis/redis-consumer.py new file mode 100755 index 00000000..f9d51f69 --- /dev/null +++ b/AlirezaMirzaei/Problem2_Redis/redis-consumer.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import redis +import sys +import threading +import time + + +def subscribe_to_channel(r, channel): + """Subscribe to the specified Redis channel and print messages""" + print(f"Consumer: Subscribing to channel '{channel}'...") + pubsub = r.pubsub() + pubsub.subscribe(channel) + + # Process messages + print(f"Consumer: Listening for messages on channel '{channel}'...") + for message in pubsub.listen(): + if message["type"] == "message": + print(f"Consumer: Received message: {message['data']}") + + +def fetch_data(r): + """Periodically fetch and display data from Redis""" + while True: + try: + # Get simple key-value pairs + service_status = r.get("service_status") + last_update = r.get("last_update") + message_count = r.get("message_count") + + # Get hash data + system_info = r.hgetall("system_info") + + # Print the data + print("\nConsumer: Current Redis Data:") + print(f" - service_status: {service_status}") + print(f" - last_update: {last_update}") + print(f" - message_count: {message_count}") + print(" - system_info:") + for key, value in system_info.items(): + print(f" {key}: {value}") + + # Wait before polling again + time.sleep(5) + except Exception as e: + print(f"Consumer: Error fetching data: {e}", file=sys.stderr) + time.sleep(5) + + +def main(): + print("Consumer: Connecting to Redis server...") + try: + # Connect to Redis server + r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True) + r.ping() # Test connection + print("Consumer: Successfully connected to Redis server") + except redis.ConnectionError as e: + print(f"Consumer: Failed to connect to Redis: {e}", file=sys.stderr) + sys.exit(1) + + # Start a thread to fetch data periodically + data_thread = threading.Thread(target=fetch_data, args=(r,), daemon=True) + data_thread.start() + + # Subscribe to channel in the main thread + try: + subscribe_to_channel(r, "notifications") + except KeyboardInterrupt: + print("\nConsumer: Shutting down...") + + print("Consumer: Done") + + +if __name__ == "__main__": + main() diff --git a/AlirezaMirzaei/Problem2_Redis/redis-producer.py b/AlirezaMirzaei/Problem2_Redis/redis-producer.py new file mode 100755 index 00000000..8e0bb174 --- /dev/null +++ b/AlirezaMirzaei/Problem2_Redis/redis-producer.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +import redis +import time +import sys + + +def main(): + print("Producer: Connecting to Redis server...") + try: + # Connect to Redis server + r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True) + r.ping() # Test connection + print("Producer: Successfully connected to Redis server") + except redis.ConnectionError as e: + print(f"Producer: Failed to connect to Redis: {e}", file=sys.stderr) + sys.exit(1) + + # Set some key-value pairs + print("Producer: Setting key-value pairs...") + r.set("service_status", "running") + r.set("last_update", time.strftime("%Y-%m-%d %H:%M:%S")) + r.set("message_count", "0") + + # Create a hash (dictionary) + r.hset( + "system_info", + mapping={ + "hostname": "redis-test", + "version": "1.0.0", + "environment": "development", + }, + ) + + print("Producer: Key-value pairs set successfully") + + # Publish messages to a channel + channel = "notifications" + message_count = 0 + + print(f"Producer: Starting to publish messages to channel '{channel}'") + print("Producer: Press Ctrl+C to stop") + + try: + while True: + message_count += 1 + message = f"Message #{message_count} at {time.strftime('%H:%M:%S')}" + r.publish(channel, message) + r.set("message_count", str(message_count)) + print(f"Producer: Published: {message}") + + # Sleep for 2 seconds between messages + time.sleep(2) + except KeyboardInterrupt: + print("\nProducer: Stopping message publication") + + print("Producer: Done") + + +if __name__ == "__main__": + main() diff --git a/AlirezaMirzaei/Problem2_Redis/run-demo.sh b/AlirezaMirzaei/Problem2_Redis/run-demo.sh new file mode 100755 index 00000000..1497a638 --- /dev/null +++ b/AlirezaMirzaei/Problem2_Redis/run-demo.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "Ensuring Redis is running..." +docker ps | grep redis-server > /dev/null +if [ $? -ne 0 ]; then + echo "Redis server not running. Starting it now..." + ./setup-redis.sh +fi + +# Run the consumer in background +echo "Starting Redis consumer in background..." +./redis-consumer.py > consumer.log 2>&1 & +CONSUMER_PID=$! + +echo "Consumer started with PID $CONSUMER_PID" +echo "Consumer logs are being written to consumer.log" + +# Wait a bit to ensure consumer is ready +sleep 2 + +echo "Starting Redis producer in foreground..." +echo "Press Ctrl+C to stop the producer when done" +./redis-producer.py + +# When producer is stopped, also stop the consumer +echo "Stopping consumer process..." +kill $CONSUMER_PID + +echo "Demo completed. You can view the consumer logs in consumer.log" diff --git a/AlirezaMirzaei/Problem2_Redis/setup-redis.sh b/AlirezaMirzaei/Problem2_Redis/setup-redis.sh new file mode 100755 index 00000000..e2aa1a6b --- /dev/null +++ b/AlirezaMirzaei/Problem2_Redis/setup-redis.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Create a directory for Redis data +mkdir -p redis-data + +# Create a simple Redis configuration file for our needs +cat >redis.conf <$h=DAy?83PXvC%*@2Uej4R$Bc@A$nhd~4F=y|IR7)O}c9wC}UOf|cF*#vW#?i4> zX}Uvh-_#+?>wS#23E58V&pY>)M~zlJGQBwTw{VW8%x^+S2^D}1b^y5PPt$7asXjW~ X@kP3yAB_3`uZ0leyDY_$mr4ErDYsLK literal 0 HcmV?d00001 diff --git a/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/celery_app.cpython-313.pyc b/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/celery_app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2db5a557bc3e3186cdb238b5c7dc09dbe2d64872 GIT binary patch literal 641 zcmZWmK~K~`6n@hwZC$bo8~}+i)!QmE0m6kC6S5~H7()+uAeju^+3vV)XPI_2^n?ea zH%})11^>+&NHlXY@#JlBso&-?SEoK33}O+oT*%b#ikLTkmi!jdPDRl3p-gil z;EEc&dCejg609 zs2pfl$%M+|mpsh%0vqPCBeGDwGN=FbOIgB=$4r(pm4Ajwd0~A*dNSdJ#X|9dCFD@5 zel}ns&qx@{VTYK5`&{*2lXh7?+Q~8zr3p`SvQugHg(?^q1Vs z8=PwIaUwgzm_Jc-<_6`*W``z?F+%9lwvheX1GIAKfZH53{d4Eu86G=;?60u+1r|q3 p>ld(5Z!A9;Ej_$|)$e%reQ}O&f3w|pyKi>i?u{|nE9$24{sUYptiu2R literal 0 HcmV?d00001 diff --git a/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/celery_config.cpython-313.pyc b/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/celery_config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7a40365b6c9577a1095fde6421c567f5f38de37 GIT binary patch literal 768 zcmb7C%Wl&^6dfmNk{UHpN@-dJiEJVz5->m|9uY#Sx~70!!~&tw#GWKW#vbdAL#bEc z57_V#@HJ&u&6Xbsix5|q3$lyO$PGIe0Xt3cJ;fEv!aSA|Wl2JWOj*i1X_kx&B8y1tIaaxw>= zNF8;@QVBQR0})6wq*Q62tEo20@czMrNA%7=D|c<>t|mF~=vRzS8r6;yv*I(^Gns!CQyM$Vn99mh^DF=Bb2~3rU}Y9w`wXuyrp` zKW_=f6cQVy82W1!g@mI17O*H-qqS^jp literal 0 HcmV?d00001 diff --git a/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc b/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b49559d41668a9b0d1448ee1c6756613c977db8a GIT binary patch literal 8689 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 literal 0 HcmV?d00001 diff --git a/AlirezaMirzaei/Problem3_Celery/celery_app/celery_app.py b/AlirezaMirzaei/Problem3_Celery/celery_app/celery_app.py new file mode 100755 index 00000000..523d39bc --- /dev/null +++ b/AlirezaMirzaei/Problem3_Celery/celery_app/celery_app.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +""" +Main Celery application file +""" + +from celery import Celery + +# Create Celery app +app = Celery("container_manager") + +# Load configuration from config file +app.config_from_object("celery_app.celery_config") + +# Auto-discover tasks in specified modules +app.autodiscover_tasks(["celery_app.tasks"]) + +if __name__ == "__main__": + app.start() diff --git a/AlirezaMirzaei/Problem3_Celery/celery_app/celery_config.py b/AlirezaMirzaei/Problem3_Celery/celery_app/celery_config.py new file mode 100755 index 00000000..49d4ea02 --- /dev/null +++ b/AlirezaMirzaei/Problem3_Celery/celery_app/celery_config.py @@ -0,0 +1,29 @@ +""" +Celery configuration file +""" + +# Broker settings +broker_url = "redis://localhost:6379/0" + +# Result backend settings +result_backend = "redis://localhost:6379/1" + +# Task serialization format +task_serializer = "json" + +# Result serialization format +result_serializer = "json" + +# Accepted content types +accept_content = ["json"] + +# Task result expires in 1 day +result_expires = 86400 + +# Enable task events for monitoring +worker_send_task_events = True +task_send_sent_event = True + +# Logging +worker_log_format = "[%(asctime)s: %(levelname)s/%(processName)s] %(message)s" +worker_task_log_format = "[%(asctime)s: %(levelname)s/%(processName)s] [%(task_name)s(%(task_id)s)] %(message)s" diff --git a/AlirezaMirzaei/Problem3_Celery/celery_app/tasks.py b/AlirezaMirzaei/Problem3_Celery/celery_app/tasks.py new file mode 100755 index 00000000..8f2a6fd2 --- /dev/null +++ b/AlirezaMirzaei/Problem3_Celery/celery_app/tasks.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +""" +Celery tasks for managing Docker containers for CTF challenges +""" + +import logging +import docker +from celery_app.celery_app import app + +# Set up logging +logging.basicConfig( + level=logging.INFO, + format="[%(asctime)s] [%(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) + +logger = logging.getLogger(__name__) + +# Available CTF challenges per team +CTF_CHALLENGES = { + "todo-app": { + "image": "pasapples/apjctf-todo-java-app:latest", + "ports": {"8080/tcp": ("0.0.0.0", 8080)}, + "environment": {"JAVA_OPTS": "-Xmx512m"}, + }, + "juice-shop": { + "image": "bkimminich/juice-shop", + "ports": {"3000/tcp": ("0.0.0.0", 3000)}, + "environment": {}, + }, +} + + +@app.task(bind=True, name="tasks.start_container", max_retries=3) +def start_container(self, challenge_name, team_id): + """ + Start a Docker container for a specific CTF challenge + + Args: + challenge_name (str): Name of the challenge ('todo-app' or 'juice-shop') + team_id (str): Unique identifier for the team + + Returns: + dict: Container information including ID and status + """ + try: + if challenge_name not in CTF_CHALLENGES: + error_msg = f"Unknown challenge: {challenge_name}" + logger.error(error_msg) + return {"status": "error", "message": error_msg} + + # Initialize Docker client + client = docker.from_env() + + # Get challenge configuration + challenge = CTF_CHALLENGES[challenge_name] + + # Create container name with team_id to make it unique + container_name = f"{challenge_name}-team-{team_id}" + + # Check if container with this name already exists + existing_containers = client.containers.list( + all=True, filters={"name": container_name} + ) + if existing_containers: + # If container exists, start it if it's not running + container = existing_containers[0] + if container.status != "running": + logger.info(f"Starting existing container {container_name}") + container.start() + else: + logger.info(f"Container {container_name} is already running") + else: + # Create and start new container + logger.info(f"Creating new container for {challenge_name} (Team {team_id})") + container = client.containers.run( + image=challenge["image"], + detach=True, + name=container_name, + ports=challenge["ports"], + environment=challenge["environment"], + restart_policy={"Name": "unless-stopped"}, + ) + + # Get container info + container.reload() + container_info = { + "id": container.id, + "name": container.name, + "status": container.status, + "image": container.image.tags[0] + if container.image.tags + else str(container.image.id), + "ports": challenge["ports"], + } + + logger.info(f"Container started successfully: {container_info}") + return {"status": "success", "container": container_info} + + except docker.errors.APIError as e: + logger.error(f"Docker API error: {str(e)}") + # Retry with exponential backoff + self.retry(exc=e, countdown=2**self.request.retries) + except Exception as e: + logger.error(f"Error starting container: {str(e)}") + return {"status": "error", "message": str(e)} + + +@app.task(bind=True, name="tasks.stop_container", max_retries=3) +def stop_container(self, container_id): + """ + Stop a running Docker container + + Args: + container_id (str): ID of the container to stop + + Returns: + dict: Status information + """ + try: + # Initialize Docker client + client = docker.from_env() + + # Try to get the container + try: + container = client.containers.get(container_id) + except docker.errors.NotFound: + error_msg = f"Container {container_id} not found" + logger.error(error_msg) + return {"status": "error", "message": error_msg} + + # Stop the container + logger.info(f"Stopping container {container_id}") + container.stop(timeout=10) # Give it 10 seconds to gracefully stop + + # Verify container is stopped + container.reload() + + result = { + "status": "success", + "container_id": container_id, + "container_status": container.status, + "message": f"Container {container_id} stopped successfully", + } + + logger.info(result["message"]) + return result + + except docker.errors.APIError as e: + logger.error(f"Docker API error: {str(e)}") + # Retry with exponential backoff + self.retry(exc=e, countdown=2**self.request.retries) + except Exception as e: + error_msg = f"Error stopping container: {str(e)}" + logger.error(error_msg) + return {"status": "error", "message": error_msg} + + +@app.task(bind=True, name="tasks.get_container_status", max_retries=3) +def get_container_status(self, container_id=None, team_id=None, challenge_name=None): + """ + Get status information for containers + + Args: + container_id (str, optional): Specific container ID + team_id (str, optional): Team ID to filter containers + challenge_name (str, optional): Challenge name to filter containers + + Returns: + dict: Container status information + """ + try: + # Initialize Docker client + client = docker.from_env() + + # If container_id is provided, get specific container + if container_id: + try: + container = client.containers.get(container_id) + container.reload() + return { + "status": "success", + "container": { + "id": container.id, + "name": container.name, + "status": container.status, + "image": container.image.tags[0] + if container.image.tags + else str(container.image.id), + }, + } + except docker.errors.NotFound: + return { + "status": "error", + "message": f"Container {container_id} not found", + } + + # Otherwise get all containers matching filters + filters = {} + if team_id and challenge_name: + filters["name"] = f"{challenge_name}-team-{team_id}" + elif team_id: + filters["name"] = f"team-{team_id}" + elif challenge_name: + filters["name"] = f"{challenge_name}" + + containers = client.containers.list(all=True, filters=filters) + + results = { + "status": "success", + "count": len(containers), + "containers": [ + { + "id": container.id, + "name": container.name, + "status": container.status, + "image": container.image.tags[0] + if container.image.tags + else str(container.image.id), + } + for container in containers + ], + } + + return results + + except docker.errors.APIError as e: + logger.error(f"Docker API error: {str(e)}") + # Retry with exponential backoff + self.retry(exc=e, countdown=2**self.request.retries) + except Exception as e: + error_msg = f"Error getting container status: {str(e)}" + logger.error(error_msg) + return {"status": "error", "message": error_msg} diff --git a/AlirezaMirzaei/Problem3_Celery/run_celery_test.sh b/AlirezaMirzaei/Problem3_Celery/run_celery_test.sh new file mode 100755 index 00000000..5c4ddc60 --- /dev/null +++ b/AlirezaMirzaei/Problem3_Celery/run_celery_test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +echo "Running Celery container management test..." +cd $(dirname "$0") + +# Set Python path to include current directory +export PYTHONPATH=$PYTHONPATH:$(pwd) + +# Default values +CHALLENGE=${1:-"todo-app"} +TEAM_ID=${2:-"team1"} + +# Run the test script +python test_celery_tasks.py $CHALLENGE $TEAM_ID + +echo "Test completed!" diff --git a/AlirezaMirzaei/Problem3_Celery/run_celery_worker.sh b/AlirezaMirzaei/Problem3_Celery/run_celery_worker.sh new file mode 100755 index 00000000..1f3b23ee --- /dev/null +++ b/AlirezaMirzaei/Problem3_Celery/run_celery_worker.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +echo "Starting Celery worker..." +cd $(dirname "$0") + +# Set Python path to include current directory +export PYTHONPATH=$PYTHONPATH:$(pwd) + +# Start the Celery worker +celery -A celery_app.celery_app worker --loglevel=info diff --git a/AlirezaMirzaei/Problem3_Celery/setup_celery.sh b/AlirezaMirzaei/Problem3_Celery/setup_celery.sh new file mode 100755 index 00000000..ad847fb1 --- /dev/null +++ b/AlirezaMirzaei/Problem3_Celery/setup_celery.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "Setting up Celery with Redis as message broker..." + +# Create directory structure +mkdir -p celery_app + +# Install required packages +echo "Installing required Python packages..." +pip install celery redis docker + +echo "Creating Celery application structure..." +echo "Celery setup completed!" diff --git a/AlirezaMirzaei/Problem3_Celery/test_celery_tasks.py b/AlirezaMirzaei/Problem3_Celery/test_celery_tasks.py new file mode 100755 index 00000000..263bd893 --- /dev/null +++ b/AlirezaMirzaei/Problem3_Celery/test_celery_tasks.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Script to test Celery tasks for container management +""" + +import sys +import time +import json +from celery_app.tasks import start_container, stop_container, get_container_status + + +def print_response(title, response): + """Print task response in a formatted way""" + print(f"\n===== {title} =====") + print(json.dumps(response, indent=2)) + print("=" * (len(title) + 12)) + + +def main(): + """Test Celery container management tasks""" + if len(sys.argv) < 3: + print("Usage: python test_celery_tasks.py ") + print(" challenge_name: 'todo-app' or 'juice-shop'") + print(" team_id: Team identifier (e.g., 'team1')") + sys.exit(1) + + challenge_name = sys.argv[1] + team_id = sys.argv[2] + + print( + f"Testing Celery tasks with challenge '{challenge_name}' for team '{team_id}'..." + ) + + # First, check container status before starting anything + print("\nChecking initial container status...") + status = get_container_status.delay( + team_id=team_id, challenge_name=challenge_name + ).get() + print_response("Initial Status", status) + + # Start container + print("\nStarting container...") + start_result = start_container.delay(challenge_name, team_id).get() + print_response("Start Container Result", start_result) + + if start_result.get("status") != "success": + print("Failed to start container. Exiting.") + sys.exit(1) + + container_id = start_result["container"]["id"] + + # Wait a moment and check container status + print("\nWaiting for container to fully start...") + time.sleep(5) + + status = get_container_status.delay(container_id=container_id).get() + print_response("Container Status After Start", status) + + # Let the user see the running container + input("\nPress Enter to stop the container...") + + # Stop container + print("\nStopping container...") + stop_result = stop_container.delay(container_id).get() + print_response("Stop Container Result", stop_result) + + # Check final status + time.sleep(2) + status = get_container_status.delay(container_id=container_id).get() + print_response("Final Container Status", status) + + print("\nTest completed!") + + +if __name__ == "__main__": + main() diff --git a/AlirezaMirzaei/Problem6_DockerCompose/README.md b/AlirezaMirzaei/Problem6_DockerCompose/README.md new file mode 100644 index 00000000..8862a6e5 --- /dev/null +++ b/AlirezaMirzaei/Problem6_DockerCompose/README.md @@ -0,0 +1,145 @@ +# PostgreSQL Container with Automatic Schema Initialization + +This document explains how to set up a PostgreSQL container that automatically initializes with your database schema and sample data when it starts. + +## How It Works + +PostgreSQL's official Docker image looks for initialization scripts in the `/docker-entrypoint-initdb.d/` directory. Any `.sql`, `.sql.gz`, or `.sh` files in this directory will be executed in alphabetical order when the container is first initialized. + +## Setup Instructions + +### 1. Create the SQL Initialization Script + +Create a file named `init-db.sql` with your database schema and sample data: + +```sql +-- Create a new database +CREATE DATABASE ctf_db; + +-- Connect to the newly created database +\connect ctf_db; + +-- Create a table to store team information +CREATE TABLE teams ( + id SERIAL PRIMARY KEY, + team_name VARCHAR(100) NOT NULL, + challenge_assigned BOOLEAN DEFAULT FALSE +); + +-- Insert sample data into the table +INSERT INTO teams (team_name, challenge_assigned) +VALUES + ('Red Team', true), + ('Blue Team', false), + ('Green Team', false); + +-- Update a team's challenge assignment status +UPDATE teams +SET challenge_assigned = true +WHERE team_name = 'Blue Team'; + +-- Delete a team from the table +DELETE FROM teams +WHERE team_name = 'Red Team'; + +-- Insert an additional row as requested +INSERT INTO teams (team_name, challenge_assigned) +VALUES ('Yellow Team', true); + +-- Final state of the table +SELECT * FROM teams; +``` + +### 2. Create a Bash Script to Run the Container + +Create a file named `setup-postgres.sh`: + +```bash +#!/bin/bash + +# Create volume for PostgreSQL data persistence +docker volume create postgres_data + +# Run PostgreSQL container with initialization +docker run --name postgres_ctf \ + -e POSTGRES_PASSWORD=secretpassword \ + -d \ + -p 5432:5432 \ + -v postgres_data:/var/lib/postgresql/data \ + -v $(pwd)/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql \ + postgres:latest + +# Wait for PostgreSQL to be ready +echo "Waiting for PostgreSQL to initialize..." +sleep 5 + +# Verify container is running +docker ps -a | grep postgres_ctf + +echo "PostgreSQL container initialized with your database schema!" +``` + +### 3. Make the Script Executable and Run It + +```bash +chmod +x run-postgres.sh +./setup-postgres.sh +``` + +## Important Notes + +1. **First-Time Initialization Only**: The initialization scripts only run when the container is first created and the data volume is empty. If you stop and restart the container, the scripts will not run again. + +2. **Checking Initialization Results**: You can check if your initialization worked by connecting to the database: + + ```bash + docker exec -it postgres_ctf psql -U postgres -d ctf_db -c "SELECT * FROM teams;" + ``` + +3. **For Re-initialization**: If you need to rerun the initialization, you must remove both the container and the volume: + + ```bash + docker stop postgres_ctf + docker rm postgres_ctf + docker volume rm postgres_data + ./setup-postgres.sh + ``` + +## Demonstration + +When you run the setup script: + +1. A Docker volume named `postgres_data` is created for data persistence +2. The PostgreSQL container starts with the initialization script mounted +3. PostgreSQL automatically executes the initialization script +4. The database, tables, and sample data are created + +After the container initialization, your database will contain: +- A database named `ctf_db` +- A table named `teams` with: + - Blue Team (challenge_assigned = true) + - Green Team (challenge_assigned = false) + - Yellow Team (challenge_assigned = true) +- The Red Team row was deleted as per your SQL script + +## For Docker Compose Integration + +For Docker Compose later on, we will place our initialization script in a directory and map it in the services section: + +```yaml +services: + postgres: + image: postgres:latest + environment: + POSTGRES_PASSWORD: somepassword + volumes: + - postgres_data:/var/lib/postgresql/data + - ./postgres-init:/docker-entrypoint-initdb.d + ports: + - "5432:5432" + +volumes: + postgres_data: +``` + +Then we place our `init-db.sql` file in the `./postgres-init` directory to initialize the db. \ No newline at end of file From 888b8c03cbb5e3dd2283d71630554f41d0428064 Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Sat, 10 May 2025 23:44:31 +0330 Subject: [PATCH 3/8] Minor bug fixes in part2-redis --- .../Problem1_PostgreSql/setup_postgres.sh | 2 +- .../Problem2_Redis/redis-consumer.py | 56 ++++++++++--------- AlirezaMirzaei/Problem2_Redis/run-demo.sh | 3 +- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh b/AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh index af4e5dfa..1fc1cc51 100755 --- a/AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh +++ b/AlirezaMirzaei/Problem1_PostgreSql/setup_postgres.sh @@ -13,7 +13,7 @@ docker run --name postgres_ctf \ -v $(pwd)/initdb.sql:/docker-entrypoint-initdb.d/init-db.sql \ postgres:14-alpine -# 3. Wait until Postgres is actually ready +# Wait until Postgres is actually ready echo "Waiting for PostgreSQL to initialize…" until docker exec postgres_ctf pg_isready -U postgres >/dev/null 2>&1; do sleep 1 diff --git a/AlirezaMirzaei/Problem2_Redis/redis-consumer.py b/AlirezaMirzaei/Problem2_Redis/redis-consumer.py index f9d51f69..fad78726 100755 --- a/AlirezaMirzaei/Problem2_Redis/redis-consumer.py +++ b/AlirezaMirzaei/Problem2_Redis/redis-consumer.py @@ -3,71 +3,73 @@ import sys import threading import time +import logging + +# Configure logging to write everything to consumer.log +logging.basicConfig( + filename="consumer.log", + filemode="a", + format="%(asctime)s %(levelname)s %(message)s", + level=logging.INFO, +) def subscribe_to_channel(r, channel): - """Subscribe to the specified Redis channel and print messages""" - print(f"Consumer: Subscribing to channel '{channel}'...") + """Subscribe to the specified Redis channel and log messages""" + logging.info(f"Subscribing to channel '{channel}'...") pubsub = r.pubsub() pubsub.subscribe(channel) - # Process messages - print(f"Consumer: Listening for messages on channel '{channel}'...") for message in pubsub.listen(): if message["type"] == "message": - print(f"Consumer: Received message: {message['data']}") + data = message["data"] + logging.info(f"Received message on '{channel}': {data}") def fetch_data(r): - """Periodically fetch and display data from Redis""" + """Periodically fetch and log data from Redis""" while True: try: - # Get simple key-value pairs service_status = r.get("service_status") last_update = r.get("last_update") message_count = r.get("message_count") - - # Get hash data system_info = r.hgetall("system_info") - # Print the data - print("\nConsumer: Current Redis Data:") - print(f" - service_status: {service_status}") - print(f" - last_update: {last_update}") - print(f" - message_count: {message_count}") - print(" - system_info:") + logging.info("Current Redis Data:") + logging.info(f" service_status: {service_status}") + logging.info(f" last_update: {last_update}") + logging.info(f" message_count: {message_count}") + logging.info(" system_info:") for key, value in system_info.items(): - print(f" {key}: {value}") + logging.info(f" {key}: {value}") - # Wait before polling again time.sleep(5) except Exception as e: - print(f"Consumer: Error fetching data: {e}", file=sys.stderr) + logging.error(f"Error fetching data: {e}", exc_info=True) time.sleep(5) def main(): - print("Consumer: Connecting to Redis server...") + logging.info("Connecting to Redis server...") try: - # Connect to Redis server r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True) - r.ping() # Test connection - print("Consumer: Successfully connected to Redis server") + r.ping() + logging.info("Successfully connected to Redis server") except redis.ConnectionError as e: - print(f"Consumer: Failed to connect to Redis: {e}", file=sys.stderr) + logging.error(f"Failed to connect to Redis: {e}") sys.exit(1) - # Start a thread to fetch data periodically + # Start data-fetch thread data_thread = threading.Thread(target=fetch_data, args=(r,), daemon=True) data_thread.start() - # Subscribe to channel in the main thread + # Run subscriber in main thread try: subscribe_to_channel(r, "notifications") except KeyboardInterrupt: - print("\nConsumer: Shutting down...") + logging.info("Shutting down consumer due to keyboard interrupt") - print("Consumer: Done") + logging.info("Consumer exiting") if __name__ == "__main__": diff --git a/AlirezaMirzaei/Problem2_Redis/run-demo.sh b/AlirezaMirzaei/Problem2_Redis/run-demo.sh index 1497a638..2cc5a88d 100755 --- a/AlirezaMirzaei/Problem2_Redis/run-demo.sh +++ b/AlirezaMirzaei/Problem2_Redis/run-demo.sh @@ -9,12 +9,13 @@ fi # Run the consumer in background echo "Starting Redis consumer in background..." -./redis-consumer.py > consumer.log 2>&1 & +./redis-consumer.py & CONSUMER_PID=$! echo "Consumer started with PID $CONSUMER_PID" echo "Consumer logs are being written to consumer.log" + # Wait a bit to ensure consumer is ready sleep 2 From 343734690b767101975c9723426c889e11329e18 Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Sun, 11 May 2025 00:52:37 +0330 Subject: [PATCH 4/8] Part1 Complete --- AlirezaMirzaei/Problem1_PostgreSql/README.md | 4 ++++ AlirezaMirzaei/Problem2_Redis/README.md | 3 +++ AlirezaMirzaei/Problem3_Celery/README.md | 3 +++ 3 files changed, 10 insertions(+) diff --git a/AlirezaMirzaei/Problem1_PostgreSql/README.md b/AlirezaMirzaei/Problem1_PostgreSql/README.md index 58f173c9..2a5c69d1 100644 --- a/AlirezaMirzaei/Problem1_PostgreSql/README.md +++ b/AlirezaMirzaei/Problem1_PostgreSql/README.md @@ -40,3 +40,7 @@ This here is a quick way to spin up a PostgreSQL database with my schema and sam docker volume rm postgres_data ./setup-postgres.sh ``` + +### End. The video links: + +https://iutbox.iut.ac.ir/index.php/s/oLbwink98GPyA36 diff --git a/AlirezaMirzaei/Problem2_Redis/README.md b/AlirezaMirzaei/Problem2_Redis/README.md index 81ddc1c9..5b22c1f3 100644 --- a/AlirezaMirzaei/Problem2_Redis/README.md +++ b/AlirezaMirzaei/Problem2_Redis/README.md @@ -53,6 +53,9 @@ We also need a lightweight pub/sub demo alongside Redis key/value storage, so he - Run `docker stop redis-server` and `docker rm redis-server`. - Run the commands above again. +### End. The video links: +https://iutbox.iut.ac.ir/index.php/s/oLbwink98GPyA36 + --- That’s exactly how I went step-by-step through each setup for this assignment—hope it’s clear and easy to follow from my perspective! diff --git a/AlirezaMirzaei/Problem3_Celery/README.md b/AlirezaMirzaei/Problem3_Celery/README.md index 47595ff1..edbfbfa1 100644 --- a/AlirezaMirzaei/Problem3_Celery/README.md +++ b/AlirezaMirzaei/Problem3_Celery/README.md @@ -119,3 +119,6 @@ This Celery setup integrates with: 1. **Redis Server**: Used as message broker and result backend 2. **Docker Engine**: For container management 3. **Web Application**: Can call these tasks to provision CTF environments + +### End. The video links: +https://iutbox.iut.ac.ir/index.php/s/oLbwink98GPyA36 From 64dcdba3054e03f77d8c78bd857bae950b67177a Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Sun, 11 May 2025 01:10:32 +0330 Subject: [PATCH 5/8] Removed additional files --- DockerAssignment.pdf | Bin 121993 -> 0 bytes .../Problem1_PostgreSQL/README.md | 1 - MohamadMahdiReisi/Problem2_Redis/README.md | 1 - MohamadMahdiReisi/README.md | 1 - Samples and Hints/Problem 1/README.md | 32 ------------------ Samples and Hints/Problem 2/README.md | 18 ---------- Samples and Hints/Problem 3 /README.md | 24 ------------- Samples and Hints/Problem 4/README.md | 21 ------------ Samples and Hints/Problem 5/README.md | 0 9 files changed, 98 deletions(-) delete mode 100644 DockerAssignment.pdf delete mode 100644 MohamadMahdiReisi/Problem1_PostgreSQL/README.md delete mode 100644 MohamadMahdiReisi/Problem2_Redis/README.md delete mode 100644 MohamadMahdiReisi/README.md delete mode 100644 Samples and Hints/Problem 1/README.md delete mode 100644 Samples and Hints/Problem 2/README.md delete mode 100644 Samples and Hints/Problem 3 /README.md delete mode 100644 Samples and Hints/Problem 4/README.md delete mode 100644 Samples and Hints/Problem 5/README.md diff --git a/DockerAssignment.pdf b/DockerAssignment.pdf deleted file mode 100644 index 8415eebe634902251a6055ea81934d8eb6556ec8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121993 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 >RdsCsq-i+ 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 sT*49RL6T diff --git a/MohamadMahdiReisi/Problem1_PostgreSQL/README.md b/MohamadMahdiReisi/Problem1_PostgreSQL/README.md deleted file mode 100644 index 8b137891..00000000 --- a/MohamadMahdiReisi/Problem1_PostgreSQL/README.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/MohamadMahdiReisi/Problem2_Redis/README.md b/MohamadMahdiReisi/Problem2_Redis/README.md deleted file mode 100644 index 8b137891..00000000 --- a/MohamadMahdiReisi/Problem2_Redis/README.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/MohamadMahdiReisi/README.md b/MohamadMahdiReisi/README.md deleted file mode 100644 index 5fd544e5..00000000 --- a/MohamadMahdiReisi/README.md +++ /dev/null @@ -1 +0,0 @@ -Sample diff --git a/Samples and Hints/Problem 1/README.md b/Samples and Hints/Problem 1/README.md deleted file mode 100644 index 386d0523..00000000 --- a/Samples and Hints/Problem 1/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Hint: SQL Commands for Question 1 - -```sql --- Create a new database -CREATE DATABASE ctf_db; - - --- Create a table to store team information -CREATE TABLE teams ( - id SERIAL PRIMARY KEY, - team_name VARCHAR(100) NOT NULL, - challenge_assigned BOOLEAN DEFAULT FALSE -); - --- Insert sample data into the table -INSERT INTO teams (team_name, challenge_assigned) -VALUES - ('Red Team', true), - ('Blue Team', false); - --- Retrieve all records from the table -SELECT * FROM teams; - --- Update a team's challenge assignment status -UPDATE teams -SET challenge_assigned = true -WHERE team_name = 'Blue Team'; - --- Delete a team from the table -DELETE FROM teams -WHERE team_name = 'Red Team'; -``` diff --git a/Samples and Hints/Problem 2/README.md b/Samples and Hints/Problem 2/README.md deleted file mode 100644 index 7c683334..00000000 --- a/Samples and Hints/Problem 2/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Hint: Redis Key-Value Example in Python - -This simple Python script shows how to connect to Redis, set a key-value pair, and retrieve it. - -```python -import redis - -# Connect to the Redis server -r = redis.Redis(host='localhost', port=6379, decode_responses=True) - -# Set a key-value pair -r.set("team:red", "assigned") - -# Get the value for the key -value = r.get("team:red") - -print(f"The value of 'team:red' is: {value}") -``` \ No newline at end of file diff --git a/Samples and Hints/Problem 3 /README.md b/Samples and Hints/Problem 3 /README.md deleted file mode 100644 index 9f0462e4..00000000 --- a/Samples and Hints/Problem 3 /README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Hint: Basic Celery Task (Without Redis) - -This example shows how to define and run a basic Celery task that prints `'doing task'`. - -### 📄 `tasks.py` - -```python -from celery import Celery - -app = Celery('simple_task', broker='memory://') - -@app.task -def do_something(): - print("doing task") -``` - -### 📄 `main.py` - -```python -from tasks import do_something - -do_something.delay() -``` - diff --git a/Samples and Hints/Problem 4/README.md b/Samples and Hints/Problem 4/README.md deleted file mode 100644 index 627c908f..00000000 --- a/Samples and Hints/Problem 4/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Hint: Simple Flask Echo API for Question 4 - -This example Flask app echoes back any JSON data sent to it via POST requests. - -### 📄 `app.py` - -```python -from flask import Flask, request, jsonify - -app = Flask(__name__) - -@app.route('/echo', methods=['POST']) -def echo(): - data = request.get_json() - return jsonify({ - "you_sent": data - }) - -if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=5000) -``` \ No newline at end of file diff --git a/Samples and Hints/Problem 5/README.md b/Samples and Hints/Problem 5/README.md deleted file mode 100644 index e69de29b..00000000 From bf24e6b78e43594e56ae8079f122973f1a999236 Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Sat, 24 May 2025 21:24:22 +0330 Subject: [PATCH 6/8] Part 4 completely added --- .gitignore | 5 + .../__pycache__/tasks.cpython-313.pyc | Bin 8689 -> 8689 bytes AlirezaMirzaei/Problem4_WebAPI/.env.example | 3 + AlirezaMirzaei/Problem4_WebAPI/README.md | 124 ++++++++++++++++++ .../Problem4_WebAPI/app/__init__.py | 1 + .../Problem4_WebAPI/app/database.py | 12 ++ AlirezaMirzaei/Problem4_WebAPI/app/main.py | 29 ++++ AlirezaMirzaei/Problem4_WebAPI/app/models.py | 13 ++ AlirezaMirzaei/Problem4_WebAPI/app/schemas.py | 27 ++++ AlirezaMirzaei/Problem4_WebAPI/app/tasks.py | 85 ++++++++++++ AlirezaMirzaei/Problem4_WebAPI/celery_app.py | 27 ++++ .../Problem4_WebAPI/requirements.txt | 9 ++ AlirezaMirzaei/Problem4_WebAPI/run_app.sh | 18 +++ AlirezaMirzaei/Problem4_WebAPI/test_api.py | 67 ++++++++++ .../README.md | 0 AlirezaMirzaei/Problem5_NGINX/README.md | 0 16 files changed, 420 insertions(+) create mode 100644 AlirezaMirzaei/Problem4_WebAPI/.env.example create mode 100644 AlirezaMirzaei/Problem4_WebAPI/app/__init__.py create mode 100644 AlirezaMirzaei/Problem4_WebAPI/app/database.py create mode 100644 AlirezaMirzaei/Problem4_WebAPI/app/main.py create mode 100644 AlirezaMirzaei/Problem4_WebAPI/app/models.py create mode 100644 AlirezaMirzaei/Problem4_WebAPI/app/schemas.py create mode 100644 AlirezaMirzaei/Problem4_WebAPI/app/tasks.py create mode 100644 AlirezaMirzaei/Problem4_WebAPI/celery_app.py create mode 100644 AlirezaMirzaei/Problem4_WebAPI/requirements.txt create mode 100755 AlirezaMirzaei/Problem4_WebAPI/run_app.sh create mode 100755 AlirezaMirzaei/Problem4_WebAPI/test_api.py rename AlirezaMirzaei/{Problem6_DockerCompose => Problem5_DockerCompose}/README.md (100%) delete mode 100644 AlirezaMirzaei/Problem5_NGINX/README.md diff --git a/.gitignore b/.gitignore index 2f5cee40..9c68ad67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +__pycache__/ +AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc AlirezaMirzaei/Problem2_Redis/redis.conf AlirezaMirzaei/Problem2_Redis/redis-data AlirezaMirzaei/Problem2_Redis/consumer.log +.pyc +.env +AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc diff --git a/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc b/AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc index b49559d41668a9b0d1448ee1c6756613c977db8a..6375fd3e9eb33b1e2e7530a9e5ccf178bb5f6fba 100644 GIT binary patch delta 20 acmez9{Lz{FGcPX}0}%L&YHZ|wrU(E=O9k2h delta 20 acmez9{Lz{FGcPX}0}$kWmf6VtOc4M~90tn( diff --git a/AlirezaMirzaei/Problem4_WebAPI/.env.example b/AlirezaMirzaei/Problem4_WebAPI/.env.example new file mode 100644 index 00000000..3d8d0d6a --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/.env.example @@ -0,0 +1,3 @@ +DATABASE_URL=postgresql://postgres:password@postgres_ctf:5432/ctf_db +CELERY_BROKER_URL=redis://localhost:6379/0 +CELERY_RESULT_BACKEND=redis://localhost:6379/1 diff --git a/AlirezaMirzaei/Problem4_WebAPI/README.md b/AlirezaMirzaei/Problem4_WebAPI/README.md index e69de29b..9f218585 100644 --- a/AlirezaMirzaei/Problem4_WebAPI/README.md +++ b/AlirezaMirzaei/Problem4_WebAPI/README.md @@ -0,0 +1,124 @@ +## Problem 4: Web API for Team Challenge Management + +### Overview + +This service provides HTTP endpoints to assign and remove CTF challenge containers to/from teams. It uses: + +- **FastAPI** for HTTP API +- **PostgreSQL** for persistent storage +- **Celery** with **Redis** for background task processing +- **Docker SDK** for container management + +### Endpoints + +#### `POST /assign` + +Request: + +```json +{ + "team_id": 1, + "challenge_id": 2 +} +``` + +Response: + +```json +{ + "team_id": 1, + "challenge_id": 2, + "container_id": "...", + "address": "http://:", + "status": "running" +} +``` + +#### `POST /remove` + +Request: + +```json +{ + "team_id": 1, + "challenge_id": 2 +} +``` + +Response: + +```json +{ + "team_id": 1, + "challenge_id": 2, + "container_id": "...", + "status": "stopped" +} +``` + +### Database Schema + +Table: `team_challenges` + +| Column | Type | Description | +| -------------- | ------- | ------------------------------------ | +| `id` | Integer | Primary key | +| `team_id` | Integer | ID of the team | +| `challenge_id` | Integer | ID of the challenge | +| `container_id` | String | Docker container ID | +| `address` | String | URL address of the running container | +| `status` | String | `running` or `stopped` | + +### Setup and Run + +1. Configure environment variables (copy from .env.example in project root) in a `.env` file: + +```ini +DATABASE_URL=postgresql://postgres:password@postgres_ctf:5432/ctf_db +CELERY_BROKER_URL=redis://redis-server-ip:6379/0 +... +``` + +#### Instead of the steps below, until the testing section, after running the redis and pgsql containers using .sh files given in the previous sections, you can run ./run_app.sh to do the steps 2 through 4 + +2. Build and install dependencies: + +```bash +pip install -r requirements.txt +``` + +3. Start Celery worker: + +```bash +celery -A celery_app.celery_app worker --loglevel=info +``` + +4. Run the API: + +```bash +uvicorn app.main:app --host 0.0.0.0 --port 8000 +``` + +### Testing with Postman or Python + +Send a `POST` to `http://localhost:8000/assign` with JSON: + +```json +{ "team_id": 1, "challenge_id": 2 } +``` + +Observe that a container starts, and the database record is created. + +Send a `POST` to `http://localhost:8000/remove` with JSON: + +```json +{ "team_id": 1, "challenge_id": 2 } +``` + +Observe the container stops and the record status updates. + +You can also use `test_api.py` to quickly verify the endpoints using Python. + +### End. The video links: + +https://iutbox.iut.ac.ir/index.php/s/oLbwink98GPyA36 diff --git a/AlirezaMirzaei/Problem4_WebAPI/app/__init__.py b/AlirezaMirzaei/Problem4_WebAPI/app/__init__.py new file mode 100644 index 00000000..8d7c9bc2 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/app/__init__.py @@ -0,0 +1 @@ +# to mark this directory as a package diff --git a/AlirezaMirzaei/Problem4_WebAPI/app/database.py b/AlirezaMirzaei/Problem4_WebAPI/app/database.py new file mode 100644 index 00000000..d1f70bb7 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/app/database.py @@ -0,0 +1,12 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import os + +DATABASE_URL = os.getenv( + "DATABASE_URL", "postgresql://postgres:password@postgres_ctf:5432/ctf_db" +) + +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() diff --git a/AlirezaMirzaei/Problem4_WebAPI/app/main.py b/AlirezaMirzaei/Problem4_WebAPI/app/main.py new file mode 100644 index 00000000..04bd44cc --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/app/main.py @@ -0,0 +1,29 @@ +from fastapi import FastAPI, HTTPException +from app.schemas import AssignRequest, AssignResponse, RemoveRequest, RemoveResponse +from app.database import engine, Base, SessionLocal +from app.models import TeamChallenge +from app.tasks import start_challenge, stop_challenge +import uvicorn + +# create tables +Base.metadata.create_all(bind=engine) + +app = FastAPI(title="CTF Challenge Manager") + + +@app.post("/assign", response_model=AssignResponse) +def assign_challenge(req: AssignRequest): + result = start_challenge.delay(req.team_id, req.challenge_id) + res = result.get(timeout=30) + return res + + +@app.post("/remove", response_model=RemoveResponse) +def remove_challenge(req: RemoveRequest): + result = stop_challenge.delay(req.team_id, req.challenge_id) + res = result.get(timeout=30) + return res + + +if __name__ == "__main__": + uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/AlirezaMirzaei/Problem4_WebAPI/app/models.py b/AlirezaMirzaei/Problem4_WebAPI/app/models.py new file mode 100644 index 00000000..14654912 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/app/models.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, Integer, String +from app.database import Base + + +class TeamChallenge(Base): + __tablename__ = "team_challenges" + + id = Column(Integer, primary_key=True, index=True) + team_id = Column(Integer, index=True, nullable=False) + challenge_id = Column(Integer, index=True, nullable=False) + container_id = Column(String, unique=True, nullable=False) + address = Column(String, nullable=False) + status = Column(String, nullable=False, default="running") diff --git a/AlirezaMirzaei/Problem4_WebAPI/app/schemas.py b/AlirezaMirzaei/Problem4_WebAPI/app/schemas.py new file mode 100644 index 00000000..422828d7 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/app/schemas.py @@ -0,0 +1,27 @@ +# app/schemas.py +from pydantic import BaseModel + + +class AssignRequest(BaseModel): + team_id: int + challenge_id: int + + +class AssignResponse(BaseModel): + team_id: int + challenge_id: int + container_id: str + address: str + status: str + + +class RemoveRequest(BaseModel): + team_id: int + challenge_id: int + + +class RemoveResponse(BaseModel): + team_id: int + challenge_id: int + container_id: str + status: str diff --git a/AlirezaMirzaei/Problem4_WebAPI/app/tasks.py b/AlirezaMirzaei/Problem4_WebAPI/app/tasks.py new file mode 100644 index 00000000..d7ed22f6 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/app/tasks.py @@ -0,0 +1,85 @@ +from celery_app import celery_app +import docker +import os +from app.database import SessionLocal +from app.models import TeamChallenge + +docker_client = docker.from_env() + + +@celery_app.task(bind=True) +def start_challenge(self, team_id: int, challenge_id: int) -> dict: + # select image based on challenge_id + images = {1: "pasapples/apjctf-todo-java-app:latest", 2: "bkimminich/juice-shop"} + image = images.get(challenge_id) + if not image: + raise ValueError(f"Unknown challenge {challenge_id}") + + container = docker_client.containers.run(image, detach=True, ports={"80/tcp": None}) + container.reload() + + network_settings = container.attrs["NetworkSettings"] + ports = network_settings.get("Ports", {}) + + host_port = None + for pinfo in ports.values(): + if pinfo and isinstance(pinfo, list): + host_port = pinfo[0].get("HostPort") + break + + if not host_port: + raise RuntimeError("Could not determine mapped host port for container.") + + ip = os.getenv( + "DOCKER_HOST_IP", "localhost" + ) # Use .env override or default to localhost + addr = f"http://{ip}:{host_port}" + db = SessionLocal() + tc = TeamChallenge( + team_id=team_id, + challenge_id=challenge_id, + container_id=container.id, + address=addr, + status="running", + ) + db.add(tc) + db.commit() + db.refresh(tc) + db.close() + + return { + "team_id": team_id, + "challenge_id": challenge_id, + "container_id": container.id, + "address": addr, + "status": "running", + } + + +@celery_app.task(bind=True) +def stop_challenge(self, team_id: int, challenge_id: int) -> dict: + db = SessionLocal() + tc = ( + db.query(TeamChallenge) + .filter_by(team_id=team_id, challenge_id=challenge_id, status="running") + .first() + ) + if not tc: + raise ValueError( + f"No active container for team {team_id} challenge {challenge_id}" + ) + + container = docker_client.containers.get(tc.container_id) + container.stop() + container.remove() + tc.status = "stopped" + db.commit() + db.refresh(tc) + db.close() + + return { + "team_id": team_id, + "challenge_id": challenge_id, + "container_id": tc.container_id, + "status": "stopped", + } diff --git a/AlirezaMirzaei/Problem4_WebAPI/celery_app.py b/AlirezaMirzaei/Problem4_WebAPI/celery_app.py new file mode 100644 index 00000000..7d4356f4 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/celery_app.py @@ -0,0 +1,27 @@ +from dotenv import load_dotenv +import os +from celery import Celery + +# Load .env from project root +load_dotenv() + +# Broker and backend URLs from environment +CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://redis-server:6379/0") +CELERY_RESULT_BACKEND = os.getenv( + "CELERY_RESULT_BACKEND", "redis://redis-server:6379/1" +) + +# Include the task modules so Celery registers them +celery_app = Celery( + "ctf_manager", + broker=CELERY_BROKER_URL, + backend=CELERY_RESULT_BACKEND, + include=["app.tasks"], +) +celery_app.conf.update( + task_serializer="json", + accept_content=["json"], + result_serializer="json", + timezone="UTC", + enable_utc=True, +) diff --git a/AlirezaMirzaei/Problem4_WebAPI/requirements.txt b/AlirezaMirzaei/Problem4_WebAPI/requirements.txt new file mode 100644 index 00000000..d707790a --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/requirements.txt @@ -0,0 +1,9 @@ +fastapi +uvicorn +celery +redis +docker +sqlalchemy +pydantic +psycopg2-binary +python-dotenv \ No newline at end of file diff --git a/AlirezaMirzaei/Problem4_WebAPI/run_app.sh b/AlirezaMirzaei/Problem4_WebAPI/run_app.sh new file mode 100755 index 00000000..b48ae31d --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/run_app.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Load env variables from .env if it exists +if [ -f .env ]; then + export $(cat .env | grep -v '^#' | xargs) +fi + +# Install dependencies +pip install -r requirements.txt + +# Run database migrations (create tables) +python -c "from app.database import Base, engine; import app.models; Base.metadata.create_all(bind=engine)" + +# Start celery worker in background +celery -A celery_app.celery_app worker --loglevel=info & + +# Start FastAPI server +uvicorn app.main:app --host 0.0.0.0 --port 8000 diff --git a/AlirezaMirzaei/Problem4_WebAPI/test_api.py b/AlirezaMirzaei/Problem4_WebAPI/test_api.py new file mode 100755 index 00000000..c5c4b727 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/test_api.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +A more complete test script for the CTF Challenge Manager API, +including Celery connectivity check and assign/remove flows. +""" + +import os +import time +import argparse +import requests + +# Load .env if present +from dotenv import load_dotenv + +load_dotenv() + +BASE_URL = os.getenv("API_URL", "http://localhost:8000") + +# Check Celery broker connection +from celery_app import celery_app + +ping = celery_app.control.ping(timeout=1) +if not ping: + print("[!] Celery broker not reachable. Ping returned:", ping) + exit(1) +print("[+] Celery broker reachable:", ping) + + +def assign(team_id: int, challenge_id: int) -> dict: + url = f"{BASE_URL}/assign" + resp = requests.post(url, json={"team_id": team_id, "challenge_id": challenge_id}) + if resp.ok: + data = resp.json() + print(f"[+] Assigned: {data}") + return data + else: + print(f"[!] Assign failed {resp.status_code}: {resp.text}") + exit(1) + + +def remove(team_id: int, challenge_id: int) -> dict: + url = f"{BASE_URL}/remove" + resp = requests.post(url, json={"team_id": team_id, "challenge_id": challenge_id}) + if resp.ok: + data = resp.json() + print(f"[+] Removed: {data}") + return data + else: + print(f"[!] Remove failed {resp.status_code}: {resp.text}") + exit(1) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="CTF API Tester") + parser.add_argument("--team", type=int, default=1, help="Team ID to use") + parser.add_argument( + "--challenge", type=int, default=2, help="Challenge ID to assign/remove" + ) + parser.add_argument( + "--wait", type=int, default=10, help="Seconds to wait before removal" + ) + args = parser.parse_args() + + result = assign(args.team, args.challenge) + print(f"[.] Waiting {args.wait} seconds before removal...") + time.sleep(args.wait) + remove(args.team, args.challenge) diff --git a/AlirezaMirzaei/Problem6_DockerCompose/README.md b/AlirezaMirzaei/Problem5_DockerCompose/README.md similarity index 100% rename from AlirezaMirzaei/Problem6_DockerCompose/README.md rename to AlirezaMirzaei/Problem5_DockerCompose/README.md diff --git a/AlirezaMirzaei/Problem5_NGINX/README.md b/AlirezaMirzaei/Problem5_NGINX/README.md deleted file mode 100644 index e69de29b..00000000 From 2c100050a92ea93b89be9e3af92687d45b9b6f01 Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Sun, 25 May 2025 01:21:31 +0330 Subject: [PATCH 7/8] Part 5 completed. --- .gitignore | 2 + AlirezaMirzaei/Problem4_WebAPI/Dockerfile | 19 +++ .../Problem5_DockerCompose/.env.example | 7 + .../Problem5_DockerCompose/README.md | 153 +++--------------- .../Problem5_DockerCompose/docker-compose.yml | 70 ++++++++ 5 files changed, 117 insertions(+), 134 deletions(-) create mode 100644 AlirezaMirzaei/Problem4_WebAPI/Dockerfile create mode 100644 AlirezaMirzaei/Problem5_DockerCompose/.env.example create mode 100644 AlirezaMirzaei/Problem5_DockerCompose/docker-compose.yml diff --git a/.gitignore b/.gitignore index 9c68ad67..28ac27ae 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ AlirezaMirzaei/Problem2_Redis/consumer.log .pyc .env AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc +AlirezaMirzaei/Problem2_Redis/redis_data/appendonly.aof +AlirezaMirzaei/Problem2_Redis/redis_data/dump.rdb diff --git a/AlirezaMirzaei/Problem4_WebAPI/Dockerfile b/AlirezaMirzaei/Problem4_WebAPI/Dockerfile new file mode 100644 index 00000000..17e759c1 --- /dev/null +++ b/AlirezaMirzaei/Problem4_WebAPI/Dockerfile @@ -0,0 +1,19 @@ +# For dockerizing this part, for the docker-compose based deployment +# Use official Python image +FROM python:3.11-slim + +# Set working directory +WORKDIR /usr/src/app + +# Copy only requirements first for caching +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application +COPY . . + +# Expose FastAPI port +EXPOSE 8000 + +# Default command (overridden by docker-compose for celery or web) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/AlirezaMirzaei/Problem5_DockerCompose/.env.example b/AlirezaMirzaei/Problem5_DockerCompose/.env.example new file mode 100644 index 00000000..c500f6d0 --- /dev/null +++ b/AlirezaMirzaei/Problem5_DockerCompose/.env.example @@ -0,0 +1,7 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=password +POSTGRES_DB=ctf_db +DATABASE_URL=postgresql://postgres:password@postgres_ctf:5432/ctf_db +CELERY_BROKER_URL=redis://redis-server:6379/0 +CELERY_RESULT_BACKEND=redis://redis-server:6379/1 +DOCKER_HOST_IP=localhost # for host-access fallback diff --git a/AlirezaMirzaei/Problem5_DockerCompose/README.md b/AlirezaMirzaei/Problem5_DockerCompose/README.md index 8862a6e5..4aa1763c 100644 --- a/AlirezaMirzaei/Problem5_DockerCompose/README.md +++ b/AlirezaMirzaei/Problem5_DockerCompose/README.md @@ -1,145 +1,30 @@ -# PostgreSQL Container with Automatic Schema Initialization +# Problem 5: Docker Compose Integration – Full System README -This document explains how to set up a PostgreSQL container that automatically initializes with your database schema and sample data when it starts. +## Overview -## How It Works +This project combines all components of a CTF (Capture The Flag) management system using Docker Compose. It consists of microservices for database management, Redis queueing, Celery-based task execution, a web API, and dynamic challenge containers. -PostgreSQL's official Docker image looks for initialization scripts in the `/docker-entrypoint-initdb.d/` directory. Any `.sql`, `.sql.gz`, or `.sh` files in this directory will be executed in alphabetical order when the container is first initialized. +## How Services Are Connected -## Setup Instructions +The system is composed of four main services: -### 1. Create the SQL Initialization Script +- **PostgreSQL** stores all persistent information, including which team is assigned to which challenge. +- **Redis** acts as the broker and result backend for Celery. +- **Celery** executes background tasks such as starting or stopping challenge containers. +- **FastAPI Web API** allows clients to assign and remove challenges via HTTP endpoints. -Create a file named `init-db.sql` with your database schema and sample data: +These services communicate internally through a shared Docker network, ensuring isolated and reliable inter-service communication. The Celery worker uses Docker SDK to manage containers based on requests received from the FastAPI service. -```sql --- Create a new database -CREATE DATABASE ctf_db; +## How to Start and Use the System --- Connect to the newly created database -\connect ctf_db; +1. Navigate to the `AlirezaMirzaei` root directory in your terminal. +2. Move into the `Problem5_DockerCompose` folder. +3. Ensure that the `.env` file exists inside `Problem4_WebAPI` and contains the required configuration. +4. Start the system with `docker-compose up --build`. +5. Use the Python script provided in `Problem4_WebAPI/test_api.py` or any HTTP client (like curl or Postman) to assign and remove challenges for different teams. --- Create a table to store team information -CREATE TABLE teams ( - id SERIAL PRIMARY KEY, - team_name VARCHAR(100) NOT NULL, - challenge_assigned BOOLEAN DEFAULT FALSE -); +The API provides two main endpoints: --- Insert sample data into the table -INSERT INTO teams (team_name, challenge_assigned) -VALUES - ('Red Team', true), - ('Blue Team', false), - ('Green Team', false); +- `/assign` — Starts a challenge container and registers it. +- `/remove` — Stops the container and updates the database. --- Update a team's challenge assignment status -UPDATE teams -SET challenge_assigned = true -WHERE team_name = 'Blue Team'; - --- Delete a team from the table -DELETE FROM teams -WHERE team_name = 'Red Team'; - --- Insert an additional row as requested -INSERT INTO teams (team_name, challenge_assigned) -VALUES ('Yellow Team', true); - --- Final state of the table -SELECT * FROM teams; -``` - -### 2. Create a Bash Script to Run the Container - -Create a file named `setup-postgres.sh`: - -```bash -#!/bin/bash - -# Create volume for PostgreSQL data persistence -docker volume create postgres_data - -# Run PostgreSQL container with initialization -docker run --name postgres_ctf \ - -e POSTGRES_PASSWORD=secretpassword \ - -d \ - -p 5432:5432 \ - -v postgres_data:/var/lib/postgresql/data \ - -v $(pwd)/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql \ - postgres:latest - -# Wait for PostgreSQL to be ready -echo "Waiting for PostgreSQL to initialize..." -sleep 5 - -# Verify container is running -docker ps -a | grep postgres_ctf - -echo "PostgreSQL container initialized with your database schema!" -``` - -### 3. Make the Script Executable and Run It - -```bash -chmod +x run-postgres.sh -./setup-postgres.sh -``` - -## Important Notes - -1. **First-Time Initialization Only**: The initialization scripts only run when the container is first created and the data volume is empty. If you stop and restart the container, the scripts will not run again. - -2. **Checking Initialization Results**: You can check if your initialization worked by connecting to the database: - - ```bash - docker exec -it postgres_ctf psql -U postgres -d ctf_db -c "SELECT * FROM teams;" - ``` - -3. **For Re-initialization**: If you need to rerun the initialization, you must remove both the container and the volume: - - ```bash - docker stop postgres_ctf - docker rm postgres_ctf - docker volume rm postgres_data - ./setup-postgres.sh - ``` - -## Demonstration - -When you run the setup script: - -1. A Docker volume named `postgres_data` is created for data persistence -2. The PostgreSQL container starts with the initialization script mounted -3. PostgreSQL automatically executes the initialization script -4. The database, tables, and sample data are created - -After the container initialization, your database will contain: -- A database named `ctf_db` -- A table named `teams` with: - - Blue Team (challenge_assigned = true) - - Green Team (challenge_assigned = false) - - Yellow Team (challenge_assigned = true) -- The Red Team row was deleted as per your SQL script - -## For Docker Compose Integration - -For Docker Compose later on, we will place our initialization script in a directory and map it in the services section: - -```yaml -services: - postgres: - image: postgres:latest - environment: - POSTGRES_PASSWORD: somepassword - volumes: - - postgres_data:/var/lib/postgresql/data - - ./postgres-init:/docker-entrypoint-initdb.d - ports: - - "5432:5432" - -volumes: - postgres_data: -``` - -Then we place our `init-db.sql` file in the `./postgres-init` directory to initialize the db. \ No newline at end of file diff --git a/AlirezaMirzaei/Problem5_DockerCompose/docker-compose.yml b/AlirezaMirzaei/Problem5_DockerCompose/docker-compose.yml new file mode 100644 index 00000000..9f42f000 --- /dev/null +++ b/AlirezaMirzaei/Problem5_DockerCompose/docker-compose.yml @@ -0,0 +1,70 @@ +version: "3.8" + +services: + postgres_ctf: + image: postgres:14-alpine + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - pgdata:/var/lib/postgresql/data + networks: + - ctf_net + + redis-server: + image: redis:6-alpine + restart: unless-stopped + command: ["redis-server", "/usr/local/etc/redis/redis.conf"] + volumes: + - ../Problem2_Redis/redis.conf:/usr/local/etc/redis/redis.conf:ro + - ../Problem2_Redis/redis_data:/data + ports: + - "6379:6379" + networks: + - ctf_net + + celery_worker: + build: ../Problem4_WebAPI + working_dir: /usr/src/app + command: > + celery -A celery_app.celery_app + --loglevel=info + worker + env_file: + - .env + depends_on: + - redis-server + - postgres_ctf + volumes: + - ../Problem4_WebAPI:/usr/src/app + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - ctf_net + + web: + build: ../Problem4_WebAPI + working_dir: /usr/src/app + command: uvicorn app.main:app --host 0.0.0.0 --port 8000 + env_file: + - .env + ports: + - "8000:8000" + depends_on: + - postgres_ctf + - redis-server + - celery_worker + volumes: + - ../Problem4_WebAPI:/usr/src/app + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - ctf_net + +volumes: + pgdata: + redis_data: + +networks: + ctf_net: + driver: bridge From 8765b7451aafd0bdcc6fb0385bd8db671b3e7705 Mon Sep 17 00:00:00 2001 From: AlirezaM02 Date: Mon, 26 May 2025 19:10:22 +0330 Subject: [PATCH 8/8] clear .gitignore to avoid conflicts --- .gitignore | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 28ac27ae..8b137891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1 @@ -__pycache__/ -AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc -AlirezaMirzaei/Problem2_Redis/redis.conf -AlirezaMirzaei/Problem2_Redis/redis-data -AlirezaMirzaei/Problem2_Redis/consumer.log -.pyc -.env -AlirezaMirzaei/Problem3_Celery/celery_app/__pycache__/tasks.cpython-313.pyc -AlirezaMirzaei/Problem2_Redis/redis_data/appendonly.aof -AlirezaMirzaei/Problem2_Redis/redis_data/dump.rdb +