diff --git a/src/autoconf/Dockerfile b/src/autoconf/Dockerfile index 1880a1432..36cb44900 100644 --- a/src/autoconf/Dockerfile +++ b/src/autoconf/Dockerfile @@ -8,9 +8,6 @@ RUN mkdir -p /usr/share/bunkerweb/deps && \ cat /tmp/req/requirements.txt /tmp/req/requirements.txt.1 > /usr/share/bunkerweb/deps/requirements.txt && \ rm -rf /tmp/req -# Update apk -RUN apk update - # Install python dependencies RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev diff --git a/src/bw/Dockerfile b/src/bw/Dockerfile index 76891a7db..5e412bbbe 100644 --- a/src/bw/Dockerfile +++ b/src/bw/Dockerfile @@ -3,9 +3,6 @@ FROM nginx:1.24.0-alpine AS builder # Copy dependencies sources folder COPY src/deps /tmp/bunkerweb/deps -# Update apk -RUN apk update - # Compile and install dependencies RUN apk add --no-cache --virtual .build-deps bash autoconf libtool automake geoip-dev g++ gcc curl-dev libxml2-dev pcre-dev make linux-headers musl-dev gd-dev gnupg brotli-dev openssl-dev patch readline-dev && \ mkdir -p /usr/share/bunkerweb/deps && \ diff --git a/src/common/core/headers/confs/server-http/cookies.conf b/src/common/core/headers/confs/server-http/cookies.conf new file mode 100644 index 000000000..f0efd7072 --- /dev/null +++ b/src/common/core/headers/confs/server-http/cookies.conf @@ -0,0 +1,9 @@ +{% for k, v in all.items() %} + {% if k.startswith("COOKIE_FLAGS") and v != "" +%} + {% if COOKIE_AUTO_SECURE_FLAG == "yes" and (AUTO_LETS_ENCRYPT == "yes" or USE_CUSTOM_SSL == "yes" or GENERATE_SELF_SIGNED_SSL == "yes") +%} + set_cookie_flag {{ v }} secure; + {% else +%} + set_cookie_flag {{ v }}; + {% endif +%} + {% endif +%} +{% endfor %} \ No newline at end of file diff --git a/src/common/core/headers/confs/server-http/custom-headers.conf b/src/common/core/headers/confs/server-http/custom-headers.conf deleted file mode 100644 index ea3a1c045..000000000 --- a/src/common/core/headers/confs/server-http/custom-headers.conf +++ /dev/null @@ -1,5 +0,0 @@ -{% for k, v in all.items() +%} - {% if k.startswith("CUSTOM_HEADER") and v != "" +%} -more_set_headers "{{ v }}"; - {% endif %} -{% endfor %} \ No newline at end of file diff --git a/src/common/core/headers/confs/server-http/remove-headers.conf b/src/common/core/headers/confs/server-http/remove-headers.conf deleted file mode 100644 index 5eed2d974..000000000 --- a/src/common/core/headers/confs/server-http/remove-headers.conf +++ /dev/null @@ -1,5 +0,0 @@ -{% if REMOVE_HEADERS != "" %} - {% for header in REMOVE_HEADERS.split(" ") +%} -more_clear_headers '{{ header }}'; - {% endfor %} -{% endif %} \ No newline at end of file diff --git a/src/common/core/headers/confs/server-http/security-headers.conf b/src/common/core/headers/confs/server-http/security-headers.conf deleted file mode 100644 index 2e4c4a285..000000000 --- a/src/common/core/headers/confs/server-http/security-headers.conf +++ /dev/null @@ -1,41 +0,0 @@ -{% if STRICT_TRANSPORT_SECURITY != "" and (AUTO_LETS_ENCRYPT == "yes" or USE_CUSTOM_SSL == "yes" or GENERATE_SELF_SIGNED_SSL == "yes") +%} -more_set_headers "Strict-Transport-Security: {{ STRICT_TRANSPORT_SECURITY }}"; -{% endif +%} - -{% for k, v in all.items() %} - {% if k.startswith("COOKIE_FLAGS") and v != "" +%} - {% if COOKIE_AUTO_SECURE_FLAG == "yes" and (AUTO_LETS_ENCRYPT == "yes" or USE_CUSTOM_SSL == "yes" or GENERATE_SELF_SIGNED_SSL == "yes") +%} - set_cookie_flag {{ v }} secure; - {% else +%} - set_cookie_flag {{ v }}; - {% endif +%} - {% endif +%} -{% endfor %} - -{% if CONTENT_SECURITY_POLICY != "" +%} -more_set_headers "Content-Security-Policy: {{ CONTENT_SECURITY_POLICY }}"; -{% endif +%} - -{% if REFERRER_POLICY != "" +%} -more_set_headers "Referrer-Policy: {{ REFERRER_POLICY }}"; -{% endif +%} - -{% if PERMISSIONS_POLICY != "" +%} -more_set_headers "Permissions-Policy: {{ PERMISSIONS_POLICY }}"; -{% endif +%} - -{% if FEATURE_POLICY != "" +%} -more_set_headers "Feature-Policy: {{ FEATURE_POLICY }}"; -{% endif +%} - -{% if X_FRAME_OPTIONS != "" +%} -more_set_headers "X-Frame-Options: {{ X_FRAME_OPTIONS }}"; -{% endif +%} - -{% if X_CONTENT_TYPE_OPTIONS != "" +%} -more_set_headers "X-Content-Type-Options: {{ X_CONTENT_TYPE_OPTIONS }}"; -{% endif +%} - -{% if X_XSS_PROTECTION != "" +%} -more_set_headers "X-XSS-Protection: {{ X_XSS_PROTECTION }}"; -{% endif +%} \ No newline at end of file diff --git a/src/common/core/headers/headers.lua b/src/common/core/headers/headers.lua new file mode 100644 index 000000000..bc855da70 --- /dev/null +++ b/src/common/core/headers/headers.lua @@ -0,0 +1,71 @@ +local class = require "middleclass" +local plugin = require "bunkerweb.plugin" +local utils = require "bunkerweb.utils" + +local headers = class("headers", plugin) + +function headers:initialize() + -- Call parent initialize + plugin.initialize(self, "headers") + self.all_headers = { + ["STRICT_TRANSPORT_SECURITY"] = "Strict-Transport-Security", + ["CONTENT_SECURITY_POLICY"] = "Content-Security-Policy", + ["REFERRER_POLICY"] = "Referrer-Policy", + ["PERMISSIONS_POLICY"] = "Permissions-Policy", + ["FEATURE_POLICY"] = "Feature-Policy", + ["X_FRAME_OPTIONS"] = "X-Frame-Options", + ["X_CONTENT_TYPE_OPTIONS"] = "X-Content-Type-Options", + ["X_XSS_PROTECTION"] = "X-XSS-Protection" + } +end + +function headers:header() + -- Override upstream headers if needed + local ssl = utils.get_variable("AUTO_LETS_ENCRYPT") == "yes" or utils.get_variable("USE_CUSTOM_SSL") == "yes" or utils.get_variable("GENERATE_SELF_SIGNED_SSL") == "yes" + for variable, header in pairs(self.all_headers) do + if ngx.header[header] == nil or self.variables[variable] and self.variables["KEEP_UPSTREAM_HEADERS"] ~= "*" and utils.regex_match(self.variables["KEEP_UPSTREAM_HEADERS"], "(^| )" .. header .. "($| )") == nil then + if header ~= "Strict-Transport-Security" or ssl then + ngx.header[header] = self.variables[variable] + end + end + end + -- Get variables + local variables, err = utils.get_multiple_variables({ "CUSTOM_HEADER" }) + if variables == nil then + return self:ret(false, err) + end + -- Add custom headers + for srv, vars in pairs(variables) do + if srv == ngx.ctx.bw.server_name then + for var, value in pairs(vars) do + if utils.regex_match(var, "CUSTOM_HEADER") and value then + local m = utils.regex_match(value, "([\\w-]+): ([^,]+)") + if m then + ngx.header[m[1]] = m[2] + end + end + end + end + end + -- Remove headers + if self.variables["REMOVE_HEADERS"] ~= "" then + local iterator, err = ngx.re.gmatch(self.variables["REMOVE_HEADERS"], "([\\w-]+)") + if not iterator then + return self:ret(false, "Error while matching remove headers: " .. err) + end + while true do + local m, err = iterator() + if err then + return self:ret(false, "Error while matching remove headers: " .. err) + end + if not m then + -- No more remove headers + break + end + ngx.header[m[1]] = nil + end + end + return self:ret(true, "Edited headers for request") +end + +return headers diff --git a/src/common/core/headers/plugin.json b/src/common/core/headers/plugin.json index 5a59c474b..1ec999407 100644 --- a/src/common/core/headers/plugin.json +++ b/src/common/core/headers/plugin.json @@ -11,7 +11,7 @@ "help": "Custom header to add (HeaderName: HeaderValue).", "id": "custom-header", "label": "Custom header (HeaderName: HeaderValue)", - "regex": "^([\\w-]+: .+)?$", + "regex": "^([\\w-]+: [^,]+)?$", "type": "text", "multiple": "custom-headers" }, @@ -24,6 +24,15 @@ "regex": "^(?! )( ?[\\w-]+)*$", "type": "text" }, + "KEEP_UPSTREAM_HEADERS": { + "context": "multisite", + "default": "*", + "help": "Headers to keep from upstream (Header1 Header2 Header3 ... or * for all).", + "id": "keep-upstream-headers", + "label": "Keep upstream headers", + "regex": "^((?! )( ?[\\w-]+)+|\\*)?$", + "type": "text" + }, "STRICT_TRANSPORT_SECURITY": { "context": "multisite", "default": "max-age=31536000", diff --git a/src/scheduler/Dockerfile b/src/scheduler/Dockerfile index 05af7131d..9a57cd60b 100644 --- a/src/scheduler/Dockerfile +++ b/src/scheduler/Dockerfile @@ -9,9 +9,6 @@ RUN mkdir -p /usr/share/bunkerweb/deps && \ cat /tmp/req/requirements.txt /tmp/req/requirements.txt.1 /tmp/req/requirements.txt.2 > /usr/share/bunkerweb/deps/requirements.txt && \ rm -rf /tmp/req -# Update apk -RUN apk update - # Install python dependencies RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev diff --git a/src/ui/Dockerfile b/src/ui/Dockerfile index dced23cb9..27587fefb 100755 --- a/src/ui/Dockerfile +++ b/src/ui/Dockerfile @@ -9,9 +9,6 @@ RUN mkdir -p /usr/share/bunkerweb/deps && \ cat /tmp/req/requirements.txt /tmp/req/requirements.txt.1 /tmp/req/requirements.txt.2 > /usr/share/bunkerweb/deps/requirements.txt && \ rm -rf /tmp/req -# Update apk -RUN apk update - # Install python dependencies RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev file make diff --git a/src/ui/main.py b/src/ui/main.py index 435f76e41..24f7b633e 100755 --- a/src/ui/main.py +++ b/src/ui/main.py @@ -247,10 +247,6 @@ def update_config(): server_name = service.get("SERVER_NAME", {"value": None})["value"] endpoint = service.get("REVERSE_PROXY_URL", {"value": "/"})["value"] - logger.warning(service.get("AUTO_LETS_ENCRYPT", {"value": "no"})) - logger.warning(service.get("GENERATE_SELF_SIGNED_SSL", {"value": "no"})) - logger.warning(service.get("USE_CUSTOM_SSL", {"value": "no"})) - if any( [ service.get("AUTO_LETS_ENCRYPT", {"value": "no"})["value"] == "yes", @@ -282,9 +278,9 @@ def update_config(): if SCRIPT_NAME != getenv("SCRIPT_NAME"): environ["SCRIPT_NAME"] = f"/{basename(ABSOLUTE_URI[:-1])}" - logger.info(f"The script name is now {environ['SCRIPT_NAME']}") + logger.info(f"The SCRIPT_NAME is now {environ['SCRIPT_NAME']}") else: - logger.info(f"The script name is still {environ['SCRIPT_NAME']}") + logger.info(f"The SCRIPT_NAME is still {environ['SCRIPT_NAME']}") def check_config_changes(): @@ -387,6 +383,15 @@ def manage_bunkerweb(method: str, *args, operation: str = "reloads"): app.config["RELOADING"] = False +@app.after_request +def set_csp_header(response): + """Set the Content-Security-Policy header to prevent XSS attacks.""" + response.headers[ + "Content-Security-Policy" + ] = "object-src 'none'; frame-ancestors 'self';" + return response + + @login_manager.user_loader def load_user(user_id): return User(user_id, vars["ADMIN_PASSWORD"]) diff --git a/tests/core/badbehavior/main.py b/tests/core/badbehavior/main.py index 4e3c2cb88..36027b7e0 100644 --- a/tests/core/badbehavior/main.py +++ b/tests/core/badbehavior/main.py @@ -68,10 +68,10 @@ try: exit(1) elif bad_behavior_ban_time != "86400": print( - "ℹ️ Sleeping for 7s to wait if Bad Behavior's ban time changed ...", + "ℹ️ Sleeping for 65s to wait if Bad Behavior's ban time changed ...", flush=True, ) - sleep(7) + sleep(65) status_code = get( f"http://www.example.com", @@ -86,11 +86,11 @@ try: exit(1) elif bad_behavior_count_time != "60": print( - "ℹ️ Sleeping for 7s to wait if Bad Behavior's count time changed ...", + "ℹ️ Sleeping for 35s to wait if Bad Behavior's count time changed ...", flush=True, ) current_time = datetime.now().timestamp() - sleep(7) + sleep(35) print( "ℹ️ Checking BunkerWeb's logs to see if Bad Behavior's count time changed ...", diff --git a/tests/core/badbehavior/test.sh b/tests/core/badbehavior/test.sh index b2c321df9..00e7449e8 100755 --- a/tests/core/badbehavior/test.sh +++ b/tests/core/badbehavior/test.sh @@ -21,9 +21,9 @@ cleanup_stack () { if [[ $end -eq 1 || $exit_code = 1 ]] || [[ $end -eq 0 && $exit_code = 0 ]] && [ $manual = 0 ] ; then find . -type f -name 'docker-compose.*' -exec sed -i 's@USE_BAD_BEHAVIOR: "no"@USE_BAD_BEHAVIOR: "yes"@' {} \; find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_STATUS_CODES: "400 401 404 405 429 444"@BAD_BEHAVIOR_STATUS_CODES: "400 401 403 404 405 429 444"@' {} \; - find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_BAN_TIME: "5"@BAD_BEHAVIOR_BAN_TIME: "86400"@' {} \; + find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_BAN_TIME: "60"@BAD_BEHAVIOR_BAN_TIME: "86400"@' {} \; find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_THRESHOLD: "20"@BAD_BEHAVIOR_THRESHOLD: "10"@' {} \; - find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_COUNT_TIME: "5"@BAD_BEHAVIOR_COUNT_TIME: "60"@' {} \; + find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_COUNT_TIME: "30"@BAD_BEHAVIOR_COUNT_TIME: "60"@' {} \; if [[ $end -eq 1 && $exit_code = 0 ]] ; then return fi @@ -56,17 +56,17 @@ do find . -type f -name 'docker-compose.*' -exec sed -i 's@USE_BAD_BEHAVIOR: "no"@USE_BAD_BEHAVIOR: "yes"@' {} \; find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_STATUS_CODES: "400 401 403 404 405 429 444"@BAD_BEHAVIOR_STATUS_CODES: "400 401 404 405 429 444"@' {} \; elif [ "$test" = "ban_time" ] ; then - echo "📟 Running tests with badbehavior's ban time changed to 5 seconds ..." + echo "📟 Running tests with badbehavior's ban time changed to 60 seconds ..." find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_STATUS_CODES: "400 401 404 405 429 444"@BAD_BEHAVIOR_STATUS_CODES: "400 401 403 404 405 429 444"@' {} \; - find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_BAN_TIME: "86400"@BAD_BEHAVIOR_BAN_TIME: "5"@' {} \; + find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_BAN_TIME: "86400"@BAD_BEHAVIOR_BAN_TIME: "60"@' {} \; elif [ "$test" = "threshold" ] ; then echo "📟 Running tests with badbehavior's threshold set to 20 ..." - find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_BAN_TIME: "5"@BAD_BEHAVIOR_BAN_TIME: "86400"@' {} \; + find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_BAN_TIME: "60"@BAD_BEHAVIOR_BAN_TIME: "86400"@' {} \; find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_THRESHOLD: "10"@BAD_BEHAVIOR_THRESHOLD: "20"@' {} \; elif [ "$test" = "count_time" ] ; then - echo "📟 Running tests with badbehavior's count time set to 5 seconds ..." + echo "📟 Running tests with badbehavior's count time set to 30 seconds ..." find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_THRESHOLD: "20"@BAD_BEHAVIOR_THRESHOLD: "10"@' {} \; - find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_COUNT_TIME: "60"@BAD_BEHAVIOR_COUNT_TIME: "5"@' {} \; + find . -type f -name 'docker-compose.*' -exec sed -i 's@BAD_BEHAVIOR_COUNT_TIME: "60"@BAD_BEHAVIOR_COUNT_TIME: "30"@' {} \; fi echo "📟 Starting stack ..."