Flask -> FastAPI

This commit is contained in:
DomySh
2022-06-28 13:26:06 +02:00
parent d781801bac
commit 3f2e8bb2f8
9 changed files with 307 additions and 435 deletions

View File

@@ -1,26 +1,17 @@
#Building main conteiner #Building main conteiner
FROM python:slim-buster FROM python:slim-buster
RUN apt-get update && apt-get -y install curl supervisor gettext-base build-essential libboost-dev nginx libboost-system-dev libboost-thread-dev RUN apt-get update && apt-get -y install build-essential libboost-system-dev libboost-thread-dev
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash
RUN apt-get install nodejs
RUN npm install serve -g --silent
RUN mkdir /execute RUN mkdir /execute
WORKDIR /execute WORKDIR /execute
ADD ./backend/requirements.txt /execute/requirements.txt
RUN pip install --no-cache-dir -r /execute/requirements.txt
COPY ./backend/ /execute/ COPY ./backend/ /execute/
RUN pip install --no-cache-dir -r /execute/requirements.txt
ARG GCC_PARAMS ARG GCC_PARAMS
RUN c++ -O3 $GCC_PARAMS -o proxy/proxy proxy/proxy.cpp -pthread -lboost_system -lboost_thread RUN c++ -O3 $GCC_PARAMS -o proxy/proxy proxy/proxy.cpp -pthread -lboost_system -lboost_thread
COPY ./config/supervisord.conf /etc/supervisor/supervisord.conf
COPY ./config/nginx.conf /tmp/nginx.conf
COPY ./config/start_nginx.sh /tmp/start_nginx.sh
COPY ./frontend/build/ ./frontend/ COPY ./frontend/build/ ./frontend/
RUN usermod -a -G root nobody RUN usermod -a -G root nobody
@@ -29,6 +20,6 @@ RUN chown -R nobody:root /execute && \
RUN chmod ug+x /execute/proxy/proxy RUN chmod ug+x /execute/proxy/proxy
ENTRYPOINT ["/usr/bin/supervisord","-c","/etc/supervisor/supervisord.conf"] ENTRYPOINT ["python3", "app.py", "DOCKER"]

View File

@@ -1,351 +1,297 @@
from base64 import b64decode from base64 import b64decode
import sqlite3, subprocess, sys, threading, bcrypt, secrets, time, re import sqlite3, uvicorn, sys, bcrypt, secrets, re, os, asyncio, httpx, urllib, websockets
from flask import Flask, jsonify, request, abort, session from fastapi import FastAPI, Request, HTTPException, WebSocket
from functools import wraps from starlette.middleware.sessions import SessionMiddleware
from flask_cors import CORS from pydantic import BaseModel
from jsonschema import validate from fastapi.responses import FileResponse, StreamingResponse
from utils import SQLite, KeyValueStorage, gen_internal_port, ProxyManager, from_name_get_id, STATUS from utils import SQLite, KeyValueStorage, gen_internal_port, ProxyManager, from_name_get_id, STATUS
ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER"
DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG"
# DB init # DB init
db = SQLite('firegex') db = SQLite('firegex')
db.connect() db.connect()
conf = KeyValueStorage(db) conf = KeyValueStorage(db)
firewall = ProxyManager(db) firewall = ProxyManager(db)
try: app = FastAPI(debug=DEBUG)
import uwsgi
IN_UWSGI = True
except ImportError:
IN_UWSGI = False
app = Flask(__name__) app.add_middleware(SessionMiddleware, secret_key=os.urandom(32))
SESSION_TOKEN = secrets.token_hex(8)
APP_STATUS = "init"
REACT_BUILD_DIR = "../frontend/build/" if not ON_DOCKER else "../frontend/"
REACT_HTML_PATH = os.path.join(REACT_BUILD_DIR,"index.html")
DEBUG = not ((len(sys.argv) > 1 and sys.argv[1] == "UWSGI") or IN_UWSGI) if not conf.get("password") is None:
APP_STATUS = "run"
def is_loggined(): def is_loggined(request: Request):
if DEBUG: return True return request.session.get("token", "") == SESSION_TOKEN
return True if session.get("loggined") else False
def login_required(f): def login_check(request: Request):
@wraps(f) if is_loggined(request): return True
def decorated_function(*args, **kwargs): raise HTTPException(status_code=401, detail="Invalid login session!")
if is_loggined() or DEBUG:
return f(*args, **kwargs)
else:
return abort(401)
return decorated_function @app.get("/api/status")
async def get_status(request: Request):
global APP_STATUS
@app.before_first_request
def before_first_request():
firewall.reload()
app.config['SECRET_KEY'] = secrets.token_hex(32)
if DEBUG:
app.config["STATUS"] = "run"
elif conf.get("password") is None:
app.config["STATUS"] = "init"
else:
app.config["STATUS"] = "run"
@app.route("/api/status")
def get_status():
if DEBUG:
return {
"status":app.config["STATUS"],
"loggined": is_loggined(),
"debug":True
}
else:
return {
"status":app.config["STATUS"],
"loggined": is_loggined()
}
@app.route("/api/login", methods = ['POST'])
def login():
if DEBUG: return { "status":"ok" }
if app.config["STATUS"] != "run": return abort(404)
req = request.get_json(force = True)
try:
validate(
instance=req,
schema={
"type" : "object",
"properties" : {
"password" : {"type" : "string"}
},
})
except Exception:
return abort(400)
if req["password"] == "":
return {"status":"Cannot insert an empty password!"}
time.sleep(.3) # No bruteforce :)
if bcrypt.checkpw(req["password"].encode(), conf.get("password").encode()):
session["loggined"] = True
return { "status":"ok" }
return {"status":"Wrong password!"}
@app.route("/api/logout")
def logout():
if DEBUG: return { "status":"ok" }
session["loggined"] = False
return { "status":"ok" }
@app.route('/api/change-password', methods = ['POST'])
@login_required
def change_password():
if DEBUG: return { "status":"ok" }
if app.config["STATUS"] != "run": return abort(404)
req = request.get_json(force = True)
try:
validate(
instance=req,
schema={
"type" : "object",
"properties" : {
"password" : {"type" : "string"},
"expire": {"type" : "boolean"},
},
})
except Exception:
return abort(400)
if req["password"] == "":
return {"status":"Cannot insert an empty password!"}
if req["expire"]:
app.config['SECRET_KEY'] = secrets.token_hex(32)
session["loggined"] = True
hash_psw = bcrypt.hashpw(req["password"].encode(), bcrypt.gensalt())
conf.put("password",hash_psw.decode())
return {"status":"ok"}
@app.route('/api/set-password', methods = ['POST'])
def set_password():
if DEBUG: return { "status":"ok" }
if app.config["STATUS"] != "init": return abort(404)
req = request.get_json(force = True)
try:
validate(
instance=req,
schema={
"type" : "object",
"properties" : {
"password" : {"type" : "string"}
},
})
except Exception:
return abort(400)
if not "password" in req or not isinstance(req["password"],str):
return abort(400)
if req["password"] == "":
return {"status":"Cannot insert an empty password!"}
hash_psw = bcrypt.hashpw(req["password"].encode(), bcrypt.gensalt())
conf.put("password",hash_psw.decode())
app.config["STATUS"] = "run"
session["loggined"] = True
return {"status":"ok"}
@app.route('/api/general-stats')
@login_required
def get_general_stats():
n_packets = db.query("SELECT SUM(blocked_packets) FROM regexes;")[0][0]
return { return {
'services': db.query("SELECT COUNT (*) FROM services;")[0][0], "status":APP_STATUS,
'regexes': db.query("SELECT COUNT (*) FROM regexes;")[0][0], "loggined": is_loggined(request)
'closed': n_packets if n_packets else 0
} }
@app.route('/api/services') class PasswordForm(BaseModel):
@login_required password: str
def get_services():
res = []
for i in db.query('SELECT * FROM services;'):
n_regex = db.query('SELECT COUNT (*) FROM regexes WHERE service_id = ?;', (i[1],))[0][0]
n_packets = db.query('SELECT SUM(blocked_packets) FROM regexes WHERE service_id = ?;', (i[1],))[0][0]
res.append({ class PasswordChangeForm(BaseModel):
'id': i[1], password: str
'status': i[0], expire: bool
'public_port': i[3],
'internal_port': i[2],
'n_regex': n_regex,
'n_packets': n_packets if n_packets else 0,
'name': i[4]
})
return jsonify(res) @app.post("/api/login")
async def login_api(request: Request, form: PasswordForm):
global APP_STATUS
if APP_STATUS != "run": raise HTTPException(status_code=400)
if form.password == "":
return {"status":"Cannot insert an empty password!"}
await asyncio.sleep(0.3) # No bruteforce :)
if bcrypt.checkpw(form.password.encode(), conf.get("password").encode()):
request.session["token"] = SESSION_TOKEN
return { "status":"ok" }
return {"status":"Wrong password!"}
@app.get("/api/logout")
async def logout(request: Request):
request.session["token"] = False
return { "status":"ok" }
@app.post('/api/change-password')
async def change_password(request: Request, form: PasswordChangeForm):
login_check(request)
global APP_STATUS
if APP_STATUS != "run": raise HTTPException(status_code=400)
if form.password == "":
return {"status":"Cannot insert an empty password!"}
if form.expire:
SESSION_TOKEN = secrets.token_hex(8)
request.session["token"] = SESSION_TOKEN
hash_psw = bcrypt.hashpw(form.password.encode(), bcrypt.gensalt())
conf.put("password",hash_psw.decode())
return {"status":"ok"}
@app.route('/api/service/<serv>') @app.post('/api/set-password')
@login_required async def set_password(request: Request, form: PasswordForm):
def get_service(serv): global APP_STATUS
q = db.query('SELECT * FROM services WHERE service_id = ?;', (serv,)) if APP_STATUS != "init": raise HTTPException(status_code=400)
if len(q) != 0: if form.password == "":
n_regex = db.query('SELECT COUNT (*) FROM regexes WHERE service_id = ?;', (serv,))[0][0] return {"status":"Cannot insert an empty password!"}
n_packets = db.query('SELECT SUM(blocked_packets) FROM regexes WHERE service_id = ?;', (serv,))[0][0]
return {
'id': q[0][1],
'status': q[0][0],
'public_port': q[0][3],
'internal_port': q[0][2],
'n_packets': n_packets if n_packets else 0,
'n_regex': n_regex,
'name': q[0][4]
}
else:
return abort(404)
@app.route('/api/service/<serv>/stop') hash_psw = bcrypt.hashpw(form.password.encode(), bcrypt.gensalt())
@login_required conf.put("password",hash_psw.decode())
def get_service_stop(serv): APP_STATUS = "run"
firewall.change_status(serv,STATUS.STOP) request.session["token"] = SESSION_TOKEN
return {"status":"ok"}
@app.get('/api/general-stats')
async def get_general_stats(request: Request):
login_check(request)
return db.query("""
SELECT
(SELECT COALESCE(SUM(blocked_packets),0) FROM regexes) closed,
(SELECT COUNT(*) FROM regexes) regexes,
(SELECT COUNT(*) FROM services) services
""")[0]
@app.get('/api/services')
async def get_services(request: Request):
login_check(request)
return db.query("""
SELECT
s.service_id `id`,
s.status status,
s.public_port public_port,
s.internal_port internal_port,
s.name name,
COUNT(*) n_regex,
COALESCE(SUM(r.blocked_packets),0) n_packets
FROM services s LEFT JOIN regexes r ON r.service_id = s.service_id
GROUP BY s.service_id;
""")
@app.get('/api/service/{service_id}')
async def get_service(request: Request, service_id: str):
login_check(request)
res = db.query("""
SELECT
s.service_id `id`,
s.status status,
s.public_port public_port,
s.internal_port internal_port,
s.name name,
COUNT(*) n_regex,
COALESCE(SUM(r.blocked_packets),0) n_packets
FROM services s LEFT JOIN regexes r ON r.service_id = s.service_id WHERE s.service_id = ?
GROUP BY s.service_id;
""", service_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0]
@app.get('/api/service/{service_id}/stop')
async def get_service_stop(request: Request, service_id: str):
login_check(request)
firewall.change_status(service_id,STATUS.STOP)
return {'status': 'ok'} return {'status': 'ok'}
@app.route('/api/service/<serv>/pause') @app.get('/api/service/{service_id}/pause')
@login_required async def get_service_pause(request: Request, service_id: str):
def get_service_pause(serv): login_check(request)
firewall.change_status(serv,STATUS.PAUSE) firewall.change_status(service_id,STATUS.PAUSE)
return {'status': 'ok'} return {'status': 'ok'}
@app.route('/api/service/<serv>/start') @app.get('/api/service/{service_id}/start')
@login_required async def get_service_start(request: Request, service_id: str):
def get_service_start(serv): login_check(request)
firewall.change_status(serv,STATUS.ACTIVE) firewall.change_status(service_id,STATUS.ACTIVE)
return {'status': 'ok'} return {'status': 'ok'}
@app.route('/api/service/<serv>/delete') @app.get('/api/service/{service_id}/delete')
@login_required async def get_service_delete(request: Request, service_id: str):
def get_service_delete(serv): login_check(request)
db.query('DELETE FROM services WHERE service_id = ?;', (serv,)) db.query('DELETE FROM services WHERE service_id = ?;', service_id)
db.query('DELETE FROM regexes WHERE service_id = ?;', (serv,)) db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
firewall.fire_update(serv) firewall.fire_update(service_id)
return {'status': 'ok'} return {'status': 'ok'}
@app.route('/api/service/<serv>/regen-port') @app.get('/api/service/{service_id}/regen-port')
@login_required async def get_regen_port(request: Request, service_id: str):
def get_regen_port(serv): login_check(request)
db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', (gen_internal_port(db), serv)) db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', gen_internal_port(db), service_id)
firewall.fire_update(serv) firewall.fire_update(service_id)
return {'status': 'ok'} return {'status': 'ok'}
@app.route('/api/service/<serv>/regexes') @app.get('/api/service/{service_id}/regexes')
@login_required async def get_service_regexes(request: Request, service_id: str):
def get_service_regexes(serv): login_check(request)
return jsonify([ return db.query("""
{ SELECT
'id': row[5], regex, mode, regex_id `id`, service_id, is_blacklist,
'service_id': row[2], blocked_packets n_packets, is_case_sensitive
'regex': row[0], FROM regexes WHERE service_id = ?;
'is_blacklist': True if row[3] == "1" else False, """, service_id)
'is_case_sensitive' : True if row[6] == "1" else False,
'mode': row[1],
'n_packets': row[4],
} for row in db.query('SELECT * FROM regexes WHERE service_id = ?;', (serv,))
])
@app.get('/api/regex/{regex_id}')
async def get_regex_id(request: Request, regex_id: int):
login_check(request)
res = db.query("""
SELECT
regex, mode, regex_id `id`, service_id, is_blacklist,
blocked_packets n_packets, is_case_sensitive
FROM regexes WHERE `id` = ?;
""", regex_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This regex does not exists!")
return res[0]
@app.route('/api/regex/<int:regex_id>') @app.get('/api/regex/{regex_id}/delete')
@login_required async def get_regex_delete(request: Request, regex_id: int):
def get_regex_id(regex_id): login_check(request)
q = db.query('SELECT * FROM regexes WHERE regex_id = ?;', (regex_id,)) res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(q) != 0:
return {
'id': regex_id,
'service_id': q[0][2],
'regex': q[0][0],
'is_blacklist': True if q[0][3] == "1" else False,
'is_case_sensitive' : True if q[0][7] == "1" else False,
'mode': q[0][1],
'n_packets': q[0][4],
}
else:
return abort(404)
if len(res) != 0:
@app.route('/api/regex/<int:regex_id>/delete') db.query('DELETE FROM regexes WHERE regex_id = ?;', regex_id)
@login_required firewall.fire_update(res[0]["service_id"])
def get_regex_delete(regex_id):
q = db.query('SELECT * FROM regexes WHERE regex_id = ?;', (regex_id,))
if len(q) != 0:
db.query('DELETE FROM regexes WHERE regex_id = ?;', (regex_id,))
firewall.fire_update(q[0][2])
return {'status': 'ok'} return {'status': 'ok'}
@app.route('/api/regexes/add', methods = ['POST']) class RegexAddForm(BaseModel):
@login_required service_id: str
def post_regexes_add(): regex: str
req = request.get_json(force = True) mode: str
is_blacklist: bool
is_case_sensitive: bool
@app.post('/api/regexes/add')
async def post_regexes_add(request: Request, form: RegexAddForm):
login_check(request)
try: try:
validate( re.compile(b64decode(form.regex))
instance=req,
schema={
"type" : "object",
"properties" : {
"service_id" : {"type" : "string"},
"regex" : {"type" : "string"},
"is_blacklist" : {"type" : "boolean"},
"mode" : {"type" : "string"},
"is_case_sensitive" : {"type" : "boolean"}
},
})
except Exception:
return abort(400)
try:
re.compile(b64decode(req["regex"]))
except Exception: except Exception:
return {"status":"Invalid regex"} return {"status":"Invalid regex"}
try: try:
db.query("INSERT INTO regexes (service_id, regex, is_blacklist, mode, is_case_sensitive ) VALUES (?, ?, ?, ?, ?);", db.query("INSERT INTO regexes (service_id, regex, is_blacklist, mode, is_case_sensitive ) VALUES (?, ?, ?, ?, ?);",
(req['service_id'], req['regex'], req['is_blacklist'], req['mode'], req['is_case_sensitive'])) form.service_id, form.regex, form.is_blacklist, form.mode, form.is_case_sensitive)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
return {'status': 'An identical regex already exists'} return {'status': 'An identical regex already exists'}
firewall.fire_update(req['service_id']) firewall.fire_update(form.service_id)
return {'status': 'ok'} return {'status': 'ok'}
class ServiceAddForm(BaseModel):
name: str
port: int
@app.route('/api/services/add', methods = ['POST']) @app.post('/api/services/add')
@login_required async def post_services_add(request: Request, form: ServiceAddForm):
def post_services_add(): login_check(request)
req = request.get_json(force = True) serv_id = from_name_get_id(form.name)
try:
validate(
instance=req,
schema={
"type" : "object",
"properties" : {
"name" : {"type" : "string"},
"port" : {"type" : "number"}
},
})
except Exception:
return abort(400)
serv_id = from_name_get_id(req['name'])
try: try:
db.query("INSERT INTO services (name, service_id, internal_port, public_port, status) VALUES (?, ?, ?, ?, ?)", db.query("INSERT INTO services (name, service_id, internal_port, public_port, status) VALUES (?, ?, ?, ?, ?)",
(req['name'], serv_id, gen_internal_port(db), req['port'], 'stop')) form.name, serv_id, gen_internal_port(db), form.port, 'stop')
firewall.reload() firewall.reload()
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
return {'status': 'Name or/and port of the service has been already assigned to another service'} return {'status': 'Name or/and port of the service has been already assigned to another service'}
return {'status': 'ok'} return {'status': 'ok'}
async def frontend_debug_proxy(path):
httpc = httpx.AsyncClient()
req = httpc.build_request("GET",urllib.parse.urljoin(f"http://0.0.0.0:{os.getenv('F_PORT','3000')}", path))
resp = await httpc.send(req, stream=True)
return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code)
async def react_deploy(path):
file_request = os.path.join(REACT_BUILD_DIR, path)
if not os.path.isfile(file_request):
return FileResponse(REACT_HTML_PATH, media_type='text/html')
else:
return FileResponse(file_request)
if DEBUG: if DEBUG:
CORS(app, resources={r"/api/*": {"origins": "*"}}, supports_credentials=True ) async def forward_websocket(ws_a: WebSocket, ws_b: websockets.WebSocketClientProtocol):
while True:
data = await ws_a.receive_bytes()
await ws_b.send(data)
async def reverse_websocket(ws_a: WebSocket, ws_b: websockets.WebSocketClientProtocol):
while True:
data = await ws_b.recv()
await ws_a.send_text(data)
@app.websocket("/ws")
async def websocket_debug_proxy(ws: WebSocket):
await ws.accept()
async with websockets.connect(f"ws://0.0.0.0:{os.getenv('F_PORT','3000')}/ws") as ws_b_client:
fwd_task = asyncio.create_task(forward_websocket(ws, ws_b_client))
rev_task = asyncio.create_task(reverse_websocket(ws, ws_b_client))
await asyncio.gather(fwd_task, rev_task)
@app.get("/{full_path:path}")
async def catch_all(request: Request, full_path:str):
if DEBUG:
try:
return await frontend_debug_proxy(full_path)
except Exception:
return {"details":"Frontend not started at "+f"http://0.0.0.0:{os.getenv('F_PORT','3000')}"}
else: return await react_deploy(full_path)
if __name__ == '__main__': if __name__ == '__main__':
db.check_integrity({ db.check_integrity({
@@ -360,10 +306,10 @@ if __name__ == '__main__':
'regex': 'TEXT NOT NULL', 'regex': 'TEXT NOT NULL',
'mode': 'VARCHAR(1) NOT NULL', 'mode': 'VARCHAR(1) NOT NULL',
'service_id': 'VARCHAR(100) NOT NULL', 'service_id': 'VARCHAR(100) NOT NULL',
'is_blacklist': 'VARCHAR(1) NOT NULL', 'is_blacklist': 'BOOLEAN NOT NULL CHECK (is_blacklist IN (0, 1))',
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0', 'blocked_packets': 'INTEGER UNSIGNED NOT NULL async defAULT 0',
'regex_id': 'INTEGER PRIMARY KEY', 'regex_id': 'INTEGER PRIMARY KEY',
'is_case_sensitive' : 'VARCHAR(1) NOT NULL', 'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))',
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)', 'FOREIGN KEY (service_id)':'REFERENCES services (service_id)',
}, },
'keys_values': { 'keys_values': {
@@ -372,7 +318,13 @@ if __name__ == '__main__':
}, },
}) })
db.query("CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);") db.query("CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);")
if DEBUG:
app.run(host="0.0.0.0", port=8080 ,debug=True) firewall.reload()
else: # os.environ {PORT = Backend Port (Main Port), F_PORT = Frontend Port}
subprocess.run(["uwsgi","--socket","./uwsgi.sock","--master","--module","app:app", "--enable-threads"]) uvicorn.run(
"app:app",
host="0.0.0.0",
port=int(os.getenv("PORT","4444")),
reload=DEBUG,
access_log=DEBUG,
)

View File

@@ -1,6 +1,5 @@
Flask fastapi[all]
uwsgi httpx
flask-cors uvicorn[standard]
bcrypt bcrypt
kthread kthread
jsonschema

View File

@@ -17,9 +17,15 @@ class SQLite():
try: try:
self.conn = sqlite3.connect("db/" + self.db_name + '.db', check_same_thread = False) self.conn = sqlite3.connect("db/" + self.db_name + '.db', check_same_thread = False)
except Exception: except Exception:
with open("db/" + self.db_name + '.db', 'x') as f: with open("db/" + self.db_name + '.db', 'x'):
pass pass
self.conn = sqlite3.connect("db/" + self.db_name + '.db', check_same_thread = False) self.conn = sqlite3.connect("db/" + self.db_name + '.db', check_same_thread = False)
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
self.conn.row_factory = dict_factory
def disconnect(self) -> None: def disconnect(self) -> None:
self.conn.close() self.conn.close()
@@ -35,7 +41,7 @@ class SQLite():
cur.execute('''CREATE TABLE main.{}({});'''.format(t, ''.join([(c + ' ' + tables[t][c] + ', ') for c in tables[t]])[:-2])) cur.execute('''CREATE TABLE main.{}({});'''.format(t, ''.join([(c + ' ' + tables[t][c] + ', ') for c in tables[t]])[:-2]))
cur.close() cur.close()
def query(self, query, values = ()): def query(self, query, *values):
cur = self.conn.cursor() cur = self.conn.cursor()
try: try:
with self.lock: with self.lock:
@@ -51,17 +57,17 @@ class KeyValueStorage:
self.db = db self.db = db
def get(self, key): def get(self, key):
q = self.db.query('SELECT value FROM keys_values WHERE key = ?', (key,)) q = self.db.query('SELECT value FROM keys_values WHERE key = ?', key)
if len(q) == 0: if len(q) == 0:
return None return None
else: else:
return q[0][0] return q[0]["value"]
def put(self, key, value): def put(self, key, value):
if self.get(key) is None: if self.get(key) is None:
self.db.query('INSERT INTO keys_values (key, value) VALUES (?, ?);', (key,str(value))) self.db.query('INSERT INTO keys_values (key, value) VALUES (?, ?);', key, str(value))
else: else:
self.db.query('UPDATE keys_values SET value=? WHERE key = ?;', (str(value), key)) self.db.query('UPDATE keys_values SET value=? WHERE key = ?;', str(value), key)
class STATUS: class STATUS:
WAIT = "wait" WAIT = "wait"
@@ -92,8 +98,8 @@ class ProxyManager:
def reload(self): def reload(self):
self.__clean_proxy_table() self.__clean_proxy_table()
with self.lock: with self.lock:
for srv_id in self.db.query('SELECT service_id, status FROM services;'): for srv in self.db.query('SELECT service_id, status FROM services;'):
srv_id, n_status = srv_id srv_id, n_status = srv["service_id"], srv["status"]
if srv_id in self.proxy_table: if srv_id in self.proxy_table:
continue continue
update_signal = threading.Event() update_signal = threading.Event()
@@ -111,24 +117,23 @@ class ProxyManager:
callback_signal.clear() callback_signal.clear()
def get_service_data(self, id): def get_service_data(self, id):
q = self.db.query('SELECT * FROM services WHERE service_id=?;',(id,)) res = self.db.query("""
if len(q) == 0: return None SELECT
srv = q[0] service_id `id`,
filters = [{ status,
'id': row[5], public_port,
'regex': row[0], internal_port
'is_blacklist': True if row[3] == "1" else False, FROM services WHERE service_id = ?;
'is_case_sensitive' : True if row[6] == "1" else False, """, id)
'mode': row[1], if len(res) == 0: return None
'n_packets': row[4], else: res = res[0]
} for row in self.db.query('SELECT * FROM regexes WHERE service_id = ?;', (id,))] res["filters"] = self.db.query("""
return { SELECT
'id': srv[1], regex, mode, regex_id `id`, is_blacklist,
'status': srv[0], blocked_packets n_packets, is_case_sensitive
'public_port': srv[3], FROM regexes WHERE service_id = ?;
'internal_port': srv[2], """, id)
'filters':filters return res
}
def change_status(self, id, to): def change_status(self, id, to):
with self.lock: with self.lock:
@@ -152,7 +157,7 @@ class ProxyManager:
del self.proxy_table[id] del self.proxy_table[id]
def __update_status_db(self, id, status): def __update_status_db(self, id, status):
self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", (status, id)) self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, id)
def __proxy_starter(self, id, proxy:Proxy, next_status): def __proxy_starter(self, id, proxy:Proxy, next_status):
def func(): def func():
@@ -217,7 +222,7 @@ class ProxyManager:
def stats_updater(filter:Filter): def stats_updater(filter:Filter):
self.db.query("UPDATE regexes SET blocked_packets = ? WHERE regex_id = ?;", (filter.blocked, filter.code)) self.db.query("UPDATE regexes SET blocked_packets = ? WHERE regex_id = ?;", filter.blocked, filter.code)
if not proxy: if not proxy:
proxy = Proxy( proxy = Proxy(
@@ -291,7 +296,7 @@ def from_name_get_id(name):
def gen_internal_port(db): def gen_internal_port(db):
while True: while True:
res = random.randint(30000, 45000) res = random.randint(30000, 45000)
if len(db.query('SELECT 1 FROM services WHERE internal_port = ?;', (res,))) == 0: if len(db.query('SELECT 1 FROM services WHERE internal_port = ?;', res)) == 0:
break break
return res return res

View File

@@ -1,26 +0,0 @@
worker_processes 5; ## Default: 1
pid /var/run/nginx.pid;
user nobody nogroup;
events {
worker_connections 1024;
}
http{
server {
listen $NGINX_PORT;
location / {
include proxy_params;
proxy_pass http://unix:/execute/react.sock;
}
location /api/ {
include uwsgi_params;
uwsgi_pass unix:/execute/uwsgi.sock;
}
}
}

View File

@@ -1,6 +0,0 @@
#/bin/bash
chown :root -R /execute/db
chmod g+w -R /execute/db
envsubst '$NGINX_PORT' < /tmp/nginx.conf > /etc/nginx/nginx.conf
/usr/sbin/nginx -g "daemon off;" || exit 1

View File

@@ -1,42 +0,0 @@
[supervisord]
logfile = /dev/null
loglevel = info
user = root
pidfile = /var/run/supervisord.pid
nodaemon = true
[program:backend]
directory=/execute
user = nobody
group = root
command=python3 app.py UWSGI
startsecs=10
stopsignal=QUIT
stopasgroup=true
killasgroup=true
stderr_logfile=/var/log/supervisor/%(program_name)s_stderr.log
stderr_logfile_maxbytes=10MB
stdout_logfile=/var/log/supervisor/%(program_name)s_stdout.log
stdout_logfile_maxbytes=10MB
[program:frontend]
directory=/execute
user = nobody
command=serve -s frontend -l unix:/execute/react.sock
startsecs=10
stopsignal=QUIT
stopasgroup=true
killasgroup=true
[program:nginx]
command=bash /tmp/start_nginx.sh
autostart=true
autorestart=true
user = root
startretries=5
numprocs=1
startsecs=0
stderr_logfile=/var/log/supervisor/%(program_name)s_stderr.log
stderr_logfile_maxbytes=10MB
stdout_logfile=/var/log/supervisor/%(program_name)s_stdout.log
stdout_logfile_maxbytes=10MB

View File

@@ -7,11 +7,10 @@ var Buffer = require('buffer').Buffer
export const eventUpdateName = "update-info" export const eventUpdateName = "update-info"
const custom_url = (!process.env.NODE_ENV || process.env.NODE_ENV === 'development')?"http://127.0.0.1:8080":""
export async function getapi(path:string):Promise<any>{ export async function getapi(path:string):Promise<any>{
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
fetch(`${custom_url}/api/${path}`,{credentials: "same-origin"}) fetch(`/api/${path}`,{credentials: "same-origin"})
.then(res => { .then(res => {
if(res.status === 401) window.location.reload() if(res.status === 401) window.location.reload()
if(!res.ok) reject(res.statusText) if(!res.ok) reject(res.statusText)
@@ -25,7 +24,7 @@ export async function getapi(path:string):Promise<any>{
export async function postapi(path:string,data:any):Promise<any>{ export async function postapi(path:string,data:any):Promise<any>{
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
fetch(`${custom_url}/api/${path}`, { fetch(`/api/${path}`, {
method: 'POST', method: 'POST',
credentials: "same-origin", credentials: "same-origin",
cache: 'no-cache', cache: 'no-cache',

View File

@@ -50,7 +50,7 @@ services:
- GCC_PARAMS={gcc_params} - GCC_PARAMS={gcc_params}
network_mode: "host" network_mode: "host"
environment: environment:
- NGINX_PORT={args.port} - PORT={args.port}
volumes: volumes:
- /execute/db - /execute/db
""") """)
@@ -72,7 +72,7 @@ services:
ports: ports:
- {args.port}:{args.port} - {args.port}:{args.port}
environment: environment:
- NGINX_PORT={args.port} - PORT={args.port}
- LOCALHOST_IP=host.docker.internal - LOCALHOST_IP=host.docker.internal
volumes: volumes:
- /execute/db - /execute/db