Files
OutFleet/main.py

401 lines
13 KiB
Python
Raw Normal View History

2023-09-25 01:19:50 +03:00
import yaml
import logging
from datetime import datetime
2023-09-25 03:53:08 +03:00
import random
import string
2023-12-17 17:06:30 +02:00
import argparse
import uuid
2023-09-25 01:19:50 +03:00
2024-03-18 16:07:48 +02:00
import k8s
2023-09-25 01:19:50 +03:00
from flask import Flask, render_template, request, url_for, redirect
2023-09-25 23:48:25 +03:00
from flask_cors import CORS
2024-03-18 18:53:38 +02:00
from lib import Server, write_config, get_config, args
2023-09-25 01:19:50 +03:00
2024-03-18 16:07:48 +02:00
2023-12-19 12:35:00 +02:00
logging.getLogger("werkzeug").setLevel(logging.ERROR)
2024-03-16 04:29:49 +02:00
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%d-%m-%Y %H:%M:%S",
)
log = logging.getLogger("OutFleet")
file_handler = logging.FileHandler("sync.log")
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
file_handler.setFormatter(formatter)
log.addHandler(file_handler)
2023-09-27 22:42:32 +03:00
2024-03-18 18:53:38 +02:00
CFG_PATH = args.config
NAMESPACE = k8s.NAMESPACE
2023-09-25 01:19:50 +03:00
SERVERS = list()
2024-03-18 16:07:48 +02:00
BROKEN_SERVERS = list()
2023-09-25 23:48:25 +03:00
CLIENTS = dict()
2024-03-18 22:08:43 +02:00
VERSION = '4'
2023-12-19 12:35:00 +02:00
HOSTNAME = ""
2023-09-25 01:19:50 +03:00
app = Flask(__name__)
2023-09-25 23:48:25 +03:00
CORS(app)
2023-09-25 01:19:50 +03:00
2023-12-19 12:35:00 +02:00
2023-09-25 01:19:50 +03:00
def format_timestamp(ts):
2023-12-19 12:35:00 +02:00
return datetime.fromtimestamp(ts // 1000).strftime("%Y-%m-%d %H:%M:%S")
2023-09-25 03:53:08 +03:00
2023-09-26 00:55:02 +03:00
def random_string(length=64):
2023-09-25 03:53:08 +03:00
letters = string.ascii_letters + string.digits
2023-12-19 12:35:00 +02:00
return "".join(random.choice(letters) for i in range(length))
2023-09-25 01:19:50 +03:00
2024-03-18 16:07:48 +02:00
2023-09-25 01:19:50 +03:00
def update_state():
global SERVERS
2023-09-25 03:53:08 +03:00
global CLIENTS
2024-03-18 16:07:48 +02:00
global BROKEN_SERVERS
2023-09-26 00:55:02 +03:00
global HOSTNAME
2024-03-18 16:07:48 +02:00
2023-09-25 01:19:50 +03:00
SERVERS = list()
2024-03-18 16:07:48 +02:00
BROKEN_SERVERS = list()
2023-09-25 23:48:25 +03:00
CLIENTS = dict()
2024-03-18 18:53:38 +02:00
config = get_config()
2023-09-25 01:19:50 +03:00
if config:
2023-12-19 12:35:00 +02:00
HOSTNAME = config.get("ui_hostname", "my-own-SSL-ENABLED-domain.com")
servers = config.get("servers", dict())
2023-12-17 17:06:30 +02:00
for local_server_id, server_config in servers.items():
2023-09-26 00:55:02 +03:00
try:
2023-12-19 12:35:00 +02:00
server = Server(
url=server_config["url"],
cert=server_config["cert"],
comment=server_config["comment"],
local_server_id=local_server_id,
)
2023-09-26 00:55:02 +03:00
SERVERS.append(server)
2023-12-19 12:35:00 +02:00
log.info(
"Server state updated: %s, [%s]",
server.info()["name"],
local_server_id,
)
2023-09-26 00:55:02 +03:00
except Exception as e:
2024-03-18 16:07:48 +02:00
BROKEN_SERVERS.append({
"config": server_config,
"error": e,
"id": local_server_id
})
2023-09-26 00:55:02 +03:00
log.warning("Can't access server: %s - %s", server_config["url"], e)
2023-09-25 01:19:50 +03:00
2023-12-19 12:35:00 +02:00
CLIENTS = config.get("clients", dict())
2023-09-25 03:53:08 +03:00
2023-09-25 01:19:50 +03:00
2023-12-19 12:35:00 +02:00
@app.route("/", methods=["GET", "POST"])
2023-09-25 01:19:50 +03:00
def index():
2023-12-19 12:35:00 +02:00
if request.method == "GET":
2024-03-18 16:07:48 +02:00
#if request.args.get("broken") == True:
2023-09-25 01:19:50 +03:00
return render_template(
2023-12-19 12:35:00 +02:00
"index.html",
2023-09-25 01:19:50 +03:00
SERVERS=SERVERS,
VERSION=VERSION,
2024-03-18 22:08:43 +02:00
K8S_NAMESPACE=k8s.NAMESPACE,
2024-03-18 16:07:48 +02:00
BROKEN_SERVERS=BROKEN_SERVERS,
2023-12-19 12:35:00 +02:00
nt=request.args.get("nt"),
nl=request.args.get("nl"),
selected_server=request.args.get("selected_server"),
2024-03-18 16:07:48 +02:00
broken=request.args.get("broken", False),
2023-12-19 12:35:00 +02:00
add_server=request.args.get("add_server", None),
2023-09-25 03:53:08 +03:00
format_timestamp=format_timestamp,
)
2023-12-19 12:35:00 +02:00
elif request.method == "POST":
server = request.form["server_id"]
server = next(
2024-02-18 15:52:26 +02:00
(item for item in SERVERS if item.info()["local_server_id"] == server), None
)
2023-12-17 17:06:30 +02:00
server.apply_config(request.form, CFG_PATH)
2023-09-25 01:19:50 +03:00
update_state()
2023-09-25 03:53:08 +03:00
return redirect(
2023-12-19 12:35:00 +02:00
url_for(
"index",
nt="Updated Outline VPN Server",
selected_server=request.args.get("selected_server"),
)
)
2023-11-05 19:32:50 +02:00
else:
2023-12-19 12:35:00 +02:00
return redirect(url_for("index"))
2023-09-25 01:19:50 +03:00
2023-12-19 12:35:00 +02:00
@app.route("/clients", methods=["GET", "POST"])
2023-09-25 03:53:08 +03:00
def clients():
2023-12-19 12:35:00 +02:00
if request.method == "GET":
2023-09-25 03:53:08 +03:00
return render_template(
2023-12-19 12:35:00 +02:00
"clients.html",
2023-09-25 03:53:08 +03:00
SERVERS=SERVERS,
CLIENTS=CLIENTS,
2024-03-16 00:22:05 +02:00
VERSION=VERSION,
2024-03-18 22:08:43 +02:00
K8S_NAMESPACE=k8s.NAMESPACE,
2023-12-19 12:35:00 +02:00
nt=request.args.get("nt"),
nl=request.args.get("nl"),
selected_client=request.args.get("selected_client"),
add_client=request.args.get("add_client", None),
2023-09-25 23:48:25 +03:00
format_timestamp=format_timestamp,
dynamic_hostname=HOSTNAME,
)
2023-09-25 03:53:08 +03:00
2023-12-19 12:35:00 +02:00
@app.route("/add_server", methods=["POST"])
2023-09-25 03:53:08 +03:00
def add_server():
2023-12-19 12:35:00 +02:00
if request.method == "POST":
2023-09-26 01:20:30 +03:00
try:
2024-03-18 18:53:38 +02:00
config = get_config()
2023-12-19 12:35:00 +02:00
servers = config.get("servers", dict())
local_server_id = str(uuid.uuid4())
2023-09-25 01:19:50 +03:00
2023-12-19 12:35:00 +02:00
new_server = Server(
url=request.form["url"],
cert=request.form["cert"],
comment=request.form["comment"],
local_server_id=local_server_id,
)
2023-09-26 01:20:30 +03:00
2023-12-17 17:06:30 +02:00
servers[new_server.data["local_server_id"]] = {
2023-12-19 12:35:00 +02:00
"name": new_server.data["name"],
"url": new_server.data["url"],
"comment": new_server.data["comment"],
"cert": request.form["cert"],
2023-09-26 01:20:30 +03:00
}
config["servers"] = servers
2024-03-18 18:53:38 +02:00
write_config(config)
2023-09-27 22:42:32 +03:00
log.info("Added server: %s", new_server.data["name"])
2023-09-26 01:20:30 +03:00
update_state()
2023-12-19 12:35:00 +02:00
return redirect(url_for("index", nt="Added Outline VPN Server"))
2023-09-26 01:20:30 +03:00
except Exception as e:
2023-12-19 12:35:00 +02:00
return redirect(
url_for(
"index", nt=f"Couldn't access Outline VPN Server: {e}", nl="error"
)
)
2023-09-25 01:19:50 +03:00
2024-03-15 20:15:43 +02:00
@app.route("/del_server", methods=["POST"])
def del_server():
if request.method == "POST":
2024-03-18 18:53:38 +02:00
config = get_config()
2024-03-15 20:15:43 +02:00
local_server_id = request.form.get("local_server_id")
2024-03-16 00:22:05 +02:00
server_name = None
2024-03-15 20:15:43 +02:00
try:
2024-03-16 00:22:05 +02:00
server_name = config["servers"].pop(local_server_id)["name"]
2024-03-15 20:15:43 +02:00
except KeyError as e:
pass
for client_id, client_config in config["clients"].items():
try:
client_config["servers"].remove(local_server_id)
except ValueError as e:
pass
2024-03-18 18:53:38 +02:00
write_config(config)
2024-03-16 00:22:05 +02:00
log.info("Deleting server %s [%s]", server_name, request.form.get("local_server_id"))
2024-03-15 20:15:43 +02:00
update_state()
2024-03-16 00:22:05 +02:00
return redirect(url_for("index", nt=f"Server {server_name} has been deleted"))
2024-03-15 20:15:43 +02:00
2023-09-25 01:19:50 +03:00
2023-12-19 12:35:00 +02:00
@app.route("/add_client", methods=["POST"])
2023-09-25 03:53:08 +03:00
def add_client():
2023-12-19 12:35:00 +02:00
if request.method == "POST":
2024-03-18 18:53:38 +02:00
config = get_config()
2023-09-25 03:53:08 +03:00
2023-12-19 12:35:00 +02:00
clients = config.get("clients", dict())
user_id = request.form.get("user_id", random_string())
2023-09-25 03:53:08 +03:00
clients[user_id] = {
2023-12-19 12:35:00 +02:00
"name": request.form.get("name"),
"comment": request.form.get("comment"),
"servers": request.form.getlist("servers"),
2023-09-25 03:53:08 +03:00
}
config["clients"] = clients
2024-03-18 18:53:38 +02:00
write_config(config)
2023-12-19 12:35:00 +02:00
log.info("Client %s updated", request.form.get("name"))
2023-09-25 03:53:08 +03:00
for server in SERVERS:
2023-12-19 12:35:00 +02:00
if server.data["local_server_id"] in request.form.getlist("servers"):
client = next(
(
item
for item in server.data["keys"]
if item.name == request.form.get("old_name")
),
None,
)
2023-09-25 20:22:44 +03:00
if client:
2023-12-19 12:35:00 +02:00
if client.name == request.form.get("name"):
2023-09-25 20:22:44 +03:00
pass
else:
2023-12-19 12:35:00 +02:00
server.rename_key(client.key_id, request.form.get("name"))
log.info(
"Renaming key %s to %s on server %s",
request.form.get("old_name"),
request.form.get("name"),
server.data["name"],
)
2023-09-25 20:22:44 +03:00
else:
2023-12-19 12:35:00 +02:00
server.create_key(request.form.get("name"))
log.info(
"Creating key %s on server %s",
request.form.get("name"),
server.data["name"],
)
2023-09-25 20:22:44 +03:00
else:
2023-12-19 12:35:00 +02:00
client = next(
(
item
for item in server.data["keys"]
if item.name == request.form.get("old_name")
),
None,
)
2023-09-25 20:22:44 +03:00
if client:
server.delete_key(client.key_id)
2023-12-19 12:35:00 +02:00
log.info(
"Deleting key %s on server %s",
request.form.get("name"),
server.data["name"],
)
2023-09-25 03:53:08 +03:00
update_state()
2023-12-19 12:35:00 +02:00
return redirect(
url_for(
"clients",
nt="Clients updated",
selected_client=request.form.get("user_id"),
)
)
2023-09-27 22:42:32 +03:00
else:
2023-12-19 12:35:00 +02:00
return redirect(url_for("clients"))
2023-09-25 20:22:44 +03:00
2023-12-19 12:35:00 +02:00
@app.route("/del_client", methods=["POST"])
2023-09-25 20:22:44 +03:00
def del_client():
2023-12-19 12:35:00 +02:00
if request.method == "POST":
2024-03-18 18:53:38 +02:00
config = get_config()
2023-12-19 12:35:00 +02:00
clients = config.get("clients", dict())
user_id = request.form.get("user_id")
2023-09-25 20:22:44 +03:00
if user_id in clients:
for server in SERVERS:
2023-12-19 12:35:00 +02:00
client = next(
(
item
for item in server.data["keys"]
if item.name == request.form.get("name")
),
None,
)
2023-09-25 20:22:44 +03:00
if client:
server.delete_key(client.key_id)
config["clients"].pop(user_id)
2024-03-18 18:53:38 +02:00
write_config(config)
2023-12-19 12:35:00 +02:00
log.info("Deleting client %s", request.form.get("name"))
2023-09-25 20:22:44 +03:00
update_state()
2023-12-19 12:35:00 +02:00
return redirect(url_for("clients", nt="User has been deleted"))
2023-09-25 03:53:08 +03:00
2023-12-19 12:35:00 +02:00
@app.route("/dynamic/<server_name>/<client_id>", methods=["GET"], strict_slashes=False)
2023-09-25 23:48:25 +03:00
def dynamic(server_name, client_id):
2023-09-27 17:31:34 +03:00
try:
2023-12-19 12:35:00 +02:00
client = next(
(keys for client, keys in CLIENTS.items() if client == client_id), None
)
server = next(
(item for item in SERVERS if item.info()["name"] == server_name), None
)
key = next(
2024-02-18 16:57:41 +02:00
(item for item in server.data["keys"] if item.key_id == client["name"]), None
2023-12-19 12:35:00 +02:00
)
2023-09-27 17:31:34 +03:00
if server and client and key:
2023-12-17 17:06:30 +02:00
if server.data["local_server_id"] in client["servers"]:
2023-12-19 12:35:00 +02:00
log.info(
"Client %s wants ssconf for %s", client["name"], server.data["name"]
)
2023-09-27 17:31:34 +03:00
return {
2023-12-19 12:35:00 +02:00
"server": server.data["hostname_for_access_keys"],
"server_port": key.port,
"password": key.password,
"method": key.method,
"info": "Managed by OutFleet [github.com/house-of-vanity/OutFleet/]",
2023-09-27 17:31:34 +03:00
}
2023-09-27 22:42:32 +03:00
else:
2023-12-19 12:35:00 +02:00
log.warning(
"Hack attempt! Client %s denied by ACL on %s",
client["name"],
server.data["name"],
)
2023-09-27 22:42:32 +03:00
return "Hey buddy, i think you got the wrong door the leather-club is two blocks down"
2023-09-27 17:31:34 +03:00
except:
2023-09-27 22:42:32 +03:00
log.warning("Hack attempt! Client or server doesn't exist. SCAM")
2023-09-27 17:31:34 +03:00
return "Hey buddy, i think you got the wrong door the leather-club is two blocks down"
2023-09-25 23:48:25 +03:00
2023-12-19 12:35:00 +02:00
@app.route("/dynamic/", methods=["GET"], strict_slashes=False)
2023-12-17 17:06:30 +02:00
def _dynamic():
log.warning("Hack attempt! Client or server doesn't exist. SCAM")
2023-12-19 12:35:00 +02:00
return (
"Hey buddy, i think you got the wrong door the leather-club is two blocks down"
)
2023-12-17 12:32:18 +00:00
2023-12-19 12:35:00 +02:00
@app.route("/sync", methods=["GET", "POST"])
2023-12-17 12:32:18 +00:00
def sync():
2023-12-19 12:35:00 +02:00
if request.method == "GET":
2023-12-17 17:15:54 +02:00
try:
2023-12-19 12:35:00 +02:00
with open("sync.log", "r") as file:
2023-12-17 17:15:54 +02:00
lines = file.readlines()
except:
lines = []
2023-12-17 17:06:30 +02:00
return render_template(
2023-12-19 12:35:00 +02:00
"sync.html",
2023-12-17 17:06:30 +02:00
SERVERS=SERVERS,
CLIENTS=CLIENTS,
lines=lines,
)
2023-12-19 12:35:00 +02:00
if request.method == "POST":
log = logging.getLogger("sync")
file_handler = logging.FileHandler("sync.log")
2023-12-17 17:06:30 +02:00
file_handler.setLevel(logging.DEBUG)
2023-12-19 12:35:00 +02:00
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
2023-12-17 17:06:30 +02:00
file_handler.setFormatter(formatter)
log.addHandler(file_handler)
2024-03-16 04:29:49 +02:00
if request.form.get("wipe") == 'all':
for server in SERVERS:
log.info("Wiping all keys on [%s]", server.data["name"])
for client in server.data['keys']:
server.delete_key(client.key_id)
2023-12-17 17:06:30 +02:00
server_hash = {}
for server in SERVERS:
2023-12-19 12:35:00 +02:00
server_hash[server.data["local_server_id"]] = server
2023-12-17 17:06:30 +02:00
for key, client in CLIENTS.items():
2023-12-19 12:35:00 +02:00
for u_server_id in client["servers"]:
if u_server_id in server_hash:
if not server_hash[u_server_id].check_client(client["name"]):
log.warning(
2024-03-16 00:22:05 +02:00
f"Client {client['name']} absent on {server_hash[u_server_id].data['name']}"
2023-12-19 12:35:00 +02:00
)
server_hash[u_server_id].create_key(client["name"])
2023-12-17 17:06:30 +02:00
else:
2023-12-19 12:35:00 +02:00
log.info(
2024-03-16 00:22:05 +02:00
f"Client {client['name']} already present on {server_hash[u_server_id].data['name']}"
2023-12-19 12:35:00 +02:00
)
2023-12-17 17:06:30 +02:00
else:
2023-12-19 12:35:00 +02:00
log.info(
2024-03-16 00:22:05 +02:00
f"Client {client['name']} incorrect server_id {u_server_id}"
2023-12-19 12:35:00 +02:00
)
2023-12-17 17:06:30 +02:00
update_state()
2023-12-19 12:35:00 +02:00
return redirect(url_for("sync"))
2023-12-17 12:32:18 +00:00
2023-12-19 12:35:00 +02:00
if __name__ == "__main__":
2023-09-25 01:19:50 +03:00
update_state()
2023-12-19 12:35:00 +02:00
app.run(host="0.0.0.0")