autoconf refactoring and fix CVE-2021-36159

This commit is contained in:
bunkerity 2021-07-28 17:27:39 +02:00
parent a68ad53c3f
commit 26db144df4
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
9 changed files with 82 additions and 91 deletions

View File

@ -12,8 +12,8 @@ RUN chmod +x /tmp/docker.sh && \
/tmp/docker.sh && \ /tmp/docker.sh && \
rm -f /tmp/docker.sh rm -f /tmp/docker.sh
# Fix CVE-2021-22901, CVE-2021-22898, CVE-2021-22897 and CVE-2021-33560 # 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" 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 VOLUME /www /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /pre-server-confs /acme-challenge /plugins

View File

@ -8,6 +8,7 @@ COPY jobs/ /opt/bunkerized-nginx/jobs
COPY settings.json /opt/bunkerized-nginx/ COPY settings.json /opt/bunkerized-nginx/
COPY misc/cron /etc/crontabs/nginx COPY misc/cron /etc/crontabs/nginx
COPY autoconf/entrypoint.sh /opt/bunkerized-nginx/entrypoint/ COPY autoconf/entrypoint.sh /opt/bunkerized-nginx/entrypoint/
COPY autoconf/requirements.txt /opt/bunkerized-nginx/entrypoint/
COPY autoconf/src/* /opt/bunkerized-nginx/entrypoint/ COPY autoconf/src/* /opt/bunkerized-nginx/entrypoint/
RUN apk add --no-cache py3-pip bash certbot curl openssl && \ RUN apk add --no-cache py3-pip bash certbot curl openssl && \
@ -20,4 +21,7 @@ RUN chmod +x /tmp/prepare.sh && \
/tmp/prepare.sh && \ /tmp/prepare.sh && \
rm -f /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"] ENTRYPOINT ["/opt/bunkerized-nginx/entrypoint/entrypoint.sh"]

View File

@ -8,8 +8,9 @@ class ControllerType(Enum) :
class Controller(ABC) : class Controller(ABC) :
def __init__(self, type) : def __init__(self, type, api_uri=None, lock=None) :
self.__config = Config.from_controller_type(type) self.__config = Config(type, api_uri)
self.lock = lock
@abstractmethod @abstractmethod
def get_env(self) : def get_env(self) :
@ -29,3 +30,6 @@ class Controller(ABC) :
@abstractmethod @abstractmethod
def process_events(self) : def process_events(self) :
pass pass
def reload(self) :
return self.__config.reload()

View File

@ -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"]}) : for event in client.events(decode=True, filter={"type": "container", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) :
new_env = self.get_env() new_env = self.get_env()
if new_env != old_env : if new_env != old_env :
if (self.gen_conf(new_env)) : if self.gen_conf(new_env) :
old_env.copy(new_env) old_env.copy(new_env)
utils.log("[*] Successfully generated new configuration") utils.log("[*] Successfully generated new configuration")

View File

@ -1,16 +1,15 @@
from kubernetes import client, config, watch from kubernetes import client, config, watch
from threading import Thread, Lock from threading import Thread
from Controller import Controller, ControllerType from Controller import Controller, ControllerType
class IngressController : class IngressController :
def __init__(self) : def __init__(self, api_uri) :
super().__init__(ControllerType.KUBERNETES) super().__init__(ControllerType.KUBERNETES, api_uri=api_uri, lock=Lock())
config.load_incluster_config() config.load_incluster_config()
self.__api = client.CoreV1Api() self.__api = client.CoreV1Api()
self.__extensions_api = client.ExtensionsV1beta1Api() self.__extensions_api = client.ExtensionsV1beta1Api()
self.__lock = Lock()
self.__old_env = {} self.__old_env = {}
def __get_ingresses(self) : def __get_ingresses(self) :
@ -71,25 +70,24 @@ class IngressController :
t_ingress.join() t_ingress.join()
t_service.join() t_service.join()
def __watch_ingress(self) : def __watch_ingress(self) :
w = watch.Watch() w = watch.Watch()
for event in w.stream(self.__extensions_api.list_ingress_for_all_namespaces) : for event in w.stream(self.__extensions_api.list_ingress_for_all_namespaces) :
self.__lock.acquire() self.lock.acquire()
new_env = self.get_env() new_env = self.get_env()
if new_env != self.__old_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) self.__old_env.copy(new_env)
utils.log("[*] Successfully generated new configuration") utils.log("[*] Successfully generated new configuration")
self.__lock.release() self.lock.release()
def __watch_service(self) : def __watch_service(self) :
w = watch.Watch() w = watch.Watch()
for event in w.stream(self.__api.list_service_for_all_namespaces) : for event in w.stream(self.__api.list_service_for_all_namespaces) :
self.__lock.acquire() self.lock.acquire()
new_env = self.get_env() new_env = self.get_env()
if new_env != self.__old_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) self.__old_env.copy(new_env)
utils.log("[*] Successfully generated new configuration") utils.log("[*] Successfully generated new configuration")
self.__lock.release() self.lock.release()

View File

@ -3,25 +3,38 @@ import socketserver, threading, utils, os, stat
class ReloadServerHandler(socketserver.StreamRequestHandler): class ReloadServerHandler(socketserver.StreamRequestHandler):
def handle(self) : def handle(self) :
locked = False
try : try :
# Get lock order from client
data = self.request.recv(512) data = self.request.recv(512)
if not data : if not data or data != b"lock" :
return return
with self.server.lock : self.server.controller.lock.acquire()
ret = self.server.autoconf.reload() locked = True
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))
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) server = socketserver.UnixStreamServer("/tmp/autoconf.sock", ReloadServerHandler)
os.chown("/tmp/autoconf.sock", 0, 101) os.chown("/tmp/autoconf.sock", 0, 101)
os.chmod("/tmp/autoconf.sock", 0o770) os.chmod("/tmp/autoconf.sock", 0o770)
server.autoconf = autoconf server.controller = controller
server.lock = lock
thread = threading.Thread(target=server.serve_forever) thread = threading.Thread(target=server.serve_forever)
thread.daemon = True thread.daemon = True
thread.start() thread.start()

View File

@ -4,8 +4,8 @@ import utils
class SwarmController(Controller) : class SwarmController(Controller) :
def __init__(self) : def __init__(self, api_uri) :
super().__init__(ControllerType.SWARM) super().__init__(ControllerType.SWARM, api_uri=api_uri, lock=Lock())
# TODO : honor env vars like DOCKER_HOST # TODO : honor env vars like DOCKER_HOST
self.__client = docker.DockerClient(base_url='unix:///var/run/docker.sock') 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"]}) : for event in client.events(decode=True, filter={"type": "service", "label": ["bunkerized-nginx.AUTOCONF", "bunkerized-nginx.SERVER_NAME"]}) :
new_env = self.get_env() new_env = self.get_env()
if new_env != old_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) old_env.copy(new_env)
utils.log("[*] Successfully generated new configuration") utils.log("[*] Successfully generated new configuration")
self.lock.release()

View File

@ -5,67 +5,34 @@ from ReloadServer import run_reload_server
import utils import utils
import docker, os, stat, sys, select, threading import docker, os, stat, sys, select, threading
# Check if we are in Swarm mode from DockerController import DockerController
swarm = os.getenv("SWARM_MODE") == "yes" 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 : if swarm :
# Connect to the endpoint utils.log("[*] Swarm mode detected")
endpoint = "/var/run/docker.sock" controller = SwarmController(api_uri)
if not os.path.exists(endpoint) or not stat.S_ISSOCK(os.stat(endpoint).st_mode) : elif kubernetes :
utils.log("[!] /var/run/docker.sock not found (is it mounted ?)") utils.log("[*] Kubernetes mode detected")
sys.exit(1) controller = KubernetesController(api_uri)
try : else :
client = docker.DockerClient(base_url='unix:///var/run/docker.sock') utils.log("[*] Docker mode detected")
except Exception as e : controller = DockerController()
utils.log("[!] Can't instantiate DockerClient : " + str(e))
sys.exit(2)
# Our object to process events # Run the reload server in background if needed
api = "" if swarm or kubernetes :
if swarm : (server, thread) = run_reload_server(controller)
api = os.getenv("API_URI")
autoconf = AutoConf(swarm, api)
lock = threading.Lock()
if swarm :
(server, thread) = run_reload_server(autoconf, lock)
# Get all bunkerized-nginx instances and web services created before # Apply the first config for existing services
try : current_env = controller.get_env()
if swarm : if env != {} :
before = client.services.list(filters={"label" : "bunkerized-nginx.AUTOCONF"}) + client.services.list(filters={"label" : "bunkerized-nginx.SERVER_NAME"}) controller.gen_conf(current_env)
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)
# Process them before events # Process events
autoconf.pre_process(before) controller.process_events()
# 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)

View File

@ -15,6 +15,9 @@ RUN chmod +x /tmp/prepare.sh && \
/tmp/prepare.sh && \ /tmp/prepare.sh && \
rm -f /tmp/prepare.sh rm -f /tmp/prepare.sh
# Fix CVE-2021-36159
RUN apk add "apk-tools>=2.12.6-r0"
EXPOSE 5000 EXPOSE 5000
WORKDIR /opt/bunkerized-nginx/ui WORKDIR /opt/bunkerized-nginx/ui