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-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
|
|
|
|
|
|
|
|
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:
|
|
|
|
with open("config.yaml", "r") as file:
|
|
|
|
config = yaml.safe_load(file)
|
|
|
|
except:
|
|
|
|
with open("config.yaml", "w"):
|
|
|
|
pass
|
|
|
|
|
|
|
|
if config:
|
2023-09-26 00:55:02 +03: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-09-25 03:53:08 +03:00
|
|
|
for server_id, server_config in servers.items():
|
2023-09-26 00:55:02 +03:00
|
|
|
try:
|
|
|
|
server = Server(url=server_config["url"], cert=server_config["cert"], comment=server_config["comment"])
|
|
|
|
SERVERS.append(server)
|
|
|
|
log.info("Server found: %s", server.info()["name"])
|
|
|
|
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-09-25 01:19:50 +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()
|
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-09-25 01:19:50 +03:00
|
|
|
|
|
|
|
|
2023-09-25 03:53:08 +03:00
|
|
|
@app.route('/clients', methods=['GET', 'POST'])
|
|
|
|
def clients():
|
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-25 01:19:50 +03:00
|
|
|
else:
|
2023-09-25 03:53:08 +03:00
|
|
|
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')))
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/add_server', methods=['POST'])
|
|
|
|
def add_server():
|
2023-09-26 01:20:30 +03:00
|
|
|
if request.method == 'POST':
|
|
|
|
try:
|
|
|
|
with open("config.yaml", "r") as file:
|
|
|
|
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-09-25 01:19:50 +03:00
|
|
|
|
|
|
|
new_server = Server(url=request.form['url'], cert=request.form['cert'], comment=request.form['comment'])
|
2023-09-26 01:20:30 +03:00
|
|
|
|
|
|
|
servers[new_server.data["server_id"]] = {
|
|
|
|
'name': new_server.data["name"],
|
|
|
|
'url': new_server.data["url"],
|
|
|
|
'comment': new_server.data["comment"],
|
|
|
|
'cert': request.form['cert']
|
|
|
|
}
|
|
|
|
config["servers"] = servers
|
|
|
|
with open("config.yaml", "w") as file:
|
|
|
|
yaml.safe_dump(config, file)
|
|
|
|
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':
|
|
|
|
with open("config.yaml", "r") as file:
|
|
|
|
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
|
|
|
|
with open("config.yaml", "w") as file:
|
|
|
|
yaml.safe_dump(config, file)
|
|
|
|
|
|
|
|
for server in SERVERS:
|
|
|
|
if server.data["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'))
|
|
|
|
else:
|
|
|
|
server.create_key(request.form.get('name'))
|
|
|
|
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-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')))
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/del_client', methods=['POST'])
|
|
|
|
def del_client():
|
|
|
|
if request.method == 'POST':
|
|
|
|
with open("config.yaml", "r") as file:
|
|
|
|
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)
|
|
|
|
with open("config.yaml", "w") as file:
|
|
|
|
yaml.safe_dump(config, file)
|
|
|
|
update_state()
|
|
|
|
return redirect(url_for('clients', nt="User has been deleted"))
|
2023-09-25 03:53:08 +03:00
|
|
|
|
|
|
|
|
2023-09-25 23:48:25 +03:00
|
|
|
@app.route('/dynamic/<server_name>/<client_id>', methods=['GET'])
|
|
|
|
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:
|
|
|
|
if server.data["server_id"] in client["servers"]:
|
|
|
|
log.info("Dynamic config for %s requested by '%s'", server.data["name"], client["name"])
|
|
|
|
|
|
|
|
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/]"
|
|
|
|
}
|
|
|
|
except:
|
|
|
|
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-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')
|