Flask -> FastAPI
This commit is contained in:
15
Dockerfile
15
Dockerfile
@@ -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"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
508
backend/app.py
508
backend/app.py
@@ -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 {
|
return {
|
||||||
"status":app.config["STATUS"],
|
"status":APP_STATUS,
|
||||||
"loggined": is_loggined(),
|
"loggined": is_loggined(request)
|
||||||
"debug":True
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
"status":app.config["STATUS"],
|
|
||||||
"loggined": is_loggined()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.route("/api/login", methods = ['POST'])
|
class PasswordForm(BaseModel):
|
||||||
def login():
|
password: str
|
||||||
if DEBUG: return { "status":"ok" }
|
|
||||||
if app.config["STATUS"] != "run": return abort(404)
|
|
||||||
req = request.get_json(force = True)
|
|
||||||
|
|
||||||
try:
|
class PasswordChangeForm(BaseModel):
|
||||||
validate(
|
password: str
|
||||||
instance=req,
|
expire: bool
|
||||||
schema={
|
|
||||||
"type" : "object",
|
|
||||||
"properties" : {
|
|
||||||
"password" : {"type" : "string"}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
except Exception:
|
|
||||||
return abort(400)
|
|
||||||
|
|
||||||
if req["password"] == "":
|
@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!"}
|
return {"status":"Cannot insert an empty password!"}
|
||||||
time.sleep(.3) # No bruteforce :)
|
await asyncio.sleep(0.3) # No bruteforce :)
|
||||||
if bcrypt.checkpw(req["password"].encode(), conf.get("password").encode()):
|
|
||||||
session["loggined"] = True
|
if bcrypt.checkpw(form.password.encode(), conf.get("password").encode()):
|
||||||
|
request.session["token"] = SESSION_TOKEN
|
||||||
return { "status":"ok" }
|
return { "status":"ok" }
|
||||||
|
|
||||||
return {"status":"Wrong password!"}
|
return {"status":"Wrong password!"}
|
||||||
|
|
||||||
@app.route("/api/logout")
|
@app.get("/api/logout")
|
||||||
def logout():
|
async def logout(request: Request):
|
||||||
if DEBUG: return { "status":"ok" }
|
request.session["token"] = False
|
||||||
session["loggined"] = False
|
|
||||||
return { "status":"ok" }
|
return { "status":"ok" }
|
||||||
|
|
||||||
@app.route('/api/change-password', methods = ['POST'])
|
@app.post('/api/change-password')
|
||||||
@login_required
|
async def change_password(request: Request, form: PasswordChangeForm):
|
||||||
def change_password():
|
login_check(request)
|
||||||
if DEBUG: return { "status":"ok" }
|
global APP_STATUS
|
||||||
if app.config["STATUS"] != "run": return abort(404)
|
if APP_STATUS != "run": raise HTTPException(status_code=400)
|
||||||
req = request.get_json(force = True)
|
|
||||||
|
|
||||||
try:
|
if form.password == "":
|
||||||
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!"}
|
return {"status":"Cannot insert an empty password!"}
|
||||||
if req["expire"]:
|
if form.expire:
|
||||||
app.config['SECRET_KEY'] = secrets.token_hex(32)
|
SESSION_TOKEN = secrets.token_hex(8)
|
||||||
session["loggined"] = True
|
request.session["token"] = SESSION_TOKEN
|
||||||
hash_psw = bcrypt.hashpw(req["password"].encode(), bcrypt.gensalt())
|
|
||||||
|
hash_psw = bcrypt.hashpw(form.password.encode(), bcrypt.gensalt())
|
||||||
conf.put("password",hash_psw.decode())
|
conf.put("password",hash_psw.decode())
|
||||||
return {"status":"ok"}
|
return {"status":"ok"}
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/set-password', methods = ['POST'])
|
@app.post('/api/set-password')
|
||||||
def set_password():
|
async def set_password(request: Request, form: PasswordForm):
|
||||||
if DEBUG: return { "status":"ok" }
|
global APP_STATUS
|
||||||
if app.config["STATUS"] != "init": return abort(404)
|
if APP_STATUS != "init": raise HTTPException(status_code=400)
|
||||||
req = request.get_json(force = True)
|
if form.password == "":
|
||||||
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!"}
|
return {"status":"Cannot insert an empty password!"}
|
||||||
|
|
||||||
hash_psw = bcrypt.hashpw(req["password"].encode(), bcrypt.gensalt())
|
hash_psw = bcrypt.hashpw(form.password.encode(), bcrypt.gensalt())
|
||||||
conf.put("password",hash_psw.decode())
|
conf.put("password",hash_psw.decode())
|
||||||
app.config["STATUS"] = "run"
|
APP_STATUS = "run"
|
||||||
session["loggined"] = True
|
request.session["token"] = SESSION_TOKEN
|
||||||
return {"status":"ok"}
|
return {"status":"ok"}
|
||||||
|
|
||||||
@app.route('/api/general-stats')
|
@app.get('/api/general-stats')
|
||||||
@login_required
|
async def get_general_stats(request: Request):
|
||||||
def get_general_stats():
|
login_check(request)
|
||||||
n_packets = db.query("SELECT SUM(blocked_packets) FROM regexes;")[0][0]
|
return db.query("""
|
||||||
return {
|
SELECT
|
||||||
'services': db.query("SELECT COUNT (*) FROM services;")[0][0],
|
(SELECT COALESCE(SUM(blocked_packets),0) FROM regexes) closed,
|
||||||
'regexes': db.query("SELECT COUNT (*) FROM regexes;")[0][0],
|
(SELECT COUNT(*) FROM regexes) regexes,
|
||||||
'closed': n_packets if n_packets else 0
|
(SELECT COUNT(*) FROM services) services
|
||||||
}
|
""")[0]
|
||||||
|
|
||||||
@app.route('/api/services')
|
@app.get('/api/services')
|
||||||
@login_required
|
async def get_services(request: Request):
|
||||||
def get_services():
|
login_check(request)
|
||||||
res = []
|
return db.query("""
|
||||||
for i in db.query('SELECT * FROM services;'):
|
SELECT
|
||||||
n_regex = db.query('SELECT COUNT (*) FROM regexes WHERE service_id = ?;', (i[1],))[0][0]
|
s.service_id `id`,
|
||||||
n_packets = db.query('SELECT SUM(blocked_packets) FROM regexes WHERE service_id = ?;', (i[1],))[0][0]
|
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;
|
||||||
|
""")
|
||||||
|
|
||||||
res.append({
|
@app.get('/api/service/{service_id}')
|
||||||
'id': i[1],
|
async def get_service(request: Request, service_id: str):
|
||||||
'status': i[0],
|
login_check(request)
|
||||||
'public_port': i[3],
|
res = db.query("""
|
||||||
'internal_port': i[2],
|
SELECT
|
||||||
'n_regex': n_regex,
|
s.service_id `id`,
|
||||||
'n_packets': n_packets if n_packets else 0,
|
s.status status,
|
||||||
'name': i[4]
|
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]
|
||||||
|
|
||||||
return jsonify(res)
|
@app.get('/api/service/{service_id}/stop')
|
||||||
|
async def get_service_stop(request: Request, service_id: str):
|
||||||
|
login_check(request)
|
||||||
@app.route('/api/service/<serv>')
|
firewall.change_status(service_id,STATUS.STOP)
|
||||||
@login_required
|
|
||||||
def get_service(serv):
|
|
||||||
q = db.query('SELECT * FROM services WHERE service_id = ?;', (serv,))
|
|
||||||
if len(q) != 0:
|
|
||||||
n_regex = db.query('SELECT COUNT (*) FROM regexes WHERE service_id = ?;', (serv,))[0][0]
|
|
||||||
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')
|
|
||||||
@login_required
|
|
||||||
def get_service_stop(serv):
|
|
||||||
firewall.change_status(serv,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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
Flask
|
fastapi[all]
|
||||||
uwsgi
|
httpx
|
||||||
flask-cors
|
uvicorn[standard]
|
||||||
bcrypt
|
bcrypt
|
||||||
kthread
|
kthread
|
||||||
jsonschema
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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',
|
||||||
|
|||||||
4
start.py
4
start.py
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user