diff --git a/Dockerfile b/Dockerfile index 8d4b18d01..4462160c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,9 +44,6 @@ COPY VERSION /opt/bunkerweb/VERSION # Install runtime dependencies, pypi packages, move bwcli, create data folders and set permissions RUN apk add --no-cache bash python3 libgcc libstdc++ openssl git && \ - chown root:nginx /opt/bunkerweb/modules && \ - chmod 750 /opt/bunkerweb/modules && \ - chmod 740 /opt/bunkerweb/modules/*.so && \ cp /opt/bunkerweb/helpers/bwcli /usr/local/bin && \ for dir in $(echo "cache configs configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs cache/letsencrypt plugins www") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/opt/bunkerweb/${dir}" ; done && \ chown -R root:nginx /data && \ diff --git a/autoconf/Dockerfile b/autoconf/Dockerfile index 713128977..4c59d87df 100644 --- a/autoconf/Dockerfile +++ b/autoconf/Dockerfile @@ -15,6 +15,7 @@ RUN apk add --no-cache --virtual build g++ gcc python3-dev musl-dev libffi-dev o # can't exclude specific files/dir from . so we are copying everything by hand COPY autoconf /opt/bunkerweb/autoconf COPY bw/api /opt/bunkerweb/api +COPY bw/cli /opt/bunkerweb/cli COPY bw/core /opt/bunkerweb/core COPY bw/settings.json /opt/bunkerweb/settings.json COPY db /opt/bunkerweb/db @@ -24,11 +25,17 @@ COPY utils /opt/bunkerweb/utils RUN apk add --no-cache bash && \ addgroup -g 101 nginx && \ adduser -h /var/cache/nginx -g nginx -s /bin/sh -G nginx -D -H -u 101 nginx && \ + cp /opt/bunkerweb/helpers/bwcli /usr/local/bin && \ chown -R nginx:nginx /opt/bunkerweb && \ find /opt/bunkerweb -type f -exec chmod 0740 {} \; && \ - find /opt/bunkerweb -type d -exec chmod 0750 {} \; + find /opt/bunkerweb -type d -exec chmod 0750 {} \; && \ + chmod 750 /opt/bunkerweb/cli/main.py /usr/local/bin/bwcli /opt/bunkerweb/autoconf/main.py /opt/bunkerweb/deps/python/bin/* && \ + chown root:nginx /usr/local/bin/bwcli -VOLUME /data +# Fix CVEs +RUN apk add "libssl1.1>=1.1.1q-r0" "libcrypto1.1>=1.1.1q-r0" "git>=2.32.3-r0" "ncurses-libs>=6.2_p20210612-r1" "ncurses-terminfo-base>=6.2_p20210612-r1" "libtirpc>=1.3.2-r1" "libtirpc-conf>=1.3.2-r1" "zlib>=1.2.12-r2" "libxml2>=2.9.14-r1" + +VOLUME /data /etc/nginx WORKDIR /opt/bunkerweb/autoconf diff --git a/bw/gen/main.py b/bw/gen/main.py index 8e108e694..f6552510f 100644 --- a/bw/gen/main.py +++ b/bw/gen/main.py @@ -179,14 +179,11 @@ if __name__ == "__main__": logger = setup_logger("Generator", config_files["LOG_LEVEL"]) bw_integration = "Local" - if config_files.get("KUBERNETES_MODE", "no") == "yes": + if getenv("KUBERNETES_MODE", "no") == "yes": bw_integration = "Kubernetes" elif ( integration == "Docker" - or config_files.get( - "SWARM_MODE", config_files.get("AUTOCONF_MODE", "no") - ) - == "yes" + or getenv("SWARM_MODE", getenv("AUTOCONF_MODE", "no")) == "yes" ): bw_integration = "Cluster" diff --git a/db/Database.py b/db/Database.py index 488212923..46779386d 100644 --- a/db/Database.py +++ b/db/Database.py @@ -511,6 +511,7 @@ class Database: self, custom_configs: List[Dict[str, Tuple[str, List[str]]]], method: str ) -> str: """Save the custom configs in the database""" + message = "" with self.__db_session() as session: # Delete all the old config session.query(Custom_configs).filter( @@ -537,7 +538,7 @@ class Database: .filter_by(id=custom_config["exploded"][0]) .first() ): - return f"Service {custom_config['exploded'][0]} not found, please check your config" + message += f"{'\n' if message else ''}Service {custom_config['exploded'][0]} not found, please check your config" config.update( { @@ -594,9 +595,9 @@ class Database: session.add_all(to_put) session.commit() except BaseException: - return format_exc() + return f"{f'{message}\n' if message else ''}{format_exc()}" - return "" + return message def get_config(self, methods: bool = False) -> Dict[str, Any]: """Get the config from the database""" diff --git a/scheduler/entrypoint.sh b/scheduler/entrypoint.sh index 426eef773..defdf3ebd 100755 --- a/scheduler/entrypoint.sh +++ b/scheduler/entrypoint.sh @@ -6,6 +6,9 @@ if [ -f /opt/bunkerweb/tmp/scheduler.pid ] ; then rm -f /opt/bunkerweb/tmp/scheduler.pid fi +# setup and check /data folder +/opt/bunkerweb/helpers/data.sh "ENTRYPOINT" + # trap SIGTERM and SIGINT function trap_exit() { log "ENTRYPOINT" "ℹ️ " "Catched stop operation" diff --git a/scheduler/main.py b/scheduler/main.py index 2c7f0799a..844e96355 100644 --- a/scheduler/main.py +++ b/scheduler/main.py @@ -211,7 +211,7 @@ if __name__ == "__main__": env = db.get_config() # Checking if any custom config has been created by the user - custom_configs = [] + custom_confs = [] root_dirs = listdir("/opt/bunkerweb/configs") for (root, dirs, files) in walk("/opt/bunkerweb/configs", topdown=True): if ( @@ -222,7 +222,7 @@ if __name__ == "__main__": path_exploded = root.split("/") for file in files: with open(join(root, file), "r") as f: - custom_configs.append( + custom_confs.append( { "value": f.read(), "exploded": ( @@ -235,11 +235,12 @@ if __name__ == "__main__": } ) - ret = db.save_custom_configs(custom_configs, "manual") - if ret: - logger.error( - f"Couldn't save manually created custom configs to database: {ret}", - ) + if custom_confs: + ret = db.save_custom_configs(custom_confs, "manual") + if ret: + logger.error( + f"Couldn't save some manually created custom configs to database: {ret}", + ) custom_configs = db.get_custom_configs() diff --git a/ui/Dockerfile b/ui/Dockerfile index 3b2df0418..780ba94da 100755 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -29,7 +29,7 @@ COPY ui /opt/bunkerweb/ui RUN apk add --no-cache bash file && \ addgroup -g 101 ui && \ adduser -h /var/cache/nginx -g ui -s /bin/sh -G ui -D -H -u 101 ui && \ - for dir in $(echo "cache configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/opt/bunkerweb/${dir}" ; done && \ + for dir in $(echo "cache configs configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/opt/bunkerweb/${dir}" ; done && \ mkdir /opt/bunkerweb/tmp && \ chown -R root:ui /opt/bunkerweb && \ find /opt/bunkerweb -type f -exec chmod 0740 {} \; && \ @@ -50,4 +50,4 @@ WORKDIR /opt/bunkerweb/ui USER ui:ui -CMD ["python", "-m", "gunicorn", "--bind=0.0.0.0:7000", "--workers=1", "--threads=2", "main:app"] +CMD ["python3", "-m", "gunicorn", "--bind=0.0.0.0:7000", "--workers=1", "--threads=2", "--user", "ui", "--group", "ui", "main:app"] diff --git a/ui/main.py b/ui/main.py index e5f962cd1..a465b577e 100755 --- a/ui/main.py +++ b/ui/main.py @@ -1,3 +1,4 @@ +from subprocess import DEVNULL, STDOUT, run from sys import path as sys_path, exit as sys_exit, modules as sys_modules sys_path.append("/opt/bunkerweb/ui/deps/python") @@ -26,7 +27,6 @@ from flask_login import LoginManager, login_required, login_user, logout_user from flask_wtf.csrf import CSRFProtect, CSRFError, generate_csrf from json import JSONDecodeError, load as json_load from jinja2 import Template -from logging import getLogger, INFO, ERROR, StreamHandler, Formatter from os import chmod, getenv, getpid, listdir, mkdir, walk from os.path import exists, isdir, isfile, join from re import match as re_match @@ -35,7 +35,7 @@ from requests.utils import default_headers from shutil import rmtree, copytree, chown from tarfile import CompressionError, HeaderError, ReadError, TarError, open as tar_open from threading import Thread -from time import sleep, time +from time import time from traceback import format_exc from typing import Optional from uuid import uuid4 @@ -622,8 +622,10 @@ def configs(): return redirect(url_for("loading", next=url_for("configs"))) + db_configs = db.get_custom_configs() return render_template( - "configs.html", folders=[path_to_dict("/opt/bunkerweb/configs")] + "configs.html", + folders=[path_to_dict("/opt/bunkerweb/configs", db_configs=db_configs)], ) diff --git a/ui/src/Instances.py b/ui/src/Instances.py index 4757a1927..c5238f4d0 100644 --- a/ui/src/Instances.py +++ b/ui/src/Instances.py @@ -1,4 +1,3 @@ -from docker.errors import APIError from kubernetes import client as kube_client from os.path import exists from subprocess import run @@ -109,7 +108,7 @@ class Instances: is_swarm = True try: self.__docker.swarm.version - except APIError: + except: is_swarm = False if is_swarm: diff --git a/ui/utils.py b/ui/utils.py index 3ed8f66cf..7e8ed6017 100644 --- a/ui/utils.py +++ b/ui/utils.py @@ -324,7 +324,9 @@ def form_plugin_gen( return soup.prettify() -def path_to_dict(path, *, level: int = 0, is_cache: bool = False) -> dict: +def path_to_dict( + path, *, level: int = 0, is_cache: bool = False, db_configs: dict = None +) -> dict: d = {"name": os.path.basename(path)} if os.path.isdir(path): @@ -335,25 +337,43 @@ def path_to_dict(path, *, level: int = 0, is_cache: bool = False) -> dict: "can_create_files": level > 0 and not is_cache, "can_create_folders": level > 0 and not is_cache, "can_edit": level > 1 and not is_cache, - "can_delete": level > 1 and not is_cache, + "can_delete": False, "children": [ path_to_dict( - os.path.join(path, x), level=level + 1, is_cache=is_cache + os.path.join(path, x), + level=level + 1, + is_cache=is_cache, + db_configs=db_configs, ) for x in sorted(os.listdir(path)) ], } ) + + if level > 1 and not is_cache and not d["children"]: + d["can_delete"] = True else: d.update( { "type": "file", "path": path, - "can_edit": level > 1 and not is_cache, "can_download": is_cache, } ) + can_edit = False + if level > 1 and not is_cache: + exploded_path = path.split("/") + for conf in db_configs: + if exploded_path[-1].replace(".conf", "") == conf["name"]: + if level > 2 and exploded_path[-2] != conf["service_id"]: + continue + + can_edit = True + break + + d["can_edit"] = can_edit + magic_file = magic.from_file(path, mime=True) if (