Files
OutFleet/main.py

295 lines
11 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
from flask import Flask, render_template, request, url_for, redirect
2023-09-25 23:48:25 +03:00
from flask_cors import CORS
2023-09-25 01:19:50 +03:00
from lib import Server
2023-09-27 22:42:32 +03:00
logging.getLogger('werkzeug').setLevel(logging.ERROR)
2023-12-17 17:06:30 +02:00
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
2023-09-27 22:42:32 +03:00
2023-09-25 01:19:50 +03:00
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%d-%m-%Y %H:%M:%S')
2023-09-25 20:22:44 +03:00
log = logging.getLogger('OutFleet')
2023-09-25 01:19:50 +03:00
SERVERS = list()
2023-09-25 23:48:25 +03:00
CLIENTS = dict()
2023-09-26 00:55:02 +03: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
def format_timestamp(ts):
2023-09-25 03:53:08 +03:00
return datetime.fromtimestamp(ts // 1000).strftime('%Y-%m-%d %H:%M:%S')
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
return ''.join(random.choice(letters) for i in range(length))
2023-09-25 01:19:50 +03:00
def update_state():
global SERVERS
2023-09-25 03:53:08 +03:00
global CLIENTS
2023-09-26 00:55:02 +03:00
global HOSTNAME
2023-09-25 01:19:50 +03:00
SERVERS = list()
2023-09-25 23:48:25 +03:00
CLIENTS = dict()
2023-09-25 01:19:50 +03:00
config = dict()
try:
2023-10-31 04:51:57 +02:00
with open(CFG_PATH, "r") as file:
2023-09-25 01:19:50 +03:00
config = yaml.safe_load(file)
except:
2023-11-05 20:58:21 +02:00
try:
with open(CFG_PATH, "w"):
pass
except Exception as exp:
log.error(f"Couldn't create config. {exp}")
2023-09-25 01:19:50 +03:00
if config:
2023-12-17 12:32:18 +00:00
HOSTNAME = config.get('ui_hostname', 'my-own-SSL-ENABLED-domain.com')
2023-09-26 01:20:30 +03:00
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-17 17:06:30 +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-17 17:06:30 +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:
log.warning("Can't access server: %s - %s", server_config["url"], e)
2023-09-25 01:19:50 +03:00
2023-09-26 01:20:30 +03:00
CLIENTS = config.get('clients', dict())
2023-09-25 03:53:08 +03:00
2023-09-25 01:19:50 +03:00
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template(
'index.html',
SERVERS=SERVERS,
nt=request.args.get('nt'),
nl=request.args.get('nl'),
selected_server=request.args.get('selected_server'),
2023-09-25 03:53:08 +03:00
add_server=request.args.get('add_server', None),
format_timestamp=format_timestamp,
)
2023-11-05 19:32:50 +02:00
elif request.method == 'POST':
2023-09-25 01:19:50 +03:00
server = request.form['server_id']
server = next((item for item in SERVERS if item.info()["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(
url_for('index', nt="Updated Outline VPN Server", selected_server=request.args.get('selected_server')))
2023-11-05 19:32:50 +02:00
else:
return redirect(
url_for('index'))
2023-09-25 01:19:50 +03:00
2023-09-25 03:53:08 +03:00
@app.route('/clients', methods=['GET', 'POST'])
def clients():
2023-12-17 12:32:18 +00:00
# {% 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 %}
2023-09-25 01:19:50 +03:00
if request.method == 'GET':
2023-09-25 03:53:08 +03:00
return render_template(
'clients.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),
2023-09-25 23:48:25 +03:00
format_timestamp=format_timestamp,
dynamic_hostname=HOSTNAME,
)
2023-09-27 22:42:32 +03:00
# else:
# server = request.form['server_id']
# server = next((item for item in SERVERS if item.info()["server_id"] == server), None)
# server.apply_config(request.form)
# update_state()
# return redirect(
# url_for('index', nt="Updated Outline VPN Server", selected_server=request.args.get('selected_server')))
2023-09-25 03:53:08 +03:00
@app.route('/add_server', methods=['POST'])
def add_server():
2023-09-26 01:20:30 +03:00
if request.method == 'POST':
try:
2023-10-31 04:51:57 +02:00
with open(CFG_PATH, "r") as file:
2023-09-26 01:20:30 +03:00
config = yaml.safe_load(file) or {}
2023-09-25 01:19:50 +03:00
2023-09-26 01:20:30 +03:00
servers = config.get('servers', dict())
2023-12-17 17:06:30 +02:00
local_server_id = uuid.uuid4()
2023-09-25 01:19:50 +03:00
2023-12-17 17:06:30 +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-09-26 01:20:30 +03:00
'name': new_server.data["name"],
'url': new_server.data["url"],
'comment': new_server.data["comment"],
'cert': request.form['cert']
}
config["servers"] = servers
2023-10-31 04:51:57 +02:00
with open(CFG_PATH, "w") as file:
2023-09-26 01:20:30 +03:00
yaml.safe_dump(config, file)
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()
return redirect(url_for('index', nt="Added Outline VPN Server"))
except Exception as e:
return redirect(url_for('index', nt=f"Couldn't access Outline VPN Server: {e}", nl="error"))
2023-09-25 01:19:50 +03:00
2023-09-25 03:53:08 +03:00
@app.route('/add_client', methods=['POST'])
def add_client():
if request.method == 'POST':
2023-10-31 04:51:57 +02:00
with open(CFG_PATH, "r") as file:
2023-09-25 03:53:08 +03:00
config = yaml.safe_load(file) or {}
clients = config.get('clients', dict())
user_id = request.form.get('user_id', random_string())
clients[user_id] = {
'name': request.form.get('name'),
'comment': request.form.get('comment'),
'servers': request.form.getlist('servers')
}
config["clients"] = clients
2023-10-31 04:51:57 +02:00
with open(CFG_PATH, "w") as file:
2023-09-25 03:53:08 +03:00
yaml.safe_dump(config, file)
2023-09-27 22:42:32 +03:00
log.info("Client %s updated", request.form.get('name'))
2023-09-25 03:53:08 +03:00
for server in SERVERS:
2023-12-17 17:06:30 +02:00
if server.data["local_server_id"] in request.form.getlist('servers'):
2023-09-25 20:22:44 +03:00
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'):
pass
else:
server.rename_key(client.key_id, request.form.get('name'))
2023-09-27 22:42:32 +03:00
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:
server.create_key(request.form.get('name'))
2023-09-27 22:42:32 +03:00
log.info("Creating key %s on server %s", request.form.get('name'), server.data["name"])
2023-09-25 20:22:44 +03:00
else:
client = next((item for item in server.data["keys"] if item.name == request.form.get('old_name')), None)
if client:
server.delete_key(client.key_id)
2023-09-27 22:42:32 +03: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-09-25 20:22:44 +03: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-11-05 19:32:50 +02:00
return redirect(url_for('clients'))
2023-09-25 20:22:44 +03:00
@app.route('/del_client', methods=['POST'])
def del_client():
if request.method == 'POST':
2023-10-31 04:51:57 +02:00
with open(CFG_PATH, "r") as file:
2023-09-25 20:22:44 +03:00
config = yaml.safe_load(file) or {}
clients = config.get('clients', dict())
user_id = request.form.get('user_id')
if user_id in clients:
for server in SERVERS:
client = next((item for item in server.data["keys"] if item.name == request.form.get('name')), None)
if client:
server.delete_key(client.key_id)
config["clients"].pop(user_id)
2023-10-31 04:51:57 +02:00
with open(CFG_PATH, "w") as file:
2023-09-25 20:22:44 +03:00
yaml.safe_dump(config, file)
2023-09-27 22:42:32 +03:00
log.info("Deleting client %s", request.form.get('name'))
2023-09-25 20:22:44 +03:00
update_state()
return redirect(url_for('clients', nt="User has been deleted"))
2023-09-25 03:53:08 +03:00
2023-12-17 17:06:30 +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:
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:
2023-12-17 17:06:30 +02:00
if server.data["local_server_id"] in client["servers"]:
2023-09-27 22:42:32 +03:00
log.info("Client %s wants ssconf for %s", client["name"], server.data["name"])
2023-09-27 17:31:34 +03:00
return {
"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 22:42:32 +03:00
else:
log.warning("Hack attempt! Client %s denied by ACL on %s", client["name"], server.data["name"])
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-12-17 17:06:30 +02:00
2023-09-25 23:48:25 +03:00
2023-12-17 17:06:30 +02:00
@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"
2023-12-17 12:32:18 +00:00
@app.route('/sync', methods=['GET', 'POST'])
def sync():
2023-12-17 17:06:30 +02:00
if request.method == 'GET':
2023-12-17 17:15:54 +02:00
try:
with open('sync.log', 'r') as file:
lines = file.readlines()
except:
lines = []
2023-12-17 17:06:30 +02:00
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'))
2023-12-17 12:32:18 +00:00
2023-09-25 01:19:50 +03:00
if __name__ == '__main__':
update_state()
2023-09-25 20:22:44 +03:00
app.run(host='0.0.0.0')