From 1a32e7c02cde49f5effcd9ff71c8e8425987dfe7 Mon Sep 17 00:00:00 2001 From: bunkerity Date: Thu, 29 Jul 2021 15:43:51 +0200 Subject: [PATCH] autoconf - various bug fixes with DockerController --- Dockerfile | 6 +++-- autoconf/src/Config.py | 45 +++++++++++++++---------------- autoconf/src/Controller.py | 6 +++-- autoconf/src/DockerController.py | 39 +++++++++++++++++++-------- autoconf/src/IngressController.py | 13 +++++---- autoconf/src/ReloadServer.py | 2 +- autoconf/src/SwarmController.py | 39 +++++++++++++++++++-------- autoconf/src/app.py | 8 +++--- confs/global/abusers.list | 0 confs/global/proxies.list | 0 confs/global/referrers.list | 0 confs/global/tor-exit-nodes.list | 0 confs/global/user-agents.list | 0 entrypoint/entrypoint.sh | 45 ++++++++++++++++++------------- entrypoint/utils.sh | 11 +++++--- gen/Configurator.py | 2 +- 16 files changed, 134 insertions(+), 82 deletions(-) delete mode 100644 confs/global/abusers.list delete mode 100644 confs/global/proxies.list delete mode 100644 confs/global/referrers.list delete mode 100644 confs/global/tor-exit-nodes.list delete mode 100644 confs/global/user-agents.list diff --git a/Dockerfile b/Dockerfile index f7b18ba..603e488 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,10 @@ RUN chmod +x /tmp/docker.sh && \ /tmp/docker.sh && \ rm -f /tmp/docker.sh -# 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" +# 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-36159 +#RUN apk add "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/src/Config.py b/autoconf/src/Config.py index ea9fae8..69d878b 100644 --- a/autoconf/src/Config.py +++ b/autoconf/src/Config.py @@ -1,19 +1,18 @@ #!/usr/bin/python3 -import utils import subprocess, shutil, os, traceback, requests, time -from Controller import ControllerType +import Controller from logger import log class Config : - def __init__(type, api_uri) : + def __init__(self, type, api_uri) : self.__type = type self.__api_uri = api_uri - def gen(env) : + def gen(self, env) : try : # Write environment variables to a file with open("/tmp/variables.env", "w") as f : @@ -27,32 +26,32 @@ class Config : stdout = proc.stdout.decode("ascii") stderr = proc.stderr.decode("ascii") if len(stdout) > 1 : - log("CONFIG", "INFO", "generator output : " + stdout) + log("config", "INFO", "generator output : " + stdout) if stderr != "" : - log("CONFIG", "ERROR", "generator error : " + stderr) + log("config", "ERROR", "generator error : " + stderr) # We're done if proc.returncode == 0 : - if self.__type == ControllerType.SWARM or self.__type == ControllerType.KUBERNETES : + if self.__type == Controller.Type.SWARM or self.__type == Controller.Type.KUBERNETES : return self.__jobs() return True - log("CONFIG", "ERROR", "error while generating config (return code = " + str(proc.returncode) + ")") + log("config", "ERROR", "error while generating config (return code = " + str(proc.returncode) + ")") except Exception as e : - log("CONFIG", "ERROR", "exception while generating site config : " + traceback.format_exc()) + log("config", "ERROR", "exception while generating site config : " + traceback.format_exc()) return False def reload(self, instances) : ret = True - if self.__type == ControllerType.DOCKER : + if self.__type == Controller.Type.DOCKER : for instance in instances : try : instance.kill("SIGHUP") except : ret = False - elif self.__type == ControllerType.SWARM : + elif self.__type == Controller.Type.SWARM : ret = self.__api_call(instances, "/reload") - elif self.__type == ControllerType.KUBERNETES : + elif self.__type == Controller.Type.KUBERNETES : ret = self.__api_call(instances, "/reload") return ret @@ -61,9 +60,9 @@ class Config : def wait(self, instances) : ret = True - if self.__type == ControllerType.DOCKER : + if self.__type == Controller.Type.DOCKER : ret = self.__wait_docker() - elif self.__type == ControllerType.SWARM or self.__type == ControllerType.KUBERNETES : + elif self.__type == Controller.Type.SWARM or self.__type == Controller.Type.KUBERNETES : ret = self.__wait_api() return ret @@ -96,20 +95,20 @@ class Config : started = True break i = i + 1 - log("CONFIG", "INFO" "waiting " + str(i) + " seconds before retrying to contact bunkerized-nginx instances") + log("config", "INFO" "waiting " + str(i) + " seconds before retrying to contact bunkerized-nginx instances") if started : - log("CONFIG", "INFO", "bunkerized-nginx instances started") + log("config", "INFO", "bunkerized-nginx instances started") return True else : - log("CONFIG", "ERROR", "bunkerized-nginx instances are not started") + log("config", "ERROR", "bunkerized-nginx instances are not started") except Exception as e : - log("CONFIG", "ERROR", "exception while waiting for bunkerized-nginx instances : " + traceback.format_exc()) + log("config", "ERROR", "exception while waiting for bunkerized-nginx instances : " + traceback.format_exc()) return False def __api_call(self, instances, path) : ret = True urls = [] - if self.__type == ControllerType.SWARM : + if self.__type == Controller.Type.SWARM : for instance in instances : name = instance.name for task in instance.tasks() : @@ -117,8 +116,8 @@ class Config : taskID = task["ID"] url = "http://" + name + "." + nodeID + "." + taskID + ":8080" + self.__api_uri + path urls.append(url) - elif self.__type == ControllerType.KUBERNETES : - log("CONFIG", "ERROR", "TODO get urls for k8s") + elif self.__type == Controller.Type.KUBERNETES : + log("config", "ERROR", "TODO get urls for k8s") for url in urls : try : @@ -126,8 +125,8 @@ class Config : except : pass if req and req.status_code == 200 and req.text == "ok" : - log("CONFIG", "INFO", "successfully sent API order to " + url) + log("config", "INFO", "successfully sent API order to " + url) else : - log("CONFIG", "INFO", "failed API order to " + url) + log("config", "INFO", "failed API order to " + url) ret = False return ret diff --git a/autoconf/src/Controller.py b/autoconf/src/Controller.py index 831bbc0..fe810f7 100644 --- a/autoconf/src/Controller.py +++ b/autoconf/src/Controller.py @@ -1,7 +1,9 @@ from abc import ABC, abstractmethod +from enum import Enum + from Config import Config -class ControllerType(Enum) : +class Type(Enum) : DOCKER = 1 SWARM = 2 KUBERNETES = 3 @@ -28,7 +30,7 @@ class Controller(ABC) : return self.__config.gen(env) @abstractmethod - def process_events(self) : + def process_events(self, current_env) : pass @abstractmethod diff --git a/autoconf/src/DockerController.py b/autoconf/src/DockerController.py index aebdc02..98a3b43 100644 --- a/autoconf/src/DockerController.py +++ b/autoconf/src/DockerController.py @@ -1,11 +1,12 @@ import docker -from Controller import Controller, ControllerType -import utils +import Controller -class DockerController(Controller) : +from logger import log + +class DockerController(Controller.Controller) : def __init__(self) : - super().__init__(ControllerType.DOCKER) + super().__init__(Controller.Type.DOCKER) # TODO : honor env vars like DOCKER_HOST self.__client = docker.DockerClient(base_url='unix:///var/run/docker.sock') @@ -21,22 +22,38 @@ class DockerController(Controller) : for variable in instance.attrs["Config"]["Env"] : env[variable.split("=")[0]] = variable.replace(variable.split("=")[0] + "=", "", 1) first_servers = [] - if "SERVER_NAME" in env : + if "SERVER_NAME" in env and env["SERVER_NAME"] != "" : first_servers = env["SERVER_NAME"].split(" ") for container in self.__get_containers() : first_server = container.labels["bunkerized-nginx.SERVER_NAME"].split(" ")[0] first_servers.append(first_server) - for variable, value in instance.labels.items() : - if variable.startswith("bunkerized-nginx.") : + for variable, value in container.labels.items() : + if variable.startswith("bunkerized-nginx.") and variable != "bunkerized-nginx.AUTOCONF" : env[first_server + "_" + variable.replace("bunkerized-nginx.", "", 1)] = value - env["SERVER_NAME"] = " ".join(first_servers) + if len(first_servers) == 0 : + env["SERVER_NAME"] = "" + else : + env["SERVER_NAME"] = " ".join(first_servers) return self._fix_env(env) def process_events(self, current_env) : old_env = current_env - for event in client.events(decode=True, filter={"type": "container", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) : + # TODO : check why filter isn't working as expected + #for event in self.__client.events(decode=True, filters={"type": "container", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) : + for event in self.__client.events(decode=True, filters={"type": "container"}) : new_env = self.get_env() if new_env != old_env : + log("controller", "INFO", "generating new configuration") if self.gen_conf(new_env) : - old_env.copy(new_env) - log("CONTROLLER", "INFO", "successfully generated new configuration") + old_env = new_env.copy() + log("controller", "INFO", "successfully generated new configuration") + if self.reload() : + log("controller", "INFO", "successful reload") + else : + log("controller", "ERROR", "failed reload") + else : + log("controller", "ERROR", "can't generate new configuration") + + def reload(self) : + return self._reload(self.__get_instances()) + diff --git a/autoconf/src/IngressController.py b/autoconf/src/IngressController.py index 58afd46..4ee7ad4 100644 --- a/autoconf/src/IngressController.py +++ b/autoconf/src/IngressController.py @@ -1,14 +1,14 @@ from kubernetes import client, config, watch from threading import Thread -from Controller import Controller, ControllerType +import Controller from logger import log -class IngressController : +class IngressController(Controller.Controller) : def __init__(self, api_uri) : - super().__init__(ControllerType.KUBERNETES, api_uri=api_uri, lock=Lock()) + super().__init__(Controller.Type.KUBERNETES, api_uri=api_uri, lock=Lock()) config.load_incluster_config() self.__api = client.CoreV1Api() self.__extensions_api = client.ExtensionsV1beta1Api() @@ -79,7 +79,7 @@ class IngressController : new_env = self.get_env() if new_env != self.__old_env() : if self.gen_conf(new_env, lock=False) : - self.__old_env.copy(new_env) + self.__old_env = new_env.copy() log("CONTROLLER", "INFO", "successfully generated new configuration") self.lock.release() @@ -90,6 +90,9 @@ class IngressController : new_env = self.get_env() if new_env != self.__old_env() : if self.gen_conf(new_env, lock=False) : - self.__old_env.copy(new_env) + self.__old_env = new_env.copy() log("CONTROLLER", "INFO", "successfully generated new configuration") self.lock.release() + + def reload(self) : + return self._reload(self.__get_ingresses()) diff --git a/autoconf/src/ReloadServer.py b/autoconf/src/ReloadServer.py index e33fbfc..2415518 100644 --- a/autoconf/src/ReloadServer.py +++ b/autoconf/src/ReloadServer.py @@ -21,7 +21,7 @@ class ReloadServerHandler(socketserver.StreamRequestHandler): locked = False self.request.sendall(b"ok") elif data == b"reload" : - ret = self.server.controller.reload() : + ret = self.server.controller.reload() if ret : self.request.sendall(b"ok") else : diff --git a/autoconf/src/SwarmController.py b/autoconf/src/SwarmController.py index 50271ac..63fd000 100644 --- a/autoconf/src/SwarmController.py +++ b/autoconf/src/SwarmController.py @@ -1,11 +1,13 @@ import docker -from Controller import Controller, ControllerType -import utils -class SwarmController(Controller) : +from logger import log + +import Controller + +class SwarmController(Controller.Controller) : def __init__(self, api_uri) : - super().__init__(ControllerType.SWARM, api_uri=api_uri, lock=Lock()) + super().__init__(Controller.Type.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') @@ -21,24 +23,39 @@ class SwarmController(Controller) : for variable in instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"]["Env"] : env[variable.split("=")[0]] = variable.replace(variable.split("=")[0] + "=", "", 1) first_servers = [] - if "SERVER_NAME" in env : + if "SERVER_NAME" in env and env["SERVER_NAME"] != "" : first_servers = env["SERVER_NAME"].split(" ") for service in self.__get_services() : first_server = service.attrs["Spec"]["Labels"]["bunkerized-nginx.SERVER_NAME"].split(" ")[0] first_servers.append(first_server) for variable, value in service.attrs["Spec"]["Labels"].items() : - if variable.startswith("bunkerized-nginx.") : + if variable.startswith("bunkerized-nginx.") and variable != "bunkerized-nginx.AUTOCONF" : env[first_server + "_" + variable.replace("bunkerized-nginx.", "", 1)] = value - env["SERVER_NAME"] = " ".join(first_servers) + if len(first_servers) == 0 : + env["SERVER_NAME"] = "" + else : + env["SERVER_NAME"] = " ".join(first_servers) return self._fix_env(env) def process_events(self, current_env) : old_env = current_env - for event in client.events(decode=True, filter={"type": "service", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) : + # TODO : check why filter isn't working as expected + #for event in self.__client.events(decode=True, filters={"type": "service", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) : + for event in self.__client.events(decode=True, filters={"type": "service"}) : new_env = self.get_env() if new_env != old_env : self.lock.acquire() - if self.gen_conf(new_env, lock=False) : - old_env.copy(new_env) - log("CONTROLLER", "INFO", "successfully generated new configuration") + log("controller", "INFO", "generating new configuration") + if self.gen_conf(new_env) : + old_env = new_env.copy() + log("controller", "INFO", "successfully generated new configuration") + if self.reload() : + log("controller", "INFO", "successful reload") + else : + log("controller", "ERROR", "failed reload") + else : + log("controller", "ERROR", "can't generate new configuration") self.lock.release() + + def reload(self) : + return self._reload(self.__get_instances()) diff --git a/autoconf/src/app.py b/autoconf/src/app.py index 3faf990..27de9cc 100644 --- a/autoconf/src/app.py +++ b/autoconf/src/app.py @@ -6,7 +6,7 @@ import docker, os, stat, sys, select, threading from DockerController import DockerController from SwarmController import SwarmController -from KubernetesController import KubernetesController +from IngressController import IngressController from logger import log @@ -21,7 +21,7 @@ if swarm : controller = SwarmController(api_uri) elif kubernetes : log("autoconf", "INFO", "kubernetes mode detected") - controller = KubernetesController(api_uri) + controller = IngressController(api_uri) else : log("autoconf", "INFO", "docker mode detected") controller = DockerController() @@ -32,8 +32,8 @@ if swarm or kubernetes : # Apply the first config for existing services current_env = controller.get_env() -if env != {} : +if current_env != {} : controller.gen_conf(current_env) # Process events -controller.process_events() +controller.process_events(current_env) diff --git a/confs/global/abusers.list b/confs/global/abusers.list deleted file mode 100644 index e69de29..0000000 diff --git a/confs/global/proxies.list b/confs/global/proxies.list deleted file mode 100644 index e69de29..0000000 diff --git a/confs/global/referrers.list b/confs/global/referrers.list deleted file mode 100644 index e69de29..0000000 diff --git a/confs/global/tor-exit-nodes.list b/confs/global/tor-exit-nodes.list deleted file mode 100644 index e69de29..0000000 diff --git a/confs/global/user-agents.list b/confs/global/user-agents.list deleted file mode 100644 index e69de29..0000000 diff --git a/entrypoint/entrypoint.sh b/entrypoint/entrypoint.sh index 62df632..4941af8 100644 --- a/entrypoint/entrypoint.sh +++ b/entrypoint/entrypoint.sh @@ -1,33 +1,35 @@ #!/bin/bash -echo "[*] Starting bunkerized-nginx ..." +. /opt/bunkerize-nginx/entrypoint/utils.sh + +log "entrypoint" "INFO" "starting bunkerized-nginx ..." # trap SIGTERM and SIGINT function trap_exit() { - echo "[*] Catched stop operation" - echo "[*] Stopping crond ..." + log "stop" "INFO" "catched stop operation" + log "stop" "INFO" "stopping crond ..." pkill -TERM crond - echo "[*] Stopping nginx ..." + log "stop" "INFO" "stopping nginx ..." /usr/sbin/nginx -s stop } trap "trap_exit" TERM INT QUIT # trap SIGHUP function trap_reload() { - echo "[*] Catched reload operation" + log "reload" "INFO" "catched reload operation" if [ "$SWARM_MODE" != "yes" ] ; then /opt/bunkerized-nginx/entrypoint/jobs.sh fi if [ -f /tmp/nginx.pid ] ; then - echo "[*] Reloading nginx ..." + log "reload" "INFO" "reloading nginx ..." nginx -s reload if [ $? -eq 0 ] ; then - echo "[*] Reload successfull" + log "reload" "INFO" "reloading successful" else - echo "[!] Reload failed" + log "reload" "ERROR" "reloading failed" fi else - echo "[!] Ignored reload operation because nginx is not running" + log "reload" "INFO" "ignored reload operation because nginx is not running" fi } trap "trap_reload" HUP @@ -35,7 +37,7 @@ trap "trap_reload" HUP # do the configuration magic if needed if [ ! -f "/etc/nginx/global.env" ] ; then - echo "[*] Configuring bunkerized-nginx ..." + log "entrypoint" "INFO" "configuring bunkerized-nginx ..." # check permissions if [ "$SWARM_MODE" != "yes" ] ; then @@ -50,27 +52,34 @@ if [ ! -f "/etc/nginx/global.env" ] ; then # start temp nginx to solve Let's Encrypt challenges if needed /opt/bunkerized-nginx/entrypoint/nginx-temp.sh - # only do config if we are not in swarm mode - if [ "$SWARM_MODE" != "yes" ] ; then + # only do config if we are not in swarm/kubernetes mode + if [ "$SWARM_MODE" != "yes" ] && [ "$KUBERNETES_MODE" != "yes" ] ; then # export the variables env | grep -E -v "^(HOSTNAME|PWD|PKG_RELEASE|NJS_VERSION|SHLVL|PATH|_|NGINX_VERSION|HOME)=" > "/tmp/variables.env" # call the generator - /opt/bunkerized-nginx/gen/main.py --settings /opt/bunkerized-nginx/settings.json --templates /opt/bunkerized-nginx/confs --output /etc/nginx --variables /tmp/variables.env + gen_ret="$(/opt/bunkerized-nginx/gen/main.py --settings /opt/bunkerized-nginx/settings.json --templates /opt/bunkerized-nginx/confs --output /etc/nginx --variables /tmp/variables.env 2>&1)" + if [ "$?" -ne 0 ] ; then + log "entrypoint" "ERROR" "generator failed : $gen_ret" + exit 1 + fi + if [ "$gen_ret" != "" ] ; then + log "entrypoint" "INFO" "generator output : $gen_ret" + fi # call jobs /opt/bunkerized-nginx/entrypoint/jobs.sh fi else - echo "[*] Skipping configuration process" + log "entrypoint" "INFO" "skipping configuration process" fi # start crond crond # wait until config has been generated if we are in swarm mode -if [ "$SWARM_MODE" = "yes" ] ; then - echo "[*] Waiting until config has been generated ..." +if [ "$SWARM_MODE" = "yes" ] || [ "$KUBERNETES_MODE" = "yes" ] ; then + log "entrypoint" "INFO" "waiting until config has been generated ..." while [ ! -f "/etc/nginx/autoconf" ] ; do sleep 1 done @@ -82,7 +91,7 @@ if [ -f "/tmp/nginx-temp.pid" ] ; then fi # run nginx -echo "[*] Running nginx ..." +log "entrypoint" "INFO" "running nginx ..." nginx -g 'daemon off;' & pid="$!" @@ -104,5 +113,5 @@ while [ -f "/tmp/nginx.pid" ] ; do done # sigterm trapped -echo "[*] bunkerized-nginx stopped" +log "entrypoint" "INFO" "bunkerized-nginx stopped" exit 0 diff --git a/entrypoint/utils.sh b/entrypoint/utils.sh index f3c640b..e92a6f0 100644 --- a/entrypoint/utils.sh +++ b/entrypoint/utils.sh @@ -32,9 +32,12 @@ function has_value() { done } -# log to jobs.log -function job_log() { +# log to stdout +function log() { when="$(date '+[%Y-%m-%d %H:%M:%S]')" - what="$1" - echo "$when $what" >> /var/log/nginx/jobs.log + category="$1" + severity="$2" + message="$3" + echo "$when $category - $severity - $message" } + diff --git a/gen/Configurator.py b/gen/Configurator.py index db3e1d7..b07cbb7 100644 --- a/gen/Configurator.py +++ b/gen/Configurator.py @@ -25,7 +25,7 @@ class Configurator : if check : self.__variables[var] = value else : - print("Ignoring " + var + "=" + value + " (" + reason + ")") + print("ignoring " + var + "=" + value + " (" + reason + ")", file=sys.stderr) def get_config(self) : config = {}