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 && \
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

View File

@ -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"]

View File

@ -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()

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"]}) :
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")

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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