apply changes to current core

This commit is contained in:
florian 2023-06-09 10:41:12 +02:00
commit ce24a0482a
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
71 changed files with 766 additions and 470 deletions

View File

@ -63,6 +63,12 @@ STREAM support :x:
You can automatically remove verbose headers in the HTTP responses by using the `REMOVE_HEADERS` setting (default : `Server X-Powered-By X-AspNet-Version X-AspNetMvc-Version`).
#### Keep upstream headers
STREAM support :x:
You can automatically keep headers from upstream servers and prevent BunkerWeb from overriding them in the HTTP responses by using the `KEEP_UPSTREAM_HEADERS` setting (default : `Content-Security-Policy Permissions-Policy Feature-Policy X-Frame-Options`). A special value `*` is available to keep all headers. List of headers to keep must be separated with a space. Note that if the header is not present in the upstream response, it will be added by BunkerWeb.
#### Cookies
STREAM support :x:
@ -250,6 +256,7 @@ That kind of security is implemented but not enabled by default in BunkerWeb and
- **Captcha** : force the client to solve a classical captcha (no external dependencies)
- **hCaptcha** : force the client to solve a captcha from hCaptcha
- **reCAPTCHA** : force the client to get a minimum score with Google reCAPTCHA
- **Turnstile** : enforce rate limiting and access control for APIs and web applications using various mechanisms with Coudflare Turnstile
Here is the list of related settings :
@ -262,6 +269,8 @@ Here is the list of related settings :
|`ANTIBOT_RECAPTCHA_SECRET` | |multisite|no |Secret for reCAPTCHA challenge. |
|`ANTIBOT_HCAPTCHA_SITEKEY` | |multisite|no |Sitekey for hCaptcha challenge. |
|`ANTIBOT_HCAPTCHA_SECRET` | |multisite|no |Secret for hCaptcha challenge. |
|`ANTIBOT_TURNSTILE_SITEKEY`| |multisite|no |Sitekey for Turnstile challenge. |
|`ANTIBOT_TURNSTILE_SECRET` | |multisite|no |Secret for Turnstile challenge. |
|`ANTIBOT_TIME_RESOLVE` |`60` |multisite|no |Maximum time (in seconds) clients have to resolve the challenge. Once this time has passed, a new challenge will be generated.|
|`ANTIBOT_TIME_VALID` |`86400` |multisite|no |Maximum validity time of solved challenges. Once this time has passed, clients will need to resolve a new one. |

View File

@ -67,6 +67,8 @@ Bot detection by using a challenge.
|`ANTIBOT_RECAPTCHA_SECRET` | |multisite|no |Secret for reCAPTCHA challenge. |
|`ANTIBOT_HCAPTCHA_SITEKEY` | |multisite|no |Sitekey for hCaptcha challenge. |
|`ANTIBOT_HCAPTCHA_SECRET` | |multisite|no |Secret for hCaptcha challenge. |
|`ANTIBOT_TURNSTILE_SITEKEY`| |multisite|no |Sitekey for Turnstile challenge. |
|`ANTIBOT_TURNSTILE_SECRET` | |multisite|no |Secret for Turnstile challenge. |
|`ANTIBOT_TIME_RESOLVE` |`60` |multisite|no |Maximum time (in seconds) clients have to resolve the challenge. Once this time has passed, a new challenge will be generated.|
|`ANTIBOT_TIME_VALID` |`86400` |multisite|no |Maximum validity time of solved challenges. Once this time has passed, clients will need to resolve a new one. |
@ -292,6 +294,7 @@ Manage HTTP headers sent to clients.
|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|----------------------------------------------------------------------------------------------|
|`CUSTOM_HEADER` | |multisite|yes |Custom header to add (HeaderName: HeaderValue). |
|`REMOVE_HEADERS` |`Server X-Powered-By X-AspNet-Version X-AspNetMvc-Version` |multisite|no |Headers to remove (Header1 Header2 Header3 ...) |
|`KEEP_UPSTREAM_HEADERS` |`Content-Security-Policy Permissions-Policy Feature-Policy X-Frame-Options` |multisite|no |Headers to keep from upstream (Header1 Header2 Header3 ... or * for all). |
|`STRICT_TRANSPORT_SECURITY`|`max-age=31536000` |multisite|no |Value for the Strict-Transport-Security header. |
|`COOKIE_FLAGS` |`* HttpOnly SameSite=Lax` |multisite|yes |Cookie flags automatically added to all cookies (value accepted for nginx_cookie_flag_module).|
|`COOKIE_AUTO_SECURE_FLAG` |`yes` |multisite|no |Automatically add the Secure flag to all cookies. |
@ -517,18 +520,18 @@ STREAM support :warning:
Allow access based on internal and external IP/network/rDNS/ASN whitelists.
| Setting | Default | Context |Multiple| Description |
|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|----------------------------------------------------------------------------------|
|`USE_WHITELIST` |`yes` |multisite|no |Activate whitelist feature. |
|`WHITELIST_IP` |`20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247 54.208.102.37 107.21.1.8`|multisite|no |List of IP/network, separated with spaces, to put into the whitelist. |
|`WHITELIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to whitelist. |
|`WHITELIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS whitelist checks on global IP addresses. |
|`WHITELIST_RDNS` |`.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com` |multisite|no |List of reverse DNS suffixes, separated with spaces, to whitelist. |
|`WHITELIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist.|
|`WHITELIST_ASN` |`32934` |multisite|no |List of ASN numbers, separated with spaces, to whitelist. |
|`WHITELIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to whitelist. |
|`WHITELIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. |
| Setting | Default | Context |Multiple| Description |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|----------------------------------------------------------------------------------|
|`USE_WHITELIST` |`yes` |multisite|no |Activate whitelist feature. |
|`WHITELIST_IP` |`20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247` |multisite|no |List of IP/network, separated with spaces, to put into the whitelist. |
|`WHITELIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to whitelist. |
|`WHITELIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS whitelist checks on global IP addresses. |
|`WHITELIST_RDNS` |`.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com`|multisite|no |List of reverse DNS suffixes, separated with spaces, to whitelist. |
|`WHITELIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist.|
|`WHITELIST_ASN` |`32934` |multisite|no |List of ASN numbers, separated with spaces, to whitelist. |
|`WHITELIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to whitelist. |
|`WHITELIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. |

View File

@ -1,7 +1,7 @@
{
"name": "load-balancer",
"kinds": ["docker"],
"timeout": 60,
"timeout": 120,
"no_copy_container": true,
"tests": [
{

View File

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

View File

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

View File

@ -1,11 +1,11 @@
local class = require "middleclass"
local datastore = require "bunkerweb.datastore"
local utils = require "bunkerweb.utils"
local logger = require "bunkerweb.logger"
local logger = require "bunkerweb.logger"
local cjson = require "cjson"
local upload = require "resty.upload"
local rsignal = require "resty.signal"
local process = require "ngx.process"
local rsignal = require "resty.signal"
local process = require "ngx.process"
local api = class("api")

View File

@ -1,16 +1,16 @@
local mlcache = require "resty.mlcache"
local mlcache = require "resty.mlcache"
local clusterstore = require "bunkerweb.clusterstore"
local logger = require "bunkerweb.logger"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local cachestore = class("cachestore")
local logger = require "bunkerweb.logger"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local cachestore = class("cachestore")
-- Instantiate mlcache object at module level (which will be cached when running init phase)
-- TODO : custom settings
local shm = "cachestore"
local ipc_shm = "cachestore_ipc"
local shm_miss = "cachestore_miss"
local shm_locks = "cachestore_locks"
local shm = "cachestore"
local ipc_shm = "cachestore_ipc"
local shm_miss = "cachestore_miss"
local shm_locks = "cachestore_locks"
if not ngx.shared.cachestore then
shm = "cachestore_stream"
ipc_shm = "cachestore_ipc_stream"

View File

@ -77,7 +77,8 @@ function clusterstore:close()
if self.redis_client then
-- Equivalent to close but keep a pool of connections
if self.pool then
local ok, err = self.redis_client:set_keepalive(tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]), tonumber(self.variables["REDIS_KEEPALIVE_POOL"]))
local ok, err = self.redis_client:set_keepalive(tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]),
tonumber(self.variables["REDIS_KEEPALIVE_POOL"]))
self.redis_client = nil
if not ok then
require "bunkerweb.logger":new("clusterstore-close"):log(ngx.ERR, err)

View File

@ -1,18 +1,19 @@
local class = require "middleclass"
local logger = require "bunkerweb.logger"
local datastore = require "bunkerweb.datastore"
local cachestore = require "bunkerweb.cachestore"
local class = require "middleclass"
local logger = require "bunkerweb.logger"
local datastore = require "bunkerweb.datastore"
local cachestore = require "bunkerweb.cachestore"
local clusterstore = require "bunkerweb.clusterstore"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local plugin = class("plugin")
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local plugin = class("plugin")
function plugin:initialize(id, ctx)
-- Store common, values
self.id = id
local multisite = false
local current_phase = ngx.get_phase()
for i, check_phase in ipairs({ "set", "access", "content", "header_filter", "log", "preread", "log_stream", "log_default" }) do
for i, check_phase in ipairs({ "set", "access", "content", "header_filter", "log", "preread", "log_stream",
"log_default" }) do
if current_phase == check_phase then
multisite = true
break
@ -21,11 +22,11 @@ function plugin:initialize(id, ctx)
self.is_request = multisite
-- Store common objets
self.logger = logger:new(self.id)
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
if self.is_request then
-- Store ctx
self.ctx = ctx or ngx.ctx

View File

@ -1,16 +1,16 @@
local cdatastore = require "bunkerweb.datastore"
local mmdb = require "bunkerweb.mmdb"
local clogger = require "bunkerweb.logger"
local cdatastore = require "bunkerweb.datastore"
local mmdb = require "bunkerweb.mmdb"
local clogger = require "bunkerweb.logger"
local ipmatcher = require "resty.ipmatcher"
local resolver = require "resty.dns.resolver"
local session = require "resty.session"
local cjson = require "cjson"
local ipmatcher = require "resty.ipmatcher"
local resolver = require "resty.dns.resolver"
local session = require "resty.session"
local cjson = require "cjson"
local logger = clogger:new("UTILS")
local datastore = cdatastore:new()
local logger = clogger:new("UTILS")
local datastore = cdatastore:new()
local utils = {}
local utils = {}
math.randomseed(os.time())
@ -294,7 +294,7 @@ utils.get_resolvers = function()
return resolvers
end
utils.get_rdns = function(ip)
utils.get_rdns = function(ip)
-- Check cache
local cachestore = utils.new_cachestore()
local ok, value = cachestore:get("rdns_" .. ip)
@ -344,7 +344,7 @@ utils.get_rdns = function(ip)
return ptrs, ret_err
end
utils.get_ips = function(fqdn, ipv6)
utils.get_ips = function(fqdn, ipv6)
-- Check cache
local cachestore = utils.new_cachestore()
local ok, value = cachestore:get("dns_" .. fqdn)
@ -489,7 +489,7 @@ utils.check_session = function(ctx)
local ok, err = _session:destroy()
if not ok then
_session:close()
return false, "session:destroy() error : " .. err
return false, "session:destroy() error : " .. err
end
logger:log(ngx.WARN, "session check " .. key .. " failed, destroying session")
return utils.check_session(ctx)
@ -519,14 +519,14 @@ utils.get_session = function(audience, ctx)
end
end
-- Open session with specific audience
local _session, err, exists = session.open({audience = audience})
local _session, err, exists = session.open({ audience = audience })
if err then
logger:log(ngx.INFO, "session:open() error : " .. err)
end
return _session
end
utils.get_session_data = function(_session, site, ctx)
utils.get_session_data = function(_session, site)
local site_only = site == nil or site
local data = _session:get_data()
if site_only then
@ -535,7 +535,7 @@ utils.get_session_data = function(_session, site, ctx)
return data
end
utils.set_session_data = function(_session, data, site, ctx)
utils.set_session_data = function(_session, data, site)
local site_only = site == nil or site
if site_only then
local all_data = _session:get_data()
@ -651,7 +651,7 @@ utils.new_cachestore = function()
return require "bunkerweb.cachestore":new(use_redis, true)
end
utils.regex_match = function(str, regex, options)
utils.regex_match = function(str, regex, options)
local all_options = "o"
if options then
all_options = all_options .. options
@ -664,7 +664,7 @@ utils.regex_match = function(str, regex, options)
return match
end
utils.get_phases = function()
utils.get_phases = function()
return {
"init",
"init_worker",
@ -678,7 +678,7 @@ utils.get_phases = function()
}
end
utils.is_cosocket_available = function()
utils.is_cosocket_available = function()
local phases = {
"timer",
"access",
@ -693,7 +693,7 @@ utils.is_cosocket_available = function()
return false
end
utils.kill_all_threads = function(threads)
utils.kill_all_threads = function(threads)
for i, thread in ipairs(threads) do
local ok, err = ngx.thread.kill(thread)
if not ok then
@ -702,9 +702,9 @@ utils.kill_all_threads = function(threads)
end
end
utils.get_ctx_obj = function(obj, ctx)
if ctx and ctx.bw then
return ctx.bw[obj]
utils.get_ctx_obj = function(obj)
if ngx.ctx and ngx.ctx.bw then
return ngx.ctx.bw[obj]
end
return nil
end

View File

@ -41,7 +41,7 @@ local function _createIndexWrapper(aClass, f)
return (f(self, name))
end
end
else -- if type(f) == "table" then
else -- if type(f) == "table" then
return function(self, name)
local value = aClass.__instanceDict[name]

Binary file not shown.

Binary file not shown.

View File

@ -41,7 +41,7 @@ class API:
resp = request(
method,
f"{self.__endpoint}{url}",
f"{self.__endpoint}{url if not url.startswith('/') else url[1:]}",
timeout=timeout,
headers={"User-Agent": "bwapi", "Host": self.__host},
**kwargs,

View File

@ -221,7 +221,7 @@ function antibot:display_challenge()
if self.variables["USE_ANTIBOT"] == "hcaptcha" then
template_vars.hcaptcha_sitekey = self.variables["ANTIBOT_HCAPTCHA_SITEKEY"]
end
-- Turnstile case
if self.variables["USE_ANTIBOT"] == "turnstile" then
template_vars.turnstile_sitekey = self.variables["ANTIBOT_TURNSTILE_SITEKEY"]
@ -331,7 +331,7 @@ function antibot:check_challenge()
method = "POST",
body = "secret=" ..
self.variables["ANTIBOT_HCAPTCHA_SECRET"] ..
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
"&response=" .. args["token"] .. "&remoteip=" .. ngx.ctx.bw.remote_addr,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
@ -351,7 +351,7 @@ function antibot:check_challenge()
self.session_data.time_valid = ngx.now()
return true, "resolved", self.session_data.original_uri
end
-- Turnstile case
if self.variables["USE_ANTIBOT"] == "turnstile" then
ngx.req.read_body()
@ -366,7 +366,7 @@ function antibot:check_challenge()
local data = {
secret=self.variables["ANTIBOT_TURNSTILE_SECRET"],
response=args["token"],
remoteip=self.ctx.bw.remote_addr
remoteip=ngx.ctx.bw.remote_addr
}
local res, err = httpc:request_uri("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method = "POST",

View File

@ -0,0 +1,23 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local clientcache = class("clientcache", plugin)
function clientcache:initialize()
-- Call parent initialize
plugin.initialize(self, "clientcache")
end
function clientcache:header()
-- Override Cache-Control header if needed
if self.variables["USE_CLIENT_CACHE"] == "yes" then
local keep_upstream_headers = utils.get_variable("KEEP_UPSTREAM_HEADERS")
if ngx.header["Cache-Control"] == nil or keep_upstream_headers ~= "*" and utils.regex_match(keep_upstream_headers, "(^| )Cache-Control($| )") == nil then
ngx.header["Cache-Control"] = ngx.var.cache_control
end
end
return self:ret(true, "Success")
end
return clientcache

View File

@ -1,5 +1,4 @@
{% if USE_CLIENT_CACHE == "yes" +%}
add_header Cache-Control $cache_control;
{% if CLIENT_CACHE_ETAG == "yes" and SERVE_FILES == "yes" and USE_REVERSE_PROXY == "no" +%}
etag on;
{% else +%}

View File

@ -65,7 +65,7 @@ function cors:header()
ngx.header[header] = self.variables[variable]
end
end
ngx.header["Content-Type"] = "text/html"
ngx.header["Content-Type"] = "text/html; charset=UTF-8"
ngx.header["Content-Length"] = "0"
return self:ret(true, "edited headers for preflight request")
end

View File

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

View File

@ -1,5 +0,0 @@
{% for k, v in all.items() +%}
{% if k.startswith("CUSTOM_HEADER") and v != "" +%}
more_set_headers "{{ v }}";
{% endif %}
{% endfor %}

View File

@ -1,5 +0,0 @@
{% if REMOVE_HEADERS != "" %}
{% for header in REMOVE_HEADERS.split(" ") +%}
more_clear_headers '{{ header }}';
{% endfor %}
{% endif %}

View File

@ -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 +%}

View File

@ -0,0 +1,75 @@
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",
["CROSS_ORIGIN_OPENER_POLICY"] = "Cross-Origin-Opener-Policy",
["CROSS_ORIGIN_EMBEDDER_POLICY"] = "Cross-Origin-Embedder-Policy",
["CROSS_ORIGIN_RESOURCE_POLICY"] = "Cross-Origin-Resource-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 == self.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

View File

@ -11,19 +11,28 @@
"help": "Custom header to add (HeaderName: HeaderValue).",
"id": "custom-header",
"label": "Custom header (HeaderName: HeaderValue)",
"regex": "^([\\w-]+: .+)?$",
"regex": "^([\\w-]+: [^,]+)?$",
"type": "text",
"multiple": "custom-headers"
},
"REMOVE_HEADERS": {
"context": "multisite",
"default": "Server X-Powered-By X-AspNet-Version X-AspNetMvc-Version",
"default": "Server Expect-CT X-Powered-By X-AspNet-Version X-AspNetMvc-Version",
"help": "Headers to remove (Header1 Header2 Header3 ...)",
"id": "remove-headers",
"label": "Remove headers",
"regex": "^(?! )( ?[\\w-]+)*$",
"type": "text"
},
"KEEP_UPSTREAM_HEADERS": {
"context": "multisite",
"default": "Content-Security-Policy Permissions-Policy Feature-Policy X-Frame-Options",
"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",
@ -99,6 +108,51 @@
"regex": "^(?![; ])( ?([\\w-]+)(?!.*[^-]\\2 )( ('(none|self|strict-dynamic|report-sample|unsafe-inline|unsafe-eval|unsafe-hashes|unsafe-allow-redirects)'|https?://[\\w@:%.+~#=-]+[\\w()!@:%+.~#?&/=$-]*))+;)*$",
"type": "text"
},
"CROSS_ORIGIN_OPENER_POLICY": {
"context": "multisite",
"default": "same-origin",
"help": "Value for the Cross-Origin-Opener-Policy header.",
"id": "cross-origin-opener-policy",
"label": "Cross-Origin-Opener-Policy",
"regex": "^(unsafe-none|same-origin-allow-popups|same-origin)?$",
"type": "select",
"select": [
"",
"unsafe-none",
"same-origin-allow-popups",
"same-origin"
]
},
"CROSS_ORIGIN_EMBEDDER_POLICY": {
"context": "multisite",
"default": "require-corp",
"help": "Value for the Cross-Origin-Embedder-Policy header.",
"id": "cross-origin-embedder-policy",
"label": "Cross-Origin-Embedder-Policy",
"regex": "^(unsafe-none|require-corp|credentialless)?$",
"type": "select",
"select": [
"",
"unsafe-none",
"require-corp",
"credentialless"
]
},
"CROSS_ORIGIN_RESOURCE_POLICY": {
"context": "multisite",
"default": "same-site",
"help": "Value for the Cross-Origin-Resource-Policy header.",
"id": "cross-origin-resource-policy",
"label": "Cross-Origin-Resource-Policy",
"regex": "^(same-site|same-origin|cross-origin)?$",
"type": "select",
"select": [
"",
"same-site",
"same-origin",
"cross-origin"
]
},
"X_FRAME_OPTIONS": {
"context": "multisite",
"default": "SAMEORIGIN",

View File

@ -30,14 +30,20 @@ try:
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
if getenv("KUBERNETES_MODE") == "yes":
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE") == "yes":
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE") == "yes":
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
integration = integration_path.read_text(encoding="utf-8").strip()
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(
encoding="utf-8"
):
bw_integration = "Docker"
token = getenv("CERTBOT_TOKEN", "")
validation = getenv("CERTBOT_VALIDATION", "")

View File

@ -30,14 +30,20 @@ try:
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
if getenv("KUBERNETES_MODE") == "yes":
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE") == "yes":
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE") == "yes":
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
integration = integration_path.read_text(encoding="utf-8").strip()
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(
encoding="utf-8"
):
bw_integration = "Docker"
token = getenv("CERTBOT_TOKEN", "")
# Cluster case

View File

@ -33,14 +33,20 @@ try:
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
if getenv("KUBERNETES_MODE") == "yes":
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE") == "yes":
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE") == "yes":
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
integration = integration_path.read_text(encoding="utf-8").strip()
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(
encoding="utf-8"
):
bw_integration = "Docker"
token = getenv("CERTBOT_TOKEN", "")
logger.info(f"Certificates renewal for {getenv('RENEWED_DOMAINS')} successful")

View File

@ -37,17 +37,17 @@ def certbot_new(
join(sep, "usr", "share", "bunkerweb", "deps", "python", "bin", "certbot"),
"certonly",
"--config-dir",
letsencrypt_path.joinpath("etc"),
str(letsencrypt_path.joinpath("etc")),
"--work-dir",
letsencrypt_path.joinpath("lib"),
str(letsencrypt_path.joinpath("lib")),
"--logs-dir",
letsencrypt_path.joinpath("log"),
str(letsencrypt_path.joinpath("log")),
"--manual",
"--preferred-challenges=http",
"--manual-auth-hook",
letsencrypt_job_path.joinpath("certbot-auth.py"),
str(letsencrypt_job_path.joinpath("certbot-auth.py")),
"--manual-cleanup-hook",
letsencrypt_job_path.joinpath("certbot-cleanup.py"),
str(letsencrypt_job_path.joinpath("certbot-cleanup.py")),
"-n",
"-d",
domains,
@ -147,10 +147,10 @@ try:
certbot_new(domains, real_email, letsencrypt_path, letsencrypt_job_path)
!= 0
):
status = 2
logger.error(
f"Certificate generation failed for domain(s) {domains} ...",
)
_exit(2)
else:
status = 1
logger.info(

View File

@ -32,11 +32,11 @@ def renew(domain: str, letsencrypt_path: Path) -> int:
join(sep, "usr", "share", "bunkerweb", "deps", "python", "bin", "certbot"),
"renew",
"--config-dir",
letsencrypt_path.joinpath("etc"),
str(letsencrypt_path.joinpath("etc")),
"--work-dir",
letsencrypt_path.joinpath("lib"),
str(letsencrypt_path.joinpath("lib")),
"--logs-dir",
letsencrypt_path.joinpath("log"),
str(letsencrypt_path.joinpath("log")),
"--cert-name",
domain,
"--deploy-hook",
@ -53,7 +53,8 @@ def renew(domain: str, letsencrypt_path: Path) -> int:
],
stdin=DEVNULL,
stderr=STDOUT,
env=environ,
env=environ.copy()
| {"PYTHONPATH": join(sep, "usr", "share", "bunkerweb", "deps", "python")},
check=False,
).returncode

View File

@ -75,9 +75,9 @@ try:
"-newkey",
"rsa:4096",
"-keyout",
cert_path.joinpath("cert.key"),
str(cert_path.joinpath("cert.key")),
"-out",
cert_path.joinpath("cert.pem"),
str(cert_path.joinpath("cert.pem")),
"-days",
"3650",
"-subj",

View File

@ -2,11 +2,11 @@ local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local misc = class("misc", plugin)
local misc = class("misc", plugin)
function misc:initialize()
-- Call parent initialize
plugin.initialize(self, "misc")
-- Call parent initialize
plugin.initialize(self, "misc")
end
function misc:access()
@ -24,4 +24,4 @@ function misc:access()
return self:ret(true, "method " .. method .. " is not allowed", ngx.HTTP_NOT_ALLOWED)
end
return misc
return misc

View File

@ -17,7 +17,6 @@ proxy_cache_bypass {{ PROXY_CACHE_BYPASS }};
{% for element in PROXY_CACHE_VALID.split(" ") +%}
proxy_cache_valid {{ element.split("=")[0] }} {{ element.split("=")[1] }};
{% endfor %}
add_header X-Proxy-Cache $upstream_cache_status;
{% endif %}
{% endif %}
@ -29,7 +28,6 @@ add_header X-Proxy-Cache $upstream_cache_status;
{% set host = all[k.replace("URL", "HOST")] if k.replace("URL", "HOST") in all else "" %}
{% set ws = all[k.replace("URL", "WS")] if k.replace("URL", "WS") in all else "" %}
{% set headers = all[k.replace("URL", "HEADERS")] if k.replace("URL", "HEADERS") in all else "" %}
{% set headers_client = all[k.replace("URL", "HEADERS_CLIENT")] if k.replace("URL", "HEADERS_CLIENT") in all else "" %}
{% set buffering = all[k.replace("URL", "BUFFERING")] if k.replace("URL", "BUFFERING") in all else "yes" %}
{% set keepalive = all[k.replace("URL", "KEEPALIVE")] if k.replace("URL", "KEEPALIVE") in all else "yes" %}
{% set auth_request = all[k.replace("URL", "AUTH_REQUEST")] if k.replace("URL", "AUTH_REQUEST") in all else "" %}
@ -48,6 +46,12 @@ location {{ url }} {% raw %}{{% endraw +%}
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
{% if USE_UI == "yes" +%}
{% if url.endswith("/") +%}
{% set url = url[:-1] +%}
{% endif +%}
proxy_set_header X-Forwarded-Prefix {{ url }};
{% endif +%}
{% if buffering == "yes" +%}
proxy_buffering on;
{% else +%}
@ -77,11 +81,6 @@ location {{ url }} {% raw %}{{% endraw +%}
proxy_set_header {{ header }};
{% endfor +%}
{% endif +%}
{% if headers_client != "" +%}
{% for header_client in headers_client.split(";") +%}
add_header {{ header_client }};
{% endfor +%}
{% endif +%}
proxy_connect_timeout {{ connect_timeout }};
proxy_read_timeout {{ read_timeout }};
proxy_send_timeout {{ send_timeout }};

View File

@ -0,0 +1,49 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local reverseproxy = class("reverseproxy", plugin)
function reverseproxy:initialize()
-- Call parent initialize
plugin.initialize(self, "reverseproxy")
end
function reverseproxy:header()
-- Set proxy cache header if needed
if self.variables["USE_PROXY_CACHE"] == "yes" and self.variables["PROXY_CACHE_VALID"] ~= "" then
ngx.header["X-Proxy-Cache"] = ngx.var.upstream_cache_status
end
-- Get variables
local variables, err = utils.get_multiple_variables({ "REVERSE_PROXY_HEADERS_CLIENT" })
if variables == nil then
return self:ret(false, err)
end
-- Add reverseproxy client 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, "REVERSE_PROXY_HEADERS_CLIENT") and value then
local iterator, err = ngx.re.gmatch(value, "([\\w-]+) ([^;]+)")
if not iterator then
return self:ret(false, "Error while matching reverseproxy client headers: " .. err .. " - " .. value)
end
while true do
local m, err = iterator()
if err then
return self:ret(false, "Error while matching reverseproxy client headers: " .. err .. " - " .. value)
end
if not m then
-- No more matches
break
end
ngx.header[m[1]] = m[2]
end
end
end
end
end
return self:ret(true, "Success")
end
return reverseproxy

View File

@ -2,7 +2,7 @@ local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local cjson = require "cjson"
local reversescan = class("reversescan", plugin)
@ -27,25 +27,25 @@ function reversescan:access()
ret_threads = false
ret_err = "error getting info from cachestore : " .. cached
break
-- Deny access if port opened
-- Deny access if port opened
elseif cached == "open" then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
break
-- Perform scan in a thread
-- Perform scan in a thread
elseif not cached then
local thread = ngx.thread.spawn(self.scan, self.ctx.bw.remote_addr, tonumber(port), tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
threads[port] = thread
end
end
if ret_threads ~= nil then
if #threads > 0 then
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
if #threads > 0 then
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
-- Open port case
if ret_threads then
return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
@ -58,27 +58,27 @@ function reversescan:access()
ret_err = nil
local results = {}
while true do
-- Compute threads to wait
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
-- No port opened
if #wait_threads == 0 then
break
end
-- Wait for first thread
local ok, open, port = ngx.thread.wait(unpack(wait_threads))
-- Error case
if not ok then
ret_threads = false
ret_err = "error while waiting thread : " .. open
break
end
-- Compute threads to wait
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
-- No port opened
if #wait_threads == 0 then
break
end
-- Wait for first thread
local ok, open, port = ngx.thread.wait(unpack(wait_threads))
-- Error case
if not ok then
ret_threads = false
ret_err = "error while waiting thread : " .. open
break
end
port = tostring(port)
-- Remove thread from list
threads[port] = nil
-- Add result to cache
-- Remove thread from list
threads[port] = nil
-- Add result to cache
local result = "close"
if open then
result = "open"

View File

@ -43,7 +43,7 @@ def generate_cert(
"86400",
"-noout",
"-in",
self_signed_path.joinpath(f"{first_server}.pem"),
str(self_signed_path.joinpath(f"{first_server}.pem")),
],
stdin=DEVNULL,
stderr=STDOUT,
@ -65,9 +65,9 @@ def generate_cert(
"-newkey",
"rsa:4096",
"-keyout",
self_signed_path.joinpath(f"{first_server}.key"),
str(self_signed_path.joinpath(f"{first_server}.key")),
"-out",
self_signed_path.joinpath(f"{first_server}.pem"),
str(self_signed_path.joinpath(f"{first_server}.pem")),
"-days",
days,
"-subj",

View File

@ -11,7 +11,7 @@ from os.path import basename, dirname, join
from pathlib import Path
from re import compile as re_compile
from sys import _getframe, path as sys_path
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
from time import sleep
from traceback import format_exc
@ -55,7 +55,13 @@ install_as_MySQLdb()
class Database:
def __init__(self, logger: Logger, sqlalchemy_string: Optional[str] = None) -> None:
def __init__(
self,
logger: Logger,
sqlalchemy_string: Optional[str] = None,
*,
ui: bool = False,
) -> None:
"""Initialize the database"""
self.__logger = logger
self.__sql_session = None
@ -67,10 +73,14 @@ class Database:
)
if sqlalchemy_string.startswith("sqlite"):
with suppress(FileExistsError):
Path(dirname(sqlalchemy_string.split("///")[1])).mkdir(
parents=True, exist_ok=True
)
if ui:
while not Path(sep, "var", "lib", "bunkerweb", "db.sqlite3"):
sleep(1)
else:
with suppress(FileExistsError):
Path(dirname(sqlalchemy_string.split("///")[1])).mkdir(
parents=True, exist_ok=True
)
elif "+" in sqlalchemy_string and "+pymysql" not in sqlalchemy_string:
splitted = sqlalchemy_string.split("+")
sqlalchemy_string = f"{splitted[0]}:{':'.join(splitted[1].split(':')[1:])}"
@ -254,7 +264,7 @@ class Database:
return ""
def check_changes(self) -> Union[Dict[str, bool], str]:
def check_changes(self) -> Union[Dict[str, bool], bool, str]:
"""Check if either the config, the custom configs or plugins have changed inside the database"""
with self.__db_session() as session:
try:
@ -268,6 +278,7 @@ class Database:
.filter_by(id=1)
.first()
)
return dict(
custom_configs_changed=metadata is not None
and metadata.custom_configs_changed,
@ -658,6 +669,7 @@ class Database:
if not metadata.first_config_saved:
metadata.first_config_saved = True
metadata.config_changed = bool(to_put)
metadata.ui_config_changed = bool(to_put)
try:
session.add_all(to_put)

View File

@ -1,4 +1,4 @@
sqlalchemy==2.0.15
psycopg2-binary==2.9.6
PyMySQL==1.0.3
cryptography==41.0.0
cryptography==41.0.1

View File

@ -70,26 +70,26 @@ cffi==1.15.1 \
--hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \
--hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0
# via cryptography
cryptography==41.0.0 \
--hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \
--hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \
--hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \
--hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \
--hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \
--hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \
--hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \
--hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \
--hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \
--hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \
--hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \
--hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \
--hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \
--hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \
--hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \
--hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \
--hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \
--hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \
--hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be
cryptography==41.0.1 \
--hash=sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db \
--hash=sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a \
--hash=sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039 \
--hash=sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c \
--hash=sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3 \
--hash=sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485 \
--hash=sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c \
--hash=sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca \
--hash=sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5 \
--hash=sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5 \
--hash=sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3 \
--hash=sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb \
--hash=sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43 \
--hash=sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31 \
--hash=sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc \
--hash=sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b \
--hash=sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006 \
--hash=sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a \
--hash=sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699
# via -r requirements.in
greenlet==2.0.2 \
--hash=sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a \
@ -268,7 +268,7 @@ sqlalchemy==2.0.15 \
--hash=sha256:f6fd3c88ea4b170d13527e93be1945e69facd917661d3725a63470eb683fbffe \
--hash=sha256:f7f994a53c0e6b44a2966fd6bfc53e37d34b7dca34e75b6be295de6db598255e
# via -r requirements.in
typing-extensions==4.6.2 \
--hash=sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c \
--hash=sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98
typing-extensions==4.6.3 \
--hash=sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26 \
--hash=sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5
# via sqlalchemy

View File

@ -1,4 +1,4 @@
docker==6.1.2
docker==6.1.3
kubernetes==26.1.0
jinja2==3.1.2
python-dotenv==1.0.0

View File

@ -95,9 +95,9 @@ charset-normalizer==3.1.0 \
--hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \
--hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab
# via requests
docker==6.1.2 \
--hash=sha256:134cd828f84543cbf8e594ff81ca90c38288df3c0a559794c12f2e4b634ea19e \
--hash=sha256:dcc088adc2ec4e7cfc594e275d8bd2c9738c56c808de97476939ef67db5af8c2
docker==6.1.3 \
--hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \
--hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9
# via -r requirements.in
google-auth==2.17.3 \
--hash=sha256:ce311e2bc58b130fddf316df57c9b3943c2a7b4f6ec31de9663a9333e4064efc \
@ -115,57 +115,57 @@ kubernetes==26.1.0 \
--hash=sha256:5854b0c508e8d217ca205591384ab58389abdae608576f9c9afc35a3c76a366c \
--hash=sha256:e3db6800abf7e36c38d2629b5cb6b74d10988ee0cba6fba45595a7cbe60c0042
# via -r requirements.in
markupsafe==2.1.2 \
--hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \
--hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \
--hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \
--hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \
--hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \
--hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \
--hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \
--hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \
--hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \
--hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \
--hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \
--hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \
--hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \
--hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \
--hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \
--hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \
--hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \
--hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \
--hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \
--hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \
--hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \
--hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \
--hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \
--hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \
--hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \
--hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \
--hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \
--hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \
--hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \
--hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \
--hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \
--hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \
--hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \
--hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \
--hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \
--hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \
--hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \
--hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \
--hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \
--hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \
--hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \
--hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \
--hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \
--hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \
--hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \
--hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \
--hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \
--hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \
--hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \
--hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58
markupsafe==2.1.3 \
--hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
--hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
--hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
--hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
--hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
--hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
--hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
--hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
--hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
--hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
--hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
--hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
--hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
--hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
--hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
--hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
--hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
--hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
--hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
--hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
--hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
--hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
--hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
--hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
--hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
--hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
--hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
--hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
--hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
--hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
--hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
--hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
--hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
--hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
--hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
--hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
--hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
--hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
--hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
--hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
--hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
--hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
--hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
--hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
--hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
--hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
--hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
--hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
--hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
--hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2
# via jinja2
oauthlib==3.2.2 \
--hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \

View File

@ -22,7 +22,7 @@ from kubernetes import client as kube_client, config
class ApiCaller:
def __init__(self, apis: List[API] = None):
def __init__(self, apis: Optional[List[API]] = None):
self.__apis = apis or []
self.__logger = setup_logger("Api", getenv("LOG_LEVEL", "INFO"))
@ -122,6 +122,7 @@ class ApiCaller:
response: bool = False,
) -> Tuple[bool, Tuple[bool, Optional[Dict[str, Any]]]]:
ret = True
url = url if not url.startswith("/") else url[1:]
responses = {}
for api in self.__apis:
if files is not None:

View File

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

View File

@ -167,26 +167,26 @@ configobj==5.0.8 \
--hash=sha256:6f704434a07dc4f4dc7c9a745172c1cad449feb548febd9f7fe362629c627a97 \
--hash=sha256:a7a8c6ab7daade85c3f329931a807c8aee750a2494363934f8ea84d8a54c87ea
# via certbot
cryptography==41.0.0 \
--hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \
--hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \
--hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \
--hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \
--hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \
--hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \
--hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \
--hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \
--hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \
--hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \
--hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \
--hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \
--hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \
--hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \
--hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \
--hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \
--hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \
--hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \
--hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be
cryptography==41.0.1 \
--hash=sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db \
--hash=sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a \
--hash=sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039 \
--hash=sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c \
--hash=sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3 \
--hash=sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485 \
--hash=sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c \
--hash=sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca \
--hash=sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5 \
--hash=sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5 \
--hash=sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3 \
--hash=sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb \
--hash=sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43 \
--hash=sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31 \
--hash=sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc \
--hash=sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b \
--hash=sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006 \
--hash=sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a \
--hash=sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699
# via
# acme
# certbot

View File

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

View File

@ -1,12 +1,14 @@
#!/usr/bin/python3
from os import _exit, getenv, getpid, listdir, sep
from os import _exit, getenv, listdir, sep, urandom
from os.path import basename, dirname, join
from sys import path as sys_path, modules as sys_modules
from pathlib import Path
os_release_path = Path(sep, "etc", "os-release")
if os_release_path.is_file() and "Alpine" not in os_release_path.read_text():
if os_release_path.is_file() and "Alpine" not in os_release_path.read_text(
encoding="utf-8"
):
sys_path.append(join(sep, "usr", "share", "bunkerweb", "deps", "python"))
del os_release_path
@ -96,10 +98,10 @@ def stop_gunicorn():
call(["kill", "-SIGTERM", pid])
def stop(status, stop=True):
def stop(status, _stop=True):
Path(sep, "var", "run", "bunkerweb", "ui.pid").unlink(missing_ok=True)
Path(sep, "var", "tmp", "bunkerweb", "ui.healthy").unlink(missing_ok=True)
if stop is True:
if _stop is True:
stop_gunicorn()
_exit(status)
@ -122,42 +124,28 @@ app = Flask(
static_folder="static",
template_folder="templates",
)
app.wsgi_app = ReverseProxied(app.wsgi_app)
app.wsgi_app = ReverseProxied(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
# Set variables and instantiate objects
vars = get_variables()
if "ABSOLUTE_URI" not in vars:
logger.error("ABSOLUTE_URI is not set")
stop(1)
elif "ADMIN_USERNAME" not in vars:
if not getenv("ADMIN_USERNAME"):
logger.error("ADMIN_USERNAME is not set")
stop(1)
elif "ADMIN_PASSWORD" not in vars:
elif not getenv("ADMIN_PASSWORD"):
logger.error("ADMIN_PASSWORD is not set")
stop(1)
if not vars.get("FLASK_DEBUG", False) and not regex_match(
if not getenv("FLASK_DEBUG", False) and not regex_match(
r"^(?=.*?\p{Lowercase_Letter})(?=.*?\p{Uppercase_Letter})(?=.*?\d)(?=.*?[ !\"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]).{8,}$",
vars["ADMIN_PASSWORD"],
getenv("ADMIN_PASSWORD", "changeme"),
):
logger.error(
"The admin password is not strong enough. It must contain at least 8 characters, including at least 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character (#@?!$%^&*-)."
)
stop(1)
if not vars["ABSOLUTE_URI"].endswith("/"):
vars["ABSOLUTE_URI"] += "/"
if not vars.get("FLASK_DEBUG", False) and vars["ABSOLUTE_URI"].endswith("/changeme/"):
logger.error("Please change the default URL.")
stop(1)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"
user = User(vars["ADMIN_USERNAME"], vars["ADMIN_PASSWORD"])
user = User(getenv("ADMIN_USERNAME", "admin"), getenv("ADMIN_PASSWORD", "changeme"))
PLUGIN_KEYS = [
"id",
"name",
@ -167,33 +155,44 @@ PLUGIN_KEYS = [
"settings",
]
integration = "Linux"
INTEGRATION = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
if getenv("KUBERNETES_MODE", "no").lower() == "yes":
integration = "Kubernetes"
INTEGRATION = "Kubernetes"
elif getenv("SWARM_MODE", "no").lower() == "yes":
integration = "Swarm"
INTEGRATION = "Swarm"
elif getenv("AUTOCONF_MODE", "no").lower() == "yes":
integration = "Autoconf"
INTEGRATION = "Autoconf"
elif integration_path.is_file():
integration = integration_path.read_text().strip()
INTEGRATION = integration_path.read_text(encoding="utf-8").strip()
del integration_path
docker_client = None
kubernetes_client = None
if integration in ("Docker", "Swarm", "Autoconf"):
if INTEGRATION in ("Docker", "Swarm", "Autoconf"):
try:
docker_client: DockerClient = DockerClient(
base_url=vars.get("DOCKER_HOST", "unix:///var/run/docker.sock")
base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
)
except (docker_APIError, DockerException):
logger.warning("No docker host found")
elif integration == "Kubernetes":
elif INTEGRATION == "Kubernetes":
kube_config.load_incluster_config()
kubernetes_client = kube_client.CoreV1Api()
db = Database(logger)
db = Database(logger, ui=True)
if INTEGRATION in (
"Swarm",
"Kubernetes",
"Autoconf",
):
while not db.is_autoconf_loaded():
logger.warning(
"Autoconf is not loaded yet in the database, retrying in 5s ...",
)
sleep(5)
while not db.is_initialized():
logger.warning(
@ -209,6 +208,8 @@ while not db.is_first_config_saved() or not env:
sleep(5)
env = db.get_config()
del env
logger.info("Database is ready")
Path(sep, "var", "tmp", "bunkerweb", "ui.healthy").write_text("ok", encoding="utf-8")
bw_version = (
@ -220,15 +221,10 @@ bw_version = (
try:
app.config.update(
DEBUG=True,
SECRET_KEY=vars["FLASK_SECRET"],
ABSOLUTE_URI=vars["ABSOLUTE_URI"],
INSTANCES=Instances(docker_client, kubernetes_client, integration),
SECRET_KEY=getenv("FLASK_SECRET", urandom(32)),
INSTANCES=Instances(docker_client, kubernetes_client, INTEGRATION),
CONFIG=Config(db),
CONFIGFILES=ConfigFiles(logger, db),
SESSION_COOKIE_DOMAIN=vars["ABSOLUTE_URI"]
.replace("http://", "")
.replace("https://", "")
.split("/")[0],
WTF_CSRF_SSL_STRICT=False,
USER=user,
SEND_FILE_MAX_AGE_DEFAULT=86400,
@ -298,9 +294,18 @@ 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"])
return User(user_id, getenv("ADMIN_PASSWORD", "changeme"))
@app.errorhandler(CSRFError)
@ -1346,7 +1351,7 @@ def logs_container(container_id):
tmp_logs = []
if docker_client:
try:
if integration != "Swarm":
if INTEGRATION != "Swarm":
docker_logs = docker_client.containers.get(container_id).logs(
stdout=True,
stderr=True,

View File

@ -188,57 +188,57 @@ jinja2==3.1.2 \
--hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
--hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
# via flask
markupsafe==2.1.2 \
--hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \
--hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \
--hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \
--hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \
--hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \
--hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \
--hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \
--hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \
--hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \
--hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \
--hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \
--hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \
--hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \
--hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \
--hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \
--hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \
--hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \
--hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \
--hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \
--hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \
--hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \
--hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \
--hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \
--hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \
--hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \
--hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \
--hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \
--hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \
--hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \
--hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \
--hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \
--hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \
--hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \
--hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \
--hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \
--hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \
--hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \
--hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \
--hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \
--hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \
--hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \
--hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \
--hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \
--hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \
--hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \
--hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \
--hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \
--hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \
--hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \
--hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58
markupsafe==2.1.3 \
--hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
--hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
--hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
--hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
--hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
--hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
--hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
--hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
--hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
--hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
--hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
--hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
--hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
--hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
--hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
--hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
--hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
--hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
--hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
--hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
--hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
--hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
--hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
--hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
--hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
--hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
--hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
--hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
--hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
--hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
--hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
--hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
--hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
--hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
--hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
--hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
--hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
--hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
--hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
--hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
--hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
--hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
--hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
--hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
--hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
--hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
--hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
--hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
--hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
--hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2
# via
# jinja2
# werkzeug

View File

@ -1,27 +1,72 @@
#!/usr/bin/python3
from typing import Iterable
from wsgiref.types import StartResponse, WSGIEnvironment
from werkzeug.middleware.proxy_fix import ProxyFix
class ReverseProxied(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
class ReverseProxied(ProxyFix):
def __call__(
self, environ: WSGIEnvironment, start_response: StartResponse
) -> Iterable[bytes]:
"""Modify the WSGI environ based on the various ``Forwarded``
headers before calling the wrapped application. Store the
original environ values in ``werkzeug.proxy_fix.orig_{key}``.
"""
If the app is behind a reverse proxy, it will modify the
environ object to make it look like the request was received on the app directly
environ_get = environ.get
orig_remote_addr = environ_get("REMOTE_ADDR")
orig_wsgi_url_scheme = environ_get("wsgi.url_scheme")
orig_http_host = environ_get("HTTP_HOST")
environ.update(
{
"werkzeug.proxy_fix.orig": {
"REMOTE_ADDR": orig_remote_addr,
"wsgi.url_scheme": orig_wsgi_url_scheme,
"HTTP_HOST": orig_http_host,
"SERVER_NAME": environ_get("SERVER_NAME"),
"SERVER_PORT": environ_get("SERVER_PORT"),
"SCRIPT_NAME": environ_get("SCRIPT_NAME"),
}
}
)
:param environ: The WSGI environment dict
:param start_response: This is the WSGI-compatible start_response function that the
:return: A WSGI application.
"""
script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
if script_name:
environ["SCRIPT_NAME"] = script_name
path_info = environ["PATH_INFO"]
if path_info.startswith(script_name):
environ["PATH_INFO"] = path_info[len(script_name) :]
x_for = self._get_real_value(self.x_for, environ_get("HTTP_X_FORWARDED_FOR"))
if x_for:
environ["REMOTE_ADDR"] = x_for
x_proto = self._get_real_value(
self.x_proto, environ_get("HTTP_X_FORWARDED_PROTO")
)
if x_proto:
environ["wsgi.url_scheme"] = x_proto
x_host = self._get_real_value(self.x_host, environ_get("HTTP_X_FORWARDED_HOST"))
if x_host:
environ["HTTP_HOST"] = environ["SERVER_NAME"] = x_host
# "]" to check for IPv6 address without port
if ":" in x_host and not x_host.endswith("]"):
environ["SERVER_NAME"], environ["SERVER_PORT"] = x_host.rsplit(":", 1)
x_port = self._get_real_value(self.x_port, environ_get("HTTP_X_FORWARDED_PORT"))
if x_port:
host = environ.get("HTTP_HOST")
if host:
# "]" to check for IPv6 address without port
if ":" in host and not host.endswith("]"):
host = host.rsplit(":", 1)[0]
environ["HTTP_HOST"] = f"{host}:{x_port}"
environ["SERVER_PORT"] = x_port
x_prefix = self._get_real_value(
self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX")
)
if x_prefix:
environ["SCRIPT_NAME"] = x_prefix
environ["PATH_INFO"] = environ["PATH_INFO"][len(environ["SCRIPT_NAME"]) :]
environ[
"ABSOLUTE_URI"
] = f"{environ['wsgi.url_scheme']}://{environ['HTTP_HOST']}{environ['SCRIPT_NAME']}/"
environ["SESSION_COOKIE_DOMAIN"] = environ["HTTP_HOST"]
scheme = environ.get("HTTP_X_FORWARDED_PROTO", "")
if scheme:
environ["wsgi.url_scheme"] = scheme
return self.app(environ, start_response)

View File

@ -1,26 +1,9 @@
#!/usr/bin/python3
from os import environ, urandom
from os.path import join
from typing import List, Optional
def get_variables():
vars = {}
vars["DOCKER_HOST"] = "unix:///var/run/docker.sock"
vars["ABSOLUTE_URI"] = ""
vars["FLASK_SECRET"] = urandom(32)
vars["FLASK_ENV"] = "development"
vars["ADMIN_USERNAME"] = "admin"
vars["ADMIN_PASSWORD"] = "changeme"
for k in vars:
if k in environ:
vars[k] = environ[k]
return vars
def path_to_dict(
path: str,
*,

View File

@ -51,6 +51,8 @@ try:
print(" Navigating to http://www.example.com ...", flush=True)
driver.get("http://www.example.com")
sleep(2)
try:
driver.find_element(By.XPATH, "//img[@alt='NGINX Logo']")
except NoSuchElementException:
@ -85,6 +87,8 @@ try:
print("❌ The page is accessible without auth-basic ...", flush=True)
exit(1)
sleep(2)
print(
f" Trying to access http://{auth_basic_username}:{auth_basic_password}@www.example.com ...",
flush=True,

View File

@ -50,6 +50,7 @@ try:
"http://www.example.com/?id=/etc/passwd",
headers={"Host": "www.example.com"},
)
sleep(1)
sleep(1)
@ -67,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",
@ -85,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 ...",

View File

@ -1,2 +1,2 @@
requests==2.31.0
docker==6.1.2
docker==6.1.3

View File

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

View File

@ -1,2 +1,2 @@
fastapi==0.95.2
fastapi==0.96.0
uvicorn[standard]==0.22.0

View File

@ -1,2 +1,2 @@
fastapi==0.95.2
fastapi==0.96.0
uvicorn[standard]==0.22.0

View File

@ -1 +1 @@
docker==6.1.2
docker==6.1.3

View File

@ -0,0 +1,10 @@
FROM alpine
WORKDIR /opt/init
COPY entrypoint.sh .
RUN apk add --no-cache bash && \
chmod +x entrypoint.sh
ENTRYPOINT [ "./entrypoint.sh" ]

View File

@ -0,0 +1,9 @@
version: "3.5"
services:
init:
build:
context: .
dockerfile: Dockerfile.init
volumes:
- ./www:/www

View File

@ -15,6 +15,7 @@ services:
CORS_ALLOW_HEADERS: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range"
extra_hosts:
- "www.example.com:192.168.0.2"
- "app1.example.com:192.168.0.2"
networks:
bw-services:
ipv4_address: 192.168.0.3

View File

@ -7,17 +7,20 @@ services:
labels:
- "bunkerweb.INSTANCE"
volumes:
- ./index.html:/var/www/html/index.html
- ./www:/var/www/html
environment:
SERVER_NAME: "www.example.com app1.example.com"
API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
MULTISITE: "yes"
HTTP_PORT: "80"
HTTPS_PORT: "443"
USE_BUNKERNET: "no"
USE_BLACKLIST: "no"
REMOTE_PHP: "app1"
REMOTE_PHP_PATH: "/app"
LOG_LEVEL: "info"
GENERATE_SELF_SIGNED_SSL: "no"
ALLOWED_METHODS: "GET|POST|HEAD|OPTIONS"
CUSTOM_CONF_SEVER_HTTP_main: "location /options { default_type 'text/plain'; content_by_lua_block { if ngx.var.request_method == \"OPTIONS\" then ngx.say(\"Hello, world!\") end } }"
# ? CORS settings
USE_CORS: "no"
@ -54,6 +57,14 @@ services:
networks:
- bw-docker
app1:
image: php:fpm
volumes:
- ./www/app1.example.com:/app
networks:
bw-services:
ipv4_address: 192.168.0.4
networks:
bw-universe:
name: bw-universe

View File

@ -0,0 +1,10 @@
#!/bin/bash
if [ $(id -u) -ne 0 ] ; then
echo "❌ Run me as root"
exit 1
fi
chown -R 33:101 /www
find /www -type f -exec chmod 0655 {} \;
find /www -type d -exec chmod 0755 {} \;

View File

@ -64,7 +64,7 @@ try:
f"http{'s' if ssl else ''}://www.example.com",
headers={
"Host": "www.example.com",
"Origin": f"http{'s' if ssl else ''}://bwadm.example.com",
"Origin": f"http{'s' if ssl else ''}://app1.example.com",
},
verify=False,
)
@ -121,7 +121,7 @@ try:
f"http{'s' if ssl else ''}://www.example.com/options",
headers={
"Host": "www.example.com",
"Origin": f"http{'s' if ssl else ''}://bwadm.example.com",
"Origin": f"http{'s' if ssl else ''}://app1.example.com",
},
verify=False,
)
@ -186,7 +186,6 @@ try:
if any(
[
cors_allow_origin != "*",
cors_expose_headers != "Content-Length,Content-Range",
cors_max_age != "86400",
cors_allow_credentials == "true",
@ -207,6 +206,11 @@ try:
driver.delete_all_cookies()
driver.maximize_window()
print(" Navigating to http://app1.example.com ...", flush=True)
driver.get(f"http{'s' if ssl else ''}://app1.example.com")
sleep(1.5)
print(
f" Sending a javascript request to http{'s' if ssl else ''}://www.example.com ...",
flush=True,

View File

@ -3,7 +3,7 @@
echo "🛰️ Building cors stack ..."
# Starting stack
docker compose pull bw-docker
docker compose pull bw-docker app1
if [ $? -ne 0 ] ; then
echo "🛰️ Pull failed ❌"
exit 1
@ -47,17 +47,30 @@ cleanup_stack () {
# Cleanup stack on exit
trap cleanup_stack EXIT
for test in "deactivated" "activated" "tweaked_settings"
echo "🛰️ Initializing workspace ..."
docker compose -f docker-compose.init.yml up --build
if [ $? -ne 0 ] ; then
echo "🛰️ Build failed ❌"
exit 1
elif [[ $(stat -L -c "%a %g %u" www/app1.example.com/index.php) != "655 101 33" ]] ; then
echo "🛰️ Init failed, permissions are not correct ❌"
exit 1
fi
for test in "deactivated" "activated" "allow_origin" "tweaked_settings"
do
if [ "$test" = "deactivated" ] ; then
echo "🛰️ Running tests without cors ..."
elif [ "$test" = "activated" ] ; then
echo "🛰️ Running tests with cors ..."
find . -type f -name 'docker-compose.*' -exec sed -i 's@USE_CORS: "no"@USE_CORS: "yes"@' {} \;
elif [ "$test" = "allow_origin" ] ; then
echo "🛰️ Running tests with a specific origin allowed only ..."
find . -type f -name 'docker-compose.*' -exec sed -i 's@CORS_ALLOW_ORIGIN: "\*"@CORS_ALLOW_ORIGIN: "^http://app1\\\\.example\\\\.com$$"@' {} \;
elif [ "$test" = "tweaked_settings" ] ; then
echo "🛰️ Running tests with tweaked cors settings ..."
find . -type f -name 'docker-compose.*' -exec sed -i 's@GENERATE_SELF_SIGNED_SSL: "no"@GENERATE_SELF_SIGNED_SSL: "yes"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CORS_ALLOW_ORIGIN: "\*"@CORS_ALLOW_ORIGIN: "^https://bwadm\\\\.example\\\\.com$$"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CORS_ALLOW_ORIGIN: ".*"$@CORS_ALLOW_ORIGIN: "^https://app1\\\\.example\\\\.com$$"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CORS_EXPOSE_HEADERS: "Content-Length,Content-Range"@CORS_EXPOSE_HEADERS: "X-Test"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CORS_MAX_AGE: "86400"@CORS_MAX_AGE: "3600"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CORS_ALLOW_CREDENTIALS: "no"@CORS_ALLOW_CREDENTIALS: "yes"@' {} \;

View File

@ -0,0 +1,3 @@
<?php
echo "Hello from app1 !";
?>

View File

@ -1,4 +1,4 @@
sqlalchemy==2.0.15
psycopg2-binary==2.9.6
PyMySQL==1.0.3
cryptography==41.0.0
cryptography==41.0.1

View File

@ -1,2 +1,2 @@
fastapi==0.95.2
fastapi==0.96.0
uvicorn[standard]==0.22.0

View File

@ -52,6 +52,8 @@ try:
print(f" Status code: {status_code}", flush=True)
sleep(2)
if status_code == 403:
if not use_greylist:
print(
@ -95,6 +97,8 @@ try:
)
exit(1)
sleep(2)
print("✅ Request was not rejected, User Agent is in the greylist ...")
print(
" Sending a request to http://www.example.com/?id=/etc/passwd with User-Agent BunkerBot ...",
@ -128,6 +132,8 @@ try:
)
exit(1)
sleep(2)
print("✅ Request was not rejected, URI is in the greylist ...")
print(
" Sending a request to http://www.example.com/admin/?id=/etc/passwd ...",

View File

@ -1,5 +1,5 @@
requests==2.31.0
redis==4.5.5
fastapi==0.95.2
fastapi==0.96.0
uvicorn[standard]==0.22.0
selenium==4.9.1

View File

@ -1,3 +1,3 @@
requests==2.31.0
fastapi==0.95.2
fastapi==0.96.0
uvicorn[standard]==0.22.0

View File

@ -1,2 +1,2 @@
requests==2.31.0
cryptography==41.0.0
cryptography==41.0.1

View File

@ -1,2 +1,2 @@
fastapi==0.95.2
fastapi==0.96.0
uvicorn[standard]==0.22.0