diff --git a/autoconf/prepare.sh b/autoconf/prepare.sh index d1e7e5b..692b41f 100644 --- a/autoconf/prepare.sh +++ b/autoconf/prepare.sh @@ -16,6 +16,7 @@ chmod ugo+x /opt/bunkerized-nginx/entrypoint/* /opt/bunkerized-nginx/scripts/* chmod ugo+x /opt/bunkerized-nginx/gen/main.py chmod ugo+x /opt/bunkerized-nginx/jobs/main.py chmod ugo+x /opt/bunkerized-nginx/jobs/reload.py +chmod ugo+x /opt/bunkerized-nginx/jobs/certbot-*.sh chmod 770 /opt/bunkerized-nginx chmod 440 /opt/bunkerized-nginx/settings.json diff --git a/autoconf/src/Config.py b/autoconf/src/Config.py index 823ab3b..733a22c 100644 --- a/autoconf/src/Config.py +++ b/autoconf/src/Config.py @@ -6,6 +6,15 @@ import Controller from logger import log +CONFIGS = { + "conf": "/etc/nginx", + "letsencrypt": "/etc/letsencrypt", + "http": "/http-confs", + "server": "/server-confs", + "modsec": "/modsec-confs", + "modsec-crs": "/modsec-crs-confs" +} + class Config : def __init__(self, type, api_uri, http_port="8080") : @@ -64,12 +73,32 @@ class Config : instance.kill("SIGHUP") except : ret = False - elif self.__type == Controller.Type.SWARM : - ret = self.__api_call(instances, "/reload") - elif self.__type == Controller.Type.KUBERNETES : + elif self.__type == Controller.Type.SWARM or self.__type == Controller.Type.KUBERNETES : ret = self.__api_call(instances, "/reload") return ret + def send(self, instances) : + ret = True + if self.__type == Controller.Type.DOCKER : + return ret + elif self.__type == Controller.Type.SWARM or self.__type == Controller.Type.KUBERNERTES : + fail = False + for name, path in CONFIGS.items() : + file = self.__tarball(path) + if not self.__api_call(instances, "/" + name, file=file) : + log("config", "ERROR", "can't send config " + name + " to instance(s)") + fail = True + file.close() + if fail : + ret = False + return ret + + def __tarball(path) : + file = io.BytesIO() + with tarfile.open(mode="w:gz", fileobj=file) as tar : + tar.add(path, arcname=".") + return file + def __ping(self, instances) : return self.__api_call(instances, "/ping") @@ -120,7 +149,7 @@ class Config : log("config", "ERROR", "exception while waiting for bunkerized-nginx instances : " + traceback.format_exc()) return False - def __api_call(self, instances, path) : + def __api_call(self, instances, path, file=None) : ret = True nb = 0 urls = [] @@ -146,7 +175,10 @@ class Config : for url in urls : req = None try : - req = requests.post(url) + if file == None : + req = requests.post(url) + else : + req = requests.post(url, {'file': file}) except : pass if req and req.status_code == 200 and req.text == "ok" : diff --git a/autoconf/src/Controller.py b/autoconf/src/Controller.py index 7980a08..801719b 100644 --- a/autoconf/src/Controller.py +++ b/autoconf/src/Controller.py @@ -51,3 +51,10 @@ class Controller(ABC) : except : ret = False return ret + + def _send(self, instances) : + try : + ret = self._config.send(instances) + except : + ret = False + return ret diff --git a/autoconf/src/IngressController.py b/autoconf/src/IngressController.py index 35c0bab..8ae73bf 100644 --- a/autoconf/src/IngressController.py +++ b/autoconf/src/IngressController.py @@ -96,9 +96,9 @@ class IngressController(Controller.Controller) : def process_events(self, current_env) : self.__old_env = current_env - t_pod = Thread(target=self.__watch_pod) - t_ingress = Thread(target=self.__watch_ingress) - t_service = Thread(target=self.__watch_service) + t_pod = Thread(target=self.__watch, args=("pod",)) + t_ingress = Thread(target=self.__watch, args=("ingress",)) + t_service = Thread(target=self.__watch, args=("service",)) t_pod.start() t_ingress.start() t_service.start() @@ -106,63 +106,38 @@ class IngressController(Controller.Controller) : t_ingress.join() t_service.join() - def __watch_pod(self) : + def __watch(self, type) : w = watch.Watch() - for event in w.stream(self.__api.list_pod_for_all_namespaces, label_selector="bunkerized-nginx") : + what = None + if type == "pod" : + what = self.__api.list_pod_for_all_namespaces + elif type == "ingress" : + what = self.__extensions_api.list_ingress_for_all_namespaces + elif type == "service" : + what = self.__api.list_service_for_all_namespaces + for event in w.stream(what, label_selector="bunkerized-nginx") : self.lock.acquire() new_env = self.get_env() if new_env != self.__old_env : try : - if self.gen_conf(new_env) : - self.__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") - except : - log("controller", "ERROR", "exception while receiving event") - self.lock.release() - - def __watch_ingress(self) : - w = watch.Watch() - for event in w.stream(self.__extensions_api.list_ingress_for_all_namespaces, label_selector="bunkerized-nginx") : - self.lock.acquire() - new_env = self.get_env() - if new_env != self.__old_env : - try : - if self.gen_conf(new_env) : - self.__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") - except : - log("controller", "ERROR", "exception while receiving event") - self.lock.release() - - def __watch_service(self) : - w = watch.Watch() - for event in w.stream(self.__api.list_service_for_all_namespaces, label_selector="bunkerized-nginx") : - self.lock.acquire() - new_env = self.get_env() - if new_env != self.__old_env : - try : - if self.gen_conf(new_env) : - self.__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") - except : - log("controller", "ERROR", "exception while receiving event") + if not self.gen_conf(new_env) : + raise Exception("can't generate configuration") + if not self.send() : + raise Exception("can't send configuration") + if not self.reload() : + raise Exception("can't reload configuration") + self.__old_env = new_env.copy() + log("CONTROLLER", "INFO", "successfully loaded new configuration") + except Exception as e : + log("controller", "ERROR", "error while computing new event : " + str(e)) self.lock.release() def reload(self) : return self._reload(self.__get_services(autoconf=True)) + def send(self) : + return self._send(self.__get_services(autoconf=True)) + def wait(self) : self.lock.acquire() try : diff --git a/autoconf/src/SwarmController.py b/autoconf/src/SwarmController.py index c7cc974..cb2a29a 100644 --- a/autoconf/src/SwarmController.py +++ b/autoconf/src/SwarmController.py @@ -46,23 +46,24 @@ class SwarmController(Controller.Controller) : if new_env != old_env : self.lock.acquire() try : - 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") - except : - log("controller", "ERROR", "exception while receiving event") + if not self.gen_conf(new_env) : + raise Exception("can't generate configuration") + if not self.send() : + raise Exception("can't send configuration") + if not self.reload() : + raise Exception("can't reload configuration") + self.__old_env = new_env.copy() + log("CONTROLLER", "INFO", "successfully loaded new configuration") + except Exception as e : + log("controller", "ERROR", "error while computing new event : " + str(e)) self.lock.release() def reload(self) : return self._reload(self.__get_instances()) + def send(self) : + return self._send(self.__get_instances()) + def wait(self) : self.lock.acquire() try : diff --git a/helpers/install.sh b/helpers/install.sh index 0e95e68..69b51ef 100755 --- a/helpers/install.sh +++ b/helpers/install.sh @@ -820,6 +820,7 @@ do_and_check_cmd chmod 750 /opt/bunkerized-nginx/entrypoint/* do_and_check_cmd chmod 750 /opt/bunkerized-nginx/gen/main.py do_and_check_cmd chmod 750 /opt/bunkerized-nginx/jobs/main.py do_and_check_cmd chmod 750 /opt/bunkerized-nginx/jobs/reload.py +do_and_check_cmd chmod 750 /opt/bunkerized-nginx/jobs/certbot-*.sh # Set permissions for /usr/local/bin/bunkerized-nginx do_and_check_cmd chown root:root /usr/local/bin/bunkerized-nginx do_and_check_cmd chmod 750 /usr/local/bin/bunkerized-nginx diff --git a/helpers/kubernetes-nginx.yml b/helpers/kubernetes-nginx.yml index 3d815d7..48c1e15 100644 --- a/helpers/kubernetes-nginx.yml +++ b/helpers/kubernetes-nginx.yml @@ -37,31 +37,10 @@ spec: - name: MULTISITE value: "yes" volumeMounts: - - name: confs - mountPath: /etc/nginx - readOnly: true - - name: letsencrypt - mountPath: /etc/letsencrypt - readOnly: true - - name: acme-challenge - mountPath: /acme-challenge - readOnly: true - name: www mountPath: /www readOnly: true volumes: - - name: confs - hostPath: - path: /shared/confs - type: Directory - - name: letsencrypt - hostPath: - path: /shared/letsencrypt - type: Directory - - name: acme-challenge - hostPath: - path: /shared/acme-challenge - type: Directory - name: www hostPath: path: /shared/www @@ -108,22 +87,10 @@ spec: - name: API_URI value: "/ChangeMeToSomethingHardToGuess" volumeMounts: - - name: confs - mountPath: /etc/nginx - name: letsencrypt mountPath: /etc/letsencrypt - - name: acme-challenge - mountPath: /acme-challenge volumes: - - name: confs - hostPath: - path: /shared/confs - type: Directory - name: letsencrypt hostPath: path: /shared/letsencrypt type: Directory - - name: acme-challenge - hostPath: - path: /shared/acme-challenge - type: Directory diff --git a/helpers/swarm.yml b/helpers/swarm.yml index 46f8ca7..4ae1b3a 100644 --- a/helpers/swarm.yml +++ b/helpers/swarm.yml @@ -14,10 +14,7 @@ services: mode: host protocol: tcp volumes: - - /shared/confs:/etc/nginx:ro - /shared/www:/www:ro - - /shared/letsencrypt:/etc/letsencrypt:ro - - /shared/acme-challenge:/acme-challenge:ro environment: - SWARM_MODE=yes - USE_API=yes @@ -41,9 +38,7 @@ services: image: bunkerity/bunkerized-nginx-autoconf volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - - /shared/confs:/etc/nginx - /shared/letsencrypt:/etc/letsencrypt - - /shared/acme-challenge:/acme-challenge environment: - SWARM_MODE=yes - API_URI=/ChangeMeToSomethingHardToGuess # must match API_URI from nginx diff --git a/jobs/CertbotNew.py b/jobs/CertbotNew.py index 4576eed..4e42ceb 100644 --- a/jobs/CertbotNew.py +++ b/jobs/CertbotNew.py @@ -6,7 +6,7 @@ class CertbotNew(Job) : def __init__(self, redis_host=None, copy_cache=False, domain="", email="", staging=False) : name = "certbot-new" - data = ["certbot", "certonly", "--webroot", "-w", "/opt/bunkerized-nginx/acme-challenge", "-n", "-d", domain, "--email", email, "--agree-tos"] + data = ["certbot", "certonly", "--manual", "--preferred-challenges=http", "--manual-auth-hook", "/opt/bunkerized-nginx/jobs/certbot-auth.sh", "--manual-cleanup-hook", "/opt/bunkerized-nginx/jobs/certbot-cleanup.sh", "-n", "-d", domain, "--email", email, "--agree-tos"] if staging : data.append("--staging") type = "exec" diff --git a/jobs/certbot-auth.sh b/jobs/certbot-auth.sh new file mode 100644 index 0000000..72051a9 --- /dev/null +++ b/jobs/certbot-auth.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo $CERTBOT_VALIDATION > /opt/bunkerized-nginx/acme-challenge/.well-known/acme-challenge/$CERTBOT_TOKEN diff --git a/jobs/certbot-cleanup.sh b/jobs/certbot-cleanup.sh new file mode 100644 index 0000000..a797ee0 --- /dev/null +++ b/jobs/certbot-cleanup.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +rm -f /opt/bunkerized-nginx/acme-challenge/.well-known/acme-challenge/$CERTBOT_TOKEN diff --git a/lua/api.lua b/lua/api.lua index 56d96e8..c3d0636 100644 --- a/lua/api.lua +++ b/lua/api.lua @@ -41,6 +41,13 @@ api_list["^/letsencrypt$"] = function () return M.extract_file("/tmp/letsencrypt.tar.gz", "/etc/letsencrypt/") end +api_list["^/acme$"] = function () + if not M.save_file("/tmp/acme.tar.gz") then + return false + end + return M.extract_file("/tmp/acme.tar.gz", "/acme-challenge") +end + api_list["^/http$"] = function () if not M.save_file("/tmp/http.tar.gz") then return false @@ -75,7 +82,7 @@ function M.save_file (name) return false end form:set_timeout(1000) - file = io.open(name, "a") + file = io.open(name, "w") while true do local typ, res, err = form:read() if not typ then @@ -89,6 +96,7 @@ function M.save_file (name) file:write(res) end end + file:flush() file:close() return true end