diff --git a/.github/workflows/build-bunkerized-nginx-autoconf.yml b/.github/workflows/build-bunkerized-nginx-autoconf.yml index fb6ae55..262819b 100644 --- a/.github/workflows/build-bunkerized-nginx-autoconf.yml +++ b/.github/workflows/build-bunkerized-nginx-autoconf.yml @@ -20,7 +20,6 @@ jobs: - name: Run Trivy security scanner uses: aquasecurity/trivy-action@master with: - token: ${{ secrets.GITHUB_TOKEN }} image-ref: 'bunkerized-nginx-autoconf' format: 'table' exit-code: '1' diff --git a/.github/workflows/build-bunkerized-nginx-ui.yml b/.github/workflows/build-bunkerized-nginx-ui.yml index fe7208a..987e63d 100644 --- a/.github/workflows/build-bunkerized-nginx-ui.yml +++ b/.github/workflows/build-bunkerized-nginx-ui.yml @@ -20,7 +20,6 @@ jobs: - name: Run Trivy security scanner uses: aquasecurity/trivy-action@master with: - token: ${{ secrets.GITHUB_TOKEN }} image-ref: 'bunkerized-nginx-ui' format: 'table' exit-code: '1' diff --git a/.github/workflows/build-bunkerized-nginx.yml b/.github/workflows/build-bunkerized-nginx.yml index 64fa403..7f570c5 100644 --- a/.github/workflows/build-bunkerized-nginx.yml +++ b/.github/workflows/build-bunkerized-nginx.yml @@ -20,7 +20,6 @@ jobs: - name: Run Trivy security scanner uses: aquasecurity/trivy-action@master with: - token: ${{ secrets.GITHUB_TOKEN }} image-ref: 'bunkerized-nginx' format: 'table' exit-code: '1' diff --git a/autoconf/Dockerfile b/autoconf/Dockerfile index 0b42501..085cfdf 100644 --- a/autoconf/Dockerfile +++ b/autoconf/Dockerfile @@ -11,7 +11,7 @@ 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 && \ +RUN apk add --no-cache py3-pip bash certbot curl openssl socat && \ pip3 install -r /opt/bunkerized-nginx/gen/requirements.txt && \ pip3 install -r /opt/bunkerized-nginx/entrypoint/requirements.txt && \ pip3 install -r /opt/bunkerized-nginx/jobs/requirements.txt @@ -24,4 +24,6 @@ RUN chmod +x /tmp/prepare.sh && \ # Fix CVE-2021-36159 RUN apk add "apk-tools>=2.12.6-r0" +#VOLUME /http-confs /server-confs /modsec-confs /modsec-crs-confs /cache /etc/letsencrypt /acme-challenge + ENTRYPOINT ["/opt/bunkerized-nginx/entrypoint/entrypoint.sh"] diff --git a/autoconf/prepare.sh b/autoconf/prepare.sh index 692b41f..3ded0c1 100644 --- a/autoconf/prepare.sh +++ b/autoconf/prepare.sh @@ -29,6 +29,11 @@ mkdir /var/log/letsencrypt chown nginx:nginx /var/log/letsencrypt chmod 770 /var/log/letsencrypt +# prepare /etc/nginx +mkdir /etc/nginx +chown root:nginx /etc/nginx +chmod 770 /etc/nginx + # prepare /etc/letsencrypt mkdir /etc/letsencrypt chown root:nginx /etc/letsencrypt @@ -51,6 +56,18 @@ mkdir /acme-challenge chown root:nginx /acme-challenge chmod 770 /acme-challenge +# prepare /http-confs +ln -s /http-confs /opt/bunkerized-nginx/http-confs +mkdir /http-confs +chown root:nginx /http-confs +chmod 770 /http-confs + +# prepare /server-confs +ln -s /server-confs /opt/bunkerized-nginx/server-confs +mkdir /server-confs +chown root:nginx /server-confs +chmod 770 /server-confs + # prepare /modsec-confs ln -s /modsec-confs /opt/bunkerized-nginx/modsec-confs mkdir /modsec-confs diff --git a/autoconf/src/Config.py b/autoconf/src/Config.py index 733a22c..01ed19d 100644 --- a/autoconf/src/Config.py +++ b/autoconf/src/Config.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -import subprocess, shutil, os, traceback, requests, time, dns.resolver +import subprocess, shutil, os, traceback, requests, time, dns.resolver, io, tarfile import Controller @@ -79,24 +79,25 @@ class Config : 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 + 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) : + def stop_temp(self, instances) : + return self.__api_call(instances, "/stop-temp") + + def __tarball(self, path) : file = io.BytesIO() with tarfile.open(mode="w:gz", fileobj=file) as tar : tar.add(path, arcname=".") + file.seek(0, 0) return file def __ping(self, instances) : @@ -178,7 +179,8 @@ class Config : if file == None : req = requests.post(url) else : - req = requests.post(url, {'file': file}) + file.seek(0, 0) + req = requests.post(url, files={'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 801719b..70b2358 100644 --- a/autoconf/src/Controller.py +++ b/autoconf/src/Controller.py @@ -1,3 +1,4 @@ +import traceback from abc import ABC, abstractmethod from enum import Enum @@ -55,6 +56,13 @@ class Controller(ABC) : def _send(self, instances) : try : ret = self._config.send(instances) - except : + except Exception as e : + ret = False + return ret + + def _stop_temp(self, instances) : + try : + ret = self._config.stop_temp(instances) + except Exception as e : ret = False return ret diff --git a/autoconf/src/IngressController.py b/autoconf/src/IngressController.py index 8ae73bf..b8892f3 100644 --- a/autoconf/src/IngressController.py +++ b/autoconf/src/IngressController.py @@ -138,6 +138,9 @@ class IngressController(Controller.Controller) : def send(self) : return self._send(self.__get_services(autoconf=True)) + def stop_temp(self) : + return self._stop_temp(self.__get_services(autoconf=True)) + def wait(self) : self.lock.acquire() try : @@ -146,20 +149,28 @@ class IngressController(Controller.Controller) : while len(pods) == 0 : time.sleep(1) pods = self.__get_pods() - # Wait for at least one bunkerized-nginx service services = self.__get_services(autoconf=True) while len(services) == 0 : time.sleep(1) services = self.__get_services(autoconf=True) - # Generate first config env = self.get_env() if not self.gen_conf(env) : self.lock.release() return False, env - + # Send the config + if not self.send() : + self.lock.release() + return False, env + # Stop the temporary server + if not self.stop_temp() : + self.lock.release() + return False, env # Wait for bunkerized-nginx + if not self._config.wait(instances) : + self.lock.release() + return False, env self.lock.release() return self._config.wait(services), env except : diff --git a/autoconf/src/ReloadServer.py b/autoconf/src/ReloadServer.py index 2415518..98c78a3 100644 --- a/autoconf/src/ReloadServer.py +++ b/autoconf/src/ReloadServer.py @@ -20,6 +20,12 @@ class ReloadServerHandler(socketserver.StreamRequestHandler): self.server.controller.lock.release() locked = False self.request.sendall(b"ok") + elif data == b"acme" : + ret = self.server.controller.send() + if ret : + self.request.sendall(b"ok") + else : + self.request.sendall(b"ko") elif data == b"reload" : ret = self.server.controller.reload() if ret : diff --git a/autoconf/src/SwarmController.py b/autoconf/src/SwarmController.py index cb2a29a..045e6f1 100644 --- a/autoconf/src/SwarmController.py +++ b/autoconf/src/SwarmController.py @@ -64,6 +64,9 @@ class SwarmController(Controller.Controller) : def send(self) : return self._send(self.__get_instances()) + def stop_temp(self) : + return self._stop_temp(self.__get_instances()) + def wait(self) : self.lock.acquire() try : @@ -72,14 +75,29 @@ class SwarmController(Controller.Controller) : while len(instances) == 0 : time.sleep(1) instances = self.__get_instances() + # Wait for temporary bunkerized-nginx + if not self._config.wait(instances) : + self.lock.release() + return False, env # Generate first config env = self.get_env() if not self.gen_conf(env) : self.lock.release() return False, env - # Wait for nginx + # Send the config + if not self.send() : + self.lock.release() + return False, env + # Stop the temporary server + if not self.stop_temp() : + self.lock.release() + return False, env + # Wait for bunkerized-nginx + if not self._config.wait(instances) : + self.lock.release() + return False, env self.lock.release() - return self._config.wait(instances), env + return True, env except : pass self.lock.release() diff --git a/confs/global/api-temp.conf b/confs/global/api-temp.conf index 42b4d76..379299f 100644 --- a/confs/global/api-temp.conf +++ b/confs/global/api-temp.conf @@ -1,6 +1,4 @@ -location ~ ^%API_URI%/ping { - return 444; -} +client_max_body_size 1G; location ~ %API_URI% { @@ -15,10 +13,10 @@ rewrite_by_lua_block { ngx.header.content_type = 'text/plain' if api.do_api_call(api_uri) then logger.log(ngx.NOTICE, "API", "API call " .. ngx.var.request_uri .. " successfull from " .. ngx.var.remote_addr) - ngx.say("ok") + ngx.print("ok") else logger.log(ngx.WARN, "API", "API call " .. ngx.var.request_uri .. " failed from " .. ngx.var.remote_addr) - ngx.say("ko") + ngx.print("ko") end ngx.exit(ngx.HTTP_OK) @@ -29,3 +27,4 @@ rewrite_by_lua_block { } } + diff --git a/confs/global/api.conf b/confs/global/api.conf index 8780549..cc1fdfe 100644 --- a/confs/global/api.conf +++ b/confs/global/api.conf @@ -1,4 +1,5 @@ # todo : if api_uri == "random" +client_max_body_size 1G; rewrite_by_lua_block { local api = require "api" diff --git a/confs/global/nginx-temp.conf b/confs/global/nginx-temp.conf index 09ed438..3883cb2 100644 --- a/confs/global/nginx-temp.conf +++ b/confs/global/nginx-temp.conf @@ -1,6 +1,6 @@ load_module /usr/lib/nginx/modules/ngx_http_lua_module.so; -daemon on; +#daemon on; pid /tmp/nginx-temp.pid; diff --git a/entrypoint/entrypoint.sh b/entrypoint/entrypoint.sh index 8bffa86..cd1b2c9 100644 --- a/entrypoint/entrypoint.sh +++ b/entrypoint/entrypoint.sh @@ -49,7 +49,7 @@ if [ ! -f "/etc/nginx/global.env" ] ; then exit 1 fi - # start temp nginx to solve Let's Encrypt challenges if needed + # start temp nginx to solve Let's Encrypt challenges if needed and serve API /opt/bunkerized-nginx/entrypoint/nginx-temp.sh # only do config if we are not in swarm/kubernetes mode @@ -75,15 +75,16 @@ else fi # start crond -crond - -# wait until config has been generated if we are in swarm mode -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 +if [ "$SWARM_MODE" != "yes" ] && [ "$KUBERNETES_MODE" != "yes" ] ; then + crond fi +# wait until config has been generated if we are in swarm mode +#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 +#fi # stop temp config if needed if [ -f "/tmp/nginx-temp.pid" ] ; then diff --git a/entrypoint/nginx-temp.sh b/entrypoint/nginx-temp.sh index 1f818da..d1fe246 100644 --- a/entrypoint/nginx-temp.sh +++ b/entrypoint/nginx-temp.sh @@ -7,7 +7,7 @@ if [ "$(has_value AUTO_LETS_ENCRYPT yes)" != "" ] || [ "$SWARM_MODE" = "yes" ] || [ "$AUTO_LETS_ENCRYPT" = "yes" ] || [ "$KUBERNETES_MODE" = "yes" ] ; then cp /opt/bunkerized-nginx/confs/global/nginx-temp.conf /tmp/nginx-temp.conf cp /opt/bunkerized-nginx/confs/global/api-temp.conf /tmp/api.conf - if [ "$SWARM_MODE" = "yes" ] ; then + if [ "$SWARM_MODE" = "yes" ] || [ "$KUBERNETES_MODE" = "yes" ] ; then replace_in_file "/tmp/nginx-temp.conf" "%USE_API%" "include /tmp/api.conf;" replace_in_file "/tmp/api.conf" "%API_URI%" "$API_URI" API_WHITELIST_IP="${API_WHITELIST_IP-192.168.0.0/16 172.16.0.0/12 10.0.0.0/8}" @@ -18,10 +18,15 @@ if [ "$(has_value AUTO_LETS_ENCRYPT yes)" != "" ] || [ "$SWARM_MODE" = "yes" ] | fi HTTP_PORT="${HTTP_PORT-8080}" replace_in_file "/tmp/nginx-temp.conf" "%HTTP_PORT%" "$HTTP_PORT" - nginx -c /tmp/nginx-temp.conf - if [ "$?" -eq 0 ] ; then - echo "[*] Successfully started temp nginx" + if [ "$SWARM_MODE" = "yes" ] || [ "$KUBERNETES_MODE" = "yes" ] ; then + log "nginx-temp" "INFO" "start temporary nginx server and wait for autoconf events..." + nginx -c /tmp/nginx-temp.conf -g 'daemon off;' else - echo "[!] Can't start temp nginx" + nginx -c /tmp/nginx-temp.conf -g 'daemon on;' + if [ "$?" -eq 0 ] ; then + log "nginx-temp" "INFO" "successfully started temp nginx" + else + log "nginx-temp" "ERROR" "can't start temp nginx" + fi fi fi diff --git a/helpers/install.sh b/helpers/install.sh index 69b51ef..9c0e6aa 100755 --- a/helpers/install.sh +++ b/helpers/install.sh @@ -610,6 +610,12 @@ git_secure_clone https://github.com/openresty/lua-resty-redis.git 91585affcd9a8d echo "[*] Install lua-resty-redis" CHANGE_DIR="/tmp/bunkerized-nginx/lua-resty-redis" do_and_check_cmd make PREFIX=/opt/bunkerized-nginx/deps LUA_LIB_DIR=/opt/bunkerized-nginx/deps/lib/lua install +# Download and install lua-resty-upload +echo "[*] Clone openresty/lua-resty-upload" +git_secure_clone https://github.com/openresty/lua-resty-upload.git 7baca92c7e741979ae5857989bbf6cc0402d6126 +echo "[*] Install lua-resty-upload" +CHANGE_DIR="/tmp/bunkerized-nginx/lua-resty-upload" do_and_check_cmd make PREFIX=/opt/bunkerized-nginx/deps LUA_LIB_DIR=/opt/bunkerized-nginx/deps/lib/lua install + # Download nginx and decompress sources echo "[*] Download nginx-${NGINX_VERSION}.tar.gz" do_and_check_cmd wget -O "/tmp/bunkerized-nginx/nginx-${NGINX_VERSION}.tar.gz" "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" diff --git a/jobs/certbot-auth.sh b/jobs/certbot-auth.sh index 72051a9..539ac1b 100644 --- a/jobs/certbot-auth.sh +++ b/jobs/certbot-auth.sh @@ -1,3 +1,9 @@ #!/bin/bash +. /opt/bunkerized-nginx/entrypoint/utils.sh + echo $CERTBOT_VALIDATION > /opt/bunkerized-nginx/acme-challenge/.well-known/acme-challenge/$CERTBOT_TOKEN + +if [ -S "/tmp/autoconf.sock" ] ; then + echo -e "lock\nacme\nunlock" | socat UNIX-CONNECT:/tmp/autoconf.sock - +fi diff --git a/lua/api.lua b/lua/api.lua index c3d0636..75c8490 100644 --- a/lua/api.lua +++ b/lua/api.lua @@ -2,6 +2,7 @@ local M = {} local api_list = {} local iputils = require "resty.iputils" local upload = require "resty.upload" +local logger = require "logger" api_list["^/ping$"] = function () return true @@ -27,6 +28,10 @@ api_list["^/stop$"] = function () return os.execute("/usr/sbin/nginx -s quit") == 0 end +api_list["^/stop%-temp$"] = function () + return os.execute("/usr/sbin/nginx -c /tmp/nginx-temp.conf -s stop") == 0 +end + api_list["^/conf$"] = function () if not M.save_file("/tmp/conf.tar.gz") then return false @@ -69,7 +74,7 @@ api_list["^/modsec$"] = function () return M.extract_file("/tmp/modsec.tar.gz", "/modsec-confs/") end -api_list["^/modsec-crs$"] = function () +api_list["^/modsec%-crs$"] = function () if not M.save_file("/tmp/modsec-crs.tar.gz") then return false end @@ -79,6 +84,7 @@ end function M.save_file (name) local form, err = upload:new(4096) if not form then + logger.log(ngx.ERR, "API", err) return false end form:set_timeout(1000) @@ -87,6 +93,7 @@ function M.save_file (name) local typ, res, err = form:read() if not typ then file:close() + logger.log(ngx.ERR, "API", "not typ") return false end if typ == "eof" then @@ -102,7 +109,7 @@ function M.save_file (name) end function M.extract_file(archive, destination) - return os.execute("tar xzf " .. archive .. " -C " .. destination) + return os.execute("tar xzf " .. archive .. " -C " .. destination) == 0 end function M.is_api_call (api_uri, api_whitelist_ip)