ui - fix example, subpath behind reverse proxy and add socket proxy rights for swarm

This commit is contained in:
florian 2021-08-07 21:56:08 +02:00
parent 0c1883472d
commit dd7d1a2c78
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
6 changed files with 147 additions and 126 deletions

View File

@ -16,7 +16,7 @@ services:
- ./letsencrypt:/etc/letsencrypt - ./letsencrypt:/etc/letsencrypt
- autoconf:/etc/nginx - autoconf:/etc/nginx
environment: environment:
- SERVER_NAME=admin.example.com # replace with your domain - SERVER_NAME=admin.example.com # replace with your domain
- MULTISITE=yes - MULTISITE=yes
- AUTO_LETS_ENCRYPT=yes - AUTO_LETS_ENCRYPT=yes
- REDIRECT_HTTP_TO_HTTPS=yes - REDIRECT_HTTP_TO_HTTPS=yes
@ -24,11 +24,12 @@ services:
- USE_CLIENT_CACHE=yes - USE_CLIENT_CACHE=yes
- USE_GZIP=yes - USE_GZIP=yes
- USE_API=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_SERVE_FILES=no
- admin.example.com_USE_REVERSE_PROXY=yes - 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_URL=/admin/ # change it to something hard to guess
- admin.example.com_REVERSE_PROXY_HOST=http://myui:5000/ - 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 - admin.example.com_USE_MODSECURITY=no
labels: labels:
- "bunkerized-nginx.UI" - "bunkerized-nginx.UI"
@ -41,11 +42,11 @@ services:
volumes: volumes:
- autoconf:/etc/nginx - autoconf:/etc/nginx
environment: 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 - DOCKER_HOST=tcp://myuiproxy:2375
- API_URI=/ChangeMeToSomethingHardToGuess - API_URI=/ChangeMeToSomethingHardToGuess
- ADMIN_USERNAME=admin # change it to something hard to guess - ADMIN_USERNAME=admin # change it to something hard to guess
- ADMIN_PASSWORD=changeme # change it to a good password - ADMIN_PASSWORD=changeme # change it to a good password
myuiproxy: myuiproxy:
image: tecnativa/docker-socket-proxy image: tecnativa/docker-socket-proxy
@ -54,6 +55,8 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
environment: environment:
- CONTAINERS=1 - CONTAINERS=1
- SWARM=1
- SERVICES=1
volumes: volumes:
autoconf: autoconf:

View File

@ -7,12 +7,14 @@ from flask_wtf.csrf import CSRFProtect, CSRFError
from src.Instances import Instances from src.Instances import Instances
from src.User import User from src.User import User
from src.Config import Config from src.Config import Config
from src.ReverseProxied import ReverseProxied
import utils import utils
import os, json, re, copy, traceback import os, json, re, copy, traceback
# Flask app # Flask app
app = Flask(__name__, static_url_path="/", static_folder="static", template_folder="templates") app = Flask(__name__, static_url_path="/", static_folder="static", template_folder="templates")
app.wsgi_app = ReverseProxied(app.wsgi_app)
# Set variables and instantiate objects # Set variables and instantiate objects
vars = utils.get_variables() 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 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"]) : if app.config["USER"].get_id() == request.form["username"] and app.config["USER"].check_password(request.form["password"]) :
login_user(app.config["USER"]) login_user(app.config["USER"])
return redirect("/") return redirect(app.config["ABSOLUTE_URI"])
else : else :
fail = True fail = True
if fail : if fail :
@ -61,7 +63,7 @@ def login() :
@login_required @login_required
def logout() : def logout() :
logout_user() logout_user()
return redirect("/login") return redirect(app.config["ABSOLUTE_URI"] + "/login")
@app.route('/') @app.route('/')
@app.route('/home') @app.route('/home')

View File

@ -2,119 +2,119 @@ import json, uuid, glob, copy, re, subprocess, os
class Config : class Config :
def __init__(self) : def __init__(self) :
with open("/opt/bunkerized-nginx/settings.json", "r") as f : with open("/opt/bunkerized-nginx/settings.json", "r") as f :
self.__settings = json.loads(f.read()) self.__settings = json.loads(f.read())
def __env_to_dict(self, filename) : def __env_to_dict(self, filename) :
if not os.path.isfile(filename) : if not os.path.isfile(filename) :
return {} return {}
with open(filename, "r") as f : with open(filename, "r") as f :
env = f.read() env = f.read()
data = {} data = {}
for line in env.split("\n") : for line in env.split("\n") :
var = line.split("=")[0] var = line.split("=")[0]
val = line.replace(var + "=", "", 1) val = line.replace(var + "=", "", 1)
data[var] = val data[var] = val
return data return data
def __dict_to_env(self, filename, variables) : def __dict_to_env(self, filename, variables) :
env = "" env = ""
for k, v in variables.items() : for k, v in variables.items() :
env += k + "=" + v + "\n" env += k + "=" + v + "\n"
with open(filename, "w") as f : with open(filename, "w") as f :
f.write(env) f.write(env)
def __gen_conf(self, global_conf, services_conf) : def __gen_conf(self, global_conf, services_conf) :
conf = copy.deepcopy(global_conf) conf = copy.deepcopy(global_conf)
if not "SERVER_NAME" in conf : if not "SERVER_NAME" in conf :
conf["SERVER_NAME"] = "" conf["SERVER_NAME"] = ""
servers = conf["SERVER_NAME"].split(" ") servers = conf["SERVER_NAME"].split(" ")
if conf["SERVER_NAME"] == "" : if conf["SERVER_NAME"] == "" :
servers = [] servers = []
for service in services_conf : for service in services_conf :
first_server = service["SERVER_NAME"].split(" ")[0] first_server = service["SERVER_NAME"].split(" ")[0]
if not first_server in servers : if not first_server in servers :
servers.append(first_server) servers.append(first_server)
for k, v in service.items() : for k, v in service.items() :
conf[first_server + "_" + k] = v conf[first_server + "_" + k] = v
conf["SERVER_NAME"] = " ".join(servers) conf["SERVER_NAME"] = " ".join(servers)
env_file = "/tmp/" + str(uuid.uuid4()) + ".env" env_file = "/tmp/" + str(uuid.uuid4()) + ".env"
self.__dict_to_env(env_file, conf) 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) 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") stderr = proc.stderr.decode("ascii")
stdout = proc.stdout.decode("ascii") stdout = proc.stdout.decode("ascii")
if stderr != "" or proc.returncode != 0 : if stderr != "" or proc.returncode != 0 :
raise Exception("Error from generator (return code = " + str(proc.returncode) + ") : " + stderr + "\n" + stdout) raise Exception("Error from generator (return code = " + str(proc.returncode) + ") : " + stderr + "\n" + stdout)
def get_settings(self) : def get_settings(self) :
return self.__settings return self.__settings
def get_config(self) : def get_config(self) :
return self.__env_to_dict("/etc/nginx/global.env") return self.__env_to_dict("/etc/nginx/global.env")
def get_services(self) : def get_services(self) :
services = [] services = []
for filename in glob.iglob("/etc/nginx/**/site.env") : for filename in glob.iglob("/etc/nginx/**/site.env") :
env = self.__env_to_dict(filename) env = self.__env_to_dict(filename)
services.append(env) services.append(env)
no_multisite = self.__env_to_dict("/etc/nginx/site.env") no_multisite = self.__env_to_dict("/etc/nginx/site.env")
if len(no_multisite) > 0 : if len(no_multisite) > 0 :
services.append(no_multisite) services.append(no_multisite)
return services return services
def check_variables(self, variables) : def check_variables(self, variables) :
for k, v in variables.items() : for k, v in variables.items() :
check = False check = False
for category in self.__settings : for category in self.__settings :
for param in self.__settings[category]["params"] : for param in self.__settings[category]["params"] :
multiple = False multiple = False
if param["type"] != "multiple" : if param["type"] != "multiple" :
real_params = [param] real_params = [param]
else : else :
real_params = param["params"] real_params = param["params"]
multiple = True multiple = True
for real_param in real_params : for real_param in real_params :
if (((not multiple and k == real_param["env"]) or if (((not multiple and k == real_param["env"]) or
(multiple and re.search("^" + real_param["env"] + "_" + "[0-9]+$", k))) and (multiple and re.search("^" + real_param["env"] + "_" + "[0-9]+$", k))) and
real_param["context"] == "multisite" and real_param["context"] == "multisite" and
re.search(real_param["regex"], v)) : re.search(real_param["regex"], v)) :
check = True check = True
if not check : if not check :
raise Exception("Variable " + k + " is not valid.") raise Exception("Variable " + k + " is not valid.")
def new_service(self, variables) : def new_service(self, variables) :
global_env = self.__env_to_dict("/etc/nginx/global.env") global_env = self.__env_to_dict("/etc/nginx/global.env")
services = self.get_services() services = self.get_services()
for service in services : for service in services :
if service["SERVER_NAME"] == variables["SERVER_NAME"] or service["SERVER_NAME"] in variables["SERVER_NAME"].split(" ") : if service["SERVER_NAME"] == variables["SERVER_NAME"] or service["SERVER_NAME"] in variables["SERVER_NAME"].split(" ") :
raise Exception("Service " + service["SERVER_NAME"] + " already exists.") raise Exception("Service " + service["SERVER_NAME"] + " already exists.")
services.append(variables) services.append(variables)
self.__gen_conf(global_env, services) self.__gen_conf(global_env, services)
return "Configuration for " + variables["SERVER_NAME"] + " has been generated." return "Configuration for " + variables["SERVER_NAME"] + " has been generated."
def edit_service(self, old_server_name, variables) : def edit_service(self, old_server_name, variables) :
self.delete_service(old_server_name) self.delete_service(old_server_name)
self.new_service(variables) self.new_service(variables)
return "Configuration for " + old_server_name + " has been edited." return "Configuration for " + old_server_name + " has been edited."
def delete_service(self, server_name) : def delete_service(self, server_name) :
global_env = self.__env_to_dict("/etc/nginx/global.env") global_env = self.__env_to_dict("/etc/nginx/global.env")
services = self.get_services() services = self.get_services()
new_services = [] new_services = []
found = False found = False
for service in services : for service in services :
if service["SERVER_NAME"].split(" ")[0] == server_name : if service["SERVER_NAME"].split(" ")[0] == server_name :
found = True found = True
else : else :
new_services.append(service) new_services.append(service)
if not found : if not found :
raise Exception("Can't delete missing " + server_name + " configuration.") raise Exception("Can't delete missing " + server_name + " configuration.")
new_servers = global_env["SERVER_NAME"].split(" ") new_servers = global_env["SERVER_NAME"].split(" ")
if server_name in new_servers : if server_name in new_servers :
new_servers.remove(server_name) new_servers.remove(server_name)
global_env["SERVER_NAME"] = " ".join(new_servers) global_env["SERVER_NAME"] = " ".join(new_servers)
self.__gen_conf(global_env, new_services) self.__gen_conf(global_env, new_services)
return "Configuration for " + server_name + " has been deleted." return "Configuration for " + server_name + " has been deleted."

View File

@ -47,17 +47,16 @@ class Instances :
# Docker instances (containers or services) # Docker instances (containers or services)
if self.__docker != None : if self.__docker != None :
if self.__docker.swarm == None : for instance in self.__docker.containers.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
id = instance.id name = instance.name
name = instance.name type = "container"
type = "container" status = "down"
status = "down" if instance.status == "running" :
if instance.status == "running" : status = "up"
status = "up" instances.append(self.__instance(id, name, type, status, instance))
instances.append(self.__instance(id, name, type, status, instance)) if self.__docker.swarm != None :
else : for instance in self.__docker.services.list(filters={"label" : "bunkerized-nginx.UI"}) :
for instance in self.__docker.services.list(all=True, filters={"label" : "bunkerized-nginx.UI"}) :
id = instance.id id = instance.id
name = instance.name name = instance.name
type = "service" type = "service"

17
ui/src/ReverseProxied.py Normal file
View File

@ -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)