From 66a6c3e6d6f25a312a8e2b4132044d64a940fba8 Mon Sep 17 00:00:00 2001 From: Alexandr Bogomyakov Date: Sun, 17 Dec 2023 17:06:30 +0200 Subject: [PATCH] Added sync feature --- .gitignore | 3 ++ lib.py | 21 +++++++-- main.py | 100 ++++++++++++++++++++++------------------- templates/clients.html | 14 +++--- templates/index.html | 4 +- templates/sync.html | 10 +++++ 6 files changed, 94 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 5392b09..6e303d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ config.yaml __pycache__/ +sync.log +main.py +.vscode/launch.json diff --git a/lib.py b/lib.py index 9b2f45e..1b3280e 100644 --- a/lib.py +++ b/lib.py @@ -8,10 +8,9 @@ logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%d-%m-%Y %H:%M:%S') -CFG_PATH = '/usr/local/etc/outfleet/config.yaml' - class ServerDict(TypedDict): + server_id: str name: str url: str cert: str @@ -30,9 +29,12 @@ class Server: url: str, cert: str, comment: str, + # read from config. not the same as real server id you can get from api + local_server_id: str, ): self.client = OutlineVPN(api_url=url, cert_sha256=cert) self.data: ServerDict = { + 'local_server_id': local_server_id, 'name': self.client.get_server_information()["name"], 'url': url, 'cert': cert, @@ -49,8 +51,21 @@ class Server: def info(self) -> ServerDict: return self.data + + def check_client(self, name): + # Looking for any users with provided name. len(result) != 1 is a problem. + result = [] + for key in self.client.get_keys(): + if key.name == name: + result.append(name) + self.log.info(f"check_client found client `{name}` config is correct.") + if len(result) != 1: + self.log.warning(f"check_client found client `{name}` inconsistent. Found {len(result)} keys.") + return False + else: + return True - def apply_config(self, config): + def apply_config(self, config, CFG_PATH): if config.get("name"): self.client.set_server_name(config.get("name")) self.log.info("Changed %s name to '%s'", self.data["server_id"], config.get("name")) diff --git a/main.py b/main.py index df793f4..8e7b9d3 100644 --- a/main.py +++ b/main.py @@ -3,14 +3,18 @@ import logging from datetime import datetime import random import string +import argparse +import uuid from flask import Flask, render_template, request, url_for, redirect from flask_cors import CORS from lib import Server logging.getLogger('werkzeug').setLevel(logging.ERROR) - -CFG_PATH = '/usr/local/etc/outfleet/config.yaml' +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--config", default="/usr/local/etc/outfleet/config.yaml", help="Config file location") +args = parser.parse_args() +CFG_PATH = args.config logging.basicConfig( level=logging.INFO, @@ -55,11 +59,11 @@ def update_state(): if config: HOSTNAME = config.get('ui_hostname', 'my-own-SSL-ENABLED-domain.com') servers = config.get('servers', dict()) - for server_id, server_config in servers.items(): + for local_server_id, server_config in servers.items(): try: - server = Server(url=server_config["url"], cert=server_config["cert"], comment=server_config["comment"]) + server = Server(url=server_config["url"], cert=server_config["cert"], comment=server_config["comment"], local_server_id=local_server_id) SERVERS.append(server) - log.info("Server state updated: %s", server.info()["name"]) + log.info("Server state updated: %s, [%s]", server.info()["name"], local_server_id) except Exception as e: log.warning("Can't access server: %s - %s", server_config["url"], e) @@ -81,7 +85,7 @@ def index(): elif request.method == 'POST': server = request.form['server_id'] server = next((item for item in SERVERS if item.info()["server_id"] == server), None) - server.apply_config(request.form) + server.apply_config(request.form, CFG_PATH) update_state() return redirect( url_for('index', nt="Updated Outline VPN Server", selected_server=request.args.get('selected_server'))) @@ -128,10 +132,11 @@ def add_server(): config = yaml.safe_load(file) or {} servers = config.get('servers', dict()) + local_server_id = uuid.uuid4() - new_server = Server(url=request.form['url'], cert=request.form['cert'], comment=request.form['comment']) + new_server = Server(url=request.form['url'], cert=request.form['cert'], comment=request.form['comment'], local_server_id=local_server_id) - servers[new_server.data["server_id"]] = { + servers[new_server.data["local_server_id"]] = { 'name': new_server.data["name"], 'url': new_server.data["url"], 'comment': new_server.data["comment"], @@ -167,7 +172,7 @@ def add_client(): log.info("Client %s updated", request.form.get('name')) for server in SERVERS: - if server.data["server_id"] in request.form.getlist('servers'): + 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) if client: if client.name == request.form.get('name'): @@ -214,14 +219,14 @@ def del_client(): return redirect(url_for('clients', nt="User has been deleted")) -@app.route('/dynamic//', methods=['GET']) +@app.route('/dynamic//', methods=['GET'], strict_slashes=False) def dynamic(server_name, client_id): try: 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((item for item in server.data["keys"] if item.name == client["name"]), None) if server and client and key: - if server.data["server_id"] in client["servers"]: + if server.data["local_server_id"] in client["servers"]: log.info("Client %s wants ssconf for %s", client["name"], server.data["name"]) return { "server": server.data["hostname_for_access_keys"], @@ -236,46 +241,49 @@ def dynamic(server_name, client_id): except: log.warning("Hack attempt! Client or server doesn't exist. SCAM") return "Hey buddy, i think you got the wrong door the leather-club is two blocks down" + +@app.route('/dynamic/', methods=['GET'], strict_slashes=False) +def _dynamic(): + log.warning("Hack attempt! Client or server doesn't exist. SCAM") + return "Hey buddy, i think you got the wrong door the leather-club is two blocks down" @app.route('/sync', methods=['GET', 'POST']) def sync(): - # {% for server in SERVERS %} - # {% for key in server.data["keys"] %} - # {% if key.name == client['name'] %} - # ssconf://{{ dynamic_hostname }}/dynamic/{{server.info()['name']}}/{{selected_client}}#{{server.info()['comment']}} - # {% endif %} - # {% endfor %} - # {% endfor %} - log.info(f"{SERVERS[0]}") - # clients_status = [] - # for c_id, c_data in CLIENTS: - # servers_status = [] - # for server in c_data["servers"]: - # for server in SERVERS: - # if server.data["name"] == - # for key in server.data["keys"]: - # if key.name == c_data["name"]: - # servers_status.append{ + if request.method == 'GET': + with open('sync.log', 'r') as file: + lines = file.readlines() + return render_template( + 'sync.html', + SERVERS=SERVERS, + CLIENTS=CLIENTS, + lines=lines, + ) + if request.method == 'POST': + log = logging.getLogger('sync') + 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) + + server_hash = {} + for server in SERVERS: + server_hash[server.data['local_server_id']] = server + for key, client in CLIENTS.items(): + log.info(f"Sync client `{client['name']}`") + 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(f"Client `{client['name']}` absent on `{server_hash[u_server_id].data['name']}`") + server_hash[u_server_id].create_key(client['name']) + else: + log.info(f"Client `{client['name']}` presented on `{server_hash[u_server_id].data['name']}`") + else: + log.info(f"Client `{client['name']}` incorrect server_id `{u_server_id}`") + update_state() + return redirect(url_for('sync')) - # } - # client = { - # "id": c_id, - # "name": data["name"], - # "servers_status": - # } - # if request.method == 'GET': - # return render_template( - # 'sync.html', - # SERVERS=SERVERS, - # CLIENTS=CLIENTS, - # 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), - # format_timestamp=format_timestamp, - # dynamic_hostname=HOSTNAME, - # ) if __name__ == '__main__': diff --git a/templates/clients.html b/templates/clients.html index 43af03b..1312874 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -42,7 +42,7 @@
{% for server in SERVERS %} + {% endfor %} @@ -85,10 +85,10 @@

Allow access to:

{% for server in SERVERS %} - {% endfor %} @@ -106,7 +106,7 @@ Install OutLine VPN. Copy and paste below keys to OutLine client. Same keys will work simultaneously on many devices. {% for server in SERVERS -%} -{% if server.info()['server_id'] in client['servers'] %} +{% if server.info()['local_server_id'] in client['servers'] %} {{server.info()['name']}} ```{% for key in server.data["keys"] %}{% if key.name == client['name'] %}ssconf://{{ dynamic_hostname }}/dynamic/{{server.info()['name']}}/{{selected_client}}#{{server.info()['comment']}}{% endif %}{% endfor %}``` {% endif %} @@ -125,7 +125,7 @@ Same keys will work simultaneously on many devices. {% for server in SERVERS %} - {% if server.info()['server_id'] in client['servers'] %} + {% if server.info()['local_server_id'] in client['servers'] %} {{ server.info()['name'] }} @@ -148,7 +148,7 @@ Same keys will work simultaneously on many devices. {% for server in SERVERS %} - {% if server.info()['server_id'] in client['servers'] %} + {% if server.info()['local_server_id'] in client['servers'] %} {{ server.info()['name'] }} diff --git a/templates/index.html b/templates/index.html index 3da4799..d667595 100644 --- a/templates/index.html +++ b/templates/index.html @@ -77,7 +77,7 @@

{{server.info()["name"]}}

- v.{{server.info()["version"]}} {{server.info()["server_id"]}} + v.{{server.info()["version"]}} {{server.info()["local_server_id"]}}

@@ -128,7 +128,7 @@
- +

Share anonymous metrics