adding validated services? patching forcad_local.py
This commit is contained in:
11
ctfcup24-school-ad/services/filtranator/cleaner/Dockerfile
Normal file
11
ctfcup24-school-ad/services/filtranator/cleaner/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
RUN useradd --no-create-home --shell /bin/false --uid 1000 --user-group cleaner
|
||||
|
||||
COPY cleaner.sh /var/cleaner.sh
|
||||
|
||||
RUN chmod +x /var/cleaner.sh
|
||||
|
||||
RUN mkdir /tmp/data
|
||||
|
||||
ENTRYPOINT ["/var/cleaner.sh"]
|
||||
13
ctfcup24-school-ad/services/filtranator/cleaner/cleaner.sh
Normal file
13
ctfcup24-school-ad/services/filtranator/cleaner/cleaner.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
while true; do
|
||||
date -uR
|
||||
|
||||
find "/tmp/data/" \
|
||||
-type d \
|
||||
-and -not -path "/tmp/data/" \
|
||||
-and -not -newermt "-900 seconds" \
|
||||
-exec rm -r {} +
|
||||
|
||||
sleep 60
|
||||
done
|
||||
4
ctfcup24-school-ad/services/filtranator/db/Dockerfile
Normal file
4
ctfcup24-school-ad/services/filtranator/db/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM postgres
|
||||
ENV TZ=Europe/Moscow
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
COPY db.sql /docker-entrypoint-initdb.d/database.sql
|
||||
6
ctfcup24-school-ad/services/filtranator/db/db.sql
Normal file
6
ctfcup24-school-ad/services/filtranator/db/db.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE Users (
|
||||
username VARCHAR(256) NOT NULL,
|
||||
password VARCHAR(256) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO Users (username,password) VALUES ('Test','Test')
|
||||
52
ctfcup24-school-ad/services/filtranator/docker-compose.yml
Normal file
52
ctfcup24-school-ad/services/filtranator/docker-compose.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
services:
|
||||
filtranator:
|
||||
build: server/
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- filtranator_local
|
||||
volumes:
|
||||
- cleanx:/app/images
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: 1
|
||||
memory: 500M
|
||||
ports:
|
||||
- "6969:6969"
|
||||
cleaner:
|
||||
container_name: cleaner
|
||||
build: cleaner
|
||||
cpus: 0.25
|
||||
pids_limit: 128
|
||||
mem_limit: 128M
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- cleanx:/tmp/data
|
||||
depends_on:
|
||||
- filtranator
|
||||
db:
|
||||
container_name: db
|
||||
build: db/
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: Users
|
||||
POSTGRES_PASSWORD: password
|
||||
PGDATA: /data/postgres
|
||||
networks:
|
||||
- filtranator_local
|
||||
volumes:
|
||||
- postgres:/data/postgres
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: 1
|
||||
memory: 500M
|
||||
|
||||
|
||||
networks:
|
||||
filtranator_local: {}
|
||||
|
||||
volumes:
|
||||
cleanx: {}
|
||||
postgres: {}
|
||||
18
ctfcup24-school-ad/services/filtranator/server/Dockerfile
Normal file
18
ctfcup24-school-ad/services/filtranator/server/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y python3 pip libpq-dev iproute2 netcat-traditional
|
||||
RUN useradd -m ctf
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./ /app
|
||||
RUN chmod +x ./server.py
|
||||
RUN chmod +x ./filterer/filterer
|
||||
RUN pip3 install -r ./requirements.txt --break-system-packages
|
||||
RUN chown -R ctf:ctf /app/images
|
||||
RUN chown -R ctf:ctf /app
|
||||
USER ctf
|
||||
|
||||
EXPOSE 6969
|
||||
CMD ./server.py
|
||||
31
ctfcup24-school-ad/services/filtranator/server/app/db.py
Normal file
31
ctfcup24-school-ad/services/filtranator/server/app/db.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python3
|
||||
import psycopg2
|
||||
import sys
|
||||
|
||||
class LocalDb:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def connect(self,conn_string):
|
||||
#try:
|
||||
self.conn = psycopg2.connect(
|
||||
conn_string
|
||||
)
|
||||
#except Exception as e:
|
||||
#print(e)
|
||||
|
||||
def execute(self, command):
|
||||
cursor = self.conn.cursor()
|
||||
#try:
|
||||
cursor.execute(command)
|
||||
try:
|
||||
result = cursor.fetchall()
|
||||
except Exception as e:
|
||||
print(e,file=sys.stderr)
|
||||
result = []
|
||||
#cursor.close()
|
||||
return result
|
||||
|
||||
def disconnect(self):
|
||||
self.conn.cursor.close()
|
||||
self.conn.close()
|
||||
BIN
ctfcup24-school-ad/services/filtranator/server/filterer/filterer
Executable file
BIN
ctfcup24-school-ad/services/filtranator/server/filterer/filterer
Executable file
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
flask
|
||||
psycopg2
|
||||
176
ctfcup24-school-ad/services/filtranator/server/server.py
Executable file
176
ctfcup24-school-ad/services/filtranator/server/server.py
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/python3
|
||||
from flask import (
|
||||
Flask,
|
||||
redirect,
|
||||
url_for,
|
||||
request,
|
||||
make_response,
|
||||
render_template_string,
|
||||
render_template,
|
||||
abort,
|
||||
)
|
||||
from app.db import LocalDb
|
||||
import os
|
||||
import string
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
appx = Flask(__name__)
|
||||
|
||||
|
||||
def generate_cock():
|
||||
letters = string.ascii_lowercase
|
||||
return "".join(random.choice(letters) for i in range(32))
|
||||
|
||||
|
||||
def db_conn():
|
||||
mydb = LocalDb()
|
||||
conn_string = "host='db' dbname = 'Users' user='postgres' password = 'password'"
|
||||
mydb.connect(conn_string)
|
||||
return mydb
|
||||
|
||||
|
||||
@appx.route("/")
|
||||
def redir():
|
||||
return redirect(url_for("login"))
|
||||
|
||||
|
||||
@appx.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
if request.method == "GET":
|
||||
return render_template("register.html")
|
||||
else:
|
||||
name = request.form["username"]
|
||||
password = request.form["password"]
|
||||
|
||||
if name != "" and password != "":
|
||||
# mydb = db_conn()
|
||||
# try:
|
||||
result = mydb.execute(
|
||||
"SELECT * FROM Users WHERE username='" + name + "';")
|
||||
print(result, file=sys.stderr)
|
||||
if result != []:
|
||||
return "<p1>User alredy exist</p1>"
|
||||
print(name, file=sys.stderr)
|
||||
mydb.execute(
|
||||
"INSERT INTO Users (username,password) VALUES ('"
|
||||
+ name
|
||||
+ "','"
|
||||
+ password
|
||||
+ "');"
|
||||
)
|
||||
if not os.path.isdir("./images/" + name):
|
||||
os.mkdir("./images/" + name)
|
||||
return "<p1>Sucessfully registered<p1>"
|
||||
# except Exception as e:
|
||||
# return "Exception"
|
||||
return "<p1>Vvedi normalniye credi ti che</p1>"
|
||||
|
||||
|
||||
@appx.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
global logged_users
|
||||
if request.method == "GET":
|
||||
return render_template("login.html")
|
||||
else:
|
||||
print("Login entered...")
|
||||
name = request.form["username"]
|
||||
password = request.form["password"]
|
||||
if name != "" and password != "":
|
||||
if name in logged_users.values():
|
||||
return "<p1>Already logged in</p1>"
|
||||
# mydb = db_conn()
|
||||
# try:
|
||||
result = mydb.execute(
|
||||
"SELECT * FROM Users WHERE username='"
|
||||
+ name
|
||||
+ "' AND password='"
|
||||
+ password
|
||||
+ "';"
|
||||
)
|
||||
if result == []:
|
||||
abort(404)
|
||||
cock = generate_cock()
|
||||
if len(logged_users) > 10000:
|
||||
logged_users = {}
|
||||
logged_users[cock] = name
|
||||
resp = make_response(
|
||||
render_template_string("<p1>Sucessfully logged in<p1>")
|
||||
)
|
||||
resp.set_cookie("token", cock)
|
||||
return resp
|
||||
# except Exception as e:
|
||||
# return "Exeception"
|
||||
return render_template_string("<p1>Vvedi normalniye credi ti che<p1>")
|
||||
|
||||
|
||||
@appx.route("/logout", methods=["GET"])
|
||||
def logout():
|
||||
cock = request.cookies.get("token")
|
||||
if cock is None:
|
||||
abort(401)
|
||||
elif logged_users.get(cock) is None:
|
||||
abort(401)
|
||||
del logged_users[cock]
|
||||
return "<p1>Sucessfully logout</p1>"
|
||||
|
||||
|
||||
@appx.route("/images", methods=["GET"])
|
||||
def get_images():
|
||||
cock = request.cookies.get("token")
|
||||
if cock is None:
|
||||
abort(401)
|
||||
elif logged_users.get(cock) is None:
|
||||
abort(401)
|
||||
usr = logged_users.get(cock)
|
||||
path = "./images/" + usr
|
||||
onlyfiles = [f for f in os.listdir(
|
||||
path) if os.path.isfile(os.path.join(path, f))]
|
||||
if len(onlyfiles) < 1:
|
||||
return "<p1>No images</p1>"
|
||||
img = onlyfiles[0]
|
||||
image_binary = open("./images/" + usr + "/" + img, "rb").read()
|
||||
response = make_response(image_binary)
|
||||
response.headers.set("Content-Type", "image/png")
|
||||
response.headers.set("Content-Disposition",
|
||||
"attachment", filename="%s.png" % img)
|
||||
return response
|
||||
|
||||
|
||||
@appx.route("/apply_filter", methods=["GET", "POST"])
|
||||
def filtrate():
|
||||
cock = request.cookies.get("token")
|
||||
if cock is None:
|
||||
abort(401)
|
||||
elif logged_users.get(cock) is None:
|
||||
abort(401)
|
||||
if request.method == "GET":
|
||||
return render_template("apply_filter.html")
|
||||
else:
|
||||
filter_name = request.form["filter"]
|
||||
filename = request.form["filename"]
|
||||
imagefile = request.files.get("image", "")
|
||||
print(imagefile,file=sys.stderr)
|
||||
if imagefile is None:
|
||||
return "<p1>Undefined</p1>"
|
||||
usr = logged_users.get(cock)
|
||||
if filename == "":
|
||||
return "<p1>Undefined filename</p1>"
|
||||
imagefile.save("./images/" + usr + "/" + filename)
|
||||
path = "./images/" + usr + "/" + filename
|
||||
if filter_name == "black":
|
||||
subprocess.Popen(["./filterer/filterer", path, "black"])
|
||||
return "<p1>Image blacked</p1>"
|
||||
elif filter_name == "none":
|
||||
return "<p1>Image saved</p1>"
|
||||
return "<p1>Undefined</p1>"
|
||||
|
||||
|
||||
global logged_users
|
||||
global mydb
|
||||
|
||||
if __name__ == "__main__":
|
||||
mydb = db_conn()
|
||||
logged_users = {}
|
||||
appx.run(host="0.0.0.0", port=6969)
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Apply filter</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main id="main-holder">
|
||||
<h1 id="login-header">Filtranator</h1>
|
||||
|
||||
<form id="login-form" method = "POST" enctype="multipart/form-data">
|
||||
<input type="filter" name="filter" id="username-field" class="login-form-field" placeholder="filter" method = "POST">
|
||||
<input type="filename" name="filename" id="password-field" class="login-form-field" placeholder="filename" method = "POST">
|
||||
<input type="file" id="image" name="image">
|
||||
<input type="submit" value="Uplooad" id="login-form-submit">
|
||||
</form>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
images
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main id="main-holder">
|
||||
<h1 id="login-header">Filtranator</h1>
|
||||
|
||||
<form id="login-form" method = "POST">
|
||||
<input type="text" name="username" id="username-field" class="login-form-field" placeholder="Username" method = "POST">
|
||||
<input type="password" name="password" id="password-field" class="login-form-field" placeholder="Password" method = "POST">
|
||||
<input type="submit" value="Login" id="login-form-submit">
|
||||
</form>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main id="main-holder">
|
||||
<h1 id="login-header">Filtranator</h1>
|
||||
|
||||
<form id="login-form" method = "POST">
|
||||
<input type="text" name="username" id="username-field" class="login-form-field" placeholder="Username" method = "POST">
|
||||
<input type="password" name="password" id="password-field" class="login-form-field" placeholder="Password" method = "POST">
|
||||
<input type="submit" value="Login" id="login-form-submit">
|
||||
</form>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
27
ctfcup24-school-ad/services/flysim/docker-compose.yml
Normal file
27
ctfcup24-school-ad/services/flysim/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
flysim:
|
||||
restart: always
|
||||
build: ./flysim
|
||||
ports:
|
||||
- "9000:9001"
|
||||
depends_on:
|
||||
- flysim-mongo
|
||||
environment:
|
||||
- MONGO_URI=mongodb://flysim-mongo:27017/mydatabase
|
||||
- LD_LIBRARY_PATH=/root/.codon/lib/codon
|
||||
# command: gunicorn -k gevent -w 1 --log-level debug -b 0.0.0.0:9001 server:app
|
||||
command: gunicorn -k gevent -w 1 -b 0.0.0.0:9001 server:app
|
||||
mem_limit: 2048m
|
||||
cpu_count: 1
|
||||
|
||||
flysim-mongo:
|
||||
restart: always
|
||||
image: mongo:latest
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
mem_limit: 1024m
|
||||
cpu_count: 1
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
||||
|
||||
11
ctfcup24-school-ad/services/flysim/flysim/Dockerfile
Normal file
11
ctfcup24-school-ad/services/flysim/flysim/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM python:3.12.7-bookworm
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY install_codon_v0.17.0.sh .
|
||||
RUN yes y | bash install_codon_v0.17.0.sh
|
||||
|
||||
COPY . .
|
||||
31
ctfcup24-school-ad/services/flysim/flysim/client/USAGE.txt
Normal file
31
ctfcup24-school-ad/services/flysim/flysim/client/USAGE.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
create drone:
|
||||
./client create --ip 10.80.1.2
|
||||
|
||||
you can also specify label and (or) flight plan
|
||||
./client create --ip 10.80.1.2 --label asdf --flight-plan "1 BOOSTX [drone] 200\n55 BOOSTY [drone] 400\n"
|
||||
|
||||
connect:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f"
|
||||
|
||||
connect with setting position and (or) velocity:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --position 0 0 --velocity 1 2
|
||||
|
||||
by default, client keeps connection for 30 seconds. if you need more, add --duration:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --duration 9999
|
||||
|
||||
list drones (only ids):
|
||||
./client list-drones --ip 10.80.1.2
|
||||
|
||||
list drones (detailed):
|
||||
./client list-drones-details --ip 10.80.1.2
|
||||
|
||||
list details but only for one drone (by label):
|
||||
./client list-drones-details --ip 10.80.1.2 --label p4MUiB5oEF5EHAnkMefO8
|
||||
|
||||
do not forget to use --help:
|
||||
|
||||
./client --help
|
||||
./client create --help
|
||||
./client connect --help
|
||||
./client list-drones --help
|
||||
./client list-drones-details --help
|
||||
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client
Executable file
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client
Executable file
Binary file not shown.
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client_static
Executable file
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client_static
Executable file
Binary file not shown.
77
ctfcup24-school-ad/services/flysim/flysim/flight_plans.py
Normal file
77
ctfcup24-school-ad/services/flysim/flysim/flight_plans.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import misc
|
||||
|
||||
get_var = misc.get_var
|
||||
set_vars = misc.set_vars
|
||||
|
||||
|
||||
def reinit():
|
||||
global get_var, set_vars
|
||||
get_var = misc.get_var
|
||||
set_vars = misc.set_vars
|
||||
|
||||
|
||||
def BOOSTX(drone, value):
|
||||
drone["velocity"][0] += value
|
||||
return f"Drone {drone['_id']} boosted at X axis by {value}!"
|
||||
|
||||
|
||||
def BOOSTY(drone, value):
|
||||
drone["velocity"][1] += value
|
||||
return f"Drone {drone['_id']} boosted at Y axis by {value}!"
|
||||
|
||||
|
||||
def FIRE(drone):
|
||||
return f"Drone {drone['_id']} fired!"
|
||||
|
||||
|
||||
def SETFREQ(drone, frequency):
|
||||
return f"Drone {drone['_id']} freq set to {frequency}!"
|
||||
|
||||
|
||||
def process_flight_plan(drone, curr_time):
|
||||
reinit()
|
||||
flight_plan = get_var(drone["_id"], "flight_plan")
|
||||
new_log = get_var(drone["_id"], "flight_log")
|
||||
|
||||
if not flight_plan:
|
||||
return
|
||||
|
||||
commands = flight_plan.split("\n")
|
||||
new_commands = ""
|
||||
|
||||
for command in commands:
|
||||
if not command.strip():
|
||||
continue
|
||||
|
||||
parts = command.split()
|
||||
if len(parts) == 0:
|
||||
continue
|
||||
|
||||
expected_time = int(parts[0])
|
||||
if not (curr_time > expected_time or abs(curr_time - expected_time) < 0.2):
|
||||
new_commands += command + "\n"
|
||||
continue
|
||||
|
||||
command_name = parts[1]
|
||||
args = parts[2:]
|
||||
func = globals().get(command_name)
|
||||
if func and callable(func):
|
||||
try:
|
||||
typed_args = []
|
||||
if len(args) > 0 and args[0] == "[drone]":
|
||||
typed_args.append(drone)
|
||||
args = args[1:]
|
||||
|
||||
typed_args.extend(
|
||||
[
|
||||
int(arg) if arg.replace("-", "", 1).isdigit() else arg
|
||||
for arg in args
|
||||
]
|
||||
)
|
||||
new_log += str(func(*typed_args)) + "\n"
|
||||
except Exception as e:
|
||||
print(f"Error calling {command_name}: {e}", flush=True)
|
||||
else:
|
||||
print(f"Unknown command: {command_name}", flush=True)
|
||||
|
||||
set_vars(drone["_id"], {"flight_plan": new_commands, "flight_log": new_log})
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
CODON_INSTALL_DIR=~/.codon
|
||||
OS=$(uname -s | awk '{print tolower($0)}')
|
||||
ARCH=$(uname -m)
|
||||
|
||||
if [ "$OS" != "linux" ] && [ "$OS" != "darwin" ]; then
|
||||
echo "error: Pre-built binaries only exist for Linux and macOS." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CODON_BUILD_ARCHIVE=codon-$OS-$ARCH.tar.gz
|
||||
|
||||
mkdir -p $CODON_INSTALL_DIR
|
||||
cd $CODON_INSTALL_DIR
|
||||
curl -L https://github.com/exaloop/codon/releases/download/v0.17.0/"$CODON_BUILD_ARCHIVE" | tar zxvf - --strip-components=1
|
||||
|
||||
EXPORT_COMMAND="export PATH=$(pwd)/bin:\$PATH"
|
||||
echo "PATH export command:"
|
||||
echo " $EXPORT_COMMAND"
|
||||
|
||||
update_profile () {
|
||||
if ! grep -F -q "$EXPORT_COMMAND" "$1"; then
|
||||
read -p "Update PATH in $1? [y/n] " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Updating $1"
|
||||
echo >> $1
|
||||
echo "# Codon compiler path (added by install script)" >> $1
|
||||
echo $EXPORT_COMMAND >> $1
|
||||
else
|
||||
echo "Skipping."
|
||||
fi
|
||||
else
|
||||
echo "PATH already updated in $1; skipping update."
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$SHELL" == *zsh ]]; then
|
||||
if [ -e ~/.zshenv ]; then
|
||||
update_profile ~/.zshenv
|
||||
elif [ -e ~/.zshrc ]; then
|
||||
update_profile ~/.zshrc
|
||||
else
|
||||
echo "Could not find zsh configuration file to update PATH"
|
||||
fi
|
||||
elif [[ "$SHELL" == *bash ]]; then
|
||||
if [ -e ~/.bash_profile ]; then
|
||||
update_profile ~/.bash_profile
|
||||
elif [ -e ~/.bash_login ]; then
|
||||
update_profile ~/.bash_login
|
||||
elif [ -e ~/.profile ]; then
|
||||
update_profile ~/.profile
|
||||
else
|
||||
echo "Could not find bash configuration file to update PATH"
|
||||
fi
|
||||
else
|
||||
echo "Don't know how to update configuration file for shell $SHELL"
|
||||
fi
|
||||
|
||||
echo "Codon successfully installed at: $(pwd)"
|
||||
echo "Open a new terminal session or update your PATH to use codon"
|
||||
28
ctfcup24-school-ad/services/flysim/flysim/misc.py
Normal file
28
ctfcup24-school-ad/services/flysim/flysim/misc.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from functools import partial
|
||||
from bson import ObjectId
|
||||
|
||||
|
||||
def set_vars_(drones_collection, _id, what):
|
||||
drones_collection.update_one(
|
||||
{"_id": _id},
|
||||
{"$set": what},
|
||||
)
|
||||
|
||||
|
||||
def get_var_(drones_collection, _id, what):
|
||||
drone = drones_collection.find_one({"_id": ObjectId(_id)})
|
||||
return drone[what]
|
||||
|
||||
|
||||
def set_vars(*args, **kwargs):
|
||||
raise RuntimeError("Function not initialized. Call initialize() first")
|
||||
|
||||
|
||||
def get_var(*args, **kwargs):
|
||||
raise RuntimeError("Function not initialized. Call initialize() first")
|
||||
|
||||
|
||||
def initialize(drones_collection):
|
||||
global set_vars, get_var
|
||||
set_vars = partial(set_vars_, drones_collection)
|
||||
get_var = partial(get_var_, drones_collection)
|
||||
@@ -0,0 +1,5 @@
|
||||
Flask==3.0.3
|
||||
Flask-SocketIO==5.4.1
|
||||
pymongo==4.10.1
|
||||
gunicorn==23.0.0
|
||||
gevent==24.11.1
|
||||
220
ctfcup24-school-ad/services/flysim/flysim/server.py
Normal file
220
ctfcup24-school-ad/services/flysim/flysim/server.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from flask import Flask, request, jsonify, abort
|
||||
from flask_socketio import SocketIO, join_room
|
||||
from pymongo import MongoClient
|
||||
from bson import ObjectId
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import gevent
|
||||
import json
|
||||
import session_authenticator
|
||||
from flight_plans import process_flight_plan
|
||||
from functools import partial
|
||||
import misc
|
||||
from gevent.lock import BoundedSemaphore
|
||||
db_lock = BoundedSemaphore(1)
|
||||
app = Flask(__name__)
|
||||
socketio = SocketIO(app)
|
||||
|
||||
client = MongoClient(os.getenv("MONGO_URI"))
|
||||
db = client["drone_db"]
|
||||
drones_collection = db["drones"]
|
||||
|
||||
misc.initialize(drones_collection)
|
||||
|
||||
|
||||
DRONE_LIFETIME = 8 * 60 # in seconds
|
||||
|
||||
|
||||
@app.route("/create_drone", methods=["POST"])
|
||||
def create_drone():
|
||||
with db_lock:
|
||||
label = request.json.get("label", "NO_LABEL")
|
||||
secret_data = request.json.get("secret_data", "")
|
||||
flight_plan = request.json.get("flight_plan", "")
|
||||
|
||||
creation_time = datetime.now()
|
||||
drone = {
|
||||
"label": label,
|
||||
"position": [0, 0],
|
||||
"velocity": [0, 0],
|
||||
"control_key": "",
|
||||
"flight_plan": flight_plan,
|
||||
"flight_log": "",
|
||||
"secret_data": secret_data,
|
||||
"created_at": creation_time,
|
||||
"expires_at": creation_time + timedelta(seconds=DRONE_LIFETIME),
|
||||
}
|
||||
|
||||
result = drones_collection.insert_one(drone)
|
||||
drone_id = str(result.inserted_id)
|
||||
control_key = session_authenticator.generate(drone_id, label)
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": result.inserted_id}, {"$set": {"control_key": control_key}}
|
||||
)
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"id": drone_id,
|
||||
"control_key": control_key,
|
||||
"expires_at": drone["expires_at"].isoformat(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def filter_sensitive_data(
|
||||
data, filtered_fields=["control_key", "secret_data", "flight_plan", "flight_log"]
|
||||
):
|
||||
if isinstance(data, list):
|
||||
return [filter_sensitive_data(item) for item in data]
|
||||
elif isinstance(data, dict):
|
||||
return {k: v for k, v in data.items() if k not in filtered_fields}
|
||||
return data
|
||||
|
||||
|
||||
@app.route("/get_drones", methods=["GET"])
|
||||
def get_drones():
|
||||
with db_lock:
|
||||
with_field = request.args.get("with")
|
||||
if with_field:
|
||||
projection = json.loads(with_field)
|
||||
if len(projection[0]) == 1 and list(projection[0].keys())[0] == "$match":
|
||||
drones_response = list(drones_collection.aggregate(projection))
|
||||
|
||||
drones_response_final = []
|
||||
for item in drones_response:
|
||||
new_dict = item.copy()
|
||||
new_dict["id"] = str(new_dict["_id"])
|
||||
del new_dict["_id"]
|
||||
drones_response_final.append(new_dict)
|
||||
|
||||
return json.dumps(filter_sensitive_data(drones_response_final), default=str)
|
||||
return abort(403)
|
||||
else:
|
||||
drones_cursor = drones_collection.find({}, {"_id": 1})
|
||||
return json.dumps(
|
||||
[{"id": str(drone["_id"])} for drone in drones_cursor], default=str
|
||||
)
|
||||
|
||||
|
||||
@socketio.on("connect")
|
||||
def connect():
|
||||
print("Client connected")
|
||||
|
||||
|
||||
@socketio.on("disconnect")
|
||||
def disconnect():
|
||||
print("Client disconnected")
|
||||
|
||||
|
||||
@socketio.on("join_drone")
|
||||
def join_drone(data):
|
||||
with db_lock:
|
||||
drone_id = data["drone_id"]
|
||||
control_key = data["control_key"]
|
||||
|
||||
drone = drones_collection.find_one({"_id": ObjectId(drone_id)})
|
||||
|
||||
if drone and drone["control_key"] == control_key:
|
||||
join_room(drone_id)
|
||||
socketio.emit(
|
||||
"drone_connected", {"data": f"Connected to drone {drone_id}"}, room=drone_id
|
||||
)
|
||||
else:
|
||||
socketio.emit("error", {"data": "Invalid drone ID or control key"})
|
||||
|
||||
|
||||
@socketio.on("set_position")
|
||||
def set_position(data):
|
||||
with db_lock:
|
||||
drone_id = data["drone_id"]
|
||||
position = data["position"]
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": ObjectId(drone_id)}, {"$set": {"position": position}}
|
||||
)
|
||||
|
||||
socketio.emit(
|
||||
"position_updated",
|
||||
{"id": drone_id, "new_position": position},
|
||||
room=drone_id,
|
||||
)
|
||||
|
||||
|
||||
@socketio.on("set_velocity")
|
||||
def set_velocity(data):
|
||||
with db_lock:
|
||||
drone_id = data["drone_id"]
|
||||
velocity = data["velocity"]
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": ObjectId(drone_id)}, {"$set": {"velocity": velocity}}
|
||||
)
|
||||
|
||||
socketio.emit(
|
||||
"velocity_updated",
|
||||
{"id": drone_id, "new_velocity": velocity},
|
||||
room=drone_id,
|
||||
)
|
||||
|
||||
|
||||
def update_positions():
|
||||
with db_lock:
|
||||
current_time = datetime.now()
|
||||
|
||||
expired_drones = drones_collection.find({"expires_at": {"$lt": current_time}})
|
||||
for drone in expired_drones:
|
||||
drone_id = str(drone["_id"])
|
||||
drones_collection.delete_one({"_id": drone["_id"]})
|
||||
socketio.emit("drone_expired", {"id": drone_id}, room=str(drone["_id"]))
|
||||
|
||||
active_drones = drones_collection.find({"expires_at": {"$gt": current_time}})
|
||||
|
||||
for drone in active_drones:
|
||||
cur_time = (datetime.now() - drone["created_at"]).total_seconds()
|
||||
process_flight_plan(drone, cur_time)
|
||||
new_position = [
|
||||
drone["position"][0] + drone["velocity"][0],
|
||||
drone["position"][1] + drone["velocity"][1],
|
||||
]
|
||||
new_velocity = drone["velocity"].copy()
|
||||
|
||||
for i in range(2):
|
||||
if abs(new_position[i]) > 100:
|
||||
new_velocity[i] = -new_velocity[i]
|
||||
new_position[i] = 100 if new_position[i] > 0 else -100
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": drone["_id"]},
|
||||
{"$set": {"position": new_position, "velocity": new_velocity}},
|
||||
)
|
||||
socketio.emit(
|
||||
"data_updated",
|
||||
{
|
||||
"id": str(drone["_id"]),
|
||||
"label": drone["label"],
|
||||
"position": new_position,
|
||||
"velocity": new_velocity,
|
||||
"cur_time": int(cur_time),
|
||||
"control_key": drone["control_key"],
|
||||
"flight_plan": drone["flight_plan"],
|
||||
"flight_log": drone["flight_log"],
|
||||
"secret_data": drone["secret_data"],
|
||||
"created_at": str(drone["created_at"]),
|
||||
"expires_at": str(drone["expires_at"]),
|
||||
},
|
||||
room=str(drone["_id"]),
|
||||
)
|
||||
|
||||
|
||||
def run_update_positions():
|
||||
print("start_background_task")
|
||||
while True:
|
||||
update_positions()
|
||||
gevent.sleep(1)
|
||||
|
||||
|
||||
socketio.start_background_task(run_update_positions)
|
||||
if __name__ == "__main__":
|
||||
socketio.run(app, host="0.0.0.0", port=9001)
|
||||
BIN
ctfcup24-school-ad/services/flysim/flysim/session_authenticator.so
Executable file
BIN
ctfcup24-school-ad/services/flysim/flysim/session_authenticator.so
Executable file
Binary file not shown.
Reference in New Issue
Block a user