From 26db144df45a3713957a6df5093ac20c643837b7 Mon Sep 17 00:00:00 2001 From: bunkerity Date: Wed, 28 Jul 2021 17:27:39 +0200 Subject: [PATCH] autoconf refactoring and fix CVE-2021-36159 --- Dockerfile | 4 +- autoconf/Dockerfile | 4 ++ autoconf/src/Controller.py | 8 ++- autoconf/src/DockerController.py | 2 +- autoconf/src/IngressController.py | 20 ++++--- autoconf/src/ReloadServer.py | 37 ++++++++----- autoconf/src/SwarmController.py | 8 +-- autoconf/src/app.py | 87 ++++++++++--------------------- ui/Dockerfile | 3 ++ 9 files changed, 82 insertions(+), 91 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f29f9d..f7b18ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,8 @@ RUN chmod +x /tmp/docker.sh && \ /tmp/docker.sh && \ rm -f /tmp/docker.sh -# Fix CVE-2021-22901, CVE-2021-22898, CVE-2021-22897 and CVE-2021-33560 -RUN apk add "curl>=7.77.0-r0" "libgcrypt>=1.8.8-r0" +# Fix CVE-2021-22901, CVE-2021-22898, CVE-2021-22897, CVE-2021-33560 and CVE-2021-36159 +RUN apk add "curl>=7.77.0-r0" "libgcrypt>=1.8.8-r0" "apk-tools>=2.12.6-r0" VOLUME /www /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /pre-server-confs /acme-challenge /plugins diff --git a/autoconf/Dockerfile b/autoconf/Dockerfile index ae38592..a1f4f0c 100644 --- a/autoconf/Dockerfile +++ b/autoconf/Dockerfile @@ -8,6 +8,7 @@ COPY jobs/ /opt/bunkerized-nginx/jobs COPY settings.json /opt/bunkerized-nginx/ COPY misc/cron /etc/crontabs/nginx COPY autoconf/entrypoint.sh /opt/bunkerized-nginx/entrypoint/ +COPY autoconf/requirements.txt /opt/bunkerized-nginx/entrypoint/ COPY autoconf/src/* /opt/bunkerized-nginx/entrypoint/ RUN apk add --no-cache py3-pip bash certbot curl openssl && \ @@ -20,4 +21,7 @@ RUN chmod +x /tmp/prepare.sh && \ /tmp/prepare.sh && \ rm -f /tmp/prepare.sh +# Fix CVE-2021-36159 +RUN apk add "apk-tools>=2.12.6-r0" + ENTRYPOINT ["/opt/bunkerized-nginx/entrypoint/entrypoint.sh"] diff --git a/autoconf/src/Controller.py b/autoconf/src/Controller.py index 10c6202..e7f9fab 100644 --- a/autoconf/src/Controller.py +++ b/autoconf/src/Controller.py @@ -8,8 +8,9 @@ class ControllerType(Enum) : class Controller(ABC) : - def __init__(self, type) : - self.__config = Config.from_controller_type(type) + def __init__(self, type, api_uri=None, lock=None) : + self.__config = Config(type, api_uri) + self.lock = lock @abstractmethod def get_env(self) : @@ -29,3 +30,6 @@ class Controller(ABC) : @abstractmethod def process_events(self) : pass + + def reload(self) : + return self.__config.reload() diff --git a/autoconf/src/DockerController.py b/autoconf/src/DockerController.py index b5575ac..fc2bf3f 100644 --- a/autoconf/src/DockerController.py +++ b/autoconf/src/DockerController.py @@ -37,6 +37,6 @@ class DockerController(Controller) : for event in client.events(decode=True, filter={"type": "container", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) : new_env = self.get_env() if new_env != old_env : - if (self.gen_conf(new_env)) : + if self.gen_conf(new_env) : old_env.copy(new_env) utils.log("[*] Successfully generated new configuration") diff --git a/autoconf/src/IngressController.py b/autoconf/src/IngressController.py index b82a76a..27d4f2a 100644 --- a/autoconf/src/IngressController.py +++ b/autoconf/src/IngressController.py @@ -1,16 +1,15 @@ from kubernetes import client, config, watch -from threading import Thread, Lock +from threading import Thread from Controller import Controller, ControllerType class IngressController : - def __init__(self) : - super().__init__(ControllerType.KUBERNETES) + def __init__(self, api_uri) : + super().__init__(ControllerType.KUBERNETES, api_uri=api_uri, lock=Lock()) config.load_incluster_config() self.__api = client.CoreV1Api() self.__extensions_api = client.ExtensionsV1beta1Api() - self.__lock = Lock() self.__old_env = {} def __get_ingresses(self) : @@ -71,25 +70,24 @@ class IngressController : t_ingress.join() t_service.join() - def __watch_ingress(self) : w = watch.Watch() for event in w.stream(self.__extensions_api.list_ingress_for_all_namespaces) : - self.__lock.acquire() + self.lock.acquire() new_env = self.get_env() if new_env != self.__old_env() : - if self.gen_conf(new_env) : + if self.gen_conf(new_env, lock=False) : self.__old_env.copy(new_env) utils.log("[*] Successfully generated new configuration") - self.__lock.release() + self.lock.release() def __watch_service(self) : w = watch.Watch() for event in w.stream(self.__api.list_service_for_all_namespaces) : - self.__lock.acquire() + self.lock.acquire() new_env = self.get_env() if new_env != self.__old_env() : - if self.gen_conf(new_env) : + if self.gen_conf(new_env, lock=False) : self.__old_env.copy(new_env) utils.log("[*] Successfully generated new configuration") - self.__lock.release() + self.lock.release() diff --git a/autoconf/src/ReloadServer.py b/autoconf/src/ReloadServer.py index 6af8262..8035168 100644 --- a/autoconf/src/ReloadServer.py +++ b/autoconf/src/ReloadServer.py @@ -3,25 +3,38 @@ import socketserver, threading, utils, os, stat class ReloadServerHandler(socketserver.StreamRequestHandler): def handle(self) : + locked = False try : + # Get lock order from client data = self.request.recv(512) - if not data : + if not data or data != b"lock" : return - with self.server.lock : - ret = self.server.autoconf.reload() - if ret : - self.request.sendall("ok".encode("utf-8")) - else : - self.request.sendall("ko".encode("utf-8")) - except Exception as e : - utils.log("Exception " + str(e)) + self.server.controller.lock.acquire() + locked = True -def run_reload_server(autoconf, lock) : + # Get reload order from client + data = self.request.recv(512) + if not data or data != b"reload" : + self.server.controller.lock.release() + return + if self.server.controller.reload() : + self.request.sendall(b"ok") + else : + self.request.sendall(b"ko") + + # Release the lock + self.server.controller.lock.release() + + except Exception as e : + utils.log("Exception ReloadServer : " + str(e)) + if locked : + self.server.controller.lock.release() + +def run_reload_server(controller) : server = socketserver.UnixStreamServer("/tmp/autoconf.sock", ReloadServerHandler) os.chown("/tmp/autoconf.sock", 0, 101) os.chmod("/tmp/autoconf.sock", 0o770) - server.autoconf = autoconf - server.lock = lock + server.controller = controller thread = threading.Thread(target=server.serve_forever) thread.daemon = True thread.start() diff --git a/autoconf/src/SwarmController.py b/autoconf/src/SwarmController.py index 597dafc..1a1e557 100644 --- a/autoconf/src/SwarmController.py +++ b/autoconf/src/SwarmController.py @@ -4,8 +4,8 @@ import utils class SwarmController(Controller) : - def __init__(self) : - super().__init__(ControllerType.SWARM) + def __init__(self, api_uri) : + super().__init__(ControllerType.SWARM, api_uri=api_uri, lock=Lock()) # TODO : honor env vars like DOCKER_HOST self.__client = docker.DockerClient(base_url='unix:///var/run/docker.sock') @@ -37,6 +37,8 @@ class SwarmController(Controller) : for event in client.events(decode=True, filter={"type": "service", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) : new_env = self.get_env() if new_env != old_env : - if (self.gen_conf(new_env)) : + self.lock.acquire() + if self.gen_conf(new_env) : old_env.copy(new_env) utils.log("[*] Successfully generated new configuration") + self.lock.release() diff --git a/autoconf/src/app.py b/autoconf/src/app.py index b9bc0e6..fcab991 100644 --- a/autoconf/src/app.py +++ b/autoconf/src/app.py @@ -5,67 +5,34 @@ from ReloadServer import run_reload_server import utils import docker, os, stat, sys, select, threading -# Check if we are in Swarm mode -swarm = os.getenv("SWARM_MODE") == "yes" +from DockerController import DockerController +from SwarmController import SwarmController +from KubernetesController import KubernetesController + +# Get variables +swarm = os.getenv("SWARM_MODE", "no") == "yes" +kubernetes = os.getenv("KUBERNETES_MODE", "no") == "yes" +api_uri = os.getenv("API_URI", "") + +# Instantiate the controller if swarm : - # Connect to the endpoint - endpoint = "/var/run/docker.sock" - if not os.path.exists(endpoint) or not stat.S_ISSOCK(os.stat(endpoint).st_mode) : - utils.log("[!] /var/run/docker.sock not found (is it mounted ?)") - sys.exit(1) - try : - client = docker.DockerClient(base_url='unix:///var/run/docker.sock') - except Exception as e : - utils.log("[!] Can't instantiate DockerClient : " + str(e)) - sys.exit(2) + utils.log("[*] Swarm mode detected") + controller = SwarmController(api_uri) +elif kubernetes : + utils.log("[*] Kubernetes mode detected") + controller = KubernetesController(api_uri) +else : + utils.log("[*] Docker mode detected") + controller = DockerController() -# Our object to process events -api = "" -if swarm : - api = os.getenv("API_URI") -autoconf = AutoConf(swarm, api) -lock = threading.Lock() -if swarm : - (server, thread) = run_reload_server(autoconf, lock) +# Run the reload server in background if needed +if swarm or kubernetes : + (server, thread) = run_reload_server(controller) -# Get all bunkerized-nginx instances and web services created before -try : - if swarm : - before = client.services.list(filters={"label" : "bunkerized-nginx.AUTOCONF"}) + client.services.list(filters={"label" : "bunkerized-nginx.SERVER_NAME"}) - else : - before = client.containers.list(all=True, filters={"label" : "bunkerized-nginx.AUTOCONF"}) + client.containers.list(filters={"label" : "bunkerized-nginx.SERVER_NAME"}) -except docker.errors.APIError as e : - utils.log("[!] Docker API error " + str(e)) - sys.exit(3) +# Apply the first config for existing services +current_env = controller.get_env() +if env != {} : + controller.gen_conf(current_env) -# Process them before events -autoconf.pre_process(before) - -# Process events received from Docker -try : - utils.log("[*] Listening for Docker events ...") - for event in client.events(decode=True) : - - # Process only container/service events - if (swarm and event["Type"] != "service") or (not swarm and event["Type"] != "container") : - continue - - # Get Container/Service object - try : - if swarm : - id = service_id=event["Actor"]["ID"] - server = client.services.get(service_id=id) - else : - id = event["id"] - server = client.containers.get(id) - except docker.errors.NotFound as e : - server = autoconf.get_server(id) - if not server : - continue - - # Process the event - autoconf.process(server, event["Action"]) - -except docker.errors.APIError as e : - utils.log("[!] Docker API error " + str(e)) - sys.exit(4) +# Process events +controller.process_events() diff --git a/ui/Dockerfile b/ui/Dockerfile index 5dca2ac..1393edd 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -15,6 +15,9 @@ RUN chmod +x /tmp/prepare.sh && \ /tmp/prepare.sh && \ rm -f /tmp/prepare.sh +# Fix CVE-2021-36159 +RUN apk add "apk-tools>=2.12.6-r0" + EXPOSE 5000 WORKDIR /opt/bunkerized-nginx/ui