From 0f520b891437aa15a1aee894761c94fc6e4ab3bd Mon Sep 17 00:00:00 2001 From: bunkerity Date: Wed, 23 Dec 2020 22:29:50 +0100 Subject: [PATCH] UI - services backend started --- ui/config.json | 100 ++++++++++++++++++++++++++++-- ui/entrypoint.py | 57 +++++++++++++++-- ui/static/js/custom.js | 51 +++++++++++++++ ui/templates/services-delete.html | 9 ++- ui/templates/services-edit.html | 19 +++--- ui/templates/services-new.html | 40 ++++++++++++ ui/templates/services.html | 24 ++++++- ui/utils.py | 6 +- ui/wrappers.py | 10 +++ 9 files changed, 290 insertions(+), 26 deletions(-) create mode 100644 ui/templates/services-new.html diff --git a/ui/config.json b/ui/config.json index 3df3a7e..11a6695 100644 --- a/ui/config.json +++ b/ui/config.json @@ -1,122 +1,146 @@ { "Misc":{ - "id":"max-client-size", + "id":"misc", "params":[ + { + "type":"text", + "label":"Server name", + "env":"SERVER_NAME", + "regex":"^([a-z\\-0-9]+\\.?)+$", + "id":"server-name" + }, { "type":"text", "label":"Max client size", "env":"MAX_CLIENT_SIZE", + "regex":"^[0-9]+(k|K|m|M|g|G)?$", "id":"max-client-size" }, { "type":"text", "label":"Allowed methods", "env":"ALLOWED_METHODS", + "regex":"^((GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|TRACE)\\|?)+$", "id":"allowed-methods" }, { "type":"checkbox", "label":"Serve files", "env":"SERVE_FILES", + "regex":"^(yes|no)$", "id":"serve-files" } ] }, "Info leak":{ - "id":"remove-headers", + "id":"info-leak", "params":[ { "type":"text", "label":"Remove headers", "env":"REMOVE_HEADERS", + "regex":"^([A-Za-z0-9\\-] ?)*$", "id":"remove-headers" } ] }, "Basic auth":{ - "id":"use-auth-basic", + "id":"auth-basic", "params":[ { "type":"checkbox", "label":"Use auth basic", "env":"USE_AUTH_BASIC", + "regex":"^(yes|no)$", "id":"use-auth-basic" }, { "type":"text", "label":"Auth basic location", "env":"AUTH_BASIC_LOCATION", + "regex":"^(sitewide|/[A-Za-z0-9/]*)$", "id":"auth-basic-location" }, { "type":"text", "label":"Auth basic user", "env":"AUTH_BASIC_USER", + "regex":"^([A-Za-z0-9\\-_]+)$", "id":"auth-basic-user" }, { "type":"text", "label":"Auth basic password", "env":"AUTH_BASIC_PASSWORD", + "regex":"^([\\S]+)$", "id":"auth-basic-password" }, { "type":"text", "label":"Auth basic text", + "regex":"^([\\S ]+)$", "env":"AUTH_BASIC_TEXT", "id":"auth-basic-text" } ] }, "Reverse proxy":{ - "id":"use-reverse-proxy", + "id":"reverse-proxy", "params":[ { "type":"checkbox", "label":"Use reverse proxy", "env":"USE_REVERSE_PROXY", + "regex":"^(yes|no)$", "id":"use-reverse-proxy" }, { "type":"text", "label":"Reverse proxy url", "env":"REVERSE_PROXY_URL", + "regex":".*", "id":"reverse-proxy-url" }, { "type":"text", "label":"Reverse proxy host", "env":"REVERSE_PROXY_HOST", + "regex":".*", "id":"reverse-proxy-host" }, { "type":"checkbox", "label":"Reverse proxy ws", "env":"REVERSE_PROXY_WS", + "regex":"^(yes|no)$", "id":"reverse-proxy-ws" }, { "type":"checkbox", "label":"Proxy real ip", "env":"PROXY_REAL_IP", + "regex":"^(yes|no)$", "id":"proxy-real-ip" }, { "type":"text", "label":"Proxy real ip from", "env":"PROXY_REAL_IP_FROM", + "regex":"^(\\d+.\\d+.\\d+.\\d+(/\\d+)? ?)*$", "id":"proxy-real-ip-from" }, { "type":"text", "label":"Proxy real ip header", "env":"PROXY_REAL_IP_HEADER", + "regex":"^([A-Za-z0-9\\-])+$", "id":"proxy-real-ip-header" }, { "type":"text", "label":"Proxy real ip recursive", "env":"PROXY_REAL_IP_RECURSIVE", + "regex":"^(on|off)$", "id":"proxy-real-ip-recursive" } ] @@ -128,48 +152,56 @@ "type":"checkbox", "label":"Use gzip", "env":"USE_GZIP", + "regex":"^(yes|no)$", "id":"use-gzip" }, { "type":"text", "label":"Gzip comp level", "env":"GZIP_COMP_LEVEL", + "regex":"^[1-9]$", "id":"gzip-comp-level" }, { "type":"text", "label":"Gzip min length", "env":"GZIP_MIN_LENGTH", + "regex":"^[0-9]+$", "id":"gzip-min-length" }, { "type":"text", "label":"Gzip types", "env":"GZIP_TYPES", + "regex":"^([a-z/\\+\\-\\.] ?)*$", "id":"gzip-types" }, { "type":"checkbox", "label":"Use brotli", "env":"USE_BROTLI", + "regex":"^(yes|no)$", "id":"use-brotli" }, { "type":"text", "label":"Brotli comp level", "env":"BROTLI_COMP_LEVEL", + "regex":"^[1-9]$", "id":"brotli-comp-level" }, { "type":"text", "label":"Brotli min length", "env":"BROTLI_MIN_LENGTH", + "regex":"^[0-9]+$", "id":"brotli-min-length" }, { "type":"text", "label":"Brotli types", "env":"BROTLI_TYPES", + "regex":"^([a-z/\\+\\-\\.] ?)*$", "id":"brotli-types" } ] @@ -181,108 +213,126 @@ "type":"checkbox", "label":"Use client cache", "env":"USE_CLIENT_CACHE", + "regex":"^(yes|no)$", "id":"use-client-cache" }, { "type":"text", "label":"Client cache extensions", "env":"CLIENT_CACHE_EXTENSIONS", + "regex":"^([a-z0-9]\\|?)*$", "id":"client-cache-extensions" }, { "type":"text", "label":"Client cache control", "env":"CLIENT_CACHE_CONTROL", + "regex":"^([\\S ]*)$", "id":"client-cache-control" }, { "type":"text", "label":"Client cache etag", "env":"CLIENT_CACHE_ETAG", + "regex":"^(on|off)$", "id":"client-cache-etag" }, { "type":"checkbox", "label":"Use open file cache", "env":"USE_OPEN_FILE_CACHE", + "regex":"^(yes|no)$", "id":"use-open-file-cache" }, { "type":"text", "label":"Open file cache", "env":"OPEN_FILE_CACHE", + "regex":"^([\\S ]*)$", "id":"open-file-cache" }, { "type":"text", "label":"Open file cache errors", "env":"OPEN_FILE_CACHE_ERRORS", + "regex":"^(on|off)$", "id":"open-file-cache-errors" }, { "type":"text", "label":"Open file cache min uses", "env":"OPEN_FILE_CACHE_MIN_USES", + "regex":"^([1-9]+)$", "id":"open-file-cache-min-uses" }, { "type":"text", "label":"Open file cache valid", "env":"OPEN_FILE_CACHE_VALID", + "regex":"^\\d+(ms|s|m|h|d|w|M|y)$", "id":"open-file-cache-valid" }, { "type":"checkbox", "label":"Use proxy cache", "env":"USE_PROXY_CACHE", + "regex":"^(yes|no)$", "id":"use-proxy-cache" }, { "type":"text", "label":"Proxy cache path zone size", "env":"PROXY_CACHE_PATH_ZONE_SIZE", + "regex":"^[0-9]+(k|K|m|M|g|G)?$", "id":"proxy-cache-path-zone-size" }, { "type":"text", "label":"Proxy cache path params", "env":"PROXY_CACHE_PATH_PARAMS", + "regex":"^([\\S ]*)$", "id":"proxy-cache-path-params" }, { "type":"text", "label":"Proxy cache methods", "env":"PROXY_CACHE_METHODS", + "regex":"^((GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|TRACE) ?)+$", "id":"proxy-cache-methods" }, { "type":"text", "label":"Proxy cache min uses", "env":"PROXY_CACHE_MIN_USES", + "regex":"^([1-9]+)$", "id":"proxy-cache-min-uses" }, { "type":"text", "label":"Proxy cache key", "env":"PROXY_CACHE_KEY", + "regex":"^([\\S ]*)$", "id":"proxy-cache-key" }, { "type":"text", "label":"Proxy cache valid", "env":"PROXY_CACHE_VALID", + "regex":"^(\\d{3}=\\d+(ms|s|m|h|d|w|M|y) ?)+$", "id":"proxy-cache-valid" }, { "type":"text", "label":"Proxy no cache", "env":"PROXY_NO_CACHE", + "regex":"^([\\S ]*)$", "id":"proxy-no-cache" }, { "type":"text", "label":"Proxy cache bypass", "env":"PROXY_CACHE_BYPASS", + "regex":"^([\\S ]*)$", "id":"proxy-cache-bypass" } ] @@ -294,36 +344,42 @@ "type":"checkbox", "label":"Auto lets encrypt", "env":"AUTO_LETS_ENCRYPT", + "regex":"^(yes|no)$", "id":"auto-lets-encrypt" }, { "type":"text", "label":"Email lets encrypt", "env":"EMAIL_LETS_ENCRYPT", + "regex":"^([a-z0-9\\-\\.]+@([a-z\\-0-9]+\\.?)|.{0})$", "id":"email-lets-encrypt" }, { "type":"checkbox", "label":"Redirect http to https", "env":"REDIRECT_HTTP_TO_HTTPS", + "regex":"^(yes|no)$", "id":"redirect-http-to-https" }, { "type":"checkbox", "label":"HTTP2", "env":"HTTP2", + "regex":"^(yes|no)$", "id":"http2" }, { "type":"text", "label":"HTTPS protocols", "env":"HTTPS_PROTOCOLS", + "regex":"^([\\S ]*)$", "id":"https-protocols" }, { "type":"checkbox", "label":"Listen http", "env":"LISTEN_HTTP", + "regex":"^(yes|no)$", "id":"listen-http" } ] @@ -335,12 +391,14 @@ "type":"checkbox", "label":"Use modsecurity", "env":"USE_MODSECURITY", + "regex":"^(yes|no)$", "id":"use-modsecurity" }, { "type":"checkbox", "label":"Use modsecurity crs", "env":"USE_MODSECURITY_CRS", + "regex":"^(yes|no)$", "id":"use-modsecurity-crs" } ] @@ -352,60 +410,70 @@ "type":"text", "label":"X frame options", "env":"X_FRAME_OPTIONS", + "regex":"^([\\S ]*)$", "id":"x-frame-options" }, { "type":"text", "label":"X xss protection", "env":"X_XSS_PROTECTION", + "regex":"^([\\S ]*)$", "id":"x-xss-protection" }, { "type":"text", "label":"X content type options", "env":"X_CONTENT_TYPE_OPTIONS", + "regex":"^([\\S ]*)$", "id":"x-content-type-options" }, { "type":"text", "label":"Referrer policy", "env":"REFERRER_POLICY", + "regex":"^([\\S ]*)$", "id":"referrer-policy" }, { "type":"text", "label":"Feature policy", "env":"FEATURE_POLICY", + "regex":"^([\\S ]*)$", "id":"feature-policy" }, { "type":"text", "label":"Permissions policy", "env":"PERMISSIONS_POLICY", + "regex":"^([\\S ]*)$", "id":"permissions-policy" }, { "type":"text", "label":"Cookie flags", "env":"COOKIE_FLAGS", + "regex":"^([\\S ]*)$", "id":"cookie-flags" }, { "type":"checkbox", "label":"Cookie auto secure flag", "env":"COOKIE_AUTO_SECURE_FLAG", + "regex":"^(yes|no)$", "id":"cookie-auto-secure-flag" }, { "type":"text", "label":"Strict transport security", "env":"STRICT_TRANSPORT_SECURITY", + "regex":"^([\\S ]*)$", "id":"strict-transport-security" }, { "type":"text", "label":"Content security policy", "env":"CONTENT_SECURITY_POLICY", + "regex":"^([\\S ]*)$", "id":"content-security-policy" } ] @@ -417,24 +485,28 @@ "type":"text", "label":"Use antibot", "env":"USE_ANTIBOT", + "regex":"^(no|cookie|javascript|captcha|recaptcha)$", "id":"use-antibot" }, { "type":"text", "label":"Antibot uri", "env":"ANTIBOT_URI", + "regex":"^/([A-Za-z0-9\\-]/?)*$", "id":"antibot-uri" }, { "type":"text", "label":"Antibot session secret", "env":"ANTIBOT_SESSION_SECRET", + "regex":"^([\\S]+)$", "id":"antibot-session-secret" }, { "type":"text", "label":"Antibot recaptcha score", "env":"ANTIBOT_RECAPTCHA_SCORE", + "regex":"^0\\.\\d$", "id":"antibot-recaptcha-score" } ] @@ -446,30 +518,35 @@ "type":"checkbox", "label":"Block user agent", "env":"BLOCK_USER_AGENT", + "regex":"^(yes|no)$", "id":"block-user-agent" }, { "type":"checkbox", "label":"Block tor exit node", "env":"BLOCK_TOR_EXIT_NODE", + "regex":"^(yes|no)$", "id":"block-tor-exit-node" }, { "type":"checkbox", "label":"Block proxies", "env":"BLOCK_PROXIES", + "regex":"^(yes|no)$", "id":"block-proxies" }, { "type":"checkbox", "label":"Block abusers", "env":"BLOCK_ABUSERS", + "regex":"^(yes|no)$", "id":"block-abusers" }, { "type":"checkbox", "label":"Block referrer", "env":"BLOCK_REFERRER", + "regex":"^(yes|no)$", "id":"block-referrer" } ] @@ -481,6 +558,7 @@ "type":"checkbox", "label":"Use dnsbl", "env":"USE_DNSBL", + "regex":"^(yes|no)$", "id":"use-dnsbl" } ] @@ -492,6 +570,7 @@ "type":"checkbox", "label":"Use crowdsec", "env":"USE_CROWDSEC", + "regex":"^(yes|no)$", "id":"use-crowdsec" } ] @@ -503,18 +582,21 @@ "type":"checkbox", "label":"Use whitelist ip", "env":"USE_WHITELIST_IP", + "regex":"^(yes|no)$", "id":"use-whitelist-ip" }, { "type":"checkbox", "label":"Use whitelist reverse", "env":"USE_WHITELIST_REVERSE", + "regex":"^(yes|no)$", "id":"use-whitelist-reverse" }, { "type":"text", "label":"Whitelist country", "env":"WHITELIST_COUNTRY", + "regex":"^([A-Z]{2} ?)*$", "id":"whitelist-country" } ] @@ -526,18 +608,21 @@ "type":"checkbox", "label":"Use blacklist ip", "env":"USE_BLACKLIST_IP", + "regex":"^(yes|no)$", "id":"use-blacklist-ip" }, { "type":"checkbox", "label":"Use blacklist reverse", "env":"USE_BLACKLIST_REVERSE", + "regex":"^(yes|no)$", "id":"use-blacklist-reverse" }, { "type":"text", "label":"Blacklist country", "env":"BLACKLIST_COUNTRY", + "regex":"^([A-Z]{2} ?)*$", "id":"blacklist-country" } ] @@ -549,18 +634,21 @@ "type":"checkbox", "label":"Use limit req", "env":"USE_LIMIT_REQ", + "regex":"^(yes|no)$", "id":"use-limit-req" }, { "type":"text", "label":"Limit req rate", "env":"LIMIT_REQ_RATE", + "regex":"^\\d+r/(ms|s|m|h|d)$", "id":"limit-req-rate" }, { "type":"text", "label":"Limit req burst", "env":"LIMIT_REQ_BURST", + "regex":"^\\d+$", "id":"limit-req-burst" } ] @@ -572,12 +660,14 @@ "type":"text", "label":"Remote php", "env":"REMOTE_PHP", + "regex":"^([a-z\\-0-9]+\\.?)*$", "id":"remote-php" }, { "type":"text", "label":"Remote php path", "env":"REMOTE_PHP_PATH", + "regex":"^/([A-Za-z0-9\\-]/?)*$", "id":"remote-php-path" } ] @@ -589,6 +679,7 @@ "type":"checkbox", "label":"Use fail2ban", "env":"USE_FAIL2BAN", + "regex":"^(yes|no)$", "id":"use-fail2ban" } ] @@ -600,6 +691,7 @@ "type":"checkbox", "label":"Use clamav upload", "env":"USE_CLAMAV_UPLOAD", + "regex":"^(yes|no)$", "id":"use-clamav-upload" } ] diff --git a/ui/entrypoint.py b/ui/entrypoint.py index 0e134c3..f520e6e 100644 --- a/ui/entrypoint.py +++ b/ui/entrypoint.py @@ -1,9 +1,9 @@ #!/usr/bin/python3 -from flask import Flask, render_template, current_app +from flask import Flask, render_template, current_app, request import wrappers, utils -import os, json +import os, json, re app = Flask(__name__, static_url_path="/", static_folder="static", template_folder="templates") ABSOLUTE_URI = "" @@ -33,9 +33,58 @@ def instances(): return render_template("error.html", title="Error", error=instances) return render_template("instances.html", title="Instances", instances=instances) -@app.route('/services') +@app.route('/services', methods=["GET", "POST"]) def services(): + + # Manage services + operation = "" + if request.method == "POST" : + + # Check operation + if not "operation" in request.form or not request.form["operation"] in ["new", "edit", "delete"] : + return render_template("error.html", title="Error", error="Missing operation parameter on /services.") + + # Check that all fields are present and they match the corresponding regex + env = {} + if request.form["operation"] in ["new", "edit"] : + for category in current_app.config["CONFIG"] : + for param in current_app.config["CONFIG"][category]["params"] : + if not param["env"] in request.form : + return render_template("error.html", title="Error", error="Missing " + param["env"] + " parameter.") + if not re.search(param["regex"], request.form[param["env"]]) : + return render_template("error.html", title="Error", error="Parameter " + param["env"] + " doesn't match regex.") + env[param["env"]] = request.form[param["env"]] + if request.form["operation"] == "edit" : + if not "OLD_SERVER_NAME" in request.form : + return render_template("error.html", title="Error", error="Missing OLD_SERVER_NAME parameter.") + if not re.search("^([a-z\-0-9]+\.?)+$", request.form["OLD_SERVER_NAME"]) : + return render_template("error.html", title="Error", error="Parameter OLD_SERVER_NAME doesn't match regex.") + elif request.form["operation"] == "delete" : + if not "SERVER_NAME" in request.form : + return render_template("error.html", title="Error", error="Missing SERVER_NAME parameter.") + if not re.search("^([a-z\-0-9]+\.?)+$", request.form["SERVER_NAME"]) : + return render_template("error.html", title="Error", error="Parameter SERVER_NAME doesn't match regex.") + + # Create new service + if request.form["operation"] == "new" : + check, operation = wrappers.new_service(env) + if not check : + render_template("error.html", title="Error", error=service) + + # Edit existing service + elif request.form["operation"] == "edit" : + check, operation = wrappers.edit_service(request.form["OLD_SERVER_NAME"], env) + if not check : + render_template("error.html", title="Error", error=service) + + # Delete existing service + elif request.form["operation"] == "delete" : + check, operation = wrappers.delete_service(request.form["SERVER_NAME"]) + if not check : + render_template("error.html", title="Error", error=service) + + # Display services check, services = wrappers.get_services() if not check : return render_template("error.html", title="Error", error=services) - return render_template("services.html", title="Services", services=services) + return render_template("services.html", title="Services", services=services, operation=operation) diff --git a/ui/static/js/custom.js b/ui/static/js/custom.js index f420386..23a2fc8 100644 --- a/ui/static/js/custom.js +++ b/ui/static/js/custom.js @@ -3,3 +3,54 @@ var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl, { container: 'body' }) }) +function post(operation, url, data) { + var form = document.createElement("form"); + form.method = "POST"; + form.action = url; + for (var key in data) { + var field = document.createElement("input"); + field.type = "hidden"; + field.name = key; + field.value = data[key]; + form.appendChild(field); + } + var field = document.createElement("input"); + field.type = "hidden"; + field.name = "operation"; + field.value = operation; + form.appendChild(field); + document.body.appendChild(form); + form.submit(); +} + +function getData(id) { + var elements = document.getElementById(id).elements; + var data = {}; + for (var i = 0; i < elements.length; i++) { + element = elements[i]; + if (element["type"] === "checkbox") { + if (element["value"] === "on") { + data[element["name"]] = "yes"; + } + else { + data[element["name"]] = "no"; + } + } + else { + data[element["name"]] = element["value"]; + } + } + return data; +} + +function postNew() { + post("new", "services", getData('form-new')); +} + +function postEdit(id) { + post("edit", "services", getData('form-edit-' + id)); +} + +function postDelete(id) { + post("delete", "services", getData('form-delete-' + id)); +} diff --git a/ui/templates/services-delete.html b/ui/templates/services-delete.html index 50775f3..319c349 100644 --- a/ui/templates/services-delete.html +++ b/ui/templates/services-delete.html @@ -1,16 +1,19 @@ -