Merge pull request #901 from bunkerity/dev

Merge branch "dev" into branch "staging"
This commit is contained in:
Théophile Diot 2024-02-04 18:31:28 +01:00 committed by GitHub
commit 3fe07c8c85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
140 changed files with 2610 additions and 2530 deletions

View file

@ -86,7 +86,7 @@ jobs:
# Compute metadata
- name: Extract metadata
id: meta
uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: bunkerity/${{ inputs.IMAGE }}
# Build cached image

View file

@ -137,7 +137,7 @@ jobs:
- name: Extract metadata
if: inputs.TEST == true
id: meta
uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ghcr.io/bunkerity/${{ inputs.LINUX }}-tests:${{ inputs.RELEASE }}
- name: Build test image

View file

@ -65,7 +65,7 @@ jobs:
# Compute metadata
- name: Extract metadata
id: meta
uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: bunkerity/${{ inputs.IMAGE }}
# Build and push

View file

@ -42,7 +42,7 @@ jobs:
- name: Check out repository code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install ruby
uses: ruby/setup-ruby@bd03e04863f52d169e18a2b190e8fa6b84938215 # v1.170.0
uses: ruby/setup-ruby@22fdc77bf4148f810455b226c90fb81b5cbc00a7 # v1.171.0
with:
ruby-version: "3.0"
- name: Install packagecloud

View file

@ -36,7 +36,7 @@ jobs:
' | sudo tee /etc/apt/preferences.d/mozilla-firefox
sudo apt install --no-install-recommends -y openssl git nodejs tar bzip2 wget curl grep libx11-xcb1 libappindicator3-1 libasound2 libdbus-glib-1-2 libxtst6 libxt6 php-fpm unzip firefox
- name: Download geckodriver
uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0
with:
max_attempts: 3
timeout_minutes: 20

View file

@ -33,7 +33,7 @@ jobs:
' | sudo tee /etc/apt/preferences.d/mozilla-firefox
sudo apt install --no-install-recommends -y openssl git nodejs tar bzip2 wget curl grep libx11-xcb1 libappindicator3-1 libasound2 libdbus-glib-1-2 libxtst6 libxt6 php-fpm unzip firefox
- name: Download geckodriver
uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0
with:
max_attempts: 3
timeout_minutes: 20

View file

@ -2,12 +2,27 @@
## v1.5.6 - YYYY/MM/DD
- [BUGFIX] Fix issues with the antibot feature ([#866](https://github.com/bunkerity/bunkerweb/issues/866), [#870](https://github.com/bunkerity/bunkerweb/issues/870))
- [UI] Add bans management page in the web UI
- [UI] Add blocked requests page in the web UI
- [UI] Add the possibility to clone a service in the web UI
- [UI] Add the possibility to set a service as draft in the web UI
- [FEATURE] Add setting REDIS_SSL_VERIFY to activate/disable the SSL certificate verification when using Redis
- [FEATURE] Add Redis Sentinel fallback to master automatically if no slaves are available
- [FEATURE] Add Redis Sentinel support for bwcli
- [FEATURE] Add new Metrics core plugin that will allow metrics collection and retrieval of internal metrics
- [FEATURE] Add setting DATABASE_LOG_LEVEL to control SQLAlchemy loggers separately from the main one
- [MISC] Add a better custom certificate cache handling
- [MISC] Updated Linux base images in Dockerfiles
- [MISC] Add recommended dialects to databases string
- [DOCUMENTATION] Update web UI's setup wizard instructions in the documentation
- [DEPS] Updated stream-lua-nginx-module to v0.0.14
- [DEPS] Updated lua-nginx-module version to v0.10.26
- [DEPS] Updated libmaxminddb version to v1.9.1
- [DEPS] Updated lua-resty-core to v0.1.28
- [DEPS] Updated zlib version to v1.3.1
- [DEPS] Updated ModSecurity version to v3.0.12
- [DEPS] Updated lua-resty-mlcache version to v2.6.1
## v1.5.5 - 2024/01/12

View file

@ -260,10 +260,39 @@ The first step is to install the plugin by putting the plugin files inside the c
## Writing a plugin
### Structure
!!! tip "Existing plugins"
If the documentation is not enough, you can have a look at the existing source code of [official plugins](https://github.com/bunkerity/bunkerweb-plugins) and the [core plugins](https://github.com/bunkerity/bunkerweb/tree/v1.5.6/src/common/core) (already included in BunkerWeb but they are plugins, technically speaking).
What a plugin structure looks like :
```
plugin /
confs / conf_type.conf
ui / actions.py
template.html
plugin.lua
plugin.json
```
- **conf_type.conf** : add a [custom NGINX configurations.](/quickstart-guide/#custom-configurations)
- **actions.py** : script to execute on flask server.
This script is running on flask context, you have access to lib and utils like `jinja2`, `requests`, etc...
- **template.html** : custom plugin page you can access from ui.
- **plugin.lua** : code to execute on NGINX using [NGING LUA modile.](https://github.com/openresty/lua-nginx-module)
- **plugin.json** : metadata, settings and jobs for your settings.
!!! info "Optional files"
Files like `confs` and `ui` ones are optional. Add them only to fit your needs.
### Getting started
The first step is to create a folder that will contain the plugin :
```shell
@ -515,9 +544,9 @@ BunkerWeb uses an internal job scheduler for periodic tasks like renewing certif
### Plugin page
Plugin pages are used to display information about your plugin and interact with the user inside the plugins section of the [web UI](web-ui.md).
Everything related to the web UI is located inside the subfolder **ui** as we seen in the [previous structure section.](#structure)
Everything related to the web UI is located inside a subfolder named **ui** at the root directory of your plugin. A template file named **template.html** and located inside the **ui** subfolder contains the client code and logic to display your page. Another file named **actions.py** and also located inside the **ui** subfolder contains code that will be executed when the user is interacting with your page (filling a form for example).
#### Basic example
!!! info "Jinja 2 template"
The **template.html** file is a Jinja2 template, please refer to the [Jinja2 documentation](https://jinja.palletsprojects.com) if needed.
@ -555,3 +584,40 @@ def myplugin() :
!!! info "Python libraries"
You can use Python libraries that are already available like :
`Flask`, `Flask-Login`, `Flask-WTF`, `beautifulsoup4`, `docker`, `Jinja2`, `python-magic` and `requests`. To see the full list, you can have a look at the Web UI [requirements.txt](https://github.com/bunkerity/bunkerweb/blobsrc/ui/requirements.txt). If you need external libraries, you can install them inside the **ui** folder of your plugin and then use the classical **import** directive.
#### Utils
To easily update the content of a template inside the UI with JSON, a **SetupPlugin class** is available in `src/ui/static/js/plugins/setup.js` and will be executed when the plugin template is load.
For example, in case **actions.py** return this JSON :
```python
def plugin():
return {"message": "ok", "data": {"name": "test"}}
```
I need to add this on my **template.html** :
```html
<script>
new SetupPlugin({
name: {
el: document.querySelector('[data-name]'),
value: '',
type: 'text',
},
});
</script>
```
!!! info "Check core plugins"
Core plugins are using this utils. Feel free to look at them in order to see in details how each `key` is working.
| key | Type | Description |
| :--------: | :----: | :------------------------------------------------------------------------------------------- |
| `name ` | string | Replace `name` by the JSON key to extract the related value. |
| `el` | element| Select element you want the value to be updated. |
| `value` | any | Default value on template load or in case retrieving JSON failed. |
| `type` | string | Define the script behavior with the incoming value. Available : `text`, `list` and `status`. |
| `textEl` | element| Optional additionnal text content when type is `status`. |
| `listNames`| string | List of data keys when type is `list`. |

View file

@ -1,5 +1,5 @@
mike==2.0.0
mkdocs==1.5.3
mkdocs-material[imaging]==9.5.5
mkdocs-material[imaging]==9.5.6
mkdocs-print-site-plugin==2.3.6
pytablewriter==1.2.0

View file

@ -311,9 +311,9 @@ mkdocs==1.5.3 \
# -r requirements.in
# mike
# mkdocs-material
mkdocs-material==9.5.5 \
--hash=sha256:4480d9580faf42fed0123d0465502bfc1c0c239ecc9c4d66159cf0459ea1b4ae \
--hash=sha256:ac50b2431a79a3b160fdefbba37c9132485f1a69166aba115ad49fafdbbbc5df
mkdocs-material==9.5.6 \
--hash=sha256:5b24df36d8ac6cecd611241ce6f6423ccde3e1ad89f8360c3f76d5565fc2d82a \
--hash=sha256:e115b90fccf5cd7f5d15b0c2f8e6246b21041628b8f590630e7fca66ed7fcf6c
# via
# -r requirements.in
# mkdocs-material
@ -415,9 +415,9 @@ pillow==10.2.0 \
# via
# cairosvg
# mkdocs-material
platformdirs==4.1.0 \
--hash=sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380 \
--hash=sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420
platformdirs==4.2.0 \
--hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \
--hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768
# via mkdocs
pycparser==2.21 \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
@ -445,9 +445,9 @@ python-dateutil==2.8.2 \
# via
# ghp-import
# typepy
pytz==2023.3.post1 \
--hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \
--hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7
pytz==2023.4 \
--hash=sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40 \
--hash=sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a
# via typepy
pyyaml==6.0.1 \
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
@ -643,9 +643,9 @@ typepy==1.3.2 \
# pytablewriter
# tabledata
# typepy
urllib3==2.1.0 \
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
urllib3==2.2.0 \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
# via requests
verspec==0.1.0 \
--hash=sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 \

View file

@ -119,6 +119,10 @@ Here is how you can access the logs, depending on your integration :
Don't forget that BunkerWeb runs as an unprivileged user for obvious security reasons. Double-check the permissions of files and folders used by BunkerWeb, especially if you use custom configurations (more info [here](quickstart-guide.md#custom-configurations)). You will need to set at least **RW** rights on files and **_RWX_** on folders.
## Disable security checks
For debugging purposes, you may need to temporarily disable the security checks made by BunkerWeb. One quick way of doing it is by adding everyone in the whitelist (e.g. `WHITELIST_IP=0.0.0.0/0`).
## ModSecurity
The default BunkerWeb configuration of ModSecurity is to load the Core Rule Set in anomaly scoring mode with a paranoia level (PL) of 1 :

View file

@ -37,6 +37,7 @@ shopt -u globstar
# linux
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" src/linux/scripts/*.sh
# db
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" src/common/db/Database.py
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" src/common/db/model.py
# github
sed -i "s@${OLD_VERSION}@${NEW_VERSION}@g" .github/ISSUE_TEMPLATE/bug_report.yml

View file

@ -232,6 +232,7 @@ class IngressController(Controller):
obj = event["object"]
metadata = obj.metadata if obj else None
annotations = metadata.annotations if metadata else None
data = obj.data if obj else None
if not obj:
return False
if obj.kind == "Pod":
@ -242,6 +243,8 @@ class IngressController(Controller):
return annotations and "bunkerweb.io/CONFIG_TYPE" in annotations
if obj.kind == "Service":
return True
if obj.kind == "Secret":
return data and "tls.crt" in data and "tls.key" in data
return False
def __watch(self, watch_type):
@ -255,6 +258,8 @@ class IngressController(Controller):
what = self.__corev1.list_config_map_for_all_namespaces
elif watch_type == "service":
what = self.__corev1.list_service_for_all_namespaces
elif watch_type == "secret":
what = self.__corev1.list_secret_for_all_namespaces
else:
raise Exception(f"Unsupported watch_type {watch_type}")
@ -328,7 +333,7 @@ class IngressController(Controller):
def process_events(self):
self._set_autoconf_load_db()
watch_types = ("pod", "ingress", "configmap", "service")
watch_types = ("pod", "ingress", "configmap", "service", "secret")
threads = [Thread(target=self.__watch, args=(watch_type,)) for watch_type in watch_types]
for thread in threads:
thread.start()

View file

@ -185,7 +185,7 @@ helpers.fill_ctx = function(no_ref)
end
data.remote_addr = var.remote_addr
data.server_name = var.server_name
data.local_time = var.local_time
data.time_local = var.time_local
if data.kind == "http" then
data.uri = var.uri
data.request_uri = var.request_uri
@ -196,6 +196,7 @@ helpers.fill_ctx = function(no_ref)
data.http_content_length = var.http_content_length
data.http_origin = var.http_origin
data.http_version = req.http_version()
data.start_time = req.start_time()
data.scheme = var.scheme
end
-- IP data : global

View file

@ -93,4 +93,19 @@ function plugin:ret(ret, msg, status, redirect, data)
return { ret = ret, msg = msg, status = status, redirect = redirect, data = data }
end
function plugin:set_metric(kind, key, value)
if self.ctx and self.ctx.bw then
if not self.ctx.bw.metrics then
self.ctx.bw.metrics = {}
end
if not self.ctx.bw.metrics[self.id] then
self.ctx.bw.metrics[self.id] = {}
end
if not self.ctx.bw.metrics[self.id][kind] then
self.ctx.bw.metrics[self.id][kind] = {}
end
self.ctx.bw.metrics[self.id][kind][key] = value
end
end
return plugin

View file

@ -786,6 +786,7 @@ utils.get_phases = function()
"preread",
"log_stream",
"log_default",
"timer"
}
end

View file

@ -13,10 +13,15 @@ init_worker_by_lua_block {
local ngx = ngx
local INFO = ngx.INFO
local ERR = ngx.ERR
local WARN = ngx.WARN
local NOTICE = ngx.NOTICE
local worker = ngx.worker
local worker_id = worker.id
local timer_at = ngx.timer.at
local require_plugin = helpers.require_plugin
local new_plugin = helpers.new_plugin
local call_plugin = helpers.call_plugin
local tostring = tostring
-- Don't go further we are in loading state
local is_loading, err = require "bunkerweb.utils".get_variable("IS_LOADING", false)
@ -27,6 +32,71 @@ init_worker_by_lua_block {
return
end
-- Recurrent timer
local recurrent_timer
recurrent_timer = function(premature)
local worker_id = tostring(worker.id())
local logger = require "bunkerweb.logger":new("TIMER-" .. worker_id)
-- Worker exiting
if premature then
logger:log(WARN, "worker is exiting, disabling timer")
return
end
logger:log(INFO, "timer started")
-- Get plugins order
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ERR, "can't get plugins order from datastore : " .. err)
return
end
-- Call timer() methods
logger:log(INFO, "calling timer() methods of plugins ...")
for i, plugin_id in ipairs(order.timer) do
-- Require call
local plugin_lua, err = require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ERR, err)
elseif plugin_lua == nil then
logger:log(INFO, err)
else
-- Check if plugin has timer method
if plugin_lua.timer ~= nil then
-- New call
local ok, plugin_obj = new_plugin(plugin_lua)
if not ok then
logger:log(ERR, plugin_obj)
else
local ok, ret = call_plugin(plugin_obj, "timer")
if not ok then
logger:log(ERR, ret)
elseif not ret.ret then
logger:log(ERR, plugin_id .. ":timer() call failed : " .. ret.msg)
else
logger:log(INFO, plugin_id .. ":timer() call successful : " .. ret.msg)
end
end
else
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method timer() is not defined")
end
end
end
logger:log(INFO, "called timer() methods of plugins")
local hdl
hdl, err = timer_at(5, recurrent_timer)
if not hdl then
logger:log(ERR, "can't create timer : " .. err)
end
end
local hdl
hdl, err = timer_at(0, recurrent_timer)
if not hdl then
logger:log(ERR, "can't create timer : " .. err)
end
-- Instantiate lock
local lock = require "resty.lock":new("worker_lock", { timeout = 10 })
if not lock then

View file

@ -152,6 +152,7 @@ function antibot:access()
if ok == nil then
return self:ret(false, "check challenge error : " .. err, HTTP_INTERNAL_SERVER_ERROR)
elseif not ok then
self:set_metric("counters", "failed_challenges", 1)
self.logger:log(ngx.WARN, "client failed challenge : " .. err)
end
if redirect then

View file

@ -1 +1,11 @@
# Spoofing an action file
def antibot(**kwargs):
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("antibot")
if data.get("counter_failed_challenges") is None:
data["counter_failed_challenges"] = 0
return data
except:
return {"counter_failed_challenges": 0}

View file

@ -1,4 +1,11 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -9,12 +16,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ antibot_info or "Anti-bot technology is designed to detect and
mitigate suspicious or malicious bots, preventing them from reaching an
organization's websites or IT ecosystem." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -29,14 +33,12 @@
>
Challenges
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ antibot_count or "unknown" }}
</h5>
<h5 data-count class="mb-1 font-bold dark:text-white/90">"unknown"</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5"
>total number</span
>
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
>total failed
</span>
</p>
</div>
<!-- end text -->
@ -66,4 +68,20 @@
</div>
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_failed_challenges: {
el: document.querySelector("[data-count]"),
value: "unknown",
type: "text",
},
});
</script>
{% endblock %}

View file

@ -1 +0,0 @@
# Spoofing an action file

View file

@ -1,65 +0,0 @@
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ authbasic_info or "Basic Auth is a method for an HTTP user agent
(e.g. a web browser) to provide a user name and password when making a
request." }}
</p>
</div>
</div>
<!-- end info -->
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
AUTH BASIC
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ authbasic_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
>passed credentials</span
>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-green-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="stroke-none scale-[0.6] leading-none text-lg relative fill-white"
>
<path
fill-rule="evenodd"
d="M15.75 1.5a6.75 6.75 0 0 0-6.651 7.906c.067.39-.032.717-.221.906l-6.5 6.499a3 3 0 0 0-.878 2.121v2.818c0 .414.336.75.75.75H6a.75.75 0 0 0 .75-.75v-1.5h1.5A.75.75 0 0 0 9 19.5V18h1.5a.75.75 0 0 0 .53-.22l2.658-2.658c.19-.189.517-.288.906-.22A6.75 6.75 0 1 0 15.75 1.5Zm0 3a.75.75 0 0 0 0 1.5A2.25 2.25 0 0 1 18 8.25a.75.75 0 0 0 1.5 0 3.75 3.75 0 0 0-3.75-3.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<!-- end icon -->
</div>
</div>
{% endblock %}

View file

@ -49,6 +49,7 @@ function badbehavior:log()
if not ok then
return self:ret(false, "can't create increase timer : " .. err)
end
self:set_metric("counters", tostring(ngx.status), 1)
return self:ret(true, "success")
end

View file

@ -1 +1,13 @@
# Spoofing an action file
def badbehavior(**kwargs):
try:
# Here we will have a list { 'counter_403': X, 'counter_401': Y ... }
data = kwargs["app"].config["INSTANCES"].get_metrics("badbehavior")
format_data = []
# Format to fit [{code: 403, count: X}, {code: 401, count: Y} ...]
for key, value in data.items():
format_data[key] = {"code": int(key.split("_")[1]), "count": value}
return {"items": format_data}
except:
return {"items": []}

View file

@ -1,72 +1,31 @@
{% extends "base.html" %} {% block content %} {% set items = [{"code" : 400,
"count" : 24}, {"code" : 403, "count" : 845}, {"code" : 402, "count" : 12}]%}
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div class="col-span-12 grid grid-cols-12 gap-4">
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ bad_behavior_info or "Ban IP generating too much 'bad' HTTP status
code in a period of time." }}
</p>
</div>
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
></p>
</div>
</div>
<!-- end info -->
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
BAD BEHAVIOR
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ bad_behavior_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
>total ip bans</span
>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
aria-label="version"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="stroke-none scale-75 leading-none text-lg relative fill-white"
>
<path
fill-rule="evenodd"
d="m6.72 5.66 11.62 11.62A8.25 8.25 0 0 0 6.72 5.66Zm10.56 12.68L5.66 6.72a8.25 8.25 0 0 0 11.62 11.62ZM5.105 5.106c3.807-3.808 9.98-3.808 13.788 0 3.808 3.807 3.808 9.98 0 13.788-3.807 3.808-9.98 3.808-13.788 0-3.808-3.807-3.808-9.98 0-13.788Z"
clip-rule="evenodd"
/>
</svg>
</div>
<!-- end icon -->
</div>
{% if items|length != 0 %}
<div
class="2xl:col-span-4 3xl:col-span-3 w-full md:max-w-[350px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden 2xl:col-span-4 3xl:col-span-3 w-full md:max-w-[350px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -91,29 +50,41 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="code"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-6 m-0 my-1"
>
{{item['code']}}
</p>
></p>
<p
data-name="count"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-6 m-0 my-1"
>
{{item['count']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["code", "count"],
},
});
</script>
</div>
{% endblock %}

View file

@ -132,12 +132,14 @@ function blacklist:access()
if not ok then
self.logger:log(ERR, "error while checking cache : " .. cached)
elseif cached and cached ~= "ok" then
local data = self:get_data(cached)
self:set_metric("counters", "failed_" .. data.id, 1)
return self:ret(
true,
k .. " is in cached blacklist (info : " .. cached .. ")",
get_deny_status(),
nil,
self:get_data(cached)
data
)
end
if ok and cached then
@ -161,12 +163,14 @@ function blacklist:access()
self.logger:log(ERR, "error while adding element to cache : " .. err)
end
if blacklisted ~= "ok" then
local data = self:get_data(blacklisted)
self:set_metric("counters", "failed_" .. data.id, 1)
return self:ret(
true,
k .. " is blacklisted (info : " .. blacklisted .. ")",
get_deny_status(),
nil,
self:get_data(blacklisted)
data
)
end
end

View file

@ -1 +1,23 @@
# Spoofing an action file
def blacklist(**kwargs):
keys = [
"counter_blacklist_url",
"counter_blacklist_ip",
"counter_blacklist_rdns",
"counter_blacklist_asn",
"counter_blacklist_usa",
]
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("blacklist")
for key in keys:
if data.get(key) is None:
data[key] = 0
return data
except:
data = {}
for key in keys:
data[key] = 0
return data

View file

@ -1,4 +1,11 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<div class="col-span-12 grid grid-cols-12 gap-4">
@ -10,11 +17,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ blacklist_info or "Deny access based on internal and external
IP/network/rDNS/ASN blacklists." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -30,9 +35,7 @@
>
URL
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_url_count or "unknown" }}
</h5>
<h5 data-count-url class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -72,9 +75,7 @@
>
IP
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_ip_count or "unknown" }}
</h5>
<h5 data-count-ip class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -112,9 +113,7 @@
>
RDNS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_rdns_count or "unknown" }}
</h5>
<h5 data-count-rdns class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -160,9 +159,7 @@
>
ASN
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_asn_count or "unknown" }}
</h5>
<h5 data-count-asn class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -200,9 +197,7 @@
>
User Agent
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_user_agent_count or "unknown" }}
</h5>
<h5 data-count-user-agent class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -231,6 +226,41 @@
</div>
<!-- end icon -->
</div>
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_blacklist_url: {
el: document.querySelector("[data-count-url]"),
value: "unknown",
type: "text",
},
counter_blacklist_ip: {
el: document.querySelector("[data-count-ip]"),
value: "unknown",
type: "text",
},
counter_blacklist_rdns: {
el: document.querySelector("[data-count-rdns]"),
value: "unknown",
type: "text",
},
counter_blacklist_asn: {
el: document.querySelector("[data-count-asn]"),
value: "unknown",
type: "text",
},
counter_blacklist_ua: {
el: document.querySelector("[data-count-user-agent]"),
value: "unknown",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -10,6 +10,8 @@ local ngx = ngx
local ERR = ngx.ERR
local NOTICE = ngx.NOTICE
local WARN = ngx.WARN
local HTTP_INTERNAL_SERVER_ERROR = ngx.HTTP_INTERNAL_SERVER_ERROR
local HTTP_OK = ngx.HTTP_OK
local timer_at = ngx.timer.at
local get_phase = ngx.get_phase
local get_version = utils.get_version
@ -28,6 +30,7 @@ local open = io.open
local encode = cjson.encode
local decode = cjson.decode
local http_new = http.new
local match = string.match
function bunkernet:initialize(ctx)
-- Call parent initialize
@ -308,4 +311,28 @@ function bunkernet:report(ip, reason, reason_data, method, url, headers)
return self:request("POST", "/report", data)
end
function bunkernet:api()
-- Match request
if not match(self.ctx.bw.uri, "^/bunkernet/ping$") or self.ctx.bw.request_method ~= "POST" then
return self:ret(false, "success")
end
-- Check id
if not self.bunkernet_id then
return self:ret(true, "missing instance ID", HTTP_INTERNAL_SERVER_ERROR)
end
-- Send ping request
local ok, err, status, _ = self:ping()
if not ok then
return self:ret(true, "error while sending request to API : " .. err, HTTP_INTERNAL_SERVER_ERROR)
end
if status ~= 200 then
return self:ret(
true,
"received status " .. tostring(status) .. " from API using instance ID " .. self.bunkernet_id,
HTTP_INTERNAL_SERVER_ERROR
)
end
return self:ret(true, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful", HTTP_OK)
end
return bunkernet

View file

@ -1 +1,8 @@
# Spoofing an action file
def bunkernet():
return {
"message": "ok",
"data": {
"info": "test",
"status": "active",
},
}

View file

@ -1,59 +1,34 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- status -->
<div class="col-span-12 grid grid-cols-12 gap-4">
{% if bunkernet_status %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
data-status-svg
class="w-6 h-6"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-green-500"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p
data-status-text
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Active
</p>
></p>
</div>
{% else %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-red-500"
>
<path
fill-rule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
clip-rule="evenodd"
/>
</svg>
</div>
<p
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Inactive
</p>
</div>
{% endif %}
<!-- end status -->
</div>
@ -66,11 +41,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ bunkernet_info or "BunkerNet is a crowdsourced database of malicious
requests shared between all BunkerWeb instances over the world. " }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -180,6 +153,25 @@
</div>
</div>
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status-svg]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
});
</script>
<!-- end test -->
<script async>
function ping() {

View file

@ -56,6 +56,7 @@ function cors:header()
and self.variables["CORS_ALLOW_ORIGIN"] ~= "*"
and not regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"])
then
self:set_metric("counters", "failed_cors", 1)
self.logger:log(WARN, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
end

View file

@ -1 +1,11 @@
# Spoofing an action file
def cors(**kwargs):
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("cors")
if data.get("counter_failed_cors") is None:
data["counter_failed_cors"] = 0
return data
except:
return {"counter_failed_cors": 0}

View file

@ -1,5 +1,13 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
@ -9,11 +17,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ cors_info or "Cross-Origin Resource Sharing lets you manage how your
service can be contacted from different origins." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -28,9 +34,7 @@
>
CORS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ cors_count or "unknown" }}
</h5>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
@ -55,12 +59,27 @@
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
/>
</svg>
</div>
<!-- end icon -->
</div>
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_failed_cors: {
el: document.querySelector("[data-count]"),
value: "unknown",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -34,6 +34,7 @@ function country:access()
.. ")"
)
end
self:set_metric("counters", "failed_country", 1)
return self:ret(
true,
"client IP "
@ -85,6 +86,7 @@ function country:access()
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
self:set_metric("counters", "failed_country", 1)
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country_data .. ")",
@ -105,6 +107,7 @@ function country:access()
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
self:set_metric("counters", "failed_country", 1)
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country_data .. ")",

View file

@ -1 +1,22 @@
# Spoofing an action file
def country():
return {
"message": "ok",
"data": {
"info": "test",
"blacklist_count": 3,
"whitelist_count": 23,
},
}
def country(**kwargs):
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("country")
if data.get("counter_failed_country") is None:
data["counter_failed_country"] = 0
return data
except:
return {"counter_failed_country": 0}

View file

@ -1,22 +1,24 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div class="col-span-12 grid grid-cols-12 gap-4">
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ country_info or "The country security feature allows you to apply
policy based on the country of the IP address of clients (blacklist /
whitelist)." }}
</p>
</div>
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
></p>
</div>
</div>
<!-- end info -->
@ -31,13 +33,11 @@
>
Country
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ country_blacklist_count or "unknown" }}
</h5>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
>blacklist request blocked</span
>request blocked</span
>
</p>
</div>
@ -58,54 +58,27 @@
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
Country
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ country_blacklist_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
>whitelist request passed</span
>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-green-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-75 leading-none text-lg relative stroke-green-700 fill-white"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
</svg>
</div>
<!-- end icon -->
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_failed_country: {
el: document.querySelector("[data-count]"),
value: "",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -1 +0,0 @@
# Spoofing an action file

View file

@ -1,87 +0,0 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-4 2xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ custom_certificate_info or "Custom certificates allow you to get
HTTPS / SSL / TLS on your requests." }}
</p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
CUSTOM CERTIFICATE LIST
</h5>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<!-- list container-->
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
Server name
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
CN
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
Expiry date
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['server_name']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['cn']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['expire']}}
</p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1 +1,10 @@
# Spoofing an action file
def db():
return {
"message": "ok",
"data": {
"info": "test",
"driver": "SQLite",
"version": "13.2",
"size": "14.8",
},
}

View file

@ -1,4 +1,11 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -10,50 +17,47 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
></p>
</div>
</div>
<!-- driver-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">DB</h5>
<div class="mx-1 flex justify-start items-center mt-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
{{ db_info or "BunkerWeb securely stores its current configuration in
a backend database, which contains essential data for smooth
operation." }}
DRIVER
<span
data-driver
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
</span>
</p>
</div>
<div class="mx-1 flex justify-start items-center mt-1 mb-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
VERSION
<span
data-version
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
</span>
</p>
</div>
</div>
<!-- end driver -->
</div>
<!-- end info -->
<!-- driver-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">DB</h5>
<div class="mx-1 flex justify-start items-center mt-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
DRIVER
<span
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ db_driver or "unknown" }}
</span>
</p>
</div>
<div class="mx-1 flex justify-start items-center mt-1 mb-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
VERSION
<span
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ db_version or "unknown" }}
</span>
</p>
</div>
</div>
<!-- end driver -->
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
@ -64,9 +68,7 @@
>
SIZE
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ db_count or "unknown" }}
</h5>
<h5 data-size class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
@ -102,6 +104,31 @@
</div>
<!-- end icon -->
</div>
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
driver: {
el: document.querySelector("[data-driver]"),
value: "",
type: "text",
},
version: {
el: document.querySelector("[data-version]"),
value: "",
type: "text",
},
size: {
el: document.querySelector("[data-size]"),
value: "",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -91,6 +91,7 @@ function dnsbl:access()
if cached == "ok" then
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (not blacklisted)")
end
self:set_metric("counters", "failed_dnsbl", 1)
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",

View file

@ -1 +1,11 @@
# Spoofing an action file
def dnsbl(**kwargs):
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("dnsbl")
if data.get("counter_failed_dnsbl") is None:
data["counter_failed_dnsbl"] = 0
return data
except:
return {"counter_failed_dnsbl": 0}

View file

@ -1,6 +1,11 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "status" : "ok"}, {"server_name" : "app1.com", "status" :
"ok"}, {"server_name" : "test.2.fr", "status" : "ko"} ]%}
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -11,87 +16,70 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ dnsbl_info or "Deny access based on external DNSBL servers." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">DNSBL LIST</h5>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<!-- list container-->
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-8 m-0 pb-2 border-b border-gray-400"
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
DNSBL
</p>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
>request blocked</span
>
Server name
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
Status
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
>
{{item['server_name']}}
</p>
{% if item['status'] == "ko"%}
<div class="col-span-4 ml-2 m-0 my-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6 fill-red-500"
>
<path
fill-rule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
clip-rule="evenodd"
/>
</svg>
</div>
{% else %}
<div class="col-span-4 ml-2 m-0 my-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6 fill-green-500"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
</svg>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="scale-75 leading-none text-lg relative fill-red-700 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
/>
</svg>
</div>
<!-- end icon -->
</div>
{% endif %}
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_failed_dnsbl: {
el: document.querySelector("[data-count]"),
value: "unknown",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -3,6 +3,7 @@ local plugin = require "bunkerweb.plugin"
local ngx = ngx
local subsystem = ngx.config.subsystem
local tostring = tostring
local template
local render = nil
@ -69,6 +70,11 @@ function errors:initialize(ctx)
}
end
function errors:log()
self:set_metric("counters", tostring(ngx.status), 1)
return self:ret(true, "success")
end
function errors:render_template(code)
-- Render template
render("error.html", {

View file

@ -1 +1,13 @@
# Spoofing an action file
def errors(**kwargs):
try:
# Here we will have a list { 'counter_403': X, 'counter_401': Y ... }
data = kwargs["app"].config["INSTANCES"].get_metrics("errors")
format_data = []
# Format to fit [{code: 403, count: X}, {code: 401, count: Y} ...]
for key, value in data.items():
format_data[key] = {"code": int(key.split("_")[1]), "count": value}
return {"items": format_data}
except:
return {"items": []}

View file

@ -1,6 +1,11 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"count" : 74,
"code" : "403"}, {"count" : 82, "code" : "404"}, {"count" : "32", "code" :
"400"} ]%}
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -11,17 +16,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ dnsbl_info or "Deny access based on external DNSBL servers." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full md:max-w-[400px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full md:max-w-[400px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">ERRORS LIST</h5>
@ -43,29 +47,42 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="code"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
>
{{item['code']}}
</p>
></p>
<p
data-name="count"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['count']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["code", "count"],
},
});
</script>
</div>
{% endblock %}

View file

@ -151,6 +151,7 @@ function greylist:access()
end
-- Return
self:set_metric("counters", "failed_greylist", 1)
return self:ret(true, "not in greylist", get_deny_status())
end

View file

@ -1 +1,11 @@
# Spoofing an action file
def greylist(**kwargs):
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("greylist")
if data.get("counter_failed_greylist") is None:
data["counter_failed_greylist"] = 0
return data
except:
return {"counter_failed_greylist": 0}

View file

@ -1,236 +1,83 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
></p>
</div>
</div>
<!-- end info -->
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
GREYLIST
</p>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
>request blocked</span
>
{{ greylist_info or "Allow access while keeping security features
based on internal and external IP/network/rDNS/ASN greylists. " }}
</p>
</div>
</div>
<!-- end info -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
URL
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_url_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-600"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.6] leading-none text-lg relative fill-white"
stroke-width="1.5"
stroke="currentColor"
class="scale-75 leading-none text-lg relative fill-red-700 stroke-white"
>
<path
fill-rule="evenodd"
d="M19.902 4.098a3.75 3.75 0 0 0-5.304 0l-4.5 4.5a3.75 3.75 0 0 0 1.035 6.037.75.75 0 0 1-.646 1.353 5.25 5.25 0 0 1-1.449-8.45l4.5-4.5a5.25 5.25 0 1 1 7.424 7.424l-1.757 1.757a.75.75 0 1 1-1.06-1.06l1.757-1.757a3.75 3.75 0 0 0 0-5.304Zm-7.389 4.267a.75.75 0 0 1 1-.353 5.25 5.25 0 0 1 1.449 8.45l-4.5 4.5a5.25 5.25 0 1 1-7.424-7.424l1.757-1.757a.75.75 0 1 1 1.06 1.06l-1.757 1.757a3.75 3.75 0 1 0 5.304 5.304l4.5-4.5a3.75 3.75 0 0 0-1.035-6.037.75.75 0 0 1-.354-1Z"
clip-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
IP
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_ip_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-lime-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-50 leading-none text-lg relative fill-white"
>
<path
d="M3.53 2.47a.75.75 0 0 0-1.06 1.06l18 18a.75.75 0 1 0 1.06-1.06l-18-18ZM20.25 5.507v11.561L5.853 2.671c.15-.043.306-.075.467-.094a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93ZM3.75 21V6.932l14.063 14.063L12 18.088l-7.165 3.583A.75.75 0 0 1 3.75 21Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
RDNS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_rdns_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-indigo-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.6] leading-none text-lg relative fill-white"
>
<path
d="M11.625 16.5a1.875 1.875 0 1 0 0-3.75 1.875 1.875 0 0 0 0 3.75Z"
/>
<path
fill-rule="evenodd"
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm6 16.5c.66 0 1.277-.19 1.797-.518l1.048 1.048a.75.75 0 0 0 1.06-1.06l-1.047-1.048A3.375 3.375 0 1 0 11.625 18Z"
clip-rule="evenodd"
/>
<path
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
ASN
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_asn_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-blue-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.6] leading-none text-lg relative fill-white"
>
<path
d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
User Agent
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_user_agent_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-amber-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-50 leading-none text-lg relative fill-white"
>
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd"
/>
</svg>
</div>
<!-- end icon -->
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_failed_greylist: {
el: document.querySelector("[data-count]"),
value: "unknown",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -1 +0,0 @@
# Spoofing an action file

View file

@ -1,86 +0,0 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-4 2xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ lets_encrypt_info or "Let's Encrypt certificates for secure HTTP
requests." }}
</p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
LET'S ENCRYPT LIST
</h5>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<!-- list container-->
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
Server name
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
CN
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
Expiry date
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['server_name']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['cn']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['expire']}}
</p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -158,6 +158,7 @@ function limit:access()
end
-- Limit reached
if limited then
self:set_metric("counters", "limited_uri_" .. self.ctx.bw.uri, 1)
return self:ret(
true,
"client IP "

View file

@ -1 +1,13 @@
# Spoofing an action file
def limit(**kwargs):
try:
# Here we will have a list { 'limit_uri_url1': X, 'limit_uri_url2': Y ... }
data = kwargs["app"].config["INSTANCES"].get_metrics("limit")
format_data = []
# Format to fit [{url: "url1", count: X}, {url: "url2", count: Y} ...]
for key, value in data.items():
format_data[key] = {"url": key.replace("limit_uri_", ""), "count": value}
return {"items": format_data}
except:
return {"items": []}

View file

@ -1,6 +1,11 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"url" :
"http://www.example.com", "count" : 24},{"url" : "http://www.example.com",
"count" : 24} ]%}
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -11,17 +16,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ limit_info or "Limit maximum number of requests and connections." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -46,29 +50,41 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="url"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
>
{{item['url']}}
</p>
></p>
<p
data-name="count"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['count']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["url", "count"],
},
});
</script>
</div>
{% endblock %}

View file

@ -3,22 +3,36 @@ local class = require "middleclass"
local datastore = require "bunkerweb.datastore"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local lrucache = require "resty.lrucache"
local metrics = class("metrics", plugin)
local lru, err_lru = lrucache.new(100000)
if not lru then
require "bunkerweb.logger":new("METRICS"):log(ERR, "failed to instantiate LRU cache : " .. err_lru)
end
local ngx = ngx
local shared = ngx.shared
local subsystem = ngx.config.subsystem
local ERR = ngx.ERR
local HTTP_INTERNAL_SERVER_ERROR = ngx.HTTP_INTERNAL_SERVER_ERROR
local HTTP_OK = ngx.HTTP_OK
local worker = ngx.worker
local worker_id = worker.id
local get_reason = utils.get_reason
local get_country = utils.get_country
local has_variable = utils.has_variable
local encode = cjson.encode
local decode = cjson.decode
local match = string.match
local time = os.time
local tonumber = tonumber
local tostring = tostring
local table_insert = table.insert
local table_remove = table.remove
function metrics:initialize(ctx)
-- Call parent initialize
@ -32,9 +46,9 @@ function metrics:initialize(ctx)
self.metrics_datastore = datastore:new(dict)
end
function metrics:log()
function metrics:log(bypass_checks)
-- Don't go further if metrics is not enabled
if self.variables["USE_METRICS"] == "no" then
if not bypass_checks and self.variables["USE_METRICS"] == "no" then
return self:ret(true, "metrics are disabled")
end
-- Store blocked requests
@ -50,58 +64,169 @@ function metrics:log()
end
end
local request = {
date = os.time(),
date = self.ctx.bw.start_time or time(),
ip = self.ctx.bw.remote_addr,
country = country,
method = self.ctx.bw.request_method,
url = self.ctx.bw.request_uri,
status = ngx.status,
["user-agent"] = self.ctx.bw.http_user_agent or "",
user_agent = self.ctx.bw.http_user_agent or "",
reason = reason,
data = data,
}
local ok
ok, err = self.metrics_datastore:safe_rpush("metrics_requests", encode(request))
if not ok then
self.logger:log(ERR, "can't save request to datastore : " .. err)
-- Get current requests
local requests = lru:get("requests")
if not requests then
requests = {}
end
-- Add current request
table_insert(requests, request)
-- Remove old requests
local nb_delete = #requests - tonumber(self.variables["METRICS_MAX_BLOCKED_REQUESTS"])
while nb_delete > 0 do
table_remove(requests, 1)
nb_delete = nb_delete - 1
end
-- Update worker cache
lru:set("requests", requests)
end
-- Get metrics from plugins
local all_metrics = self.ctx.bw.metrics
if all_metrics then
-- Loop on plugins
for plugin, plugin_metrics in pairs(all_metrics) do
-- Loop on kinds
for kind, kind_metrics in pairs(plugin_metrics) do
-- Increment counters
if kind == "counters" then
for metric_key, metric_value in pairs(kind_metrics) do
local lru_key = plugin .. "_counter_" .. metric_key
local metric_counter = lru:get(lru_key)
if not metric_counter then
metric_counter = metric_value
else
metric_counter = metric_counter + metric_value
end
lru:set(lru_key, metric_counter)
end
end
end
end
end
return self:ret(true, "success")
end
function metrics:log_default()
return self:log()
local is_needed, err = has_variable("USE_METRICS", "yes")
if is_needed == nil then
return self:ret(false, "can't check USE_METRICS variable : " .. err)
end
if is_needed then
return self:log(true)
end
return self:ret(true, "metrics not used")
end
function metrics:timer()
-- Check if metrics is used
local is_needed, err = has_variable("USE_METRICS", "yes")
if is_needed == nil then
return self:ret(false, "can't check USE_METRICS variable : " .. err)
end
if not is_needed then
return self:ret(true, "metrics not used")
end
local ret = true
local ret_err = "metrics updated"
local wid = tostring(worker_id())
-- Purpose of following code is to populate the LRU cache.
-- In case of a reload, everything in LRU cache is removed
-- so we need to copy it from SHM cache if it exists.
local setup = lru:get("setup")
if not setup then
for _, key in ipairs(self.metrics_datastore:keys()) do
if key:match("_" .. wid .. "$") then
local value, err = self.metrics_datastore:get(key)
if not value and err ~= "not found" then
ret = false
ret_err = err
self.logger:log(ERR, "error while checking " .. key .. " : " .. err)
end
if value then
local ok, decoded = pcall(decode, value)
if ok then
value = decoded
end
lru:set(key:gsub("_" .. wid .. "$", ""), value)
end
end
end
lru:set("setup", true)
end
-- Loop on all keys
for _, key in ipairs(lru:get_keys()) do
-- Get LRU data
local value = lru:get(key)
if type(value) == "table" then
value = encode(value)
end
-- Push to dict
local ok, err = self.metrics_datastore:set(key .. "_" .. wid, value)
if not ok then
ret = false
ret_err = err
self.logger:log(ERR, "can't update " .. key .. "_" .. wid .. " : " .. err)
end
end
-- Done
return self:ret(ret, ret_err)
end
function metrics:api()
-- Match request
if not match(self.ctx.bw.uri, "^/metrics/requests$") or self.ctx.bw.request_method ~= "GET" then
if not match(self.ctx.bw.uri, "^/metrics/.+$") or self.ctx.bw.request_method ~= "GET" then
return self:ret(false, "success")
end
-- Get requests metrics
local len, err = self.metrics_datastore:llen("metrics_requests")
if not len then
return self:ret(true, "error while getting length of metrics_requests : " .. err, HTTP_INTERNAL_SERVER_ERROR)
end
local i = 0
local data = {}
while i < len do
local request
request, err = self.metrics_datastore:lpop("metrics_requests")
if request then
table.insert(data, decode(request))
else
return self:ret(true, "error while getting metrics_requests : " .. err, HTTP_INTERNAL_SERVER_ERROR)
-- Extract filter parameter
local filter = self.ctx.bw.uri:gsub("^/metrics/", "")
-- Loop on keys
local metrics_data = {}
for _, key in ipairs(self.metrics_datastore:keys()) do
-- Check if key starts with our filter
if key:match("^" .. filter .. "_") then
-- Get the value
local data, err = self.metrics_datastore:get(key)
if not data then
return self:ret(true, "error while fetching requests : " .. err, HTTP_INTERNAL_SERVER_ERROR)
end
local metric_key = key:gsub("_[0-9]+$", ""):gsub("^" .. filter .. "_", "")
if metric_key == "" then
metric_key = filter
end
-- Table case
local ok, decoded = pcall(decode, data)
if ok then
data = decoded
end
if type(data) == "table" then
if not metrics_data[metric_key] then
metrics_data[metric_key] = {}
end
for _, metric_value in ipairs(data) do
table_insert(metrics_data[metric_key], metric_value)
end
-- Counter case
else
if not metrics_data[metric_key] then
metrics_data[metric_key] = 0
end
metrics_data[metric_key] = metrics_data[metric_key] + data
end
end
local ok
ok, err = self.metrics_datastore:safe_rpush("metrics_requests", request)
if not ok then
self.logger:log(ERR, "can't save request to datastore : " .. err)
end
i = i + 1
end
return self:ret(true, data, HTTP_OK)
return self:ret(true, metrics_data, HTTP_OK)
end
return metrics

View file

@ -6,7 +6,7 @@
"stream": "partial",
"settings": {
"USE_METRICS": {
"context": "global",
"context": "multisite",
"default": "yes",
"help": "Enable collection and retrieval of internal metrics.",
"id": "use-metrics",
@ -22,6 +22,15 @@
"label": "Metrics memory size",
"regex": "^\\d+[kKmMgG]?$",
"type": "text"
},
"METRICS_MAX_BLOCKED_REQUESTS": {
"context": "global",
"default": "100",
"help": "Maximum number of blocked requests to store (per worker).",
"id": "metrics-max-blocked-requests",
"label": "Metrics max blocked requests",
"regex": "^\\d+$",
"type": "text"
}
}
}

View file

@ -23,10 +23,18 @@ function misc:access()
-- Check if method is allowed
for allowed_method in self.variables["ALLOWED_METHODS"]:gmatch("[^|]+") do
if method == allowed_method then
self:set_metric("counters", "failed_method", 1)
return self:ret(true, "method " .. method .. " is allowed")
end
end
return self:ret(true, "method " .. method .. " is not allowed", HTTP_NOT_ALLOWED)
end
function misc:log_default()
if self.variables["DISABLE_DEFAULT_SERVER"] == "yes" then
self:set_metric("counters", "failed_default", 1)
end
return self:ret(true, "success")
end
return misc

View file

@ -1 +1,14 @@
# Spoofing an action file
def misc(**kwargs):
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("misc")
if "counter_failed_default" not in data:
data["counter_failed_default"] = 0
if "counter_failed_method" not in data:
data["counter_failed_method"] = 0
return data
except:
return {"counter_failed_default": 0, "counter_failed_method": 0}

View file

@ -1,4 +1,11 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<div class="col-span-12 grid grid-cols-12 gap-4">
@ -10,10 +17,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ misc_info or "Miscellaneous settings (methods, servers...)." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -29,9 +35,10 @@
>
DEFAULT SERVER DISABLED
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ default_server_count or "unknown" }}
</h5>
<h5
data-count-server-disabled
class="mb-1 font-bold dark:text-white/90"
></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
@ -74,9 +81,10 @@
>
DISALLOWED METHODS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ disallowed_methods_count or "unknown" }}
</h5>
<h5
data-count-disallowed-methods
class="mb-1 font-bold dark:text-white/90"
></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
@ -103,6 +111,26 @@
</div>
<!-- end icon -->
</div>
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_failed_default: {
el: document.querySelector("[data-count-server-disabled]"),
value: "unknown",
type: "text",
},
counter_failed_method: {
el: document.querySelector("[data-count-disallowed-methods]"),
value: "unknown",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -1 +0,0 @@
# Spoofing an action file

View file

@ -1,64 +0,0 @@
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ modsec_info or "ModSecurity is an open source, cross platform web application firewall (WAF) engine for Apache, IIS and Nginx that is developed by Trustwave's SpiderLabs." }}
</p>
</div>
</div>
<!-- end info -->
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
MODSECURITY
</p>
<h5 class="mb-1 font-bold dark:text-white/90">{{ modsec_count or "unknown" }}</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
>request blocked
</span
>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="scale-75 leading-none text-lg relative fill-red-700 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
</div>
{% endblock %}

View file

@ -32,7 +32,7 @@
"antibot"
],
"headers": ["headers", "cors", "reverseproxy", "clientcache", "antibot"],
"log": ["badbehavior", "bunkernet", "metrics"],
"log": ["badbehavior", "bunkernet", "errors", "metrics"],
"preread": [
"whitelist",
"blacklist",
@ -42,5 +42,6 @@
"reversescan"
],
"log_stream": ["badbehavior", "bunkernet"],
"log_default": ["badbehavior", "bunkernet", "metrics"]
"log_default": ["badbehavior", "bunkernet", "errors", "misc", "metrics"],
"timer": ["metrics"]
}

View file

@ -5,6 +5,9 @@ local redis = class("redis", plugin)
local ngx = ngx
local NOTICE = ngx.NOTICE
local HTTP_INTERNAL_SERVER_ERROR = ngx.HTTP_INTERNAL_SERVER_ERROR
local HTTP_OK = ngx.HTTP_OK
local match = string.match
function redis:initialize(ctx)
-- Call parent initialize
@ -34,4 +37,46 @@ function redis:init_worker()
return self:ret(true, "success")
end
function redis:api()
if self.ctx.bw.uri == "/redis/ping" and self.ctx.bw.request_method == "POST" then
-- Check redis connection
local ok, err = self.clusterstore:connect(true)
if not ok then
return self:ret(true, "redis connect error : " .. err, HTTP_INTERNAL_SERVER_ERROR)
end
-- Send ping
local ok, err = self.clusterstore:call("ping")
self.clusterstore:close()
if err then
return self:ret(true, "error while sending ping command to redis server : " .. err, HTTP_INTERNAL_SERVER_ERROR)
end
if not ok then
return self:ret(true, "redis ping command failed", HTTP_INTERNAL_SERVER_ERROR)
end
return self:ret(true, "success", HTTP_OK)
end
if self.ctx.bw.uri == "/redis/stats" and self.ctx.bw.request_method == "GET" then
-- Connect to redis
local ok, err = self.clusterstore:connect(true)
if not ok then
return self:ret(true, "redis connect error : " .. err, HTTP_INTERNAL_SERVER_ERROR)
end
-- Get number of keys
local nb_keys, err = self.clusterstore:call("dbsize")
self.clusterstore:close()
if err then
return self:ret(true, "error while sending dbsize command to redis server : " .. err, HTTP_INTERNAL_SERVER_ERROR)
end
if not ok then
return self:ret(true, "redis dbsize command failed", HTTP_INTERNAL_SERVER_ERROR)
end
-- Return data
local data = {
redis_nb_keys = nb_keys
}
return self:ret(true, data, HTTP_OK)
end
return self:ret(false, "success")
end
return redis

View file

@ -1 +1,8 @@
# Spoofing an action file
def redis():
return {
"message": "ok",
"data": {
"info": "test",
"status": "active",
},
}

View file

@ -1,60 +1,34 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- status -->
<div class="col-span-12 grid grid-cols-12 gap-4">
{% if redis_status %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
data-status-svg
class="w-6 h-6"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-green-500"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p
data-status-text
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Active
</p>
></p>
</div>
{% else %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-red-500"
>
<path
fill-rule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
clip-rule="evenodd"
/>
</svg>
</div>
<p
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Inactive
</p>
</div>
{% endif %}
<!-- end status -->
</div>
@ -66,11 +40,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ redis_info or "Redis server configuration when using BunkerWeb in
cluster mode. " }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -179,7 +151,25 @@
</div>
</div>
</div>
<!-- end test -->
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status-svg]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
});
</script>
<script async>
function ping() {
let data = new FormData();

View file

@ -37,6 +37,7 @@ function reversescan:access()
elseif cached == "open" then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
self:set_metric("counters", "failed_" .. port, 1)
break
-- Perform scan in a thread
elseif not cached then
@ -99,6 +100,7 @@ function reversescan:access()
if open then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
self:set_metric("counters", "failed_" .. port, 1)
break
end
end

View file

@ -1 +1,13 @@
# Spoofing an action file
def reversescan(**kwargs):
try:
# Here we will have a list { 'counter_403': X, 'counter_401': Y ... }
data = kwargs["app"].config["INSTANCES"].get_metrics("reversescan")
format_data = []
# Format to fit [{code: 403, count: X}, {code: 401, count: Y} ...]
for key, value in data.items():
format_data[key] = {"port": int(key.split("_")[1]), "count": value}
return {"items": format_data}
except:
return {"items": []}

View file

@ -1,6 +1,11 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"port" : 4000,
"count" : 400}, {"port" : 4400, "count" : 780}, {"port" : 5000, "count" : 40},
]%}
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -11,21 +16,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ reversescan_info or "Reverse scan is a feature designed to detect
open ports by establishing TCP connections with clients' IP addresses.
Consider adding this feature if you want to detect possible open proxies
or connections from servers." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[500px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[500px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -50,29 +50,41 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="port"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-5 m-0 my-1"
>
{{item['port']}}
</p>
></p>
<p
data-name="count"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-7 m-0 my-1"
>
{{item['count']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["port", "count"],
},
});
</script>
</div>
{% endblock %}

View file

@ -1 +0,0 @@
# Spoofing an action file

View file

@ -1,86 +0,0 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-4 2xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ lets_encrypt_info or "Selfsigned certificates for secure HTTP
requests." }}
</p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
SELFSIGNED LIST
</h5>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<!-- list container-->
<div class="min-w-[400px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
Server name
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
CN
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-4 m-0 pb-2 border-b border-gray-400"
>
Expiry date
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['server_name']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['cn']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['expire']}}
</p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1,4 +1,5 @@
{% if USE_UI == "yes" +%}
SecRule REQUEST_FILENAME "@rx /services$" "id:7771,ctl:ruleRemoveByTag=attack-rce,ctl:ruleRemoveByTag=attack-xss,ctl:ruleRemoveByTag=attack-generic,nolog"
SecRule REQUEST_FILENAME "@rx /global_config$" "id:7772,ctl:ruleRemoveByTag=platform-pgsql,nolog"
SecRule REQUEST_FILENAME "@rx /configs$" "id:7773,ctl:ruleRemoveByTag=language-shell,ctl:ruleRemoveByTag=attack-lfi,nolog"
{% endif +%}

View file

@ -1 +1,11 @@
# Spoofing an action file
def whitelist(**kwargs):
try:
data = kwargs["app"].config["INSTANCES"].get_metrics("whitelist")
if "counter_passed_whitelist" not in data:
data["counter_passed_whitelist"] = 0
return data
except:
return {"counter_passed_whitelist": 0}

View file

@ -1,60 +1,76 @@
{% extends "base.html" %} {% block content %}
<input
type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden
/>
<div class="col-span-12 grid grid-cols-12 gap-4">
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<!-- info-->
<div
class="h-fit transition hover:scale-102 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
></p>
</div>
</div>
<!-- end info -->
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
WHITELIST
</p>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
>request passed</span
>
{{ whitelist_info or "Allow access based on internal and external
IP/network/rDNS/ASN whitelists." }}
</p>
</div>
</div>
<!-- end info -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
URL
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_url_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-red-600"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-green-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m4.5 12.75 6 6 9-13.5"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.6] leading-none text-lg relative fill-white"
class="scale-75 leading-none text-lg relative fill-white"
>
<path
fill-rule="evenodd"
d="M19.902 4.098a3.75 3.75 0 0 0-5.304 0l-4.5 4.5a3.75 3.75 0 0 0 1.035 6.037.75.75 0 0 1-.646 1.353 5.25 5.25 0 0 1-1.449-8.45l4.5-4.5a5.25 5.25 0 1 1 7.424 7.424l-1.757 1.757a.75.75 0 1 1-1.06-1.06l1.757-1.757a3.75 3.75 0 0 0 0-5.304Zm-7.389 4.267a.75.75 0 0 1 1-.353 5.25 5.25 0 0 1 1.449 8.45l-4.5 4.5a5.25 5.25 0 1 1-7.424-7.424l1.757-1.757a.75.75 0 1 1 1.06 1.06l-1.757 1.757a3.75 3.75 0 1 0 5.304 5.304l4.5-4.5a3.75 3.75 0 0 0-1.035-6.037.75.75 0 0 1-.354-1Z"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
</svg>
@ -62,175 +78,20 @@
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
IP
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_ip_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-lime-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-50 leading-none text-lg relative fill-white"
>
<path
d="M3.53 2.47a.75.75 0 0 0-1.06 1.06l18 18a.75.75 0 1 0 1.06-1.06l-18-18ZM20.25 5.507v11.561L5.853 2.671c.15-.043.306-.075.467-.094a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93ZM3.75 21V6.932l14.063 14.063L12 18.088l-7.165 3.583A.75.75 0 0 1 3.75 21Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
RDNS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_rdns_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-indigo-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.6] leading-none text-lg relative fill-white"
>
<path
d="M11.625 16.5a1.875 1.875 0 1 0 0-3.75 1.875 1.875 0 0 0 0 3.75Z"
/>
<path
fill-rule="evenodd"
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm6 16.5c.66 0 1.277-.19 1.797-.518l1.048 1.048a.75.75 0 0 0 1.06-1.06l-1.047-1.048A3.375 3.375 0 1 0 11.625 18Z"
clip-rule="evenodd"
/>
<path
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
ASN
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_asn_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-blue-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.6] leading-none text-lg relative fill-white"
>
<path
d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
<div
class="h-fit dark:brightness-110 max-h-none sm:max-h-28 hover:scale-102 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<!-- text -->
<div>
<p
class="mb-2 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-60"
>
User Agent
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_user_agent_count or "unknown" }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-amber-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-50 leading-none text-lg relative fill-white"
>
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
clip-rule="evenodd"
/>
</svg>
</div>
<!-- end icon -->
</div>
<script>
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
el: document.querySelector("[data-info]"),
value: "{{ plugin['description'] or ''}}",
type: "text",
},
counter_passed_whitelist: {
el: document.querySelector("[data-count]"),
value: "unknown",
type: "text",
},
});
</script>
</div>
{% endblock %}

View file

@ -137,6 +137,7 @@ function whitelist:access()
ngx_var.is_whitelisted = "yes"
self.ctx.bw.is_whitelisted = "yes"
env_set("is_whitelisted", "yes")
self:set_metric("counters", "passed_whitelist", 1)
return self:ret(true, err, OK)
end
-- Perform checks
@ -155,7 +156,8 @@ function whitelist:access()
ngx_var.is_whitelisted = "yes"
self.ctx.bw.is_whitelisted = "yes"
env_set("is_whitelisted", "yes")
return self:ret(true, k .. " is whitelisted (info : " .. whitelisted .. ")", ngx.OK)
self:set_metric("counters", "passed_whitelist", 1)
return self:ret(true, k .. " is whitelisted (info : " .. whitelisted .. ")", OK)
end
end
end

View file

@ -274,7 +274,7 @@ class Database:
def get_metadata(self) -> Dict[str, str]:
"""Get the metadata from the database"""
data = {"version": "1.5.4", "integration": "unknown"}
data = {"version": "1.5.6", "integration": "unknown"}
with self.__db_session() as session:
with suppress(ProgrammingError, OperationalError):
metadata = session.query(Metadata).with_entities(Metadata.version, Metadata.integration).filter_by(id=1).first()

View file

@ -1,4 +1,4 @@
cryptography==42.0.1
cryptography==42.0.2
psycopg[binary,pool]==3.1.17
PyMySQL==1.1.0
sqlalchemy==2.0.25

View file

@ -58,39 +58,39 @@ cffi==1.16.0 \
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
# via cryptography
cryptography==42.0.1 \
--hash=sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4 \
--hash=sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6 \
--hash=sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e \
--hash=sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881 \
--hash=sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323 \
--hash=sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77 \
--hash=sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824 \
--hash=sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407 \
--hash=sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60 \
--hash=sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411 \
--hash=sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14 \
--hash=sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0 \
--hash=sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04 \
--hash=sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77 \
--hash=sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302 \
--hash=sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c \
--hash=sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986 \
--hash=sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa \
--hash=sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11 \
--hash=sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8 \
--hash=sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc \
--hash=sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49 \
--hash=sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035 \
--hash=sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca \
--hash=sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7 \
--hash=sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2 \
--hash=sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f \
--hash=sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404 \
--hash=sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453 \
--hash=sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009 \
--hash=sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa \
--hash=sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7
cryptography==42.0.2 \
--hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \
--hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \
--hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \
--hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \
--hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \
--hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \
--hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \
--hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \
--hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \
--hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \
--hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \
--hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \
--hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \
--hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \
--hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \
--hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \
--hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \
--hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \
--hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \
--hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \
--hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \
--hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \
--hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \
--hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \
--hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \
--hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \
--hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \
--hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \
--hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \
--hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \
--hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \
--hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f
# via -r requirements.in
greenlet==3.0.3 \
--hash=sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67 \

View file

@ -298,9 +298,9 @@ six==1.16.0 \
# via
# kubernetes
# python-dateutil
urllib3==2.1.0 \
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
urllib3==2.2.0 \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
# via
# docker
# kubernetes

View file

@ -181,9 +181,9 @@ toposort==1.10 \
--hash=sha256:bfbb479c53d0a696ea7402601f4e693c97b0367837c8898bc6471adfca37a6bd \
--hash=sha256:cbdbc0d0bee4d2695ab2ceec97fe0679e9c10eab4b2a87a9372b929e70563a87
# via pip-compile-multi
urllib3==2.1.0 \
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
urllib3==2.2.0 \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
# via requests
wheel==0.42.0 \
--hash=sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d \

View file

@ -1,6 +1,6 @@
certbot==2.8.0
configobj==5.0.8
cryptography==42.0.1
cryptography==42.0.2
maxminddb==2.5.2
python-magic==0.4.27
schedule==1.2.1

View file

@ -172,39 +172,39 @@ configobj==5.0.8 \
# via
# -r requirements.in
# certbot
cryptography==42.0.1 \
--hash=sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4 \
--hash=sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6 \
--hash=sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e \
--hash=sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881 \
--hash=sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323 \
--hash=sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77 \
--hash=sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824 \
--hash=sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407 \
--hash=sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60 \
--hash=sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411 \
--hash=sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14 \
--hash=sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0 \
--hash=sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04 \
--hash=sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77 \
--hash=sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302 \
--hash=sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c \
--hash=sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986 \
--hash=sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa \
--hash=sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11 \
--hash=sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8 \
--hash=sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc \
--hash=sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49 \
--hash=sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035 \
--hash=sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca \
--hash=sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7 \
--hash=sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2 \
--hash=sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f \
--hash=sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404 \
--hash=sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453 \
--hash=sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009 \
--hash=sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa \
--hash=sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7
cryptography==42.0.2 \
--hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \
--hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \
--hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \
--hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \
--hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \
--hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \
--hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \
--hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \
--hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \
--hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \
--hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \
--hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \
--hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \
--hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \
--hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \
--hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \
--hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \
--hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \
--hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \
--hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \
--hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \
--hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \
--hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \
--hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \
--hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \
--hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \
--hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \
--hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \
--hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \
--hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \
--hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \
--hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f
# via
# -r requirements.in
# acme
@ -303,9 +303,9 @@ python-magic==0.4.27 \
--hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
--hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
# via -r requirements.in
pytz==2023.3.post1 \
--hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \
--hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7
pytz==2023.4 \
--hash=sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40 \
--hash=sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a
# via
# acme
# certbot
@ -328,9 +328,9 @@ six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via configobj
urllib3==2.1.0 \
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
urllib3==2.2.0 \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
# via requests
zipp==3.17.0 \
--hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \

View file

@ -41,7 +41,6 @@ from tarfile import CompressionError, HeaderError, ReadError, TarError, open as
from threading import Thread
from tempfile import NamedTemporaryFile
from time import sleep, time
from traceback import format_exc
from zipfile import BadZipFile, ZipFile
from src.Instances import Instances
@ -191,7 +190,6 @@ try:
WTF_CSRF_SSL_STRICT=False,
USER=USER,
SEND_FILE_MAX_AGE_DEFAULT=86400,
PLUGIN_ARGS={},
RELOADING=False,
LAST_RELOAD=0,
TO_FLASH=[],
@ -1188,21 +1186,6 @@ def plugins():
return redirect(url_for("loading", next=url_for("plugins"), message="Reloading plugins"))
plugin_args = app.config["PLUGIN_ARGS"]
app.config["PLUGIN_ARGS"] = {}
if request.args.get("plugin_id", False):
plugin_id = request.args.get("plugin_id")
page = db.get_plugin_template(plugin_id)
if page:
return render_template(
Environment(loader=FileSystemLoader(join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")).from_string(page.decode("utf-8")),
dark_mode=app.config["DARK_MODE"],
username=current_user.get_id(),
**(app.jinja_env.globals | (plugin_args["args"] if plugin_args.get("plugin", None) == plugin_id else {})),
)
plugins = app.config["CONFIG"].get_plugins()
plugins_internal = 0
plugins_external = 0
@ -1218,7 +1201,6 @@ def plugins():
plugins=plugins,
plugins_internal=plugins_internal,
plugins_external=plugins_external,
plugins_errors=db.get_plugins_errors(),
username=current_user.get_id(),
dark_mode=app.config["DARK_MODE"],
)
@ -1276,22 +1258,44 @@ def upload_plugin():
@app.route("/plugins/<plugin>", methods=["GET", "POST"])
@login_required
def custom_plugin(plugin):
def custom_plugin(plugin: str):
plugins = app.config["CONFIG"].get_plugins()
curr_plugin = {}
for plug in plugins:
if plug["id"] == plugin:
curr_plugin = plug
break
message = ""
if not plugin_id_rx.match(plugin):
flash(
f"Invalid plugin id, <b>{plugin}</b> (must be between 1 and 64 characters, only letters, numbers, underscores and hyphens)",
"error",
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
message = f'Invalid plugin id, "{plugin}" (must be between 1 and 64 characters, only letters, numbers, underscores and hyphens)'
app.logger.error(message)
if request.method == "GET":
return message, 400
return {"message": f'Invalid plugin id, "{plugin}" (must be between 1 and 64 characters, only letters, numbers, underscores and hyphens)'}, 400
if request.method == "GET":
page = db.get_plugin_template(plugin)
if page:
return render_template(
Environment(loader=FileSystemLoader(join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")).from_string(page.decode("utf-8")),
dark_mode=app.config["DARK_MODE"],
username=current_user.get_id(),
current_endpoint=plugin,
plugin=curr_plugin,
**app.jinja_env.globals,
)
message = f'The plugin "{plugin}" does not have a template'
app.logger.error(message)
return message, 404
module = db.get_plugin_actions(plugin)
if module is None:
flash(
f"The <i>actions.py</i> file for the plugin <b>{plugin}</b> does not exist",
"error",
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
return {"message": f'The actions.py file for the plugin "{plugin}" does not exist'}, 404
try:
# Try to import the custom plugin
@ -1302,32 +1306,28 @@ def custom_plugin(plugin):
loader = SourceFileLoader("actions", temp.name)
actions = loader.load_module()
except:
flash(
f"An error occurred while importing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
"error",
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
message = f'An error occurred while importing the plugin "{plugin}", see logs for more details'
app.logger.exception(message)
return {"message": message}, 500
error = False
error = None
res = None
try:
# Try to get the custom plugin custom function and call it
method = getattr(actions, plugin)
res = method()
if request.args:
res = method(app=app, args=request.args.to_dict())
elif request.is_json:
res = method(app=app, args=request.json)
else:
res = method(app=app)
except AttributeError:
flash(
f"The plugin <b>{plugin}</b> does not have a <i>{plugin}</i> method",
"error",
)
error = True
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
message = f'The plugin "{plugin}" does not have a "{plugin}" method, see logs for more details'
error = 404
except:
flash(
f"An error occurred while executing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
"error",
)
error = True
message = f'An error occurred while executing the plugin "{plugin}", see logs for more details'
error = 500
finally:
if sbin_nginx_path.is_file():
# Remove the custom plugin from the shared library
@ -1335,13 +1335,15 @@ def custom_plugin(plugin):
sys_modules.pop("actions")
del actions
if request.method != "POST" or error is True or res is None or isinstance(res, dict) is False:
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
if message or not isinstance(res, dict) and not res:
message = message or f'The plugin "{plugin}" did not return a valid response'
if error:
app.logger.exception(message)
else:
app.logger.error(message)
app.config["PLUGIN_ARGS"] = {"plugin": plugin, "args": res}
flash(f"Your action <b>{plugin}</b> has been executed")
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
app.logger.info(f"Plugin {plugin} action executed successfully")
return jsonify({"message": "ok", "data": res}), 200
@app.route("/cache", methods=["GET"])
@ -1580,9 +1582,11 @@ def logs_container(container_id):
logs.append(
{
"content": log,
"type": "error"
if "[error]" in log_lower or "[crit]" in log_lower or "[alert]" in log_lower or "" in log_lower
else ("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "" in log_lower else "message")),
"type": (
"error"
if "[error]" in log_lower or "[crit]" in log_lower or "[alert]" in log_lower or "" in log_lower
else ("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "" in log_lower else "message"))
),
}
)

View file

@ -1,5 +1,4 @@
#!/usr/bin/env python3
from os import sep
from os.path import join
from pathlib import Path
@ -118,6 +117,9 @@ class Instance:
def reports(self) -> Tuple[bool, dict[str, Any]]:
return self.apiCaller.send_to_apis("GET", "/metrics/requests", response=True)
def metrics(self, plugin_id) -> Tuple[bool, dict[str, Any]]:
return self.apiCaller.send_to_apis("GET", f"/metrics/{plugin_id}", response=True)
class Instances:
def __init__(self, docker_client, kubernetes_client, integration: str):
@ -352,15 +354,72 @@ class Instances:
resp, instance_reports = instance.reports()
if not resp:
return []
return instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", [])
return instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", {"requests": []})["requests"]
reports: List[dict[str, Any]] = []
for instance in self.get_instances():
resp, instance_reports = instance.reports()
try:
resp, instance_reports = instance.reports()
except:
continue
if not resp:
continue
reports.extend(instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", []))
reports.extend(instance_reports[instance.name if instance.name != "local" else "127.0.0.1"].get("msg", {"requests": []})["requests"])
reports.sort(key=lambda x: x["date"], reverse=True)
return reports
def get_metrics(self, plugin_id: str):
# Get metrics from all instances
metrics = {}
for instance in self.get_instances():
try:
resp, instance_metrics = instance.metrics(plugin_id)
except:
continue
# filters
if not resp:
continue
if instance.name not in instance_metrics or instance_metrics[instance.name]["msg"] is None or instance_metrics[instance.name]["msg"] is not dict or instance_metrics[instance.name]["status"] != "success":
continue
metric_data = instance_metrics[instance.name]["msg"]
# Update metrics looking for value type
for key, value in metric_data.items():
if key not in metrics:
metrics[key] = value
continue
# Case value is number, add it to the existing value
if isinstance(value, (int, float)):
metrics[key] += value
continue
# Case value is string, replace the existing value
elif isinstance(value, str):
metrics[key] = value
continue
# Case value is list, extend it to the existing value
if isinstance(value, list):
metrics[key].extend(value)
continue
# Case value is a dict, loop on it and update the existing value
if isinstance(value, dict):
for k, v in value.items():
if k not in metrics[key]:
metrics[key][k] = v
continue
if isinstance(v, (int, float)):
metrics[key][k] += v
continue
if isinstance(v, list):
metrics[key][k].extend(v)
continue
if isinstance(v, str):
metrics[key][k] = v
continue
return metrics

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,256 @@
class SetupPlugin {
constructor(data) {
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = data;
/* EXAMPLE
{
info: {
el: document.querySelector("[data-info]"),
value: `Anti-bot technology is designed to detect and mitigate suspicious or
malicious bots, preventing them from reaching an organization's websites
or IT ecosystem.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "cn", "expire"],
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]"
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
this.createAlertEl();
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": document.querySelector('input[name="csrf_token"]')
.value,
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
}
createAlertEl() {
// Container
this.alertEl = this.createEl(
"div",
[
["data-fetch", ""],
["role", "alert"],
],
"bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border",
"",
""
);
this.alertCloseEl = this.createEl(
"button",
[["data-fetch-close", ""]],
"absolute right-7 top-1.5",
"",
this.alertEl
);
this.alertCloseIconEl = this.createEl(
"svg",
[
["xmlns", "http://www.w3.org/2000/svg"],
["viewBox", "0 0 320 512"],
],
"cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5",
"",
this.alertCloseEl
);
// Close icon paths
const paths = [
"M11.7 2.805a.75.75 0 0 1 .6 0A60.65 60.65 0 0 1 22.83 8.72a.75.75 0 0 1-.231 1.337 49.948 49.948 0 0 0-9.902 3.912l-.003.002c-.114.06-.227.119-.34.18a.75.75 0 0 1-.707 0A50.88 50.88 0 0 0 7.5 12.173v-.224c0-.131.067-.248.172-.311a54.615 54.615 0 0 1 4.653-2.52.75.75 0 0 0-.65-1.352 56.123 56.123 0 0 0-4.78 2.589 1.858 1.858 0 0 0-.859 1.228 49.803 49.803 0 0 0-4.634-1.527.75.75 0 0 1-.231-1.337A60.653 60.653 0 0 1 11.7 2.805Z",
,
"M13.06 15.473a48.45 48.45 0 0 1 7.666-3.282c.134 1.414.22 2.843.255 4.284a.75.75 0 0 1-.46.711 47.87 47.87 0 0 0-8.105 4.342.75.75 0 0 1-.832 0 47.87 47.87 0 0 0-8.104-4.342.75.75 0 0 1-.461-.71c.035-1.442.121-2.87.255-4.286.921.304 1.83.634 2.726.99v1.27a1.5 1.5 0 0 0-.14 2.508c-.09.38-.222.753-.397 1.11.452.213.901.434 1.346.66a6.727 6.727 0 0 0 .551-1.607 1.5 1.5 0 0 0 .14-2.67v-.645a48.549 48.549 0 0 1 3.44 1.667 2.25 2.25 0 0 0 2.12 0Z",
,
"M4.462 19.462c.42-.419.753-.89 1-1.395.453.214.902.435 1.347.662a6.742 6.742 0 0 1-1.286 1.794.75.75 0 0 1-1.06-1.06Z",
];
paths.forEach((path) => {
this.createEl("path", [["d", path]], "", "", this.alertCloseIconEl);
});
// Status
this.alertStatusEl = this.createEl(
"h5",
[["data-fetch-status", ""]],
"text-lg mb-0 text-white dark:text-gray-300",
"Fetching",
this.alertEl
);
this.alertMsgEl = this.createEl(
"p",
[["data-fetch-msg", ""]],
"text-white dark:text-gray-300 mb-0 text-sm",
"Please wait...",
this.alertEl
);
document.body.appendChild(this.alertEl);
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
createEl(tag, attArr, className, text, parent) {
const el = document.createElement(tag);
attArr.forEach((att) => {
el.setAttribute(att[0], att[1]);
});
if (className) el.className = className;
if (text) el.textContent = text;
if (parent) parent.appendChild(el);
return el;
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
// Case list
if (Array.isArray(fetchDataObj[key])) {
value["value"] = fetchDataObj[key] || value["value"] || "";
continue;
}
// Case number
if (!isNaN(fetchDataObj[key])) {
value["value"] = fetchDataObj[key] == 0 ? "0" : fetchDataObj[key];
continue;
}
// Others
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(`[data-name="${nameKey}"]`).textContent =
nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove("bg-sky-500", "bg-green-500", "bg-red-500");
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch") return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}

View file

@ -19,10 +19,10 @@ class ServiceModal {
//modal forms
this.formNewEdit = this.modal.querySelector("[data-services-modal-form]");
this.formDelete = this.modal.querySelector(
"[data-services-modal-form-delete]"
"[data-services-modal-form-delete]",
);
this.submitBtn = document.querySelector(
"button[data-services-modal-submit]"
"button[data-services-modal-submit]",
);
//container
this.container = document.querySelector("main");
@ -92,7 +92,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
const oldServName = e.target
.closest("[data-services-service]")
@ -104,7 +104,7 @@ class ServiceModal {
oldServName,
this.formNewEdit,
isDraft,
method
method,
);
//get service data and parse it
//multiple type logic is launch at same time on relate class
@ -116,7 +116,7 @@ class ServiceModal {
this.updateModalData(obj);
//show modal
this.resetFilterInp();
this.changeSubmitBtn("EDIT", "edit-btn");
this.changeSubmitBtn("SAVE", "valid-btn");
this.openModal();
}
} catch (err) {}
@ -128,7 +128,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
this.setForm(
action,
@ -136,7 +136,7 @@ class ServiceModal {
serviceName,
this.formNewEdit,
isDraft,
method
method,
);
//set default value with method default
//get service data and parse it
@ -168,7 +168,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
this.setForm(
action,
@ -176,7 +176,7 @@ class ServiceModal {
serviceName,
this.formNewEdit,
isDraft,
method
method,
);
//set default value with method default
this.setSettingsDefault();
@ -200,7 +200,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
this.setForm(
action,
@ -208,7 +208,7 @@ class ServiceModal {
serviceName,
this.formDelete,
isDraft,
method
method,
);
//show modal
this.openModal();
@ -229,7 +229,7 @@ class ServiceModal {
"delete-btn",
"valid-btn",
"edit-btn",
"info-btn"
"info-btn",
);
this.submitBtn.classList.add(btnType);
}
@ -290,15 +290,15 @@ class ServiceModal {
//click the custom select dropdown to update select value
select.parentElement
.querySelector(
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
)
.click();
//set state to custom visible el
const btnCustom = document.querySelector(
`[data-setting-select=${select.getAttribute(
"data-setting-select-default"
)}]`
"data-setting-select-default",
)}]`,
);
this.setDisabledDefault(btnCustom, defaultMethod);
@ -374,9 +374,8 @@ class ServiceModal {
if (action === "delete") {
this.showDeleteForm();
formEl.querySelector(
`[data-services-modal-text]`
).textContent = `Are you sure you want to delete ${serviceName} ?`;
formEl.querySelector(`[data-services-modal-text]`).textContent =
`Are you sure you want to delete ${serviceName} ?`;
const nameInp = formEl.querySelector(`input[name="SERVER_NAME"]`);
nameInp.setAttribute("value", serviceName);
nameInp.value = serviceName;
@ -487,7 +486,7 @@ class ServiceModal {
if (inp.tagName === "SELECT") {
inp.parentElement
.querySelector(
`button[data-setting-select-dropdown-btn][value='${value}']`
`button[data-setting-select-dropdown-btn][value='${value}']`,
)
.click();
inp.setAttribute("data-method", method);
@ -599,7 +598,7 @@ class Multiple {
const attName = btn.getAttribute(`data-${this.prefix}-multiple-add`);
//get all multiple groups
const multipleEls = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple*="${attName}"]`
`[data-${this.prefix}-settings-multiple*="${attName}"]`,
);
//case no schema
if (multipleEls.length <= 0) return;
@ -611,7 +610,7 @@ class Multiple {
//and keep the highest num
multipleEls.forEach((container) => {
const ctnrName = container.getAttribute(
"data-services-settings-multiple"
"data-services-settings-multiple",
);
const num = this.getSuffixNumOrFalse(ctnrName);
if (!isNaN(num) && num > topNum) topNum = num;
@ -622,7 +621,7 @@ class Multiple {
const setNum = +currNum === 0 ? `` : `_${currNum}`;
//the default (schema) group is the last group
const schema = document.querySelector(
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`,
);
//clone schema to create a group with new num
const schemaClone = schema.cloneNode(true);
@ -660,7 +659,7 @@ class Multiple {
.hasAttribute(`data-${this.prefix}-multiple-delete`)
) {
const multContainer = e.target.closest(
"[data-services-settings-multiple]"
"[data-services-settings-multiple]",
);
multContainer.remove();
}
@ -682,13 +681,13 @@ class Multiple {
? name.replace(`_${splitName[splitName.length - 1]}`, "").trim()
: name.trim();
const relateSetting = document.querySelector(
`[data-setting-container=${nameSuffixLess}_SCHEMA]`
`[data-setting-container=${nameSuffixLess}_SCHEMA]`,
);
const relateCtnr = relateSetting.closest(
"[data-services-settings-multiple]"
"[data-services-settings-multiple]",
);
const relateCtnrName = relateCtnr.getAttribute(
"data-services-settings-multiple"
"data-services-settings-multiple",
);
//then we sort the setting on the right container name by suffixe number
if (!(relateCtnrName in sortMultiples)) {
@ -706,7 +705,7 @@ class Multiple {
addOneMultGroup() {
const settings = document.querySelector("[data-services-modal-form]");
const multAddBtns = settings.querySelectorAll(
"[data-services-multiple-add]"
"[data-services-multiple-add]",
);
multAddBtns.forEach((btn) => {
//check if already one (SCHEMA exclude so length >= 2)
@ -721,7 +720,7 @@ class Multiple {
showMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[data-services-settings-multiple^=${att}]`
`[data-services-settings-multiple^=${att}]`,
);
multContainers.forEach((container) => {
if (
@ -735,7 +734,7 @@ class Multiple {
toggleMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[data-services-settings-multiple^=${att}]`
`[data-services-settings-multiple^=${att}]`,
);
multContainers.forEach((container) => {
if (
@ -751,7 +750,7 @@ class Multiple {
//get schema settings
const multiples = {};
const schemaSettings = document.querySelectorAll(
`[data-setting-container$="SCHEMA"]`
`[data-setting-container$="SCHEMA"]`,
);
// loop on every schema settings
schemaSettings.forEach((schema) => {
@ -777,11 +776,11 @@ class Multiple {
setMultipleToDOM(sortMultObj, setMethodUI = false) {
//we loop on each multiple that contains values to render to DOM
for (const [schemaCtnrName, multGroupBySuffix] of Object.entries(
sortMultObj
sortMultObj,
)) {
//we need to access the DOM schema container
const schemaCtnr = document.querySelector(
`[data-services-settings-multiple="${schemaCtnrName}"]`
`[data-services-settings-multiple="${schemaCtnrName}"]`,
);
//now we have to loop on each multiple settings group
for (const [suffix, settings] of Object.entries(multGroupBySuffix)) {
@ -797,14 +796,14 @@ class Multiple {
for (const [name, data] of Object.entries(settings)) {
//get setting container of clone container
const settingContainer = schemaCtnrClone.querySelector(
`[data-setting-container="${name}"]`
`[data-setting-container="${name}"]`,
);
//replace input info and disabled state
this.setSetting(
data["value"],
setMethodUI ? "ui" : data["method"],
data["global"],
settingContainer
settingContainer,
);
}
//send schema clone to DOM and show it
@ -819,7 +818,7 @@ class Multiple {
"data-services-settings-multiple",
schemaCtnrClone
.getAttribute("data-services-settings-multiple")
.replace("_SCHEMA", suffix)
.replace("_SCHEMA", suffix),
);
//rename title
@ -833,18 +832,18 @@ class Multiple {
//rename setting container
const settingCtnrs = schemaCtnrClone.querySelectorAll(
"[data-setting-container]"
"[data-setting-container]",
);
settingCtnrs.forEach((settingCtnr) => {
settingCtnr.setAttribute(
"data-setting-container",
settingCtnr
.getAttribute("data-setting-container")
.replace("_SCHEMA", suffix)
.replace("_SCHEMA", suffix),
);
settingCtnr.setAttribute(
"id",
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix)
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix),
);
});
@ -922,15 +921,15 @@ class Multiple {
//click the custom select dropdown btn value to update select value
select.parentElement
.querySelector(
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
)
.click();
//set state to custom visible el
const btnCustom = document.querySelector(
`[data-setting-select=${select.getAttribute(
"data-setting-select-default"
)}]`
"data-setting-select-default",
)}]`,
);
this.setDisabledMultServ(btnCustom, method, global);
@ -966,10 +965,10 @@ class Multiple {
selects.forEach((select) => {
const method = select.getAttribute("data-default-method");
const name = select.getAttribute(
"data-services-setting-select-default"
"data-services-setting-select-default",
);
const selDOM = document.querySelector(
`button[data-services-setting-select='${name}']`
`button[data-services-setting-select='${name}']`,
);
if (method === "ui" || method === "default") {
selDOM.removeAttribute("disabled", "");
@ -1004,7 +1003,7 @@ class Multiple {
hiddenIfNoMultiples() {
//hide multiple btn if no multiple exist on a plugin
const multiples = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple]`
`[data-${this.prefix}-settings-multiple]`,
);
multiples.forEach((container) => {
if (container.querySelectorAll(`[data-setting-container]`).length <= 0)
@ -1016,7 +1015,7 @@ class Multiple {
removePrevMultiples() {
const multiPlugins = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple]`
`[data-${this.prefix}-settings-multiple]`,
);
multiPlugins.forEach((multiGrp) => {
if (
@ -1054,7 +1053,7 @@ const setModal = new ServiceModal();
const format = new FormatValue();
const setFilterGlobal = new FilterSettings(
"settings-filter",
"[data-service-content='settings']"
"[data-service-content='settings']",
);
const setMultiple = new Multiple("services");

View file

@ -8,16 +8,15 @@
}
* {
font-family: "Open Sans", sans-serif !important;
font-family: "Open Sans", sans-serif;
}
.ace_editor,
.ace_editor div,
.ace_content {
font-family: "Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro",
.ace_editor * {
font-family: "Monaco", "Menlo", "Ubuntu Mono", "Droid Sans Mono", "Consolas",
monospace !important;
font-size: 16px !important;
font-weight: normal !important;
font-weight: 400 !important;
letter-spacing: 0 !important;
}
.sr-only {

View file

@ -429,7 +429,7 @@ data-{{current_endpoint}}-modal
<!-- editor-->
<div class="mt-4 w-full justify-end flex">
<button
<button type="button"
data-{{current_endpoint}}-modal-close
class="close-btn text-xs mr-2"

View file

@ -1,4 +1,4 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip()
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip()
%}
<head>
@ -18,6 +18,7 @@
<link rel="stylesheet" type="text/css" href="./css/dashboard.css" />
<script type="module" src="./js/global.js"></script>
<script src="./js/plugins/setup.js"></script>
<script async src="./js/utils/purify/purify.min.js"></script>
<script src="./js/editor/ace.js"></script>

View file

@ -1,4 +1,4 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', ' ').replace('-', ' ')
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', ' ').replace('-', ' ')
%}
<!-- header -->

View file

@ -1,6 +1,5 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip()
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip()
%}
<!-- float button-->
<button
aria-controls="sidebar-menu"
@ -440,7 +439,7 @@
<a
class="leading-8 font-bold hover:brightness-75"
target="_blank"
href="https://docs.bunkerweb.io/1.5.4/plugins/?utm_campaign=self&utm_source=ui#writing-a-plugin"
href="https://docs.bunkerweb.io/latest/plugins/?utm_campaign=self&utm_source=ui#writing-a-plugin"
>check doc</a
>
</h6>
@ -450,7 +449,7 @@
<a
target="_blank"
class="{% if current_endpoint == 'logs' %}font-semibold text-slate-700 dark:bg-primary/50 rounded-lg dark:hover:bg-primary/60 bg-primary/20 hover:bg-primary/30{% else %}dark:hover:bg-primary/20 hover:bg-primary/5 {% endif %} hover:rounded-lg dark:text-white dark:opacity-80 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition"
href="{{request.url_root}}plugins?plugin_id={{plugin['id']}}"
href="{{request.url_root}}plugins/{{plugin['id']}}"
>
<div
class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5"

View file

@ -1,6 +1,5 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip()
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip()
%}
<div class="navigation-wrap bg-custom start-header start-style">
<div class="container">
<div class="row">

View file

@ -43,18 +43,6 @@ include "plugins_modal.html" %}
{{plugins_external}}
</p>
</div>
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
PLUGINS ERRORS
</p>
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
>
{{plugins_errors}}
</p>
</div>
</div>
<!-- end info -->
@ -229,7 +217,7 @@ include "plugins_modal.html" %}
<a
aria-label="plugin page link"
class="hover:-translate-y-px mx-1"
href="{{request.url_root}}plugins?plugin_id={{plugin['id']}}"
href="{{request.url_root}}plugins/{{plugin['id']}}"
>
<svg
class="h-6 w-6 fill-sky-500 dark dark:brightness-90"

View file

@ -50,7 +50,7 @@
</div>
<!-- action button -->
<div class="w-full justify-center flex mt-10">
<button
<button type="button"
data-plugins-modal-close
class="close-btn mb-4 mr-3 text-base"
>

View file

@ -363,7 +363,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<div
class="col-span-12 overflow-y-auto overflow-x-auto"
> <!-- list container-->
<div class="min-w-[1200px] w-full grid grid-cols-12 rounded p-2">
<div class="min-w-[1300px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 flex justify-center h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
@ -420,7 +420,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
>
<p
class="flex justify-center dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
class="text-center flex justify-center dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-date="{{report['date']}}"
>
{{report['date']}}

View file

@ -1,4 +1,4 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
%}
{% set global_config = config["CONFIG"].get_config() %}
{% set plugins = config["CONFIG"].get_plugins() %}

View file

@ -1,5 +1,4 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
%}
{% set plugins = config["CONFIG"].get_plugins() %}
<div data-{{current_endpoint}}-tabs class="col-span-12 grid grid-cols-12 {% if current_endpoint == 'services' %}mb-4{% endif %}">

View file

@ -159,9 +159,9 @@ trio-websocket==0.11.1 \
--hash=sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f \
--hash=sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638
# via selenium
urllib3==2.1.0 \
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
urllib3==2.2.0 \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
# via
# requests
# selenium

View file

@ -159,9 +159,9 @@ trio-websocket==0.11.1 \
--hash=sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f \
--hash=sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638
# via selenium
urllib3==2.1.0 \
--hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \
--hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54
urllib3==2.2.0 \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
# via
# requests
# selenium

Some files were not shown because too many files have changed in this diff Show more