mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-07-06 17:14:07 +00:00
k8s discovery works
This commit is contained in:
0
.github/workflows/main.yml
vendored
Normal file → Executable file
0
.github/workflows/main.yml
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
@ -2,7 +2,8 @@ config.yaml
|
||||
__pycache__/
|
||||
sync.log
|
||||
main.py
|
||||
.vscode/launch.json
|
||||
.idea/*
|
||||
.vscode/*
|
||||
*.swp
|
||||
*.swo
|
||||
*.swn
|
||||
|
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
0
.idea/OutlineFleet.iml
generated
Normal file → Executable file
0
.idea/OutlineFleet.iml
generated
Normal file → Executable file
0
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file → Executable file
0
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file → Executable file
0
.idea/misc.xml
generated
Normal file → Executable file
0
.idea/misc.xml
generated
Normal file → Executable file
0
.idea/modules.xml
generated
Normal file → Executable file
0
.idea/modules.xml
generated
Normal file → Executable file
0
.idea/vcs.xml
generated
Normal file → Executable file
0
.idea/vcs.xml
generated
Normal file → Executable file
0
.vscode/extensions.json
vendored
Normal file → Executable file
0
.vscode/extensions.json
vendored
Normal file → Executable file
0
Dockerfile
Normal file → Executable file
0
Dockerfile
Normal file → Executable file
0
buildx.yaml
Normal file → Executable file
0
buildx.yaml
Normal file → Executable file
0
img/servers.png
Normal file → Executable file
0
img/servers.png
Normal file → Executable file
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
45
k8s.py
Normal file → Executable file
45
k8s.py
Normal file → Executable file
@ -1,5 +1,6 @@
|
||||
import base64
|
||||
import json
|
||||
import yaml
|
||||
import logging
|
||||
from kubernetes import client, config
|
||||
from kubernetes.client.rest import ApiException
|
||||
@ -19,16 +20,58 @@ formatter = logging.Formatter(
|
||||
file_handler.setFormatter(formatter)
|
||||
log.addHandler(file_handler)
|
||||
|
||||
def write_config(config):
|
||||
config_map = client.V1ConfigMap(
|
||||
api_version="v1",
|
||||
kind="ConfigMap",
|
||||
metadata=client.V1ObjectMeta(
|
||||
name=f"config-outfleet",
|
||||
labels={
|
||||
"app": "outfleet",
|
||||
}
|
||||
),
|
||||
data={"config.yaml": yaml.dump(config)}
|
||||
)
|
||||
try:
|
||||
api_response = v1.create_namespaced_config_map(
|
||||
namespace=NAMESPACE,
|
||||
body=config_map,
|
||||
)
|
||||
except ApiException as e:
|
||||
api_response = v1.patch_namespaced_config_map(
|
||||
name="config-outfleet",
|
||||
namespace=NAMESPACE,
|
||||
body=config_map,
|
||||
)
|
||||
|
||||
config.load_incluster_config()
|
||||
|
||||
v1 = client.CoreV1Api()
|
||||
|
||||
NAMESPACE = ""
|
||||
NAMESPACE = False
|
||||
SERVERS = list()
|
||||
CONFIG = None
|
||||
|
||||
log.info("Checking for Kubernetes environment")
|
||||
try:
|
||||
with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace") as f:
|
||||
NAMESPACE = f.read().strip()
|
||||
log.info(f"Found Kubernetes environment. Namespace {NAMESPACE}")
|
||||
except IOError:
|
||||
log.info("Kubernetes environment not detected")
|
||||
pass
|
||||
|
||||
# config = v1.list_namespaced_config_map(NAMESPACE, label_selector="app=outfleet").items["data"]["config.yaml"]
|
||||
try:
|
||||
CONFIG = yaml.safe_load(v1.read_namespaced_config_map(name="config-outfleet", namespace=NAMESPACE).data['config.yaml'])
|
||||
log.info(f"ConfigMap config.yaml loaded from Kubernetes API. Servers: {len(CONFIG['servers'])}, Clients: {len(CONFIG['clients'])}")
|
||||
except ApiException as e:
|
||||
log.warning(f"ConfigMap not found. Fisrt run?")
|
||||
|
||||
#servers = v1.list_namespaced_secret(NAMESPACE, label_selector="app=shadowbox")
|
||||
|
||||
if not CONFIG:
|
||||
log.info(f"Creating new ConfigMap [config-outfleet]")
|
||||
write_config({"clients": [], "servers": [], "ui_hostname": "accessible-address.com"})
|
||||
CONFIG = yaml.safe_load(v1.read_namespaced_config_map(name="config-outfleet", namespace=NAMESPACE).data['config.yaml'])
|
||||
|
||||
|
45
lib.py
Normal file → Executable file
45
lib.py
Normal file → Executable file
@ -1,7 +1,10 @@
|
||||
import argparse
|
||||
import logging
|
||||
from typing import TypedDict, List
|
||||
from outline_vpn.outline_vpn import OutlineKey, OutlineVPN
|
||||
import yaml
|
||||
import k8s
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@ -9,6 +12,42 @@ logging.basicConfig(
|
||||
datefmt="%d-%m-%Y %H:%M:%S",
|
||||
)
|
||||
|
||||
log = logging.getLogger(f'OutFleet.lib')
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
default="/usr/local/etc/outfleet/config.yaml",
|
||||
help="Config file location",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
def get_config():
|
||||
if not k8s.NAMESPACE:
|
||||
try:
|
||||
with open(args.config, "r") as file:
|
||||
config = yaml.safe_load(file)
|
||||
except:
|
||||
try:
|
||||
with open(args.config, "w"):
|
||||
pass
|
||||
except Exception as exp:
|
||||
log.error(f"Couldn't create config. {exp}")
|
||||
return None
|
||||
return config
|
||||
else:
|
||||
return k8s.CONFIG
|
||||
|
||||
def write_config(config):
|
||||
if not k8s.NAMESPACE:
|
||||
try:
|
||||
with open(args.config, "w") as file:
|
||||
yaml.safe_dump(config, file)
|
||||
except Exception as e:
|
||||
log.error(f"Couldn't write Outfleet config: {e}")
|
||||
else:
|
||||
k8s.write_config(config)
|
||||
|
||||
|
||||
class ServerDict(TypedDict):
|
||||
server_id: str
|
||||
@ -114,13 +153,11 @@ class Server:
|
||||
config.get("hostname_for_access_keys"),
|
||||
)
|
||||
if config.get("comment"):
|
||||
with open(CFG_PATH, "r") as file:
|
||||
config_file = yaml.safe_load(file) or {}
|
||||
config_file = get_config()
|
||||
config_file["servers"][self.data["local_server_id"]]["comment"] = config.get(
|
||||
"comment"
|
||||
)
|
||||
with open(CFG_PATH, "w") as file:
|
||||
yaml.safe_dump(config_file, file)
|
||||
write_config(config_file)
|
||||
self.log.info(
|
||||
"Changed %s comment to '%s'",
|
||||
self.data["local_server_id"],
|
||||
|
94
main.py
Normal file → Executable file
94
main.py
Normal file → Executable file
@ -10,7 +10,7 @@ import uuid
|
||||
import k8s
|
||||
from flask import Flask, render_template, request, url_for, redirect
|
||||
from flask_cors import CORS
|
||||
from lib import Server
|
||||
from lib import Server, write_config, get_config, args
|
||||
|
||||
|
||||
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
||||
@ -30,22 +30,9 @@ formatter = logging.Formatter(
|
||||
file_handler.setFormatter(formatter)
|
||||
log.addHandler(file_handler)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
default="/usr/local/etc/outfleet/config.yaml",
|
||||
help="Config file location",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--k8s",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Kubernetes Outline server discovery",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
CFG_PATH = args.config
|
||||
|
||||
CFG_PATH = args.config
|
||||
NAMESPACE = k8s.NAMESPACE
|
||||
SERVERS = list()
|
||||
BROKEN_SERVERS = list()
|
||||
CLIENTS = dict()
|
||||
@ -64,20 +51,6 @@ def random_string(length=64):
|
||||
|
||||
return "".join(random.choice(letters) for i in range(length))
|
||||
|
||||
def get_config():
|
||||
if not args.k8s:
|
||||
try:
|
||||
with open(CFG_PATH, "r") as file:
|
||||
config = yaml.safe_load(file)
|
||||
except:
|
||||
try:
|
||||
with open(CFG_PATH, "w"):
|
||||
pass
|
||||
except Exception as exp:
|
||||
log.error(f"Couldn't create config. {exp}")
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def update_state():
|
||||
@ -89,16 +62,8 @@ def update_state():
|
||||
SERVERS = list()
|
||||
BROKEN_SERVERS = list()
|
||||
CLIENTS = dict()
|
||||
config = dict()
|
||||
try:
|
||||
with open(CFG_PATH, "r") as file:
|
||||
config = yaml.safe_load(file)
|
||||
except:
|
||||
try:
|
||||
with open(CFG_PATH, "w"):
|
||||
pass
|
||||
except Exception as exp:
|
||||
log.error(f"Couldn't create config. {exp}")
|
||||
config = get_config()
|
||||
|
||||
|
||||
if config:
|
||||
HOSTNAME = config.get("ui_hostname", "my-own-SSL-ENABLED-domain.com")
|
||||
@ -164,13 +129,6 @@ def index():
|
||||
|
||||
@app.route("/clients", methods=["GET", "POST"])
|
||||
def clients():
|
||||
# {% 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 %}
|
||||
if request.method == "GET":
|
||||
return render_template(
|
||||
"clients.html",
|
||||
@ -184,22 +142,13 @@ def clients():
|
||||
format_timestamp=format_timestamp,
|
||||
dynamic_hostname=HOSTNAME,
|
||||
)
|
||||
# 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')))
|
||||
|
||||
|
||||
@app.route("/add_server", methods=["POST"])
|
||||
def add_server():
|
||||
if request.method == "POST":
|
||||
try:
|
||||
with open(CFG_PATH, "r") as file:
|
||||
config = yaml.safe_load(file) or {}
|
||||
|
||||
config = get_config()
|
||||
servers = config.get("servers", dict())
|
||||
local_server_id = str(uuid.uuid4())
|
||||
|
||||
@ -217,16 +166,7 @@ def add_server():
|
||||
"cert": request.form["cert"],
|
||||
}
|
||||
config["servers"] = servers
|
||||
try:
|
||||
with open(CFG_PATH, "w") as file:
|
||||
yaml.safe_dump(config, file)
|
||||
except Exception as e:
|
||||
return redirect(
|
||||
url_for(
|
||||
"index", nt=f"Couldn't write Outfleet config: {e}", nl="error"
|
||||
)
|
||||
)
|
||||
|
||||
write_config(config)
|
||||
log.info("Added server: %s", new_server.data["name"])
|
||||
update_state()
|
||||
return redirect(url_for("index", nt="Added Outline VPN Server"))
|
||||
@ -240,8 +180,7 @@ def add_server():
|
||||
@app.route("/del_server", methods=["POST"])
|
||||
def del_server():
|
||||
if request.method == "POST":
|
||||
with open(CFG_PATH, "r") as file:
|
||||
config = yaml.safe_load(file) or {}
|
||||
config = get_config()
|
||||
|
||||
local_server_id = request.form.get("local_server_id")
|
||||
server_name = None
|
||||
@ -254,9 +193,7 @@ def del_server():
|
||||
client_config["servers"].remove(local_server_id)
|
||||
except ValueError as e:
|
||||
pass
|
||||
|
||||
with open(CFG_PATH, "w") as file:
|
||||
yaml.safe_dump(config, file)
|
||||
write_config(config)
|
||||
log.info("Deleting server %s [%s]", server_name, request.form.get("local_server_id"))
|
||||
update_state()
|
||||
return redirect(url_for("index", nt=f"Server {server_name} has been deleted"))
|
||||
@ -265,8 +202,7 @@ def del_server():
|
||||
@app.route("/add_client", methods=["POST"])
|
||||
def add_client():
|
||||
if request.method == "POST":
|
||||
with open(CFG_PATH, "r") as file:
|
||||
config = yaml.safe_load(file) or {}
|
||||
config = get_config()
|
||||
|
||||
clients = config.get("clients", dict())
|
||||
user_id = request.form.get("user_id", random_string())
|
||||
@ -277,8 +213,7 @@ def add_client():
|
||||
"servers": request.form.getlist("servers"),
|
||||
}
|
||||
config["clients"] = clients
|
||||
with open(CFG_PATH, "w") as file:
|
||||
yaml.safe_dump(config, file)
|
||||
write_config(config)
|
||||
log.info("Client %s updated", request.form.get("name"))
|
||||
|
||||
for server in SERVERS:
|
||||
@ -340,9 +275,7 @@ def add_client():
|
||||
@app.route("/del_client", methods=["POST"])
|
||||
def del_client():
|
||||
if request.method == "POST":
|
||||
with open(CFG_PATH, "r") as file:
|
||||
config = yaml.safe_load(file) or {}
|
||||
|
||||
config = get_config()
|
||||
clients = config.get("clients", dict())
|
||||
user_id = request.form.get("user_id")
|
||||
if user_id in clients:
|
||||
@ -359,8 +292,7 @@ def del_client():
|
||||
server.delete_key(client.key_id)
|
||||
|
||||
config["clients"].pop(user_id)
|
||||
with open(CFG_PATH, "w") as file:
|
||||
yaml.safe_dump(config, file)
|
||||
write_config(config)
|
||||
log.info("Deleting client %s", request.form.get("name"))
|
||||
update_state()
|
||||
return redirect(url_for("clients", nt="User has been deleted"))
|
||||
|
0
requirements.txt
Normal file → Executable file
0
requirements.txt
Normal file → Executable file
0
static/layout.css
Normal file → Executable file
0
static/layout.css
Normal file → Executable file
0
static/pure.css
Normal file → Executable file
0
static/pure.css
Normal file → Executable file
0
templates/base.html
Normal file → Executable file
0
templates/base.html
Normal file → Executable file
0
templates/clients.html
Normal file → Executable file
0
templates/clients.html
Normal file → Executable file
0
templates/index.html
Normal file → Executable file
0
templates/index.html
Normal file → Executable file
0
templates/sync.html
Normal file → Executable file
0
templates/sync.html
Normal file → Executable file
Reference in New Issue
Block a user