diff --git a/examples/web-ui/docker-compose.yml b/examples/web-ui/docker-compose.yml index 7a0cc41..fcb0fdb 100644 --- a/examples/web-ui/docker-compose.yml +++ b/examples/web-ui/docker-compose.yml @@ -16,7 +16,7 @@ services: - ./letsencrypt:/etc/letsencrypt - autoconf:/etc/nginx environment: - - SERVER_NAME=admin.example.com # replace with your domain + - SERVER_NAME=admin.example.com # replace with your domain - MULTISITE=yes - AUTO_LETS_ENCRYPT=yes - REDIRECT_HTTP_TO_HTTPS=yes @@ -24,11 +24,12 @@ services: - USE_CLIENT_CACHE=yes - USE_GZIP=yes - USE_API=yes - - API_URI=/ChangeMeToSomethingHardToGuess # change it to something hard to guess + must match API_URI for myui service + - API_URI=/ChangeMeToSomethingHardToGuess # change it to something hard to guess + must match API_URI for myui service - admin.example.com_SERVE_FILES=no - admin.example.com_USE_REVERSE_PROXY=yes - - admin.example.com_REVERSE_PROXY_URL=/admin/ # change it to something hard to guess - - admin.example.com_REVERSE_PROXY_HOST=http://myui:5000/ + - admin.example.com_REVERSE_PROXY_URL=/admin/ # change it to something hard to guess + - admin.example.com_REVERSE_PROXY_HOST=http://myui:5000/ + - admin.example.com_REVERSE_PROXY_HEADERS=X-Script-Name /admin # must match REVERSE_PROXY_URL - admin.example.com_USE_MODSECURITY=no labels: - "bunkerized-nginx.UI" @@ -41,11 +42,11 @@ services: volumes: - autoconf:/etc/nginx environment: - - ABSOLUTE_URI=https://admin.example.com/admin/ # change it to your full URI + - ABSOLUTE_URI=https://admin.example.com/admin/ # change it to your full URI - DOCKER_HOST=tcp://myuiproxy:2375 - API_URI=/ChangeMeToSomethingHardToGuess - - ADMIN_USERNAME=admin # change it to something hard to guess - - ADMIN_PASSWORD=changeme # change it to a good password + - ADMIN_USERNAME=admin # change it to something hard to guess + - ADMIN_PASSWORD=changeme # change it to a good password myuiproxy: image: tecnativa/docker-socket-proxy @@ -54,6 +55,8 @@ services: - /var/run/docker.sock:/var/run/docker.sock:ro environment: - CONTAINERS=1 + - SWARM=1 + - SERVICES=1 volumes: autoconf: diff --git a/ui/entrypoint.py b/ui/entrypoint.py index 29ffc37..7229df3 100644 --- a/ui/entrypoint.py +++ b/ui/entrypoint.py @@ -7,12 +7,14 @@ from flask_wtf.csrf import CSRFProtect, CSRFError from src.Instances import Instances from src.User import User from src.Config import Config +from src.ReverseProxied import ReverseProxied import utils import os, json, re, copy, traceback # Flask app app = Flask(__name__, static_url_path="/", static_folder="static", template_folder="templates") +app.wsgi_app = ReverseProxied(app.wsgi_app) # Set variables and instantiate objects vars = utils.get_variables() @@ -50,7 +52,7 @@ def login() : if request.method == "POST" and "username" in request.form and "password" in request.form : if app.config["USER"].get_id() == request.form["username"] and app.config["USER"].check_password(request.form["password"]) : login_user(app.config["USER"]) - return redirect("/") + return redirect(app.config["ABSOLUTE_URI"]) else : fail = True if fail : @@ -61,7 +63,7 @@ def login() : @login_required def logout() : logout_user() - return redirect("/login") + return redirect(app.config["ABSOLUTE_URI"] + "/login") @app.route('/') @app.route('/home') diff --git a/ui/src/Config.py b/ui/src/Config.py index c065f09..f0dd96d 100644 --- a/ui/src/Config.py +++ b/ui/src/Config.py @@ -2,119 +2,119 @@ import json, uuid, glob, copy, re, subprocess, os class Config : - def __init__(self) : - with open("/opt/bunkerized-nginx/settings.json", "r") as f : - self.__settings = json.loads(f.read()) + def __init__(self) : + with open("/opt/bunkerized-nginx/settings.json", "r") as f : + self.__settings = json.loads(f.read()) - def __env_to_dict(self, filename) : - if not os.path.isfile(filename) : - return {} - with open(filename, "r") as f : - env = f.read() - data = {} - for line in env.split("\n") : - var = line.split("=")[0] - val = line.replace(var + "=", "", 1) - data[var] = val - return data + def __env_to_dict(self, filename) : + if not os.path.isfile(filename) : + return {} + with open(filename, "r") as f : + env = f.read() + data = {} + for line in env.split("\n") : + var = line.split("=")[0] + val = line.replace(var + "=", "", 1) + data[var] = val + return data - def __dict_to_env(self, filename, variables) : - env = "" - for k, v in variables.items() : - env += k + "=" + v + "\n" - with open(filename, "w") as f : - f.write(env) + def __dict_to_env(self, filename, variables) : + env = "" + for k, v in variables.items() : + env += k + "=" + v + "\n" + with open(filename, "w") as f : + f.write(env) - def __gen_conf(self, global_conf, services_conf) : - conf = copy.deepcopy(global_conf) - if not "SERVER_NAME" in conf : - conf["SERVER_NAME"] = "" - servers = conf["SERVER_NAME"].split(" ") - if conf["SERVER_NAME"] == "" : - servers = [] - for service in services_conf : - first_server = service["SERVER_NAME"].split(" ")[0] - if not first_server in servers : - servers.append(first_server) - for k, v in service.items() : - conf[first_server + "_" + k] = v - conf["SERVER_NAME"] = " ".join(servers) - env_file = "/tmp/" + str(uuid.uuid4()) + ".env" - self.__dict_to_env(env_file, conf) - proc = subprocess.run(["/opt/bunkerized-nginx/gen/main.py", "--settings", "/opt/bunkerized-nginx/settings.json", "--templates", "/opt/bunkerized-nginx/confs", "--output", "/etc/nginx", "--variables", env_file], capture_output=True) - stderr = proc.stderr.decode("ascii") - stdout = proc.stdout.decode("ascii") - if stderr != "" or proc.returncode != 0 : - raise Exception("Error from generator (return code = " + str(proc.returncode) + ") : " + stderr + "\n" + stdout) + def __gen_conf(self, global_conf, services_conf) : + conf = copy.deepcopy(global_conf) + if not "SERVER_NAME" in conf : + conf["SERVER_NAME"] = "" + servers = conf["SERVER_NAME"].split(" ") + if conf["SERVER_NAME"] == "" : + servers = [] + for service in services_conf : + first_server = service["SERVER_NAME"].split(" ")[0] + if not first_server in servers : + servers.append(first_server) + for k, v in service.items() : + conf[first_server + "_" + k] = v + conf["SERVER_NAME"] = " ".join(servers) + env_file = "/tmp/" + str(uuid.uuid4()) + ".env" + self.__dict_to_env(env_file, conf) + proc = subprocess.run(["/opt/bunkerized-nginx/gen/main.py", "--settings", "/opt/bunkerized-nginx/settings.json", "--templates", "/opt/bunkerized-nginx/confs", "--output", "/etc/nginx", "--variables", env_file], capture_output=True) + stderr = proc.stderr.decode("ascii") + stdout = proc.stdout.decode("ascii") + if stderr != "" or proc.returncode != 0 : + raise Exception("Error from generator (return code = " + str(proc.returncode) + ") : " + stderr + "\n" + stdout) - def get_settings(self) : - return self.__settings + def get_settings(self) : + return self.__settings - def get_config(self) : - return self.__env_to_dict("/etc/nginx/global.env") + def get_config(self) : + return self.__env_to_dict("/etc/nginx/global.env") - def get_services(self) : - services = [] - for filename in glob.iglob("/etc/nginx/**/site.env") : - env = self.__env_to_dict(filename) - services.append(env) - no_multisite = self.__env_to_dict("/etc/nginx/site.env") - if len(no_multisite) > 0 : - services.append(no_multisite) - return services + def get_services(self) : + services = [] + for filename in glob.iglob("/etc/nginx/**/site.env") : + env = self.__env_to_dict(filename) + services.append(env) + no_multisite = self.__env_to_dict("/etc/nginx/site.env") + if len(no_multisite) > 0 : + services.append(no_multisite) + return services - def check_variables(self, variables) : - for k, v in variables.items() : - check = False - for category in self.__settings : - for param in self.__settings[category]["params"] : - multiple = False - if param["type"] != "multiple" : - real_params = [param] - else : - real_params = param["params"] - multiple = True - for real_param in real_params : - if (((not multiple and k == real_param["env"]) or - (multiple and re.search("^" + real_param["env"] + "_" + "[0-9]+$", k))) and - real_param["context"] == "multisite" and - re.search(real_param["regex"], v)) : - check = True - if not check : - raise Exception("Variable " + k + " is not valid.") + def check_variables(self, variables) : + for k, v in variables.items() : + check = False + for category in self.__settings : + for param in self.__settings[category]["params"] : + multiple = False + if param["type"] != "multiple" : + real_params = [param] + else : + real_params = param["params"] + multiple = True + for real_param in real_params : + if (((not multiple and k == real_param["env"]) or + (multiple and re.search("^" + real_param["env"] + "_" + "[0-9]+$", k))) and + real_param["context"] == "multisite" and + re.search(real_param["regex"], v)) : + check = True + if not check : + raise Exception("Variable " + k + " is not valid.") - def new_service(self, variables) : - global_env = self.__env_to_dict("/etc/nginx/global.env") - services = self.get_services() - for service in services : - if service["SERVER_NAME"] == variables["SERVER_NAME"] or service["SERVER_NAME"] in variables["SERVER_NAME"].split(" ") : - raise Exception("Service " + service["SERVER_NAME"] + " already exists.") - services.append(variables) - self.__gen_conf(global_env, services) - return "Configuration for " + variables["SERVER_NAME"] + " has been generated." + def new_service(self, variables) : + global_env = self.__env_to_dict("/etc/nginx/global.env") + services = self.get_services() + for service in services : + if service["SERVER_NAME"] == variables["SERVER_NAME"] or service["SERVER_NAME"] in variables["SERVER_NAME"].split(" ") : + raise Exception("Service " + service["SERVER_NAME"] + " already exists.") + services.append(variables) + self.__gen_conf(global_env, services) + return "Configuration for " + variables["SERVER_NAME"] + " has been generated." - def edit_service(self, old_server_name, variables) : - self.delete_service(old_server_name) - self.new_service(variables) - return "Configuration for " + old_server_name + " has been edited." + def edit_service(self, old_server_name, variables) : + self.delete_service(old_server_name) + self.new_service(variables) + return "Configuration for " + old_server_name + " has been edited." - def delete_service(self, server_name) : - global_env = self.__env_to_dict("/etc/nginx/global.env") - services = self.get_services() - new_services = [] - found = False - for service in services : - if service["SERVER_NAME"].split(" ")[0] == server_name : - found = True - else : - new_services.append(service) - if not found : - raise Exception("Can't delete missing " + server_name + " configuration.") - new_servers = global_env["SERVER_NAME"].split(" ") - if server_name in new_servers : - new_servers.remove(server_name) - global_env["SERVER_NAME"] = " ".join(new_servers) - self.__gen_conf(global_env, new_services) - return "Configuration for " + server_name + " has been deleted." + def delete_service(self, server_name) : + global_env = self.__env_to_dict("/etc/nginx/global.env") + services = self.get_services() + new_services = [] + found = False + for service in services : + if service["SERVER_NAME"].split(" ")[0] == server_name : + found = True + else : + new_services.append(service) + if not found : + raise Exception("Can't delete missing " + server_name + " configuration.") + new_servers = global_env["SERVER_NAME"].split(" ") + if server_name in new_servers : + new_servers.remove(server_name) + global_env["SERVER_NAME"] = " ".join(new_servers) + self.__gen_conf(global_env, new_services) + return "Configuration for " + server_name + " has been deleted." diff --git a/ui/src/Instances.py b/ui/src/Instances.py index 13c0b94..7fd9cbf 100644 --- a/ui/src/Instances.py +++ b/ui/src/Instances.py @@ -47,17 +47,16 @@ class Instances : # Docker instances (containers or services) if self.__docker != None : - if self.__docker.swarm == None : - for instance in self.__docker.containers.list(all=True, filters={"label" : "bunkerized-nginx.UI"}) : - id = instance.id - name = instance.name - type = "container" - status = "down" - if instance.status == "running" : - status = "up" - instances.append(self.__instance(id, name, type, status, instance)) - else : - for instance in self.__docker.services.list(all=True, filters={"label" : "bunkerized-nginx.UI"}) : + for instance in self.__docker.containers.list(all=True, filters={"label" : "bunkerized-nginx.UI"}) : + id = instance.id + name = instance.name + type = "container" + status = "down" + if instance.status == "running" : + status = "up" + instances.append(self.__instance(id, name, type, status, instance)) + if self.__docker.swarm != None : + for instance in self.__docker.services.list(filters={"label" : "bunkerized-nginx.UI"}) : id = instance.id name = instance.name type = "service" diff --git a/ui/src/ReverseProxied.py b/ui/src/ReverseProxied.py new file mode 100644 index 0000000..dc9f692 --- /dev/null +++ b/ui/src/ReverseProxied.py @@ -0,0 +1,17 @@ +class ReverseProxied(object): + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + script_name = environ.get('HTTP_X_SCRIPT_NAME', '') + if script_name: + environ['SCRIPT_NAME'] = script_name + path_info = environ['PATH_INFO'] + if path_info.startswith(script_name): + environ['PATH_INFO'] = path_info[len(script_name):] + + scheme = environ.get('HTTP_X_SCHEME', '') + if scheme: + environ['wsgi.url_scheme'] = scheme + return self.app(environ, start_response) diff --git a/ui/src/User.py b/ui/src/User.py index a46694f..588c735 100644 --- a/ui/src/User.py +++ b/ui/src/User.py @@ -5,9 +5,9 @@ class User(flask_login.UserMixin) : def __init__(self, id, password) : self.__id = id self.__password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) - + def get_id(self) : return self.__id def check_password(self, password) : - return bcrypt.checkpw(password.encode("utf-8"), self.__password) \ No newline at end of file + return bcrypt.checkpw(password.encode("utf-8"), self.__password)