Merge pull request #969 from bunkerity/dev

Merge branch "dev" into branch "staging"
This commit is contained in:
Théophile Diot 2024-03-05 14:22:14 +00:00 committed by GitHub
commit 2e661fc063
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1236 changed files with 131262 additions and 15740 deletions

View file

@ -35,12 +35,12 @@ jobs:
python -m pip install --no-cache-dir --require-hashes -r src/common/db/requirements.txt
echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV
- name: Initialize CodeQL
uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql.yml
setup-python-dependencies: false
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
with:
category: "/language:${{matrix.language}}"

View file

@ -63,10 +63,10 @@ jobs:
SSH_IP: ${{ secrets.ARM_SSH_IP }}
SSH_CONFIG: ${{ secrets.ARM_SSH_CONFIG }}
- name: Setup Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.0
if: inputs.CACHE_SUFFIX != 'arm'
- name: Setup Buildx (ARM)
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.0
if: inputs.CACHE_SUFFIX == 'arm'
with:
endpoint: ssh://root@arm
@ -117,7 +117,7 @@ jobs:
# Check OS vulnerabilities
- name: Check OS vulnerabilities
if: ${{ inputs.CACHE_SUFFIX != 'arm' }}
uses: aquasecurity/trivy-action@84384bd6e777ef152729993b8145ea352e9dd3ef # v0.17.0
uses: aquasecurity/trivy-action@062f2592684a31eb3aa050cc61e7ca1451cecd3d # v0.18.0
with:
vuln-type: os
skip-dirs: /root/.cargo

View file

@ -72,10 +72,10 @@ jobs:
SSH_IP: ${{ secrets.ARM_SSH_IP }}
SSH_CONFIG: ${{ secrets.ARM_SSH_CONFIG }}
- name: Setup Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.0
if: startsWith(env.ARCH, 'arm') == false
- name: Setup Buildx (ARM)
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.0
if: startsWith(env.ARCH, 'arm') == true
with:
endpoint: ssh://root@arm

View file

@ -58,7 +58,7 @@ jobs:
SSH_IP: ${{ secrets.ARM_SSH_IP }}
SSH_CONFIG: ${{ secrets.ARM_SSH_CONFIG }}
- name: Setup Buildx (ARM)
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.0
with:
endpoint: ssh://root@arm
platforms: linux/arm64,linux/arm/v7,linux/arm/v6

View file

@ -19,7 +19,7 @@ jobs:
# Get PDF doc
- name: Get documentation
if: inputs.VERSION != 'testing'
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: BunkerWeb_documentation_v${{ inputs.VERSION }}.pdf
# Create tag

View file

@ -42,18 +42,18 @@ jobs:
- name: Check out repository code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install ruby
uses: ruby/setup-ruby@22fdc77bf4148f810455b226c90fb81b5cbc00a7 # v1.171.0
uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0
with:
ruby-version: "3.0"
- name: Install packagecloud
run: gem install package_cloud
# Download packages
- uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
- uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
if: inputs.LINUX != 'el' && inputs.LINUX != 'el9'
with:
name: package-${{ inputs.LINUX }}-${{ inputs.PACKAGE_ARCH }}
path: /tmp/${{ inputs.LINUX }}
- uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
- uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
if: inputs.LINUX == 'el' || inputs.LINUX == 'el9'
with:
name: package-rh${{ inputs.LINUX }}-${{ inputs.PACKAGE_ARCH }}

View file

@ -25,6 +25,6 @@ jobs:
results_format: sarif
publish_results: true
- name: "Upload SARIF results to code scanning"
uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
with:
sarif_file: results.sarif

View file

@ -30,7 +30,7 @@ jobs:
uses: azure/setup-kubectl@901a10e89ea615cf61f57ac05cecdf23e7de06d8 # v3.2
if: inputs.TYPE == 'k8s'
with:
version: "v1.28.2"
version: "v1.28.6"
- name: Set up Python 3.12
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
if: inputs.TYPE != 'k8s'
@ -46,7 +46,6 @@ jobs:
- run: ./tests/create.sh ${{ inputs.TYPE }}
env:
CICD_SECRETS: ${{ secrets.CICD_SECRETS }}
K8S_IP: ${{ secrets.K8S_IP }}
- run: |
tar -cf terraform.tar /tmp/${{ inputs.TYPE }}
echo "$SECRET_KEY" > /tmp/.secret_key

View file

@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install terraform
uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v3.0.0
- uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
- uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: tf-${{ inputs.TYPE }}
path: /tmp

View file

@ -43,7 +43,7 @@ jobs:
if: inputs.TYPE == 'swarm'
- name: Install test dependencies
run: pip3 install --no-cache-dir --require-hashes --no-deps -r tests/requirements.txt
- uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
- uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: tf-k8s
path: /tmp

View file

@ -85,7 +85,6 @@ jobs:
secrets:
CICD_SECRETS: ${{ secrets.CICD_SECRETS }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
K8S_IP: ${{ secrets.K8S_IP }}
prepare-tests-core:
runs-on: ubuntu-latest
steps:

View file

@ -75,10 +75,12 @@ jobs:
echo "127.0.0.1 www.example.com" | sudo tee -a /etc/hosts
echo "127.0.0.1 app1.example.com" | sudo tee -a /etc/hosts
echo "127.0.0.1 app2.example.com" | sudo tee -a /etc/hosts
echo "127.0.0.1 app3.example.com" | sudo tee -a /etc/hosts
# BunkerWeb
sudo mkdir -p /etc/bunkerweb
echo "SERVER_NAME=" | sudo tee /etc/bunkerweb/variables.env
echo "HTTP_PORT=80" | sudo tee -a /etc/bunkerweb/variables.env
echo "BAD_BEHAVIOR_THRESHOLD=20" | sudo tee -a /etc/bunkerweb/variables.env
echo 'DNS_RESOLVERS=9.9.9.9 8.8.8.8 8.8.4.4' | sudo tee -a /etc/bunkerweb/variables.env
echo 'API_LISTEN_IP=127.0.0.1' | sudo tee -a /etc/bunkerweb/variables.env
echo "MULTISITE=yes" | sudo tee -a /etc/bunkerweb/variables.env
@ -116,6 +118,7 @@ jobs:
}' | tee plugin.json
zip discord.zip plugin.json
rm plugin.json
sudo truncate -s 0 /var/log/bunkerweb/error.log
./tests.sh "linux" ${{ inputs.TEST }}
env:
MODE: ${{ inputs.RELEASE }}

View file

@ -1 +1,9 @@
src/ui/templates/profile.html:hashicorp-tf-password:343
src/ui/templates/account.html:hashicorp-tf-password:194
src/ui/templates/account.html:hashicorp-tf-password:255
src/ui/templates/account.html:hashicorp-tf-password:292
src/ui/templates/account.html:hashicorp-tf-password:329
src/ui/templates/account.html:hashicorp-tf-password:417
src/ui/templates/account.html:hashicorp-tf-password:470
src/ui/templates/settings_plugins.html:hashicorp-tf-password:87
src/ui/templates/settings_plugins.html:hashicorp-tf-password:297

View file

@ -17,7 +17,7 @@ repos:
- id: check-case-conflict
- repo: https://github.com/psf/black
rev: e026c93888f91a47a9c9f4e029f3eb07d96375e6 # frozen: 24.1.1
rev: 6fdf8a4af28071ed1d079c01122b34c5d587207a # frozen: 24.2.0
hooks:
- id: black
name: Black Python Formatter

View file

@ -2,7 +2,7 @@ docs/
env/
*/env/
*.min*
src/common/core/modsecurity/
src/common/core/modsecurity/files/
src/deps/src/
mkdocs.yml
CHANGELOG.md
@ -16,5 +16,6 @@ flatpickr.*
src/ui/static/js/editor/*
src/ui/static/js/utils/purify/*
src/ui/templates/*
src/common/core/*/ui/*
datepicker-foundation.css
examples/*

View file

@ -5,27 +5,48 @@
- [LINUX] Support RHEL 9.3
- [BUGFIX] Fix issues with the antibot feature ([#866](https://github.com/bunkerity/bunkerweb/issues/866), [#870](https://github.com/bunkerity/bunkerweb/issues/870))
- [BUGFIX] Fix Bad behavior whitelist check in access phase
- [BUGFIX] Fix ModSecurity FP on antibot page
- [BUGFIX] Fix Whitelist core plugin missing a check for empty server_name in multisite mode
- [BUGFIX] Fix Templator missing some common configs
- [LINUX] Add logrotate support for the logs
- [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
- [UI] Enhance the Content-Security-Policy header in the web UI
- [UI] Add some core plugins pages 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
- [FEATURE] Add whitelist check for the default-server as well
- [FEATURE] Add the possibility to choose between the coreruleset v3 and v4 that will be used by ModSecurity (default is v3)
- [FEATURE] Add the TIMERS_LOG_LEVEL setting to control the log level of the lua timers
- [FEATURE] Add pro version management to core plugins, the scheduler and the web UI
- [FEATURE] Add REVERSE_PROXY_CUSTOM_HOST setting to set a custom Host header when using reverse proxy
- [MISC] Add a better custom certificate cache handling
- [MISC] Updated Linux base images in Dockerfiles
- [MISC] Add recommended dialects to databases string
- [MISC] Refine the data sent in the anonymous reporting feature and move the setting and the job to the "jobs" plugin
- [MISC] BunkerWeb will now load the default loading page even on 404 errors when generating the configuration
- [MISC] Update database schema to support the new pro version and optimize it
- [MISC] Refactor SSL/TLS logics to make it more consistent
- [MISC] Use ed5519 key instead of RSA for default/fallback certificates
- [MISC] Refactor certbot-new job to optimize the certbot requests
- [DOCUMENTATION] Update web UI's setup wizard instructions in the documentation
- [DOCUMENTATION] Update plugins documentation to reflect the new plugin system
- [DOCUMENTATION] Update ModSecurity documentation to reflect the new changes in the Security Tuning section
- [DOCUMENTATION] Add pro version 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
- [DEPS] Updated coreruleset version to v3.3.5
- [DEPS] Added coreruleset version v4.0.0
- [DEPS] Updated lua-resty-mlcache version to v2.7.0
## v1.5.5 - 2024/01/12

View file

@ -6,16 +6,28 @@ BunkerWeb is maintained by [Bunkerity](https://www.bunkerity.com/?utm_campaign=s
## Do you offer professional services ?
Yes, we offer professional services related to BunkerWeb such as :
Yes, we offer professional services related to BunkerWeb.
**We have a [dedicated panel](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc) to centralize all professional requests.**
You can also contact use at [contact@bunkerity.com](mailto:contact@bunkerity.com) if you are interested.
### Support
You can get in touch with us about any of the following :
- Consulting
- Support
- Custom development
- Partnership
**We have a [dedicated panel](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc) to centralize all professional requests.**
### Pro version
You can also contact use at [contact@bunkerity.com](mailto:contact@bunkerity.com) if you are interested.
We have a (pro version of BunkerWeb)[https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc#pro], which includes :
- additional features and plugins
- regular updates to improve your experience
- we take your feedback into account to develop the plugins or offer you custom plugins.
## Where to get community support ?

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 903 KiB

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -26,6 +26,10 @@ If you think that a new integration should be supported, do not hesitate to open
## Settings
!!! tip "Pro settings"
Some plugins are reserved for the **pro version**. [Find out more about the pro version here.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc#pro)
Once BunkerWeb is integrated into your environment, you will need to configure it to serve and protect your web applications.
The configuration of BunkerWeb is done by using what we call the "settings" or "variables". Each setting is identified by a name such as `AUTO_LETS_ENCRYPT` or `USE_ANTIBOT`. You can assign values to the settings to configure BunkerWeb.

View file

@ -27,7 +27,7 @@ BunkerWeb contains primary [security features](security-tuning.md) as part of th
- **Free as in "freedom"** : BunkerWeb is licensed under the free [AGPLv3 license](https://www.gnu.org/licenses/agpl-3.0.en.html), embracing the principles of freedom and openness. Enjoy the freedom to use, modify, and distribute the software, backed by a supportive community.
- **Professional services** : Get technical support, tailored consulting and custom development directly from the maintainers of BunkerWeb. Visit the [BunkerWeb Panel](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc) for more information.
- **Professional services** : Get technical support, tailored consulting and custom development directly from the maintainers of BunkerWeb. Visit the [BunkerWeb Panel](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc#pro) for more information.
## Security features
@ -61,12 +61,26 @@ A demo website protected with BunkerWeb is available at [demo.bunkerweb.io](http
## Professional services
### Support panel
Get the most of BunkerWeb by getting professional services directly from the maintainers of the project. From technical support to tailored consulting and development, we are here to assist you in the security of your web services.
You will find more information by visiting the [BunkerWeb Panel](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc), our dedicated platform for professional services.
Don't hesitate to [contact us](https://panel.bunkerweb.io/contact.php?utm_campaign=self&utm_source=doc) if you have any question, we will be more than happy to respond to your needs.
### Pro version
BunkerWeb has a pro version to further improve the security of your applications and make managing them faster and easier (email reporting, more settings options...).
We have centralised version management and support within the panel for greater ease of use.
More detailed information about this version can be found [by visiting our website.](https://www.bunkerweb.io/#services?utm_campaign=self&utm_source=doc)
If you're interested, upgrade to the pro version in just a few clicks, [following the steps here.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc#pro)
Note that in the documentation, it will be mentioned when a feature is for the pro version.
## Ecosystem, community and resources
Official websites, tools and resources about BunkerWeb :

View file

@ -1,23 +1,25 @@
{% extends "base.html" %} {% block outdated %} You're not viewing the
documentation of the latest version.
<a href="{{ '../' ~ base_url }}">
<strong>Click here to view latest.</strong>
</a>
{% endblock %} {% block announce %} 📢 Looking for technical support, tailored
consulting or custom development for BunkerWeb ? Visit the
<a
href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc"
style="color: #3f6ec6; text-decoration: underline"
>BunkerWeb Panel</a
>
for more information on our enterprise offers. {% endblock %} {% block libs %}
<script
async
defer
data-domain="docs.bunkerweb.io"
src="https://data.bunkerity.com/js/script.js"
></script>
<script defer>
{% extends "base.html" %}
{% block outdated %}
You're not viewing the
documentation of the latest version.
<a href="{{ '../' ~ base_url }}">
<strong>Click here to view latest.</strong>
</a>
{% endblock %}
{% block announce %}
📢 Looking for technical support, tailored
consulting or custom development for BunkerWeb ? Visit the
<a href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc"
style="color: #3f6ec6;
text-decoration: underline">BunkerWeb Panel</a>
for more information on our enterprise offers.
{% endblock %}
{% block libs %}
<script async
defer
data-domain="docs.bunkerweb.io"
src="https://data.bunkerity.com/js/script.js"></script>
<script defer>
// Lazy load images and embed youtube videos
window.addEventListener("load", () => {
document.querySelectorAll("[data-src]").forEach((el) => {
@ -30,5 +32,77 @@ for more information on our enterprise offers. {% endblock %} {% block libs %}
.querySelector('div.md-search[data-md-component="search"][role="dialog"]')
.setAttribute("aria-label", "Search in documentation");
} catch (err) {}
</script>
</script>
<script defer>
window.addEventListener('DOMContentLoaded', () => {
const bannerEl = document.querySelector('aside.md-banner');
let defaultContent = [{ "content" : 'Need premium support ? <a style="text-decoration:underline; color : white;" href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc">Check BunkerWeb Panel</a>'},
{ "content" : 'Try BunkerWeb on our <a style="text-decoration:underline; color : white;" href="https://demo.bunkerweb.io/link/?utm_campaign=self&utm_source=doc">demo web app !</a>'},
{ "content" : 'All information about BunkerWeb on our <a style="text-decoration:underline; color : white;" href="https://www.bunkerweb.io/?utm_campaign=self&utm_source=doc">website !</a>'}
]
function setBannerStyle() {
const bannerItem = bannerEl.querySelector('.md-banner__inner');
bannerEl.style.backgroundColor = "#36ce7a";
bannerItem.style.textAlign = "center";
bannerItem.style.transition = "all 0.5s ease-out";
}
function setDefault() {
const bannerItem = bannerEl.querySelector('.md-banner__inner');
const clone = bannerItem.cloneNode(true);
clone.innerHTML = defaultContent[defaultContent.length - 1]["content"];
bannerEl.replaceChild(clone, bannerItem);
}
function startBannerLoop() {
const switchDelay = 7000;
let i = 0;
setInterval(() => {
// Update or reset index
if(i + 1 === defaultContent.length - 1) {
i = 0;
} else {
i++;
}
// Prepare data with next el with opacity 0 too
const currItem = bannerEl.querySelector('.md-banner__inner');
currItem.style.opacity = 0;
const clone = currItem.cloneNode(true);
clone.innerHTML = defaultContent[i]["content"];
setTimeout(() => {
bannerEl.replaceChild(clone, currItem);
setTimeout(() => {
const newItem = bannerEl.querySelector('.md-banner__inner');
newItem.style.opacity = 1;
}, 20);
}, 600);
}, switchDelay);
}
function runBanner() {
// Try to get dynamic content
// Else keep static ones
fetch("https://www.bunkerweb.io/api/bw-ui-news")
.then((res) => {
return res.json();
})
.then((res) => {
defaultContent = res.data;
startBannerLoop();
})
.catch((e) => { startBannerLoop();
});
}
setBannerStyle();
setDefault();
runBanner()
})
</script>
{% endblock %}

View file

@ -45,11 +45,44 @@ The first step is to install the plugin by putting the plugin files inside the c
cp -rp ./bunkerweb-plugins/* ./bw-data/plugins
```
Because the scheduler runs as an unprivileged user with UID and GID 101, you will need to edit the permissions :
!!! warning "Using local folder for persistent data"
The scheduler runs as an **unprivileged user with UID 101 and GID 101** inside the container. The reason behind this is security : in case a vulnerability is exploited, the attacker won't have full root (UID/GID 0) privileges.
But there is a downside : if you use a **local folder for the persistent data**, you will need to **set the correct permissions** so the unprivileged user can write data to it. Something like that should do the trick :
```shell
chown -R 101:101 ./bw-data
```
```shell
mkdir bw-data && \
chown root:101 bw-data && \
chmod 770 bw-data
```
Alternatively, if the folder already exists :
```shell
chown -R root:101 bw-data && \
chmod -R 770 bw-data
```
If you are using [Docker in rootless mode](https://docs.docker.com/engine/security/rootless) or [podman](https://podman.io/), UIDs and GIDs in the container will be mapped to different ones in the host. You will first need to check your initial subuid and subgid :
```shell
grep ^$(whoami): /etc/subuid && \
grep ^$(whoami): /etc/subgid
```
For example, if you have a value of **100000**, the mapped UID/GID will be **100100** (100000 + 100) :
```shell
mkdir bw-data && \
sudo chgrp 100100 bw-data && \
chmod 770 bw-data
```
Or if the folder already exists :
```shell
sudo chgrp -R 100100 bw-data && \
chmod -R 770 bw-data
```
Then you can mount the volume when starting your Docker stack :
@ -276,7 +309,7 @@ plugin /
plugin.json
```
- **conf_type.conf** : add a [custom NGINX configurations.](/quickstart-guide/#custom-configurations)
- **conf_type.conf** : add a [custom NGINX configurations.](quickstart-guide.md#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...
@ -544,59 +577,97 @@ BunkerWeb uses an internal job scheduler for periodic tasks like renewing certif
### Plugin page
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 the subfolder **ui** as we seen in the [previous structure section.](#structure).
#### Prerequisites
When you want to create a plugin page, you need two files :
- **template.html** that will be accessible with a **GET /plugins/<*plugin_id*>**.
- **actions.py** where you can add some scripting and logic with a **POST /plugins/<*plugin_id*>**. Notice that this file **need a function with the same name as the plugin** to work. This file is needed even if the function is empty.
#### 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.
A plugin page can have a form that is used to submit data to the plugin. To get the values of the form, you need to put a **actions.py** file in the **ui** folder. Inside the file, **you must define a function that has the same name as the plugin**. This function will be called when the form is submitted. You can then use the **request** object (from the [Flask library](https://flask.palletsprojects.com)) to get the values of the form. The form's action must finish with **/plugins/<*plugin_id*>**. The helper function `url_for` will generate for you the prefix of the URL : `{{ url_for('plugins') }}/plugin_id`.
We can put aside the **actions.py** file and start **only using the template on a GET situation**. The template can access app context and libs, so you can use Jinja, request or flask utils.
If you want to display variables generated from your **actions.py** in your template file, you can return a dictionary with variables name as keys and variables value as values. Here is dummy example where we return a single variable :
For example, you can get the request arguments in your template like this :
```python
def myplugin() :
return {"foo": "bar"}
```
And we display it in the **template.html** file :
```html
{% if foo %}
Content of foo is : {{ foo }}.
{% endif %}
<p>request args : {{ request.args.get() }}.</p>
```
Please note that every form submission is protected via a CSRF token, you will need to include the following snippet into your forms :
```html
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
```
Retrieving user submitted data is pretty simple, thanks to the request module provided by Flask :
```python
from flask import request
def myplugin() :
my_form_value = request.form["my_form_input"]
```
#### Actions.py
!!! 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
!!! warning "CSRF Token"
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.
Please note that every form submission is protected via a CSRF token, you will need to include the following snippet into your forms :
```html
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
```
You can power-up your plugin page with additional scripting with the **actions.py** file when sending a **POST /plugins/<*plugin_id*>**.
Here is what is send to the function :
For example, in case **actions.py** return this JSON :
```python
def plugin():
return {"message": "ok", "data": {"name": "test"}}
function(app=app, args=request.args.to_dict() or request.json or None)
```
I need to add this on my **template.html** :
Some examples of what you can do :
- Retrieve form submitted data
```python
from flask import request
def myplugin(**kwargs) :
my_form_value = request.form["my_form_input"]
```
- Access app methods
```python
from flask import request
def myplugin(**kwargs) :
config = kwargs['app'].config["CONFIG"].get_config(methods=False)
```
**You need to retrieve JSON compatible data from this function**, app will return this as response :
```python
return jsonify({"message": "ok", "data": <function_output>}), 200
```
#### Updating template
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`.
!!! info "Check core plugins"
Core plugins are using this class. Feel free to look at them in order to see in details how it works.
For example, in case **actions.py** return this :
```python
def myplugin(**kwargs):
return {"name": "My awesome plugin"}
```
I can add this on my **template.html** :
```html
<p data-name></p>
<script>
new SetupPlugin({
name: {
@ -608,16 +679,25 @@ I need to add this on my **template.html** :
</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.
**This class will send a POST request, and will try to match the dict key to a JSON key and update your template**.
| 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 additional text content when type is `status`. |
| `listNames`| string | List of data keys when type is `list`. |
Here it will look for a `name` key in the JSON response, and will set the `data` on the defined `el`.
In case there is no `data` matching, this will keep or set the `value` key data.
This class has two arguments `SetupPlugin(setup, url)` :
- `url`(optional) : current endpoint by default. You can define another url or add arguments.
- `setup` : a dict of dict with needed data to update properly the template with the incoming data.
**setup details**
| key | Type | Description |
| :--------: | :--------: | :------------------------------------------------------------------------------------------- |
| `dict name` | string | Replace `dict name` by the JSON key to extract the related value. |
| `el` | DOM 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` | DOM element| Optional additional text content when type is `status`. |
| `listNames`| string | List of data keys when type is `list`. |

View file

@ -12,6 +12,8 @@ Please note that professionnal services are directly offered by [Bunkerity](http
## Which professional services do you offer ?
### Support
We offer technical support around the BunkerWeb solution. By using this service, we will assist you on the technical issues (installation, configuration, false positive, ...).
According to your needs you have the choice between "one time" and subscriptions offers.
@ -23,6 +25,20 @@ In addition to the support service, we also offer custom services around the Bun
- Consulting : a dedicated expert will give you advices on your project
- Development : if you need specific features in BunkerWeb, we can do it for you
[You can check for technical support here.](https://panel.bunkerweb.io/contact.php?utm_campaign=self&utm_source=doc)
### Pro version
A BunkerWeb pro version is available.
With this version you'll get a power-up version with more plugins and details settings.
Any features from pro version are up to date, and we have an active listening on your feedbacks in order to enhance them and fit your needs.
Switching to the pro version is done centrally via the panel, which is also used for support.
If you are interested, please [visit the dedicated page.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc#pro)
## How can I get more information ?
You will find more information by visiting the [BunkerWeb Panel](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc), our dedicated platform for professional services.

View file

@ -2426,3 +2426,17 @@ By default, BunkerWeb will only listen on IPv4 addresses and won't use IPv6 for
...
```
## And now ?
This quickstart is just a tiny fraction of the functionality that BunkerWeb has to offer. Be curious and see what else BunkerWeb has to offer.
You can take a detailed look at the many [plugins and settings available](settings.md) in this solution.
Details on the use of certain plugins can also be found on the [security tuning page.](security-tuning.md)
For simplified use of the solution, don't hesitate to [try out the web UI.](web-ui.md)
Please note that **BunkerWeb comes in both free and pro versions**, with pro features indicated to avoid confusion.
If you'd like to find out more about the pro version, and use the solution to the full, [visit the panel.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc#pro)

View file

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

View file

@ -207,9 +207,9 @@ importlib-metadata==7.0.1 \
# markdown
# mike
# mkdocs
importlib-resources==6.1.1 \
--hash=sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a \
--hash=sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6
importlib-resources==6.1.2 \
--hash=sha256:308abf8474e2dba5f867d279237cd4076482c3de7104a40b41426370e891549b \
--hash=sha256:9a0a862501dc38b68adebc82970140c9e4209fc99601782925178f8386339938
# via mike
jinja2==3.1.3 \
--hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \
@ -311,12 +311,11 @@ mkdocs==1.5.3 \
# -r requirements.in
# mike
# mkdocs-material
mkdocs-material==9.5.9 \
--hash=sha256:635df543c01c25c412d6c22991872267723737d5a2f062490f33b2da1c013c6d \
--hash=sha256:a5d62b73b3b74349e45472bfadc129c871dd2d4add68d84819580597b2f50d5d
mkdocs-material==9.5.11 \
--hash=sha256:788ee0f3e036dca2dc20298d65e480297d348a44c9d7b2ee05c5262983e66072 \
--hash=sha256:7af7f8af0dea16175558f3fb9245d26c83a17199baa5f157755e63d7437bf971
# via
# -r requirements.in
# mkdocs-material
# mkdocs-print-site-plugin
mkdocs-material-extensions==1.3.1 \
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
@ -613,9 +612,9 @@ requests==2.31.0 \
# importlib-resources
# The following packages are considered to be unsafe in a requirements file:
setuptools==69.1.0 \
--hash=sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401 \
--hash=sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6
setuptools==69.1.1 \
--hash=sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56 \
--hash=sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8
# via mkdocs-material
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
@ -642,10 +641,9 @@ typepy==1.3.2 \
# dataproperty
# pytablewriter
# tabledata
# typepy
urllib3==2.2.0 \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
# via requests
verspec==0.1.0 \
--hash=sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 \

View file

@ -106,16 +106,16 @@ STREAM support :x:
[Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) lets you manage how your service can be contacted from different origins. Please note that you will have to allow the `OPTIONS` HTTP method using the `ALLOWED_METHODS` if you want to enable it (more info [here](#allowed-methods)). Here is the list of settings related to CORS :
| Setting | Default | Context |Multiple| Description |
|------------------------|------------------------------------------------------------------------------------|---------|--------|-------------------------------------------------------------------|
|`USE_CORS` |`no` |multisite|no |Use CORS |
|`CORS_ALLOW_ORIGIN` |`*` |multisite|no |Allowed origins to make CORS requests : PCRE regex or *. |
|`CORS_EXPOSE_HEADERS` |`Content-Length,Content-Range` |multisite|no |Value of the Access-Control-Expose-Headers header. |
|`CORS_MAX_AGE` |`86400` |multisite|no |Value of the Access-Control-Max-Age header. |
|`CORS_ALLOW_CREDENTIALS`|`no` |multisite|no |Send the Access-Control-Allow-Credentials header. |
|`CORS_ALLOW_METHODS` |`GET, POST, OPTIONS` |multisite|no |Value of the Access-Control-Allow-Methods header. |
|`CORS_ALLOW_HEADERS` |`DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range`|multisite|no |Value of the Access-Control-Allow-Headers header. |
|`CORS_DENY_REQUEST` |`yes` |multisite|no |Deny request and don't send it to backend if Origin is not allowed.|
| Setting | Default | Context | Multiple | Description |
| ------------------------ | ------------------------------------------------------------------------------------ | --------- | -------- | ------------------------------------------------------------------- |
| `USE_CORS` | `no` | multisite | no | Use CORS |
| `CORS_ALLOW_ORIGIN` | `*` | multisite | no | Allowed origins to make CORS requests : PCRE regex or *. |
| `CORS_EXPOSE_HEADERS` | `Content-Length,Content-Range` | multisite | no | Value of the Access-Control-Expose-Headers header. |
| `CORS_MAX_AGE` | `86400` | multisite | no | Value of the Access-Control-Max-Age header. |
| `CORS_ALLOW_CREDENTIALS` | `no` | multisite | no | Send the Access-Control-Allow-Credentials header. |
| `CORS_ALLOW_METHODS` | `GET, POST, OPTIONS` | multisite | no | Value of the Access-Control-Allow-Methods header. |
| `CORS_ALLOW_HEADERS` | `DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range` | multisite | no | Value of the Access-Control-Allow-Headers header. |
| `CORS_DENY_REQUEST` | `yes` | multisite | no | Deny request and don't send it to backend if Origin is not allowed. |
Here is some examples of possible values for `CORS_ALLOW_ORIGIN` setting :
@ -133,7 +133,7 @@ Besides the HTTPS / SSL/TLS configuration, the following settings related to HTT
| :---------------------------: | :---------------: | :----------------------------------------------------------------------------------------------------------- |
| `REDIRECT_HTTP_TO_HTTPS` | `no` | When set to `yes`, will redirect every HTTP request to HTTPS even if BunkerWeb is not configured with HTTPS. |
| `AUTO_REDIRECT_HTTP_TO_HTTPS` | `yes` | When set to `yes`, will redirect every HTTP request to HTTPS only if BunkerWeb is configured with HTTPS. |
| `SSL_PROTOCOLS` | `TLSv1.2 TLSv1.3` | List of supported SSL/TLS protocols when SSL is enabled. |
| `SSL_PROTOCOLS` | `TLSv1.2 TLSv1.3` | List of supported SSL/TLS protocols when SSL is enabled. |
| `HTTP2` | `yes` | When set to `yes`, will enable HTTP2 protocol support when using HTTPS. |
| `LISTEN_HTTP` | `yes` | When set to `no`, BunkerWeb will not listen for HTTP requests. Useful if you want HTTPS only for example. |
@ -147,7 +147,7 @@ Here is the list of related settings :
| Setting | Default | Description |
| :------------------------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTO_LETS_ENCRYPT` | `no` | When set to `yes`, HTTPS / SSL/TLS will be enabled with automatic certificate generation and renewal from Let's Encrypt. |
| `AUTO_LETS_ENCRYPT` | `no` | When set to `yes`, HTTPS / SSL/TLS will be enabled with automatic certificate generation and renewal from Let's Encrypt. |
| `EMAIL_LETS_ENCRYPT` | `contact@{FIRST_SERVER}` | Email to use when generating certificates. Let's Encrypt will send notifications to that email like certificate expiration. |
| `USE_LETS_ENCRYPT_STAGING` | `no` | When set to `yes`, the staging server of Let's Encrypt will be used instead of the production one. Useful when doing tests to avoid being "blocked" due to limits. |
@ -159,11 +159,11 @@ STREAM support :white_check_mark:
If you want to use your own certificates, here is the list of related settings :
| Setting |Default| Context |Multiple| Description |
|-----------------|-------|---------|--------|--------------------------------------------------------------------------------|
|`USE_CUSTOM_SSL` |`no` |multisite|no |Use custom HTTPS / SSL/TLS certificate. |
|`CUSTOM_SSL_CERT`| |multisite|no |Full path of the certificate or bundle file (must be readable by the scheduler).|
|`CUSTOM_SSL_KEY` | |multisite|no |Full path of the key file (must be readable by the scheduler). |
| Setting | Default | Context | Multiple | Description |
| ----------------- | ------- | --------- | -------- | -------------------------------------------------------------------------------- |
| `USE_CUSTOM_SSL` | `no` | multisite | no | Use custom HTTPS / SSL/TLS certificate. |
| `CUSTOM_SSL_CERT` | | multisite | no | Full path of the certificate or bundle file (must be readable by the scheduler). |
| `CUSTOM_SSL_KEY` | | multisite | no | Full path of the key file (must be readable by the scheduler). |
When `USE_CUSTOM_SSL` is set to `yes`, BunkerWeb will check every day if the custom certificate specified in `CUSTOM_SSL_CERT` is modified and will reload NGINX if that's the case.
@ -176,11 +176,11 @@ STREAM support :white_check_mark:
If you want to quickly test HTTPS / SSL/TLS for staging/dev environment you can configure BunkerWeb to generate self-signed certificates, here is the list of related settings :
| Setting | Default | Description |
| :------------------------: | :--------------------: | :------------------------------------------------------------------------------------------------------------------------- |
| Setting | Default | Description |
| :------------------------: | :--------------------: | :----------------------------------------------------------------------------------------------------------------------------------- |
| `GENERATE_SELF_SIGNED_SSL` | `no` | When set to `yes`, HTTPS / SSL/TLS will be enabled with automatic self-signed certificate generation and renewal from Let's Encrypt. |
| `SELF_SIGNED_SSL_EXPIRY` | `365` | Number of days for the certificate expiration (**-days** value used with **openssl**). |
| `SELF_SIGNED_SSL_SUBJ` | `/CN=www.example.com/` | Certificate subject to use (**-subj** value used with **openssl**). |
| `SELF_SIGNED_SSL_EXPIRY` | `365` | Number of days for the certificate expiration (**-days** value used with **openssl**). |
| `SELF_SIGNED_SSL_SUBJ` | `/CN=www.example.com/` | Certificate subject to use (**-subj** value used with **openssl**). |
When using stream mode, you will need to use the `LISTEN_STREAM_PORT_SSL` setting in order to choose your listening SSL/TLS port.
@ -190,12 +190,21 @@ STREAM support :x:
ModSecurity is integrated and enabled by default alongside the OWASP Core Rule Set within BunkerWeb. Here is the list of related settings :
| Setting | Default | Description |
| :-------------------: | :-----: | :---------------------------------------------------------------------------------------------------- |
| `USE_MODSECURITY` | `yes` | When set to `yes`, ModSecurity will be enabled. |
| `USE_MODSECURITY_CRS` | `yes` | When set to `yes` and `USE_MODSECURITY` is also set to `yes`, the OWASP Core Rule Set will be loaded. |
| Setting | Default | Description |
| :-----------------------: | :-----: | :---------------------------------------------------------------------------------------------------- |
| `USE_MODSECURITY` | `yes` | When set to `yes`, ModSecurity will be enabled. |
| `USE_MODSECURITY_CRS` | `yes` | When set to `yes` and `USE_MODSECURITY` is also set to `yes`, the OWASP Core Rule Set will be loaded. |
| `MODSECURITY_CRS_VERSION` | `3` | Version of the OWASP Core Rule Set to use. |
We strongly recommend keeping both ModSecurity and the OWASP Core Rule Set enabled. The only downsides are the false positives that may occur. But they can be fixed with some efforts and the CRS team maintains a list of exclusions for common applications (e.g., WordPress, Nextcloud, Drupal, Cpanel, ...).
!!! warning "ModSecurity and the OWASP Core Rule Set"
**We strongly recommend keeping both ModSecurity and the OWASP Core Rule Set enabled**. The only downsides are the false positives that may occur. But they can be fixed with some efforts and the CRS team maintains a list of exclusions for common applications (e.g., WordPress, Nextcloud, Drupal, Cpanel, ...).
You can choose between the following versions of the OWASP Core Rule Set :
- **3** : The version [v3.3.5](https://github.com/coreruleset/coreruleset/releases/tag/v3.3.5) of the OWASP Core Rule Set (***default***)
- **4** : The version [v4.0.0](https://github.com/coreruleset/coreruleset/releases/tag/v4.0.0) of the OWASP Core Rule Set
### Custom configurations
Tuning ModSecurity and the CRS can be done using [custom configurations](quickstart-guide.md#custom-configurations) :
@ -236,7 +245,7 @@ That kind of security measure is implemented and enabled by default in BunkerWeb
| `BAD_BEHAVIOR_STATUS_CODES` | `400 401 403 404 405 429 444` | List of HTTP status codes considered as "suspicious". |
| `BAD_BEHAVIOR_BAN_TIME` | `86400` | The duration time (in seconds) of a ban when a client reached the threshold. |
| `BAD_BEHAVIOR_THRESHOLD` | `10` | Maximum number of "suspicious" HTTP status codes within the time period. |
| `BAD_BEHAVIOR_COUNT_TIME` | `60` | Period of time during which we count "suspicious" HTTP status codes. |
| `BAD_BEHAVIOR_COUNT_TIME` | `60` | Period of time during which we count "suspicious" HTTP status codes. |
In other words, with the default values, if a client generates more than `10` status codes from the list `400 401 403 404 405 429 444` within `60` seconds their IP address will be banned for `86400` seconds.
@ -259,19 +268,19 @@ That kind of security is implemented but not enabled by default in BunkerWeb and
Here is the list of related settings :
| Setting | Default | Context |Multiple| Description |
|---------------------------|------------|---------|--------|------------------------------------------------------------------------------------------------------------------------------|
|`USE_ANTIBOT` |`no` |multisite|no |Activate antibot feature. |
|`ANTIBOT_URI` |`/challenge`|multisite|no |Unused URI that clients will be redirected to to solve the challenge. |
|`ANTIBOT_RECAPTCHA_SCORE` |`0.7` |multisite|no |Minimum score required for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SITEKEY`| |multisite|no |Sitekey for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SECRET` | |multisite|no |Secret for reCAPTCHA challenge. |
|`ANTIBOT_HCAPTCHA_SITEKEY` | |multisite|no |Sitekey for hCaptcha challenge. |
|`ANTIBOT_HCAPTCHA_SECRET` | |multisite|no |Secret for hCaptcha challenge. |
|`ANTIBOT_TURNSTILE_SITEKEY`| |multisite|no |Sitekey for Turnstile challenge. |
|`ANTIBOT_TURNSTILE_SECRET` | |multisite|no |Secret for Turnstile challenge. |
|`ANTIBOT_TIME_RESOLVE` |`60` |multisite|no |Maximum time (in seconds) clients have to resolve the challenge. Once this time has passed, a new challenge will be generated.|
|`ANTIBOT_TIME_VALID` |`86400` |multisite|no |Maximum validity time of solved challenges. Once this time has passed, clients will need to resolve a new one. |
| Setting | Default | Context | Multiple | Description |
| --------------------------- | ------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `USE_ANTIBOT` | `no` | multisite | no | Activate antibot feature. |
| `ANTIBOT_URI` | `/challenge` | multisite | no | Unused URI that clients will be redirected to to solve the challenge. |
| `ANTIBOT_RECAPTCHA_SCORE` | `0.7` | multisite | no | Minimum score required for reCAPTCHA challenge. |
| `ANTIBOT_RECAPTCHA_SITEKEY` | | multisite | no | Sitekey for reCAPTCHA challenge. |
| `ANTIBOT_RECAPTCHA_SECRET` | | multisite | no | Secret for reCAPTCHA challenge. |
| `ANTIBOT_HCAPTCHA_SITEKEY` | | multisite | no | Sitekey for hCaptcha challenge. |
| `ANTIBOT_HCAPTCHA_SECRET` | | multisite | no | Secret for hCaptcha challenge. |
| `ANTIBOT_TURNSTILE_SITEKEY` | | multisite | no | Sitekey for Turnstile challenge. |
| `ANTIBOT_TURNSTILE_SECRET` | | multisite | no | Secret for Turnstile challenge. |
| `ANTIBOT_TIME_RESOLVE` | `60` | multisite | no | Maximum time (in seconds) clients have to resolve the challenge. Once this time has passed, a new challenge will be generated. |
| `ANTIBOT_TIME_VALID` | `86400` | multisite | no | Maximum validity time of solved challenges. Once this time has passed, clients will need to resolve a new one. |
Please note that antibot feature is using a cookie to maintain a session with clients. If you are using BunkerWeb in a clustered environment, you will need to set the `SESSIONS_SECRET` and `SESSIONS_NAME` settings to another value than the default one (which is `random`). You will find more info about sessions [here](settings.md#sessions).
@ -287,30 +296,30 @@ STREAM support :warning:
You can use the following settings to set up blacklisting :
| Setting | Default | Context |Multiple| Description |
|----------------------------------|------------------------------------------------------------------------------------------------------------------------------|---------|--------|------------------------------------------------------------------------------------------------|
|`USE_BLACKLIST` |`yes` |multisite|no |Activate blacklist feature. |
|`BLACKLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to block. |
|`BLACKLIST_IP_URLS` |`https://www.dan.me.uk/torlist/?exit` |global |no |List of URLs, separated with spaces, containing bad IP/network to block. |
|`BLACKLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS blacklist checks on global IP addresses. |
|`BLACKLIST_RDNS` |`.shodan.io .censys.io` |multisite|no |List of reverse DNS suffixes, separated with spaces, to block. |
|`BLACKLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to block. |
|`BLACKLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to block. |
|`BLACKLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to block. |
|`BLACKLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_USER_AGENT_URLS` |`https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list`|global |no |List of URLs, separated with spaces, containing bad User-Agent to block. |
|`BLACKLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to block. |
|`BLACKLIST_IGNORE_IP` | |multisite|no |List of IP/network, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_IP_URLS` | |global |no |List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist.|
|`BLACKLIST_IGNORE_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI_URLS` | |global |no |List of URLs, separated with spaces, containing URI to ignore in the blacklist. |
| Setting | Default | Context | Multiple | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | -------- | ------------------------------------------------------------------------------------------------ |
| `USE_BLACKLIST` | `yes` | multisite | no | Activate blacklist feature. |
| `BLACKLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to block. |
| `BLACKLIST_IP_URLS` | `https://www.dan.me.uk/torlist/?exit` | global | no | List of URLs, separated with spaces, containing bad IP/network to block. |
| `BLACKLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS blacklist checks on global IP addresses. |
| `BLACKLIST_RDNS` | `.shodan.io .censys.io` | multisite | no | List of reverse DNS suffixes, separated with spaces, to block. |
| `BLACKLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to block. |
| `BLACKLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to block. |
| `BLACKLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to block. |
| `BLACKLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_USER_AGENT_URLS` | `https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list` | global | no | List of URLs, separated with spaces, containing bad User-Agent to block. |
| `BLACKLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to block. |
| `BLACKLIST_IGNORE_IP` | | multisite | no | List of IP/network, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_IP_URLS` | | global | no | List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. |
| `BLACKLIST_IGNORE_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. |
| `BLACKLIST_IGNORE_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to ignore in the blacklist. |
| `BLACKLIST_IGNORE_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. |
| `BLACKLIST_IGNORE_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_URI_URLS` | | global | no | List of URLs, separated with spaces, containing URI to ignore in the blacklist. |
When using stream mode, only IP, RDNS and ASN checks will be done.
@ -320,20 +329,20 @@ STREAM support :warning:
You can use the following settings to set up greylisting :
| Setting |Default| Context |Multiple| Description |
|--------------------------|-------|---------|--------|----------------------------------------------------------------------------------------------|
|`USE_GREYLIST` |`no` |multisite|no |Activate greylist feature. |
|`GREYLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to put into the greylist. |
|`GREYLIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to put into the greylist. |
|`GREYLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS greylist checks on global IP addresses. |
|`GREYLIST_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist.|
|`GREYLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to put into the greylist. |
|`GREYLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to put into the greylist. |
|`GREYLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to put into the greylist. |
|`GREYLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to put into the greylist. |
| Setting | Default | Context | Multiple | Description |
| -------------------------- | ------- | --------- | -------- | ---------------------------------------------------------------------------------------------- |
| `USE_GREYLIST` | `no` | multisite | no | Activate greylist feature. |
| `GREYLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to put into the greylist. |
| `GREYLIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to put into the greylist. |
| `GREYLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS greylist checks on global IP addresses. |
| `GREYLIST_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
| `GREYLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. |
| `GREYLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to put into the greylist. |
| `GREYLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to put into the greylist. |
| `GREYLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to put into the greylist. |
| `GREYLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to put into the greylist. |
When using stream mode, only IP, RDNS and ASN checks will be done.
@ -343,20 +352,20 @@ STREAM support :warning:
You can use the following settings to set up whitelisting :
| Setting | Default | Context |Multiple| Description |
|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|----------------------------------------------------------------------------------|
|`USE_WHITELIST` |`yes` |multisite|no |Activate whitelist feature. |
|`WHITELIST_IP` |`20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247 54.208.102.37 107.21.1.8`|multisite|no |List of IP/network, separated with spaces, to put into the whitelist. |
|`WHITELIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to whitelist. |
|`WHITELIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS whitelist checks on global IP addresses. |
|`WHITELIST_RDNS` |`.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com` |multisite|no |List of reverse DNS suffixes, separated with spaces, to whitelist. |
|`WHITELIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist.|
|`WHITELIST_ASN` |`32934` |multisite|no |List of ASN numbers, separated with spaces, to whitelist. |
|`WHITELIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to whitelist. |
|`WHITELIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. |
| Setting | Default | Context | Multiple | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ---------------------------------------------------------------------------------- |
| `USE_WHITELIST` | `yes` | multisite | no | Activate whitelist feature. |
| `WHITELIST_IP` | `20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247 54.208.102.37 107.21.1.8` | multisite | no | List of IP/network, separated with spaces, to put into the whitelist. |
| `WHITELIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to whitelist. |
| `WHITELIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS whitelist checks on global IP addresses. |
| `WHITELIST_RDNS` | `.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com` | multisite | no | List of reverse DNS suffixes, separated with spaces, to whitelist. |
| `WHITELIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. |
| `WHITELIST_ASN` | `32934` | multisite | no | List of ASN numbers, separated with spaces, to whitelist. |
| `WHITELIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to whitelist. |
| `WHITELIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to whitelist. |
| `WHITELIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to whitelist. |
When using stream mode, only IP, RDNS and ASN checks will be done.
@ -373,11 +382,11 @@ Please be aware, this feature is new and further improvements will be added soon
Here is the list of settings related to reverse scan :
| Setting | Default | Description |
| :----------: | :--------------------------------------------------------------------------: | :--------------------------------------------- |
| `USE_REVERSE_SCAN` | `no` | When set to `yes`, will enable ReverseScan. |
| `REVERSE_SCAN_PORTS` | `22 80 443 3128 8000 8080` | List of suspicious ports to scan. |
| `REVERSE_SCAN_TIMEOUT` | `500` | Specify the maximum timeout (in ms) when scanning a port. |
| Setting | Default | Description |
| :--------------------: | :------------------------: | :-------------------------------------------------------- |
| `USE_REVERSE_SCAN` | `no` | When set to `yes`, will enable ReverseScan. |
| `REVERSE_SCAN_PORTS` | `22 80 443 3128 8000 8080` | List of suspicious ports to scan. |
| `REVERSE_SCAN_TIMEOUT` | `500` | Specify the maximum timeout (in ms) when scanning a port. |
## BunkerNet
@ -421,12 +430,12 @@ STREAM support :white_check_mark:
The following settings are related to the Limiting connections feature :
| Setting | Default | Description |
| :--------------------: | :-----: | :----------------------------------------------------------------------------------------- |
| `USE_LIMIT_CONN` | `yes` | When set to `yes`, will limit the maximum number of concurrent connections for a given IP. |
| `LIMIT_CONN_MAX_HTTP1` | `10` | Maximum number of concurrent connections when using HTTP1 protocol. |
| `LIMIT_CONN_MAX_HTTP2` | `100` | Maximum number of concurrent streams when using HTTP2 protocol. |
| `LIMIT_CONN_MAX_STREAM`| `10` | Maximum number of connections per IP when using stream. |
| Setting | Default | Description |
| :---------------------: | :-----: | :----------------------------------------------------------------------------------------- |
| `USE_LIMIT_CONN` | `yes` | When set to `yes`, will limit the maximum number of concurrent connections for a given IP. |
| `LIMIT_CONN_MAX_HTTP1` | `10` | Maximum number of concurrent connections when using HTTP1 protocol. |
| `LIMIT_CONN_MAX_HTTP2` | `100` | Maximum number of concurrent streams when using HTTP2 protocol. |
| `LIMIT_CONN_MAX_STREAM` | `10` | Maximum number of connections per IP when using stream. |
### Requests
@ -434,15 +443,15 @@ STREAM support :x:
The following settings are related to the Limiting requests feature :
| Setting |Default| Context |Multiple| Description |
|-----------------------|-------|---------|--------|---------------------------------------------------------------------------------------------|
|`USE_LIMIT_REQ` |`yes` |multisite|no |Activate limit requests feature. |
|`LIMIT_REQ_URL` |`/` |multisite|yes |URL (PCRE regex) where the limit request will be applied or special value / for all requests.|
|`LIMIT_REQ_RATE` |`2r/s` |multisite|yes |Rate to apply to the URL (s for second, m for minute, h for hour and d for day). |
|`USE_LIMIT_CONN` |`yes` |multisite|no |Activate limit connections feature. |
|`LIMIT_CONN_MAX_HTTP1` |`10` |multisite|no |Maximum number of connections per IP when using HTTP/1.X protocol. |
|`LIMIT_CONN_MAX_HTTP2` |`100` |multisite|no |Maximum number of streams per IP when using HTTP/2 protocol. |
|`LIMIT_CONN_MAX_STREAM`|`10` |multisite|no |Maximum number of connections per IP when using stream. |
| Setting | Default | Context | Multiple | Description |
| ----------------------- | ------- | --------- | -------- | --------------------------------------------------------------------------------------------- |
| `USE_LIMIT_REQ` | `yes` | multisite | no | Activate limit requests feature. |
| `LIMIT_REQ_URL` | `/` | multisite | yes | URL (PCRE regex) where the limit request will be applied or special value / for all requests. |
| `LIMIT_REQ_RATE` | `2r/s` | multisite | yes | Rate to apply to the URL (s for second, m for minute, h for hour and d for day). |
| `USE_LIMIT_CONN` | `yes` | multisite | no | Activate limit connections feature. |
| `LIMIT_CONN_MAX_HTTP1` | `10` | multisite | no | Maximum number of connections per IP when using HTTP/1.X protocol. |
| `LIMIT_CONN_MAX_HTTP2` | `100` | multisite | no | Maximum number of streams per IP when using HTTP/2 protocol. |
| `LIMIT_CONN_MAX_STREAM` | `10` | multisite | no | Maximum number of connections per IP when using stream. |
Please note that you can add different rates for different URLs by adding a number as a suffix to the settings for example : `LIMIT_REQ_URL_1=^/url1$`, `LIMIT_REQ_RATE_1=5r/d`, `LIMIT_REQ_URL_2=^/url2/subdir/.*$`, `LIMIT_REQ_RATE_2=1r/m`, ...
@ -459,10 +468,10 @@ The country security feature allows you to apply policy based on the country of
Here is the list of related settings :
| Setting |Default| Context |Multiple| Description |
|-------------------|-------|---------|--------|--------------------------------------------------------------------------------------------------------------|
|`BLACKLIST_COUNTRY`| |multisite|no |Deny access if the country of the client is in the list (ISO 3166-1 alpha-2 format separated with spaces). |
|`WHITELIST_COUNTRY`| |multisite|no |Deny access if the country of the client is not in the list (ISO 3166-1 alpha-2 format separated with spaces).|
| Setting | Default | Context | Multiple | Description |
| ------------------- | ------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `BLACKLIST_COUNTRY` | | multisite | no | Deny access if the country of the client is in the list (ISO 3166-1 alpha-2 format separated with spaces). |
| `WHITELIST_COUNTRY` | | multisite | no | Deny access if the country of the client is not in the list (ISO 3166-1 alpha-2 format separated with spaces). |
Using both country blacklist and whitelist at the same time makes no sense. If you do, please note that only the whitelist will be executed.
@ -474,10 +483,10 @@ STREAM support :x:
You can quickly protect sensitive resources like the admin area for example, by requiring HTTP basic authentication. Here is the list of related settings :
| Setting | Default | Description |
| :-----------------------: | :---------------: | :------------------------------------------------------------------------------------------- |
| `USE_AUTH_BASIC` | `no` | When set to `yes` HTTP auth basic will be enabled. |
| `AUTH_BASIC_LOCATION` | `sitewide` | Location (URL) of the sensitive resource. Use special value `sitewide` to enable everywhere. |
| Setting | Default | Description |
| :-------------------: | :---------------: | :------------------------------------------------------------------------------------------- |
| `USE_AUTH_BASIC` | `no` | When set to `yes` HTTP auth basic will be enabled. |
| `AUTH_BASIC_LOCATION` | `sitewide` | Location (URL) of the sensitive resource. Use special value `sitewide` to enable everywhere. |
| `AUTH_BASIC_USER` | `changeme` | The username required. |
| `AUTH_BASIC_PASSWORD` | `changeme` | The password required. |
| `AUTH_BASIC_TEXT` | `Restricted area` | Text to display in the auth prompt. |
@ -488,8 +497,8 @@ You can deploy complex authentication (e.g. SSO), by using the auth request sett
**Auth request settings are related to reverse proxy rules.**
| Setting | Default | Context |Multiple| Description |
|---------------------------------------|----------------------------------|---------|--------|--------------------------------------------------------------------------------------------------------------------|
|`REVERSE_PROXY_AUTH_REQUEST` | |multisite|yes |Enable authentication using an external provider (value of auth_request directive). |
|`REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL`| |multisite|yes |Redirect clients to sign-in URL when using REVERSE_PROXY_AUTH_REQUEST (used when auth_request call returned 401). |
|`REVERSE_PROXY_AUTH_REQUEST_SET` | |multisite|yes |List of variables to set from the authentication provider, separated with ; (values of auth_request_set directives).|
| Setting | Default | Context | Multiple | Description |
| --------------------------------------- | ------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `REVERSE_PROXY_AUTH_REQUEST` | | multisite | yes | Enable authentication using an external provider (value of auth_request directive). |
| `REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL` | | multisite | yes | Redirect clients to sign-in URL when using REVERSE_PROXY_AUTH_REQUEST (used when auth_request call returned 401). |
| `REVERSE_PROXY_AUTH_REQUEST_SET` | | multisite | yes | List of variables to set from the authentication provider, separated with ; (values of auth_request_set directives). |

View file

@ -15,39 +15,41 @@ When settings are considered as "multiple", it means that you can have multiple
STREAM support :warning:
| Setting | Default | Context |Multiple| Description |
|------------------------------|------------------------------------------------------------------------------------------------------------------------|---------|--------|--------------------------------------------------|
|`IS_LOADING` |`no` |global |no |Internal use : set to yes when BW is loading. |
|`NGINX_PREFIX` |`/etc/nginx/` |global |no |Where nginx will search for configurations. |
|`HTTP_PORT` |`8080` |global |no |HTTP port number which bunkerweb binds to. |
|`HTTPS_PORT` |`8443` |global |no |HTTPS port number which bunkerweb binds to. |
|`MULTISITE` |`no` |global |no |Multi site activation. |
|`SERVER_NAME` |`www.example.com` |multisite|no |List of the virtual hosts served by bunkerweb. |
|`WORKER_PROCESSES` |`auto` |global |no |Number of worker processes. |
|`WORKER_RLIMIT_NOFILE` |`2048` |global |no |Maximum number of open files for worker processes.|
|`WORKER_CONNECTIONS` |`1024` |global |no |Maximum number of connections per worker. |
|`LOG_FORMAT` |`$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"`|global |no |The format to use for access logs. |
|`LOG_LEVEL` |`notice` |global |no |The level to use for error logs. |
|`DNS_RESOLVERS` |`127.0.0.11` |global |no |DNS addresses of resolvers to use. |
|`DATASTORE_MEMORY_SIZE` |`64m` |global |no |Size of the internal datastore. |
|`CACHESTORE_MEMORY_SIZE` |`64m` |global |no |Size of the internal cachestore. |
|`CACHESTORE_IPC_MEMORY_SIZE` |`16m` |global |no |Size of the internal cachestore (ipc). |
|`CACHESTORE_MISS_MEMORY_SIZE` |`16m` |global |no |Size of the internal cachestore (miss). |
|`CACHESTORE_LOCKS_MEMORY_SIZE`|`16m` |global |no |Size of the internal cachestore (locks). |
|`USE_API` |`yes` |global |no |Activate the API to control BunkerWeb. |
|`API_HTTP_PORT` |`5000` |global |no |Listen port number for the API. |
|`API_LISTEN_IP` |`0.0.0.0` |global |no |Listen IP address for the API. |
|`API_SERVER_NAME` |`bwapi` |global |no |Server name (virtual host) for the API. |
|`API_WHITELIST_IP` |`127.0.0.0/8` |global |no |List of IP/network allowed to contact the API. |
|`AUTOCONF_MODE` |`no` |global |no |Enable Autoconf Docker integration. |
|`SWARM_MODE` |`no` |global |no |Enable Docker Swarm integration. |
|`KUBERNETES_MODE` |`no` |global |no |Enable Kubernetes integration. |
|`SERVER_TYPE` |`http` |multisite|no |Server type : http or stream. |
|`LISTEN_STREAM` |`yes` |multisite|no |Enable listening for non-ssl (passthrough). |
|`LISTEN_STREAM_PORT` |`1337` |multisite|no |Listening port for non-ssl (passthrough). |
|`LISTEN_STREAM_PORT_SSL` |`4242` |multisite|no |Listening port for ssl (passthrough). |
|`USE_UDP` |`no` |multisite|no |UDP listen instead of TCP (stream). |
|`USE_IPV6` |`no` |global |no |Enable IPv6 connectivity. |
| Setting | Default | Context |Multiple| Description |
|------------------------------|------------------------------------------------------------------------------------------------------------------------|---------|--------|------------------------------------------------------------|
|`IS_LOADING` |`no` |global |no |Internal use : set to yes when BW is loading. |
|`NGINX_PREFIX` |`/etc/nginx/` |global |no |Where nginx will search for configurations. |
|`HTTP_PORT` |`8080` |global |no |HTTP port number which bunkerweb binds to. |
|`HTTPS_PORT` |`8443` |global |no |HTTPS port number which bunkerweb binds to. |
|`MULTISITE` |`no` |global |no |Multi site activation. |
|`SERVER_NAME` |`www.example.com` |multisite|no |List of the virtual hosts served by bunkerweb. |
|`WORKER_PROCESSES` |`auto` |global |no |Number of worker processes. |
|`WORKER_RLIMIT_NOFILE` |`2048` |global |no |Maximum number of open files for worker processes. |
|`WORKER_CONNECTIONS` |`1024` |global |no |Maximum number of connections per worker. |
|`LOG_FORMAT` |`$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"`|global |no |The format to use for access logs. |
|`LOG_LEVEL` |`notice` |global |no |The level to use for error logs. |
|`DNS_RESOLVERS` |`127.0.0.11` |global |no |DNS addresses of resolvers to use. |
|`DATASTORE_MEMORY_SIZE` |`64m` |global |no |Size of the internal datastore. |
|`CACHESTORE_MEMORY_SIZE` |`64m` |global |no |Size of the internal cachestore. |
|`CACHESTORE_IPC_MEMORY_SIZE` |`16m` |global |no |Size of the internal cachestore (ipc). |
|`CACHESTORE_MISS_MEMORY_SIZE` |`16m` |global |no |Size of the internal cachestore (miss). |
|`CACHESTORE_LOCKS_MEMORY_SIZE`|`16m` |global |no |Size of the internal cachestore (locks). |
|`USE_API` |`yes` |global |no |Activate the API to control BunkerWeb. |
|`API_HTTP_PORT` |`5000` |global |no |Listen port number for the API. |
|`API_LISTEN_IP` |`0.0.0.0` |global |no |Listen IP address for the API. |
|`API_SERVER_NAME` |`bwapi` |global |no |Server name (virtual host) for the API. |
|`API_WHITELIST_IP` |`127.0.0.0/8` |global |no |List of IP/network allowed to contact the API. |
|`AUTOCONF_MODE` |`no` |global |no |Enable Autoconf Docker integration. |
|`SWARM_MODE` |`no` |global |no |Enable Docker Swarm integration. |
|`KUBERNETES_MODE` |`no` |global |no |Enable Kubernetes integration. |
|`SERVER_TYPE` |`http` |multisite|no |Server type : http or stream. |
|`LISTEN_STREAM` |`yes` |multisite|no |Enable listening for non-ssl (passthrough). |
|`LISTEN_STREAM_PORT` |`1337` |multisite|no |Listening port for non-ssl (passthrough). |
|`LISTEN_STREAM_PORT_SSL` |`4242` |multisite|no |Listening port for ssl (passthrough). |
|`USE_UDP` |`no` |multisite|no |UDP listen instead of TCP (stream). |
|`USE_IPV6` |`no` |global |no |Enable IPv6 connectivity. |
|`IS_DRAFT` |`no` |multisite|no |Internal use : set to yes when the service is in draft mode.|
|`TIMERS_LOG_LEVEL` |`debug` |global |no |Log level for timers. |
## Core settings
@ -219,9 +221,10 @@ STREAM support :white_check_mark:
Integrate easily the Database.
| Setting | Default |Context|Multiple| Description |
|--------------|-----------------------------------------|-------|--------|--------------------------------------------------|
|`DATABASE_URI`|`sqlite:////var/lib/bunkerweb/db.sqlite3`|global |no |The database URI, following the sqlalchemy format.|
| Setting | Default |Context|Multiple| Description |
|--------------------|-----------------------------------------|-------|--------|--------------------------------------------------|
|`DATABASE_URI` |`sqlite:////var/lib/bunkerweb/db.sqlite3`|global |no |The database URI, following the sqlalchemy format.|
|`DATABASE_LOG_LEVEL`|`warning` |global |no |The level to use for database logs. |
### DNSBL
@ -312,6 +315,16 @@ Manage HTTP headers sent to clients.
|`X_CONTENT_TYPE_OPTIONS` |`nosniff` |multisite|no |Value for the X-Content-Type-Options header. |
|`X_XSS_PROTECTION` |`1; mode=block` |multisite|no |Value for the X-XSS-Protection header. |
### Jobs
STREAM support :white_check_mark:
Fake core plugin for internal jobs.
| Setting |Default|Context|Multiple| Description |
|-----------------------|-------|-------|--------|-----------------------------------------------|
|`SEND_ANONYMOUS_REPORT`|`yes` |global |no |Send anonymous report to BunkerWeb maintainers.|
### Let's Encrypt
STREAM support :white_check_mark:
@ -340,6 +353,18 @@ Limit maximum number of requests and connections.
|`LIMIT_CONN_MAX_HTTP2` |`100` |multisite|no |Maximum number of streams per IP when using HTTP/2 protocol. |
|`LIMIT_CONN_MAX_STREAM`|`10` |multisite|no |Maximum number of connections per IP when using stream. |
### Metrics
STREAM support :warning:
Metrics collection and retrieve.
| Setting |Default| Context |Multiple| Description |
|------------------------------|-------|---------|--------|---------------------------------------------------------|
|`USE_METRICS` |`yes` |multisite|no |Enable collection and retrieval of internal metrics. |
|`METRICS_MEMORY_SIZE` |`16m` |global |no |Size of the internal storage for metrics. |
|`METRICS_MAX_BLOCKED_REQUESTS`|`100` |global |no |Maximum number of blocked requests to store (per worker).|
### Miscellaneous
STREAM support :warning:
@ -365,7 +390,6 @@ Miscellaneous settings.
|`OPEN_FILE_CACHE_VALID` |`30s` |multisite|no |Open file cache valid time |
|`EXTERNAL_PLUGIN_URLS` | |global |no |List of external plugins URLs (direct download to .zip or .tar file) to download and install (URLs are separated with space).|
|`DENY_HTTP_STATUS` |`403` |global |no |HTTP status code to send when the request is denied (403 or 444). When using 444, BunkerWeb will close the connection. |
|`SEND_ANONYMOUS_REPORT` |`yes` |global |no |Send anonymous report to BunkerWeb maintainers. |
### ModSecurity
@ -377,6 +401,7 @@ Management of the ModSecurity WAF.
|---------------------------------|--------------|---------|--------|------------------------------------------|
|`USE_MODSECURITY` |`yes` |multisite|no |Enable ModSecurity WAF. |
|`USE_MODSECURITY_CRS` |`yes` |multisite|no |Enable OWASP Core Rule Set. |
|`MODSECURITY_CRS_VERSION` |`3` |multisite|no |Version of the OWASP Core Rule Set to use.|
|`MODSECURITY_SEC_AUDIT_ENGINE` |`RelevantOnly`|multisite|no |SecAuditEngine directive of ModSecurity. |
|`MODSECURITY_SEC_RULE_ENGINE` |`On` |multisite|no |SecRuleEngine directive of ModSecurity. |
|`MODSECURITY_SEC_AUDIT_LOG_PARTS`|`ABCFHZ` |multisite|no |SecAuditLogParts directive of ModSecurity.|
@ -394,6 +419,16 @@ Manage local or remote PHP-FPM.
|`LOCAL_PHP` | |multisite|no |Path to the PHP-FPM socket file. |
|`LOCAL_PHP_PATH` | |multisite|no |Root folder containing files in the local PHP-FPM instance. |
### Pro
STREAM support :x:
Pro settings for the Pro version of BunkerWeb.
| Setting |Default|Context|Multiple| Description |
|-----------------|-------|-------|--------|-------------------------------------------------|
|`PRO_LICENSE_KEY`| |global |no |The License Key for the Pro version of BunkerWeb.|
### Real IP
STREAM support :warning:
@ -434,6 +469,7 @@ Redis server configuration when using BunkerWeb in cluster mode.
|`REDIS_PORT` |`6379` |global |no |Redis server port. |
|`REDIS_DATABASE` |`0` |global |no |Redis database number. |
|`REDIS_SSL` |`no` |global |no |Use SSL/TLS connection with Redis server. |
|`REDIS_SSL_VERIFY` |`no` |global |no |Verify the certificate of Redis server. |
|`REDIS_TIMEOUT` |`1000` |global |no |Redis server timeout (in ms) for connect, read and write. |
|`REDIS_KEEPALIVE_IDLE` |`30000`|global |no |Max idle time (in ms) before closing redis connection in the pool. |
|`REDIS_KEEPALIVE_POOL` |`10` |global |no |Max number of redis connection(s) kept in the pool. |
@ -478,6 +514,7 @@ Manage reverse proxy configurations.
|`REVERSE_PROXY_READ_TIMEOUT` |`60s` |multisite|yes |Timeout when reading from the proxied resource. |
|`REVERSE_PROXY_SEND_TIMEOUT` |`60s` |multisite|yes |Timeout when sending to the proxied resource. |
|`REVERSE_PROXY_INCLUDES` | |multisite|yes |Additional configuration to include in the location block, separated with spaces. |
|`REVERSE_PROXY_CUSTOM_HOST` | |multisite|no |Override Host header sent to upstream server. |
### Reverse scan

View file

@ -1728,3 +1728,27 @@ After a successful login/password combination, you will be prompted to enter you
```shell
systemctl restart bunkerweb
```
## Upgrade to PRO
In case you have buy a (pro version)[https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc#pro] and you already have a BunkerWeb setup with an UI, you can upgrade doing this :
- access the **global config page**.
- click on the **pro plugin**
- fill the **setting Pro License Key**
- **save** your changes
<figure markdown>
![Overview](assets/img/pro-from-ui.webp){ align=center, width="1000" }
<figcaption>Upgrade to PRO from UI</figcaption>
</figure>
If your license key is valid, the upgrade to the pro version will take place automatically in the background.
You can check the status of your version by going to the **home page**, you will see this in case of success :
<figure markdown>
![Overview](assets/img/pro-home-card.png){ align=center, width="450" }
<figcaption>PRO version card</figcaption>
</figure>

45
misc/format-jinja-files.sh Executable file
View file

@ -0,0 +1,45 @@
#!/bin/bash
first_arg=$1
args=("$@")
if [ "$first_arg" == "manual" ]; then
echo "⏳ Running djLint for web UI Jinja files..."
djlint --reformat --profile=jinja --exclude=src/ui/templates/setup.html src/ui/templates
ret=$?
if [ $ret -ne 0 ]; then
echo "❌ djLint failed for web UI template files. Exiting..."
exit $ret
fi
echo "✅ djLint for web UI template files passed."
echo "⏳ Running djLint for core plugins UI Jinja files..."
djlint --reformat --profile=jinja src/common/core/*/ui
ret=$?
if [ $ret -ne 0 ]; then
echo "❌ djLint failed for core plugins UI template files. Exiting..."
exit $ret
fi
echo "✅ djLint for core plugins UI template files passed."
echo "⏳ Running djLint for documentation Jinja files..."
djlint --reformat --profile=jinja docs/
ret=$?
if [ $ret -ne 0 ]; then
echo "❌ djLint failed for documentation files. Exiting..."
exit $ret
fi
echo "✅ djLint for documentation files passed."
echo "✅ All djLint commands passed"
else
echo "⏳ Running custom djLint command with args: ${args[*]}"
djlint --reformat --profile=jinja "${args[@]}"
ret=$?
if [ $ret -ne 0 ]; then
echo "❌ djLint failed for custom command. Exiting..."
exit $ret
fi
echo "✅ djLint for custom command passed."
fi

View file

@ -232,7 +232,7 @@ class IngressController(Controller):
obj = event["object"]
metadata = obj.metadata if obj else None
annotations = metadata.annotations if metadata else None
data = getattr(obj, 'data', None) if obj else None
data = getattr(obj, "data", None) if obj else None
if not obj:
return False
if obj.kind == "Pod":

View file

@ -1,4 +1,4 @@
FROM nginx:1.24.0-alpine@sha256:76ca7f6bfe01c3e22e3af85fd37c30149ece3ac2a444973687cab1765abca115 AS builder
FROM nginx:1.24.0-alpine@sha256:6845649eadc1f0a5dacaf5bb3f01b480ce200ae1249114be11fef9d389196eaf AS builder
# Install temporary requirements for the dependencies
RUN apk add --no-cache --virtual .build-deps bash autoconf libtool automake geoip-dev g++ gcc curl-dev libxml2-dev pcre-dev make linux-headers musl-dev gd-dev gnupg brotli-dev openssl-dev patch readline-dev yajl yajl-dev yajl-tools py3-pip
@ -67,8 +67,8 @@ RUN apk add --no-cache pcre bash python3 yajl && \
mkdir -p /var/www/html && \
mkdir -p /etc/bunkerweb && \
mkdir -p /data/cache && ln -s /data/cache /var/cache/bunkerweb && \
for dir in $(echo "configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
for dir in $(echo "pro configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
for dir in $(echo "pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
chown -R root:nginx /data /etc/nginx /var/cache/bunkerweb /etc/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /usr/bin/bwcli && \
chmod -R 770 /data /etc/nginx /var/cache/bunkerweb /var/tmp/bunkerweb /var/log/bunkerweb /var/run/bunkerweb && \
chmod 750 cli/main.py gen/main.py helpers/*.sh entrypoint.sh /usr/bin/bwcli deps/python/bin/* && \
@ -78,7 +78,7 @@ RUN apk add --no-cache pcre bash python3 yajl && \
ln -s /proc/1/fd/1 /var/log/bunkerweb/access.log
# Fix CVEs
RUN apk add --no-cache "libwebp>=1.2.4-r3" "curl>=8.3.0-r0" "libcurl>=8.3.0-r0" "nghttp2-libs>=1.51.0-r2" "libx11>=1.8.7-r0" "libssl3>=3.0.12-r1" "libcrypto3>=3.0.12-r1" "libexpat>=2.6.0-r0"
RUN apk add --no-cache "curl>=8.5.0-r0" "libcurl>=8.5.0-r0" "libcrypto3>=3.0.12-r4" "libexpat>=2.6.0-r0" "libssl3>=3.0.12-r4" "libwebp>=1.2.4-r3" "libx11>=1.8.7-r0" "nghttp2-libs>=1.51.0-r2"
EXPOSE 8080/tcp 8443/tcp

View file

@ -44,7 +44,7 @@ function trap_reload() {
trap "trap_reload" HUP
# generate "temp" config
echo -e "IS_LOADING=yes\nUSE_BUNKERNET=no\nSERVER_NAME=\nAPI_HTTP_PORT=${API_HTTP_PORT:-5000}\nAPI_SERVER_NAME=${API_SERVER_NAME:-bwapi}\nAPI_WHITELIST_IP=${API_WHITELIST_IP:-127.0.0.0/8}\nUSE_REAL_IP=${USE_REAL_IP:-no}\nUSE_PROXY_PROTOCOL=${USE_PROXY_PROTOCOL:-no}\nREAL_IP_FROM=${REAL_IP_FROM:-192.168.0.0/16 172.16.0.0/12 10.0.0.0/8}\nREAL_IP_HEADER=${REAL_IP_HEADER:-X-Forwarded-For}\nHTTP_PORT=${HTTP_PORT:-8080}\nHTTPS_PORT=${HTTPS_PORT:-8443}" > /tmp/variables.env
echo -e "IS_LOADING=yes\nUSE_BUNKERNET=no\nSEND_ANONYMOUS_REPORT=no\nSERVER_NAME=\nMODSECURITY_CRS_VERSION=${MODSECURITY_CRS_VERSION:-4}\nAPI_HTTP_PORT=${API_HTTP_PORT:-5000}\nAPI_SERVER_NAME=${API_SERVER_NAME:-bwapi}\nAPI_WHITELIST_IP=${API_WHITELIST_IP:-127.0.0.0/8}\nUSE_REAL_IP=${USE_REAL_IP:-no}\nUSE_PROXY_PROTOCOL=${USE_PROXY_PROTOCOL:-no}\nREAL_IP_FROM=${REAL_IP_FROM:-192.168.0.0/16 172.16.0.0/12 10.0.0.0/8}\nREAL_IP_HEADER=${REAL_IP_HEADER:-X-Forwarded-For}\nHTTP_PORT=${HTTP_PORT:-8080}\nHTTPS_PORT=${HTTPS_PORT:-8443}" > /tmp/variables.env
python3 /usr/share/bunkerweb/gen/main.py --variables /tmp/variables.env
# start nginx

View file

@ -128,6 +128,8 @@ api.global.POST["^/confs$"] = function(self)
destination = "/etc/bunkerweb/configs"
elseif self.ctx.bw.uri == "/plugins" then
destination = "/etc/bunkerweb/plugins"
elseif self.ctx.bw.uri == "/pro_plugins" then
destination = "/etc/bunkerweb/pro/plugins"
end
local form, err = upload:new(4096)
if not form then
@ -175,6 +177,8 @@ api.global.POST["^/custom_configs$"] = api.global.POST["^/confs$"]
api.global.POST["^/plugins$"] = api.global.POST["^/confs$"]
api.global.POST["^/pro_plugins$"] = api.global.POST["^/confs$"]
api.global.POST["^/unban$"] = function(self)
read_body()
local data = get_body_data()

View file

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

Binary file not shown.

Binary file not shown.

View file

@ -211,24 +211,30 @@ class CLI(ApiCaller):
def unban(self, ip: str) -> Tuple[bool, str]:
if self.__redis:
ok = self.__redis.delete(f"bans_ip_{ip}")
if not ok:
self.__logger.error(f"Failed to delete ban for {ip} from redis")
try:
ok = self.__redis.delete(f"bans_ip_{ip}")
if not ok:
self.__logger.error(f"Failed to delete ban for {ip} from redis")
except Exception as e:
self.__logger.error(f"Failed to delete ban for {ip} from redis: {e}")
if self.send_to_apis("POST", "/unban", data={"ip": ip}):
return True, f"IP {ip} has been unbanned"
return False, "error"
return False, f"Failed to unban {ip}"
def ban(self, ip: str, exp: float, reason: str) -> Tuple[bool, str]:
if self.__redis:
ok = self.__redis.set(f"bans_ip_{ip}", dumps({"reason": reason, "date": time()}))
if not ok:
self.__logger.error(f"Failed to ban {ip} in redis")
self.__redis.expire(f"bans_ip_{ip}", int(exp))
try:
ok = self.__redis.set(f"bans_ip_{ip}", dumps({"reason": reason, "date": time()}))
if not ok:
self.__logger.error(f"Failed to ban {ip} in redis")
self.__redis.expire(f"bans_ip_{ip}", int(exp))
except Exception as e:
self.__logger.error(f"Failed to ban {ip} in redis: {e}")
if self.send_to_apis("POST", "/ban", data={"ip": ip, "exp": exp, "reason": reason}):
return (True, f"IP {ip} has been banned for {format_remaining_time(exp)} with reason {reason}")
return False, "error"
return False, f"Failed to ban {ip}"
def bans(self) -> Tuple[bool, str]:
servers = {}

View file

@ -18,19 +18,26 @@ server {
# HTTPS listen
{% set os = import("os") %}
{% if os.path.isfile("/var/cache/bunkerweb/default-server-cert/cert.pem") +%}
{% if has_variable(all, "USE_CUSTOM_SSL", "yes") or has_variable(all, "AUTO_LETS_ENCRYPT", "yes") or has_variable(all, "GENERATE_SELF_SIGNED_SSL", "yes") +%}
listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
{% if USE_IPV6 == "yes" +%}
listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
{% endif %}
ssl_protocols {{ SSL_PROTOCOLS }};
ssl_prefer_server_ciphers on;
ssl_session_tickets off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
{% if "TLSv1.2" in SSL_PROTOCOLS +%}
ssl_dhparam /etc/nginx/dhparam;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
{% endif %}
ssl_certificate /var/cache/bunkerweb/default-server-cert/cert.pem;
ssl_certificate_key /var/cache/bunkerweb/default-server-cert/cert.key;
listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
{% if USE_IPV6 == "yes" +%}
listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
{% endif %}
{% endif %}
{% if IS_LOADING == "yes" +%}
root /usr/share/bunkerweb/loading;
index index.html;
try_files /index.html =404;
{% endif %}
# include core and plugins default-server configurations

View file

@ -40,7 +40,7 @@ resolver {{ DNS_RESOLVERS }} {% if USE_IPV6 == "no" %}ipv6=off{% endif %};
port_in_redirect off;
# lua configs
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/etc/bunkerweb/pro/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
lua_package_cpath "/usr/share/bunkerweb/deps/lib/?.so;/usr/share/bunkerweb/deps/lib/lua/?.so;;";
lua_ssl_trusted_certificate "/usr/share/bunkerweb/misc/root-ca.pem";
lua_ssl_verify_depth 2;

View file

@ -41,7 +41,7 @@ init_by_lua_block {
-- Load plugins into the datastore
logger:log(NOTICE, "saving plugins into datastore ...")
local plugins = {}
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins" }
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins", "/etc/bunkerweb/pro/plugins" }
for i, plugin_path in ipairs(plugin_paths) do
local paths = popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
for path in paths:lines() do

View file

@ -41,7 +41,7 @@ init_by_lua_block {
-- Load plugins into the datastore
logger:log(NOTICE, "saving plugins into datastore ...")
local plugins = {}
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins" }
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins", "/etc/bunkerweb/pro/plugins" }
for i, plugin_path in ipairs(plugin_paths) do
local paths = popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
for path in paths:lines() do

View file

@ -1,10 +1,90 @@
lua_shared_dict worker_lock 16k;
init_worker_by_lua_block {
-- Libs
local helpers = require "bunkerweb.helpers"
local utils = require "bunkerweb.utils"
local ngx = ngx
local ERR = ngx.ERR
local INFO = ngx.INFO
local worker = ngx.worker
local worker_id = worker.id
local worker_pid = worker.pid
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 get_variable = utils.get_variable
local tostring = tostring
local time = os.time
local randomseed = math.randomseed
-- Instantiate objects
local logger = require "bunkerweb.logger":new("INIT-WORKERS-" .. tostring(worker_id()))
local datastore = require "bunkerweb.datastore":new()
-- Random seed
randomseed(time() + worker_pid())
-- Don't go further we are in loading state
local is_loading, err = get_variable("IS_LOADING", false)
if not is_loading then
logger:log(ERR, "utils.get_variable() failed : " .. err)
return
elseif is_loading == "yes" then
return
end
logger:log(INFO, "init_workers phase 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 init_workers() methods
logger:log(INFO, "calling init_workers() methods of plugins ...")
for i, plugin_id in ipairs(order.init_workers) 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 init_worker method
if plugin_lua.init_workers ~= 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, "init_workers")
if not ok then
logger:log(ERR, ret)
elseif not ret.ret then
logger:log(ERR, plugin_id .. ":init_workers() call failed : " .. ret.msg)
else
logger:log(INFO, plugin_id .. ":init_workers() call successful : " .. ret.msg)
end
end
else
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method init_workers() is not defined")
end
end
end
logger:log(INFO, "called init_workers() methods of plugins")
-- End
logger:log(INFO, "init_workers phase ended")
-- Our timer function
local ready_work = function(premature)
-- Libs
local helpers = require "bunkerweb.helpers"
local utils = require "bunkerweb.utils"
-- Instantiate objects
local logger = require "bunkerweb.logger":new("INIT-WORKER")
@ -22,9 +102,10 @@ init_worker_by_lua_block {
local new_plugin = helpers.new_plugin
local call_plugin = helpers.call_plugin
local tostring = tostring
local get_variable = utils.get_variable
-- Don't go further we are in loading state
local is_loading, err = require "bunkerweb.utils".get_variable("IS_LOADING", false)
local is_loading, err = get_variable("IS_LOADING", false)
if not is_loading then
logger:log(ERR, "utils.get_variable() failed : " .. err)
return
@ -34,6 +115,7 @@ init_worker_by_lua_block {
-- Recurrent timer
local recurrent_timer
local timers_log_level = ngx[utils.get_variable("TIMERS_LOG_LEVEL", false):upper()]
recurrent_timer = function(premature)
local worker_id = tostring(worker.id())
@ -44,7 +126,7 @@ init_worker_by_lua_block {
logger:log(WARN, "worker is exiting, disabling timer")
return
end
logger:log(INFO, "timer started")
logger:log(timers_log_level, "timer started")
-- Get plugins order
local order, err = datastore:get("plugins_order", true)
@ -54,14 +136,14 @@ init_worker_by_lua_block {
end
-- Call timer() methods
logger:log(INFO, "calling timer() methods of plugins ...")
logger:log(timers_log_level, "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)
logger:log(timers_log_level, err)
else
-- Check if plugin has timer method
if plugin_lua.timer ~= nil then
@ -76,15 +158,15 @@ init_worker_by_lua_block {
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)
logger:log(timers_log_level, plugin_id .. ":timer() call successful : " .. ret.msg)
end
end
else
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method timer() is not defined")
logger:log(timers_log_level, "skipped execution of " .. plugin_id .. " because method timer() is not defined")
end
end
end
logger:log(INFO, "called timer() methods of plugins")
logger:log(timers_log_level, "called timer() methods of plugins")
local hdl
hdl, err = timer_at(5, recurrent_timer)
if not hdl then
@ -189,5 +271,5 @@ init_worker_by_lua_block {
end
-- Start timer
ngx.timer.at(5, ready_work)
timer_at(5, ready_work)
}

View file

@ -13,6 +13,7 @@ access_by_lua_block {
local INFO = ngx.INFO
local WARN = ngx.WARN
local NOTICE = ngx.NOTICE
local HTTP_MOVED_TEMPORARILY = ngx.HTTP_MOVED_TEMPORARILY
local fill_ctx = helpers.fill_ctx
local save_ctx = helpers.save_ctx
local require_plugin = helpers.require_plugin
@ -99,7 +100,7 @@ access_by_lua_block {
else
logger:log(INFO, plugin_id .. ":access() call successful : " .. ret.msg)
end
if ret.status then
if ret.status and not ret.redirect then
if ret.status == get_deny_status() then
set_reason(plugin_id, ret.data, ctx)
logger:log(WARN, "denied access from " .. plugin_id .. " : " .. ret.msg)
@ -109,6 +110,9 @@ access_by_lua_block {
status = ret.status
break
elseif ret.redirect then
if ret.status then
status = ret.status
end
logger:log(NOTICE, plugin_id .. " redirect to " .. ret.redirect .. " : " .. ret.msg)
redirect = ret.redirect
break
@ -141,7 +145,7 @@ access_by_lua_block {
-- Redirect if needed
if redirect then
return ngx_redirect(redirect)
return ngx_redirect(redirect, status or HTTP_MOVED_TEMPORARILY)
end
return true

View file

@ -23,6 +23,9 @@ server {
set $reason_data '';
set $ctx_ref '';
# include config files
include {{ NGINX_PREFIX }}server-http/*.conf;
# include LUA files
include {{ NGINX_PREFIX }}set-lua.conf;
include {{ NGINX_PREFIX }}ssl-certificate-lua.conf;
@ -30,6 +33,4 @@ server {
include {{ NGINX_PREFIX }}header-lua.conf;
include {{ NGINX_PREFIX }}log-lua.conf;
# include config files
include {{ NGINX_PREFIX }}server-http/*.conf;
}

View file

@ -10,12 +10,10 @@ ssl_dhparam /etc/nginx/dhparam;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
{% endif %}
{% if AUTO_LETS_ENCRYPT == "yes" or USE_CUSTOM_SSL == "yes" or GENERATE_SELF_SIGNED_SSL == "yes" %}
listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
{% if USE_IPV6 == "yes" +%}
listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
{% endif %}
{% endif %}
ssl_certificate_by_lua_block {
local class = require "middleclass"
@ -32,6 +30,7 @@ ssl_certificate_by_lua_block {
local is_internal = ngx_req.is_internal
local ERR = ngx.ERR
local INFO = ngx.INFO
local clear_certs = ssl.clear_certs
local set_cert = ssl.set_cert
local set_priv_key = ssl.set_priv_key
local require_plugin = helpers.require_plugin
@ -77,7 +76,11 @@ ssl_certificate_by_lua_block {
logger:log(INFO, plugin_id .. ":ssl_certificate() call successful : " .. ret.msg)
if ret.status then
logger:log(INFO, plugin_id .. " is setting certificate/key : " .. ret.msg)
local ok, err = set_cert(ret.status[1])
local ok, err = clear_certs()
if not ok then
logger:log(ERR, "error while clearing certificates : " .. err)
end
ok, err = set_cert(ret.status[1])
if not ok then
logger:log(ERR, "error while setting certificate : " .. err)
else

View file

@ -20,11 +20,12 @@ server {
set $ctx_ref '';
set $server_name '{{ SERVER_NAME.split(" ")[0] }}';
# include config files
include {{ NGINX_PREFIX }}server-stream/*.conf;
# include LUA files
include {{ NGINX_PREFIX }}ssl-certificate-stream-lua.conf;
include {{ NGINX_PREFIX }}preread-stream-lua.conf;
include {{ NGINX_PREFIX }}log-stream-lua.conf;
# include config files
include {{ NGINX_PREFIX }}server-stream/*.conf;
}

View file

@ -10,12 +10,10 @@ ssl_dhparam /etc/nginx/dhparam;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
{% endif %}
{% if AUTO_LETS_ENCRYPT == "yes" or USE_CUSTOM_SSL == "yes" or GENERATE_SELF_SIGNED_SSL == "yes" %}
listen 0.0.0.0:{{ LISTEN_STREAM_PORT_SSL }} ssl {% if USE_UDP == "yes" %} udp {% endif %}{% if USE_PROXY_PROTOCOL == "yes" %} proxy_protocol {% endif %};
{% if USE_IPV6 == "yes" +%}
listen [::]:{{ LISTEN_STREAM_PORT_SSL }} ssl {% if USE_UDP == "yes" %} udp {% endif %}{% if USE_PROXY_PROTOCOL == "yes" %} proxy_protocol {% endif %};
{% endif %}
{% endif %}
ssl_certificate_by_lua_block {
local class = require "middleclass"

View file

@ -23,7 +23,7 @@ resolver_timeout 30s;
tcp_nodelay on;
# lua path and dicts
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/etc/bunkerweb/pro/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
lua_package_cpath "/usr/share/bunkerweb/deps/lib/?.so;/usr/share/bunkerweb/deps/lib/lua/?.so;;";
lua_ssl_trusted_certificate "/usr/share/bunkerweb/misc/root-ca.pem";
lua_ssl_verify_depth 2;

View file

@ -0,0 +1,3 @@
{% if USE_ANTIBOT != "no" +%}
SecRule REQUEST_FILENAME "@rx ^{{ ANTIBOT_URI }}$" "nolog,phase:4,allow,id:1010"
{% endif %}

View file

@ -1,74 +1,44 @@
{% 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">
{% if is_used %}
<!-- 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
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"
>
Challenges
</p>
<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-red-500 mx-0.5"
>total failed
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-sky-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-75 leading-none text-lg relative fill-white"
>
<path
d="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"
/>
<path
d="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"
/>
<path
d="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"
/>
</svg>
</div>
<!-- end icon -->
</div>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-info-title">INFO</h5>
<div class="core-card-info-list">
<p data-info class="core-card-info-text"></p>
</div>
</div>
<!-- end info -->
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">Challenges</p>
<h5 data-count class="core-card-title">"unknown"</h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">total failed</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-metrics-svg-container blue">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-base core-card-metrics-svg">
<path d="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" />
<path d="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" />
<path d="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" />
</svg>
</div>
<!-- end icon -->
</div>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -82,47 +52,29 @@
type: "text",
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-deactivated-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,13 +1,13 @@
from operator import itemgetter
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}
format_data = [{"code": int(key.split("_")[1]), "count": int(value)} for key, value in data.items()]
format_data.sort(key=itemgetter("count"), reverse=True)
return {"items": format_data}
except:
return {"items": []}

View file

@ -1,77 +1,44 @@
{% 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">
{% if is_used %}
<!-- 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
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
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 flex">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
BAD BEHAVIOR LIST
</h5>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<!-- list container-->
<div class="min-w-[250px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-6 m-0 pb-2 border-b border-gray-400"
>
Error code
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-6 m-0 pb-2 border-b border-gray-400"
>
Count
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
<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"
></p>
<p
data-name="count"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-6 m-0 my-1"
></p>
</li>
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div data-fetch-success-show class="hidden core-card-list w-small">
<div class="core-card-list-title-container">
<h5 class="core-card-list-title">BAD BEHAVIOR LIST</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="w-small core-card-list-wrap">
<!-- header-->
<p class="core-card-list-header col-span-6">Error code</p>
<p class="core-card-list-header col-span-6">Count</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
<li data-item class="core-card-list-item col-span-6">
<p data-name="code" class="core-card-list-item-content col-span-6"></p>
<p data-name="count" class="core-card-list-item-content col-span-6"></p>
</li>
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -86,46 +53,29 @@
listNames: ["code", "count"],
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -4,7 +4,7 @@ def blacklist(**kwargs):
"counter_blacklist_ip",
"counter_blacklist_rdns",
"counter_blacklist_asn",
"counter_blacklist_usa",
"counter_blacklist_ua",
]
try:

View file

@ -1,234 +1,130 @@
{% 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">
{% if is_used %}
<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
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>
<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 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">
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"
>
<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
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"
/>
</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 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">
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 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">
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 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">
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 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">
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>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<div class="core-layout">
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
</div>
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">URL</p>
<h5 data-count-url class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">denied</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container red">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-medium core-card-metrics-svg">
<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" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">IP</p>
<h5 data-count-ip class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">denied</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container lime">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-small core-card-metrics-svg">
<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="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">RDNS</p>
<h5 data-count-rdns class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">denied</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-metrics-svg-container indigo">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-medium core-card-metrics-svg">
<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="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">ASN</p>
<h5 data-count-asn class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">denied</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container blue">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-medium core-card-metrics-svg">
<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="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">User Agent</p>
<h5 data-count-user-agent class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">denied</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container amber">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-small core-card-metrics-svg">
<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 nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -262,46 +158,29 @@
type: "text",
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -204,9 +204,20 @@ function bunkernet:log(bypass_checks)
end
local ok, err
-- luacheck: ignore 212 431
local function report_callback(premature, obj, ip, reason, reason_data, method, url, headers, use_redis)
local function report_callback(
premature,
obj,
ip,
reason,
reason_data,
method,
url,
headers,
server_name,
use_redis
)
local status, _
ok, err, status, _ = obj:report(ip, reason, reason_data, method, url, headers)
ok, err, status, _ = obj:report(ip, reason, reason_data, method, url, headers, server_name)
if status == 429 then
obj.logger:log(WARN, "bunkernet API is rate limiting us")
elseif not ok then
@ -229,7 +240,8 @@ function bunkernet:log(bypass_checks)
reason_data,
self.ctx.bw.request_method,
self.ctx.bw.request_uri,
ngx.req.get_headers()
ngx.req.get_headers(),
self.ctx.bw.server_name
)
if not hdr then
return self:ret(false, "can't create report timer : " .. err)
@ -263,10 +275,48 @@ function bunkernet:request(method, url, data)
if not httpc then
return false, "can't instantiate http object : " .. err
end
local os_data = {
name = "Linux",
version = "Unknown",
version_id = "Unknown",
version_codename = "Unknown",
id = "Unknown",
arch = "Unknown",
}
local uname_cmd = io.popen("uname -m")
if uname_cmd then
os_data.arch = uname_cmd:read("*a"):gsub("\n", "")
uname_cmd:close()
end
local file = io.open("/etc/os-release", "r")
if file then
for line in file:lines() do
local key, value = line:match("^(%w+)=(.+)$")
if key and value then
value = value:gsub('"', "")
if key == "NAME" then
os_data.name = value
elseif key == "VERSION" then
os_data.version = value
elseif key == "VERSION_ID" then
os_data.version_id = value
elseif key == "VERSION_CODENAME" then
os_data.version_codename = value
elseif key == "ID" then
os_data.id = value
end
end
end
file:close()
end
local all_data = {
id = self.bunkernet_id,
version = self.version,
integration = self.integration,
os = os_data,
}
if data then
for k, v in pairs(data) do
@ -299,7 +349,7 @@ function bunkernet:ping()
return self:request("GET", "/ping", {})
end
function bunkernet:report(ip, reason, reason_data, method, url, headers)
function bunkernet:report(ip, reason, reason_data, method, url, headers, server_name)
local data = {
ip = ip,
reason = reason,
@ -307,6 +357,7 @@ function bunkernet:report(ip, reason, reason_data, method, url, headers)
method = method,
url = url,
headers = headers,
server_name = server_name,
}
return self:request("POST", "/report", data)
end
@ -317,9 +368,15 @@ function bunkernet:api()
return self:ret(false, "success")
end
-- Check id
if not self.bunkernet_id then
local id, err_id = self.datastore:get("plugin_bunkernet_id", true)
if not id and err_id ~= "not found" then
return self:ret(true, "error while getting bunkernet id : " .. err_id, HTTP_INTERNAL_SERVER_ERROR)
elseif not id then
return self:ret(true, "missing instance ID", HTTP_INTERNAL_SERVER_ERROR)
end
self.bunkernet_id = id
self.version = get_version(self.ctx)
self.integration = get_integration(self.ctx)
-- Send ping request
local ok, err, status, _ = self:ping()
if not ok then

View file

@ -5,12 +5,19 @@ from pathlib import Path
from requests import request as requests_request, ReadTimeout
from typing import Literal, Optional, Tuple, Union
from jobs import get_os_info, get_integration, get_version # type: ignore
def request(method: Union[Literal["POST"], Literal["GET"]], url: str, _id: Optional[str] = None) -> Tuple[bool, Optional[int], Union[str, dict]]:
data = {"integration": get_integration(), "version": get_version()}
headers = {"User-Agent": f"BunkerWeb/{get_version()}"}
if _id is not None:
data = {
"integration": get_integration(),
"version": get_version(),
"os": get_os_info(),
}
if _id:
data["id"] = _id
headers = {"User-Agent": f"BunkerWeb/{data['version']}"}
try:
resp = requests_request(
method,
@ -50,27 +57,3 @@ def data() -> Tuple[bool, Optional[int], Union[str, dict]]:
def get_id() -> str:
return Path(sep, "var", "cache", "bunkerweb", "bunkernet", "instance.id").read_text(encoding="utf-8").strip()
def get_version() -> str:
return Path(sep, "usr", "share", "bunkerweb", "VERSION").read_text(encoding="utf-8").strip()
def get_integration() -> str:
try:
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no").lower() == "yes":
return "kubernetes"
elif getenv("SWARM_MODE", "no").lower() == "yes":
return "swarm"
elif getenv("AUTOCONF_MODE", "no").lower() == "yes":
return "autoconf"
elif integration_path.is_file():
return integration_path.read_text(encoding="utf-8").strip().lower()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(encoding="utf-8"):
return "docker"
return "linux"
except:
return "unknown"

View file

@ -1,53 +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">
{% if is_used %}
<!-- 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
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="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"
>
<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"
></p>
</div>
<!-- end status -->
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div class="core-card-status">
<div class="core-card-status-container">
<h5 class="core-card-status-title">STATUS</h5>
<svg data-status-svg
class="core-card-status-svg info"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p data-status-text class="core-card-text"></p>
</div>
<!-- end status -->
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -63,48 +44,29 @@
textEl: document.querySelector("[data-status-text]"),
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,73 +1,44 @@
{% 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">
{% if is_used %}
<!-- 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
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"
>
CORS
</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
>
</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>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">CORS</p>
<h5 data-count class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">request blocked</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container red">
<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>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -81,46 +52,29 @@
type: "text",
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,71 +1,44 @@
{% 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="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
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"
>
Country
</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
>
</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>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">Country</p>
<h5 data-count class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">request blocked</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container red">
<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>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -79,6 +52,29 @@
type: "text",
},
});
</script>
</div>
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -20,6 +20,14 @@ function customcert:initialize(ctx)
plugin.initialize(self, "customcert", ctx)
end
function customcert:set()
local https_configured = self.variables["USE_CUSTOM_SSL"]
if https_configured == "yes" then
self.ctx.bw.https_configured = "yes"
end
return self:ret(true, "set https_configured to " .. https_configured)
end
function customcert:init()
local ret_ok, ret_err = true, "success"
if has_variable("USE_CUSTOM_SSL", "yes") then

View file

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

View file

@ -1,134 +0,0 @@
{% 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="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"
>
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 -->
<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"
>
SIZE
</p>
<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">
MB used on database
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-sky-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 6.375c0 2.692-4.03 4.875-9 4.875S3 9.067 3 6.375 7.03 1.5 12 1.5s9 2.183 9 4.875Z"
/>
<path
d="M12 12.75c2.685 0 5.19-.586 7.078-1.609a8.283 8.283 0 0 0 1.897-1.384c.016.121.025.244.025.368C21 12.817 16.97 15 12 15s-9-2.183-9-4.875c0-.124.009-.247.025-.368a8.285 8.285 0 0 0 1.897 1.384C6.809 12.164 9.315 12.75 12 12.75Z"
/>
<path
d="M12 16.5c2.685 0 5.19-.586 7.078-1.609a8.282 8.282 0 0 0 1.897-1.384c.016.121.025.244.025.368 0 2.692-4.03 4.875-9 4.875s-9-2.183-9-4.875c0-.124.009-.247.025-.368a8.284 8.284 0 0 0 1.897 1.384C6.809 15.914 9.315 16.5 12 16.5Z"
/>
<path
d="M12 20.25c2.685 0 5.19-.586 7.078-1.609a8.282 8.282 0 0 0 1.897-1.384c.016.121.025.244.025.368 0 2.692-4.03 4.875-9 4.875s-9-2.183-9-4.875c0-.124.009-.247.025-.368a8.284 8.284 0 0 0 1.897 1.384C6.809 19.664 9.315 20.25 12 20.25Z"
/>
</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",
},
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

@ -1,72 +1,44 @@
{% 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">
{% if is_used %}
<!-- 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
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"
>
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
>
</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>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">DNSBL</p>
<h5 data-count class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">request blocked</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container red">
<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>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -80,47 +52,29 @@
type: "text",
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,13 +1,13 @@
from operator import itemgetter
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}
format_data = [{"code": int(key.split("_")[1]), "count": int(value)} for key, value in data.items()]
format_data.sort(key=itemgetter("count"), reverse=True)
return {"items": format_data}
except:
return {"items": []}

View file

@ -1,73 +1,43 @@
{% 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="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
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
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>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<!-- list container-->
<div class="min-w-[350px] 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"
>
Code error
</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"
>
Count
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
<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"
></p>
<p
data-name="count"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
></p>
</li>
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div data-fetch-success-show class="hidden core-card-list w-medium">
<div class="core-card-list-container">
<h5 class="core-card-list-title">ERRORS LIST</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="core-card-list-wrap w-medium">
<!-- header-->
<p class="core-card-list-header col-span-8">Code error</p>
<p class="core-card-list-header col-span-4">Count</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
<li data-item class="core-card-list-item">
<p data-name="code" class="core-card-list-item-content col-span-8"></p>
<p data-name="count" class="core-card-list-item-content col-span-4"></p>
</li>
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -82,7 +52,6 @@
listNames: ["code", "count"],
},
});
</script>
</div>
</script>
</div>
{% endblock %}

View file

@ -1,72 +1,44 @@
{% 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">
{% if is_used %}
<!-- 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
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
>
</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>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">GREYLIST</p>
<h5 data-count class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content error">request blocked</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container red">
<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>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -80,46 +52,29 @@
type: "text",
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,4 +0,0 @@
map $scheme $header_cookie_secure {
default "";
"https" "secure";
}

View file

@ -1,9 +1,5 @@
{% for k, v in all.items() %}
{% if k.startswith("COOKIE_FLAGS") and v != "" +%}
{% if COOKIE_AUTO_SECURE_FLAG == "yes" and (AUTO_LETS_ENCRYPT == "yes" or USE_CUSTOM_SSL == "yes" or GENERATE_SELF_SIGNED_SSL == "yes") +%}
set_cookie_flag {{ v }} secure;
{% else +%}
set_cookie_flag {{ v }};
{% endif +%}
set_cookie_flag {{ v }};
{% endif +%}
{% endfor %}

View file

@ -112,6 +112,27 @@ function headers:header()
ngx_header[header] = nil
end
end
-- Set secure flag
local set_cookie = ngx_header["Set-Cookie"]
if self.ctx.bw.scheme == "https" and self.variables["COOKIE_AUTO_SECURE_FLAG"] == "yes" and set_cookie ~= nil then
local new_set_cookie = nil
if type(set_cookie) == "string" then
new_set_cookie = set_cookie
if not set_cookie:find("[Ss]ecure") then
new_set_cookie = new_set_cookie .. "; Secure"
end
elseif type(set_cookie) == "table" then
new_set_cookie = {}
for _, single_set_cookie in ipairs(set_cookie) do
local check_set_cookie = single_set_cookie
if not check_set_cookie:find("[Ss]ecure") then
check_set_cookie = check_set_cookie .. "; Secure"
end
table.insert(new_set_cookie, check_set_cookie)
end
end
ngx_header["Set-Cookie"] = new_set_cookie
end
return self:ret(true, "edited headers for request")
end

View file

@ -0,0 +1,138 @@
#!/usr/bin/env python3
from json import dumps
from os import getenv, sep
from os.path import join
from pathlib import Path
from platform import machine
from re import compile as re_compile
from sys import exit as sys_exit, path as sys_path, version
from traceback import format_exc
from typing import Any, Dict
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from Database import Database # type: ignore
from logger import setup_logger # type: ignore
from jobs import cache_file, is_cached_file # type: ignore
from requests import post
logger = setup_logger("ANONYMOUS-REPORT", getenv("LOG_LEVEL", "INFO"))
status = 0
if getenv("SEND_ANONYMOUS_REPORT", "yes") != "yes":
logger.info("Skipping the sending of anonymous report (disabled)")
sys_exit(status)
anonymous_report_path = Path(sep, "var", "cache", "bunkerweb", "anonymous_report")
anonymous_report_path.mkdir(parents=True, exist_ok=True)
tmp_anonymous_report_path = Path(sep, "var", "tmp", "bunkerweb", "anonymous_report")
tmp_anonymous_report_path.mkdir(parents=True, exist_ok=True)
try:
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI", None), pool=False)
if is_cached_file(anonymous_report_path.joinpath("last_report.json"), "day", db):
logger.info("Skipping the sending of anonymous report (already sent today)")
sys_exit(0)
# ? Get version and integration of BunkerWeb
data: Dict[str, Any] = db.get_metadata()
data["is_pro"] = "yes" if data["is_pro"] else "no"
data.pop("pro_expire", None)
data.pop("pro_services", None)
data.pop("pro_overlapped", None)
data.pop("pro_status", None)
db_config = db.get_config(methods=True, with_drafts=True)
services = db_config.get("SERVER_NAME", {"value": ""})["value"].split(" ")
multisite = db_config.get("MULTISITE", {"value": "no"})["value"] == "yes"
DATABASE_VERSION_REGEX = re_compile(r"(\d+(?:\.\d+)*)")
database_version = DATABASE_VERSION_REGEX.search(data.pop("database_version")) or "Unknown"
if database_version != "Unknown":
database_version = database_version.group(1)
data["integration"] = data["integration"].lower()
data["database"] = f"{db.database_uri.split(':')[0].split('+')[0]}/{database_version}"
data["service_number"] = str(len(services))
data["draft_service_number"] = 0
data["python_version"] = version.split(" ")[0]
data["use_ui"] = "no"
# Multisite case
if multisite:
for server in services:
if db_config.get(f"{server}_USE_UI", db_config.get("USE_UI", {"value": "no"}))["value"] == "yes":
data["use_ui"] = "yes"
if db_config.get(f"{server}_IS_DRAFT", db_config.get("IS_DRAFT", {"value": "no"}))["value"] == "yes":
data["draft_service_number"] += 1
# Singlesite case
else:
if db_config.get("USE_UI", {"value": "no"})["value"] == "yes":
data["use_ui"] = "yes"
if db_config.get("IS_DRAFT", {"value": "no"})["value"] == "yes":
data["draft_service_number"] = 1
data["draft_service_number"] = str(data["draft_service_number"])
data["external_plugins"] = []
data["pro_plugins"] = []
for plugin in db.get_plugins():
if plugin["type"] == "external":
data["external_plugins"].append(f"{plugin['id']}/{plugin['version']}")
elif plugin["type"] == "pro":
data["pro_plugins"].append(f"{plugin['id']}/{plugin['version']}")
data["os"] = {
"name": "Linux",
"version": "Unknown",
"version_id": "Unknown",
"version_codename": "Unknown",
"id": "Unknown",
"arch": machine(),
}
os_release = Path("/etc/os-release")
if os_release.exists():
for line in os_release.read_text().splitlines():
if "=" not in line or line.split("=")[0].strip().lower() not in data["os"]:
continue
data["os"][line.split("=")[0].lower()] = line.split("=")[1].strip('"')
data["non_default_settings"] = {}
for setting, setting_data in db_config.items():
if isinstance(setting_data, dict) and setting_data["method"] != "default":
for server in services:
if setting.startswith(server + "_"):
setting = setting[len(server) + 1 :] # noqa: E203
if setting not in data["non_default_settings"]:
data["non_default_settings"][setting] = 1
break
data["non_default_settings"][setting] += 1
break
else:
if setting not in data["non_default_settings"]:
data["non_default_settings"][setting] = 1
for key in data["non_default_settings"].copy():
data["non_default_settings"][key] = str(data["non_default_settings"][key])
data["bw_instances_number"] = str(len(db.get_instances()))
tmp_anonymous_report_path.joinpath("last_report.json").write_text(dumps(data, indent=4), encoding="utf-8")
response = post("https://api.bunkerweb.io/data", json=data, headers={"User-Agent": f"BunkerWeb/{data['version']}"}, allow_redirects=True, timeout=10)
response.raise_for_status()
cached, err = cache_file(tmp_anonymous_report_path.joinpath("last_report.json"), anonymous_report_path.joinpath("last_report.json"), None, db)
except SystemExit as e:
status = e.code
except:
status = 2
logger.error(f"Exception while running anonymous-report.py :\n{format_exc()}")
sys_exit(status)

View file

@ -2,15 +2,15 @@
from hashlib import sha256
from io import BytesIO
from os import getenv, listdir, chmod, _exit, sep
from os.path import basename, dirname, join, normpath
from os import getenv, listdir, chmod, sep
from os.path import basename, join, normpath
from pathlib import Path
from stat import S_IEXEC
from sys import exit as sys_exit, path as sys_path
from threading import Lock
from uuid import uuid4
from glob import glob
from json import loads
from json import JSONDecodeError, loads
from shutil import copytree, rmtree
from tarfile import open as tar_open
from traceback import format_exc
@ -40,15 +40,26 @@ logger = setup_logger("Jobs.download-plugins", getenv("LOG_LEVEL", "INFO"))
status = 0
def install_plugin(plugin_dir, db) -> bool:
def install_plugin(plugin_dir: str, db) -> bool:
plugin_path = Path(plugin_dir)
plugin_file = plugin_path.joinpath("plugin.json")
if not plugin_file.is_file():
logger.error(f"Skipping installation of plugin {plugin_path.name} (plugin.json not found)")
return False
# Load plugin.json
metadata = loads(plugin_path.joinpath("plugin.json").read_text(encoding="utf-8"))
try:
metadata = loads(plugin_file.read_text(encoding="utf-8"))
except JSONDecodeError:
logger.error(f"Skipping installation of plugin {plugin_path.name} (plugin.json is not valid)")
return False
# Don't go further if plugin is already installed
if EXTERNAL_PLUGINS_DIR.joinpath(metadata["id"], "plugin.json").is_file():
old_version = None
for plugin in db.get_plugins(external=True):
for plugin in db.get_plugins(_type="external"):
if plugin["id"] == metadata["id"]:
old_version = plugin["version"]
break
@ -67,7 +78,7 @@ def install_plugin(plugin_dir, db) -> bool:
# Copy the plugin
copytree(plugin_dir, join(sep, "etc", "bunkerweb", "plugins", metadata["id"]))
# Add u+x permissions to jobs files
for job_file in glob(join(plugin_dir, "jobs", "*")):
for job_file in glob(join(sep, "etc", "bunkerweb", "plugins", "jobs", "*")):
st = Path(job_file).stat()
chmod(job_file, st.st_mode | S_IEXEC)
logger.info(f"Plugin {metadata['id']} installed")
@ -79,7 +90,7 @@ try:
plugin_urls = getenv("EXTERNAL_PLUGIN_URLS")
if not plugin_urls:
logger.info("No external plugins to download")
_exit(0)
sys_exit(0)
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI"), pool=False)
plugin_nbr = 0
@ -134,31 +145,25 @@ try:
logger.error(f"Unknown file type for {plugin_url}, either zip or tar are supported, skipping...")
continue
except:
logger.error(
f"Exception while decompressing plugin(s) from {plugin_url} :\n{format_exc()}",
)
logger.error(f"Exception while decompressing plugin(s) from {plugin_url} :\n{format_exc()}")
status = 2
continue
# Install plugins
try:
for plugin_dir in glob(join(temp_dir, "**", "plugin.json"), recursive=True):
for plugin_dir in glob(join(temp_dir, "*")):
try:
if install_plugin(dirname(plugin_dir), db):
if install_plugin(plugin_dir, db):
plugin_nbr += 1
except FileExistsError:
logger.warning(
f"Skipping installation of plugin {basename(dirname(plugin_dir))} (already installed)",
)
logger.warning(f"Skipping installation of plugin {basename(plugin_dir)} (already installed)")
except:
logger.error(
f"Exception while installing plugin(s) from {plugin_url} :\n{format_exc()}",
)
logger.error(f"Exception while installing plugin(s) from {plugin_url} :\n{format_exc()}")
status = 2
if not plugin_nbr:
logger.info("No external plugins to update to database")
_exit(0)
sys_exit(0)
external_plugins = []
external_plugins_ids = []
@ -179,7 +184,7 @@ try:
plugin_file.update(
{
"external": True,
"type": "external",
"page": False,
"method": "scheduler",
"data": value,
@ -195,7 +200,7 @@ try:
lock = Lock()
for plugin in db.get_plugins(external=True, with_data=True):
for plugin in db.get_plugins(_type="external", with_data=True):
if plugin["method"] != "scheduler" and plugin["id"] not in external_plugins_ids:
external_plugins.append(plugin)
@ -210,11 +215,13 @@ try:
status = 1
logger.info("External plugins downloaded and installed")
except SystemExit as e:
status = e.code
except:
status = 2
logger.error(f"Exception while running download-plugins.py :\n{format_exc()}")
for plugin_tmp in glob(join(sep, "var", "tmp", "bunkerweb", "plugins-*")):
for plugin_tmp in glob(join(sep, "var", "tmp", "bunkerweb", "plugins", "*")):
rmtree(plugin_tmp, ignore_errors=True)
sys_exit(status)

View file

@ -4,7 +4,17 @@
"description": "Fake core plugin for internal jobs.",
"version": "1.0",
"stream": "yes",
"settings": {},
"settings": {
"SEND_ANONYMOUS_REPORT": {
"context": "global",
"default": "yes",
"help": "Send anonymous report to BunkerWeb maintainers.",
"id": "send-anonymous-report",
"label": "Send anonymous report",
"regex": "^(yes|no)$",
"type": "check"
}
},
"jobs": [
{
"name": "mmdb-country",
@ -23,6 +33,12 @@
"file": "download-plugins.py",
"every": "once",
"reload": false
},
{
"name": "anonymous-report",
"file": "anonymous-report.py",
"every": "day",
"reload": false
}
]
}

View file

@ -20,6 +20,7 @@ for deps_path in [
sys_path.append(deps_path)
from Database import Database # type: ignore
from jobs import get_integration # type: ignore
from logger import setup_logger # type: ignore
from API import API # type: ignore
@ -28,25 +29,11 @@ status = 0
try:
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(encoding="utf-8"):
bw_integration = "Docker"
token = getenv("CERTBOT_TOKEN", "")
validation = getenv("CERTBOT_VALIDATION", "")
# Cluster case
if bw_integration in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
if get_integration() in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI", None), pool=False)
lock = Lock()
@ -54,39 +41,20 @@ try:
instances = db.get_instances()
for instance in instances:
api = API(
f"http://{instance['hostname']}:{instance['port']}",
host=instance["server_name"],
)
sent, err, status, resp = api.request(
"POST",
"/lets-encrypt/challenge",
data={"token": token, "validation": validation},
)
api = API(f"http://{instance['hostname']}:{instance['port']}", host=instance["server_name"])
sent, err, status, resp = api.request("POST", "/lets-encrypt/challenge", data={"token": token, "validation": validation})
if not sent:
status = 1
logger.error(f"Can't send API request to {api.endpoint}/lets-encrypt/challenge : {err}")
elif status != 200:
status = 1
logger.error(
f"Error while sending API request to {api.endpoint}/lets-encrypt/challenge : status = {resp['status']}, msg = {resp['msg']}",
)
logger.error(f"Error while sending API request to {api.endpoint}/lets-encrypt/challenge : status = {resp['status']}, msg = {resp['msg']}")
else:
logger.info(
f"Successfully sent API request to {api.endpoint}/lets-encrypt/challenge",
)
logger.info(f"Successfully sent API request to {api.endpoint}/lets-encrypt/challenge")
# Linux case
else:
root_dir = Path(
sep,
"var",
"tmp",
"bunkerweb",
"lets-encrypt",
".well-known",
"acme-challenge",
)
root_dir = Path(sep, "var", "tmp", "bunkerweb", "lets-encrypt", ".well-known", "acme-challenge")
root_dir.mkdir(parents=True, exist_ok=True)
root_dir.joinpath(token).write_text(validation, encoding="utf-8")
except:

View file

@ -20,6 +20,7 @@ for deps_path in [
sys_path.append(deps_path)
from Database import Database # type: ignore
from jobs import get_integration # type: ignore
from logger import setup_logger # type: ignore
from API import API # type: ignore
@ -28,60 +29,29 @@ status = 0
try:
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(encoding="utf-8"):
bw_integration = "Docker"
token = getenv("CERTBOT_TOKEN", "")
# Cluster case
if bw_integration in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
if get_integration() in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI", None), pool=False)
lock = Lock()
with lock:
instances = db.get_instances()
for instance in instances:
api = API(
f"http://{instance['hostname']}:{instance['port']}",
host=instance["server_name"],
)
api = API(f"http://{instance['hostname']}:{instance['port']}", host=instance["server_name"])
sent, err, status, resp = api.request("DELETE", "/lets-encrypt/challenge", data={"token": token})
if not sent:
status = 1
logger.error(f"Can't send API request to {api.endpoint}/lets-encrypt/challenge : {err}")
elif status != 200:
status = 1
logger.error(
f"Error while sending API request to {api.endpoint}/lets-encrypt/challenge : status = {resp['status']}, msg = {resp['msg']}",
)
logger.error(f"Error while sending API request to {api.endpoint}/lets-encrypt/challenge : status = {resp['status']}, msg = {resp['msg']}")
else:
logger.info(
f"Successfully sent API request to {api.endpoint}/lets-encrypt/challenge",
)
logger.info(f"Successfully sent API request to {api.endpoint}/lets-encrypt/challenge")
# Linux case
else:
challenge_path = Path(
sep,
"var",
"tmp",
"bunkerweb",
"lets-encrypt",
".well-known",
"acme-challenge",
token,
)
challenge_path.unlink(missing_ok=True)
Path(sep, "var", "tmp", "bunkerweb", "lets-encrypt", ".well-known", "acme-challenge", token).unlink(missing_ok=True)
except:
status = 1
logger.error(f"Exception while running certbot-cleanup.py :\n{format_exc()}")

View file

@ -3,7 +3,6 @@
from io import BytesIO
from os import getenv, sep
from os.path import join
from pathlib import Path
from subprocess import DEVNULL, STDOUT, run
from sys import exit as sys_exit, path as sys_path
from tarfile import open as tar_open
@ -23,6 +22,7 @@ for deps_path in [
sys_path.append(deps_path)
from Database import Database # type: ignore
from jobs import get_integration # type: ignore
from logger import setup_logger # type: ignore
from API import API # type: ignore
@ -31,34 +31,17 @@ status = 0
try:
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(encoding="utf-8"):
bw_integration = "Docker"
token = getenv("CERTBOT_TOKEN", "")
logger.info(f"Certificates renewal for {getenv('RENEWED_DOMAINS')} successful")
# Cluster case
if bw_integration in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
if get_integration() in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
# Create tarball of /var/cache/bunkerweb/letsencrypt
tgz = BytesIO()
with tar_open(mode="w:gz", fileobj=tgz, compresslevel=3) as tf:
tf.add(
join(sep, "var", "cache", "bunkerweb", "letsencrypt", "etc"),
arcname="etc",
)
tf.add(join(sep, "var", "cache", "bunkerweb", "letsencrypt", "etc"), arcname="etc")
tgz.seek(0, 0)
files = {"archive.tar.gz": tgz}
@ -95,15 +78,7 @@ try:
logger.info(f"Successfully sent API request to {api.endpoint}/reload")
# Linux case
else:
if (
run(
[join(sep, "usr", "sbin", "nginx"), "-s", "reload"],
stdin=DEVNULL,
stderr=STDOUT,
check=False,
).returncode
!= 0
):
if run([join(sep, "usr", "sbin", "nginx"), "-s", "reload"], stdin=DEVNULL, stderr=STDOUT, check=False).returncode != 0:
status = 1
logger.error("Error while reloading nginx")
else:

View file

@ -9,7 +9,7 @@ from traceback import format_exc
from tarfile import open as tar_open
from io import BytesIO
from shutil import rmtree
from re import findall, MULTILINE
from re import MULTILINE, search
for deps_path in [
join(sep, "usr", "share", "bunkerweb", *paths)
@ -29,24 +29,31 @@ from jobs import get_file_in_db, set_file_in_db # type: ignore
logger = setup_logger("LETS-ENCRYPT.new", getenv("LOG_LEVEL", "INFO"))
status = 0
CERTBOT_BIN = join(sep, "usr", "share", "bunkerweb", "deps", "python", "bin", "certbot")
def certbot_new(domains: str, email: str, letsencrypt_path: Path, letsencrypt_job_path: Path) -> int:
LETS_ENCRYPT_PATH = Path(sep, "var", "cache", "bunkerweb", "letsencrypt")
LETS_ENCRYPT_JOBS_PATH = Path(sep, "usr", "share", "bunkerweb", "core", "letsencrypt", "jobs")
LETS_ENCRYPT_WORK_DIR = join(sep, "var", "lib", "bunkerweb", "letsencrypt")
LETS_ENCRYPT_LOGS_DIR = join(sep, "var", "log", "bunkerweb")
def certbot_new(domains: str, email: str, use_letsencrypt_staging: bool = False) -> int:
return run(
[
join(sep, "usr", "share", "bunkerweb", "deps", "python", "bin", "certbot"),
CERTBOT_BIN,
"certonly",
"--config-dir",
str(letsencrypt_path.joinpath("etc")),
LETS_ENCRYPT_PATH.joinpath("etc").as_posix(),
"--work-dir",
join(sep, "var", "lib", "bunkerweb", "letsencrypt"),
LETS_ENCRYPT_WORK_DIR,
"--logs-dir",
join(sep, "var", "log", "bunkerweb"),
LETS_ENCRYPT_LOGS_DIR,
"--manual",
"--preferred-challenges=http",
"--manual-auth-hook",
str(letsencrypt_job_path.joinpath("certbot-auth.py")),
LETS_ENCRYPT_JOBS_PATH.joinpath("certbot-auth.py").as_posix(),
"--manual-cleanup-hook",
str(letsencrypt_job_path.joinpath("certbot-cleanup.py")),
LETS_ENCRYPT_JOBS_PATH.joinpath("certbot-cleanup.py").as_posix(),
"-n",
"-d",
domains,
@ -55,52 +62,26 @@ def certbot_new(domains: str, email: str, letsencrypt_path: Path, letsencrypt_jo
"--agree-tos",
"--expand",
]
+ (["--staging"] if getenv("USE_LETS_ENCRYPT_STAGING", "no") == "yes" else []),
+ (["--staging"] if use_letsencrypt_staging else []),
stdin=DEVNULL,
stderr=STDOUT,
env=environ.copy() | {"PYTHONPATH": join(sep, "usr", "share", "bunkerweb", "deps", "python")},
).returncode
def certbot_check_domains(domains: list[str], letsencrypt_path: Path) -> int:
proc = run(
[
join(sep, "usr", "share", "bunkerweb", "deps", "python", "bin", "certbot"),
"certificates",
"--config-dir",
str(letsencrypt_path.joinpath("etc")),
"--work-dir",
join(sep, "var", "lib", "bunkerweb", "letsencrypt"),
"--logs-dir",
join(sep, "var", "log", "bunkerweb"),
],
stdin=DEVNULL,
stdout=PIPE,
stderr=STDOUT,
text=True,
env=environ.copy() | {"PYTHONPATH": join(sep, "usr", "share", "bunkerweb", "deps", "python")},
)
if proc.returncode != 0:
logger.error(f"Error while checking certificates :\n{proc.stdout}")
return 2
first_needed_domain = domains[0]
needed_domains = set(domains)
for raw_domains in findall(r"^ Domains: (.*)$", proc.stdout, MULTILINE):
current_domains = raw_domains.split(" ")
if current_domains[0] == first_needed_domain and set(current_domains) == needed_domains:
return 1
return 0
status = 0
try:
# Check if we're using let's encrypt
use_letsencrypt = False
is_multisite = getenv("MULTISITE", "no") == "yes"
all_domains = getenv("SERVER_NAME", "")
server_names = all_domains.split(" ")
if getenv("AUTO_LETS_ENCRYPT", "no") == "yes":
use_letsencrypt = True
elif getenv("MULTISITE", "no") == "yes":
for first_server in getenv("SERVER_NAME", "").split(" "):
elif is_multisite:
for first_server in server_names:
if first_server and getenv(f"{first_server}_AUTO_LETS_ENCRYPT", "no") == "yes":
use_letsencrypt = True
break
@ -108,129 +89,91 @@ try:
if not use_letsencrypt:
logger.info("Let's Encrypt is not activated, skipping generation...")
_exit(0)
elif not getenv("SERVER_NAME"):
logger.warning("There are no server names, skipping generation...")
_exit(0)
# Create directory if it doesn't exist
letsencrypt_path = Path(sep, "var", "cache", "bunkerweb", "letsencrypt")
letsencrypt_path.mkdir(parents=True, exist_ok=True)
letsencrypt_job_path = Path(sep, "usr", "share", "bunkerweb", "core", "letsencrypt", "jobs")
# Create directories if they doesn't exist
LETS_ENCRYPT_PATH.mkdir(parents=True, exist_ok=True)
Path(sep, "var", "lib", "bunkerweb", "letsencrypt").mkdir(parents=True, exist_ok=True)
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(encoding="utf-8"):
bw_integration = "Docker"
# Extract letsencrypt folder if it exists in db
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI", None), pool=False)
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI"), pool=False)
tgz = get_file_in_db("folder.tgz", db, job_name="certbot-renew")
if tgz:
# Delete folder if needed
if letsencrypt_path.exists():
rmtree(str(letsencrypt_path), ignore_errors=True)
letsencrypt_path.mkdir(parents=True, exist_ok=True)
if LETS_ENCRYPT_PATH.exists():
rmtree(LETS_ENCRYPT_PATH, ignore_errors=True)
LETS_ENCRYPT_PATH.mkdir(parents=True, exist_ok=True)
# Extract it
with tar_open(name="folder.tgz", mode="r:gz", fileobj=BytesIO(tgz)) as tf:
tf.extractall(str(letsencrypt_path))
tf.extractall(LETS_ENCRYPT_PATH)
logger.info("Successfully retrieved Let's Encrypt data from db cache")
else:
logger.info("No Let's Encrypt data found in db cache")
domains_to_ask = []
# Multisite case
if getenv("MULTISITE", "no") == "yes" and getenv("SERVER_NAME"):
for first_server in getenv("SERVER_NAME", "").split(" "):
if (
not first_server
or getenv(
f"{first_server}_AUTO_LETS_ENCRYPT",
getenv("AUTO_LETS_ENCRYPT", "no"),
)
!= "yes"
):
if is_multisite:
domains_sever_names = {}
for first_server in server_names:
if not first_server or getenv(f"{first_server}_AUTO_LETS_ENCRYPT", getenv("AUTO_LETS_ENCRYPT", "no")) != "yes":
continue
domains = getenv(f"{first_server}_SERVER_NAME", first_server)
if certbot_check_domains(domains.split(" "), letsencrypt_path) == 1:
logger.info(
f"Certificates already exists for domain(s) {domains}",
)
continue
real_email = getenv(
f"{first_server}_EMAIL_LETS_ENCRYPT",
getenv("EMAIL_LETS_ENCRYPT", f"contact@{first_server}"),
)
if not real_email:
real_email = f"contact@{first_server}"
logger.info(
f"Asking certificates for domains : {domains} (email = {real_email}) ...",
)
if (
certbot_new(
domains.replace(" ", ","),
real_email,
letsencrypt_path,
letsencrypt_job_path,
)
!= 0
):
status = 2
logger.error(
f"Certificate generation failed for domain(s) {domains} ...",
)
continue
else:
status = 1 if status == 0 else status
logger.info(f"Certificate generation succeeded for domain(s) : {domains}")
domains_sever_names[first_server] = getenv(f"{first_server}_SERVER_NAME", first_server)
# Singlesite case
elif getenv("AUTO_LETS_ENCRYPT", "no") == "yes" and getenv("SERVER_NAME"):
first_server = getenv("SERVER_NAME", "").split(" ")[0]
domains = getenv("SERVER_NAME", "")
else:
domains_sever_names = {server_names[0]: all_domains}
if certbot_check_domains(domains.split(" "), letsencrypt_path) == 1:
logger.info(
f"Certificates already exists for domain(s) {domains}",
)
proc = run(
[CERTBOT_BIN, "certificates", "--config-dir", LETS_ENCRYPT_PATH.joinpath("etc").as_posix(), "--work-dir", LETS_ENCRYPT_WORK_DIR, "--logs-dir", LETS_ENCRYPT_LOGS_DIR],
stdin=DEVNULL,
stdout=PIPE,
stderr=STDOUT,
text=True,
env=environ.copy() | {"PYTHONPATH": join(sep, "usr", "share", "bunkerweb", "deps", "python")},
)
stdout = proc.stdout
if proc.returncode != 0:
logger.error(f"Error while checking certificates :\n{proc.stdout}")
domains_to_ask = server_names
else:
for first_server, domains in domains_sever_names.items():
current_domains = search(rf"Domains: {first_server}(?P<domains>.*)$", stdout, MULTILINE)
if not current_domains:
domains_to_ask.append(first_server)
continue
elif set(f"{first_server}{current_domains.groupdict()['domains']}".strip().split(" ")) != set(domains.split(" ")):
logger.warning(f"Domains for {first_server} are not the same as in the certificate, asking new certificate...")
domains_to_ask.append(first_server)
continue
logger.info(f"Certificates already exists for domain(s) {domains}")
for first_server, domains in domains_sever_names.items():
if first_server not in domains_to_ask:
continue
real_email = getenv(f"{first_server}_EMAIL_LETS_ENCRYPT", getenv("EMAIL_LETS_ENCRYPT", f"contact@{first_server}"))
if not real_email:
real_email = f"contact@{first_server}"
use_letsencrypt_staging = getenv(f"{first_server}_USE_LETS_ENCRYPT_STAGING", getenv("USE_LETS_ENCRYPT_STAGING", "no")) == "yes"
logger.info(f"Asking certificates for domain(s) : {domains} (email = {real_email}) to Let's Encrypt {'staging ' if use_letsencrypt_staging else ''}...")
if certbot_new(domains.replace(" ", ","), real_email, use_letsencrypt_staging) != 0:
status = 2
logger.error(f"Certificate generation failed for domain(s) {domains} ...")
continue
else:
real_email = getenv("EMAIL_LETS_ENCRYPT", f"contact@{first_server}")
if not real_email:
real_email = f"contact@{first_server}"
logger.info(
f"Asking certificates for domain(s) : {domains} (email = {real_email}) ...",
)
if (
certbot_new(
domains.replace(" ", ","),
real_email,
letsencrypt_path,
letsencrypt_job_path,
)
!= 0
):
status = 2
logger.error(f"Certificate generation failed for domain(s) : {domains}")
else:
status = 1
logger.info(f"Certificate generation succeeded for domain(s) : {domains}")
status = 1 if status == 0 else status
logger.info(f"Certificate generation succeeded for domain(s) : {domains}")
# Put new folder in cache
bio = BytesIO()
with tar_open("folder.tgz", mode="w:gz", fileobj=bio, compresslevel=9) as tgz:
tgz.add(str(letsencrypt_path), arcname=".")
tgz.add(LETS_ENCRYPT_PATH, arcname=".")
bio.seek(0, 0)
# Put tgz in cache

View file

@ -28,6 +28,8 @@ from jobs import get_file_in_db, set_file_in_db # type: ignore
logger = setup_logger("LETS-ENCRYPT.renew", getenv("LOG_LEVEL", "INFO"))
status = 0
LETS_ENCRYPT_PATH = Path(sep, "var", "cache", "bunkerweb", "letsencrypt")
try:
# Check if we're using let's encrypt
use_letsencrypt = False
@ -44,37 +46,21 @@ try:
_exit(0)
# Create directory if it doesn't exist
letsencrypt_path = Path(sep, "var", "cache", "bunkerweb", "letsencrypt")
letsencrypt_path.mkdir(parents=True, exist_ok=True)
LETS_ENCRYPT_PATH.mkdir(parents=True, exist_ok=True)
Path(sep, "var", "lib", "bunkerweb", "letsencrypt").mkdir(parents=True, exist_ok=True)
# Get env vars
bw_integration = "Linux"
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
os_release_path = Path(sep, "etc", "os-release")
if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
bw_integration = "Autoconf"
elif integration_path.is_file():
bw_integration = integration_path.read_text(encoding="utf-8").strip()
elif os_release_path.is_file() and "Alpine" in os_release_path.read_text(encoding="utf-8"):
bw_integration = "Docker"
# Extract letsencrypt folder if it exists in db
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI", None), pool=False)
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI"), pool=False)
tgz = get_file_in_db("folder.tgz", db)
if tgz:
# Delete folder if needed
if letsencrypt_path.exists():
rmtree(str(letsencrypt_path), ignore_errors=True)
letsencrypt_path.mkdir(parents=True, exist_ok=True)
if LETS_ENCRYPT_PATH.exists():
rmtree(LETS_ENCRYPT_PATH, ignore_errors=True)
LETS_ENCRYPT_PATH.mkdir(parents=True, exist_ok=True)
# Extract it
with tar_open(name="folder.tgz", mode="r:gz", fileobj=BytesIO(tgz)) as tf:
tf.extractall(str(letsencrypt_path))
tf.extractall(LETS_ENCRYPT_PATH)
logger.info("Successfully retrieved Let's Encrypt data from db cache")
else:
logger.info("No Let's Encrypt data found in db cache")
@ -86,7 +72,7 @@ try:
"renew",
"--no-random-sleep-on-renew",
"--config-dir",
str(letsencrypt_path.joinpath("etc")),
LETS_ENCRYPT_PATH.joinpath("etc").as_posix(),
"--work-dir",
join(sep, "var", "lib", "bunkerweb", "letsencrypt"),
"--logs-dir",
@ -105,7 +91,7 @@ try:
# Put new folder in cache
bio = BytesIO()
with tar_open("folder.tgz", mode="w:gz", fileobj=bio, compresslevel=9) as tgz:
tgz.add(str(letsencrypt_path), arcname=".")
tgz.add(LETS_ENCRYPT_PATH, arcname=".")
bio.seek(0, 0)
# Put tgz in cache

View file

@ -33,6 +33,14 @@ function letsencrypt:initialize(ctx)
plugin.initialize(self, "letsencrypt", ctx)
end
function letsencrypt:set()
local https_configured = self.variables["AUTO_LETS_ENCRYPT"]
if https_configured == "yes" then
self.ctx.bw.https_configured = "yes"
end
return self:ret(true, "set https_configured to " .. https_configured)
end
function letsencrypt:init()
local ret_ok, ret_err = true, "success"
if has_variable("AUTO_LETS_ENCRYPT", "yes") then

View file

@ -1,3 +1,6 @@
from operator import itemgetter
def limit(**kwargs):
try:
# Here we will have a list { 'limit_uri_url1': X, 'limit_uri_url2': Y ... }
@ -5,9 +8,13 @@ def limit(**kwargs):
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}
key = key.split("/", 1)
if len(key) > 1:
key = key[1]
else:
key = ""
format_data.append({"url": f"/{key}", "count": int(value)})
format_data.sort(key=itemgetter("count"), reverse=True)
return {"items": format_data}
except:
return {"items": []}

View file

@ -1,77 +1,44 @@
{% 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">
{% if is_used %}
<!-- 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
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
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">
LIMIT AND REQUEST LIST
</h5>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<!-- list container-->
<div class="min-w-[500px] 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"
>
URL
</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"
>
Count
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
<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"
></p>
<p
data-name="count"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
></p>
</li>
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
<div data-fetch-success-show class="hidden core-card-list w-large">
<div class="core-card-list-container">
<h5 class="core-card-list-title">LIMIT AND REQUEST LIST</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="core-card-list-wrap w-large">
<!-- header-->
<p class="core-card-list-header col-span-8">URL</p>
<p class="core-card-list-header col-span-4">Count</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
<li data-item class="core-card-list-item">
<p data-name="url" class="core-card-list-item-content col-span-8"></p>
<p data-name="count" class="core-card-list-item-content col-span-4"></p>
</li>
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -86,46 +53,29 @@
listNames: ["url", "count"],
},
});
</script>
{% else %}
<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"
>
<div class="flex justify-between">
<h5 class="mb-2 font-bold dark:text-white/90">Deactivated</h5>
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-yellow-500"
>
<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-yellow-500 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<!-- end icon -->
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
<div class="mx-1 flex justify-start items-center my-2">
<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"
>
This plugin need to be activated to get metrics.
</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -4,4 +4,58 @@ location / {
set $reason_data "";
return {{ DENY_HTTP_STATUS }};
}
ssl_client_hello_by_lua_block {
local ssl_clt = require "ngx.ssl.clienthello"
local utils = require "bunkerweb.utils"
local clogger = require "bunkerweb.logger"
local cdatastore = require "bunkerweb.datastore"
local logger = clogger:new("SSL-DISABLE")
local datastore = cdatastore:new()
local ngx = ngx
local exit = ngx.exit
local ERROR = ngx.ERROR
local WARN = ngx.WARN
local ERR = ngx.ERR
local INFO = ngx.INFO
local get_variable = utils.get_variable
local host, err = ssl_clt.get_client_hello_server_name()
if not host then
logger:log(WARN, "can't get SNI host, denying access : " .. (err or "no SNI"))
return exit(ERROR)
end
logger:log(INFO, "SNI host is " .. host)
local multisite, err = get_variable("MULTISITE", false)
if not multisite then
logger:log(ERR, "can't get MULTISITE variable : " .. err)
return
end
if multisite == "no" then
local domains, err = get_variable("SERVER_NAME", false)
if not domains then
logger:log(ERR, "can't get SERVER_NAME variable : " .. err)
return
end
for domain in domains:gmatch("%S+") do
if host == domain then
return
end
end
else
local variables, err = datastore:get("variables", true)
if not variables then
logger:log(ERR, "can't get variables : " .. err)
return
end
for server_name, server_vars in pairs(variables) do
local domains = server_vars["SERVER_NAME"]
for domain in domains:gmatch("%S+") do
if host == domain then
return
end
end
end
end
logger:log(WARN, "unknown SNI host " .. host .. ", denying access")
exit(ERROR)
}
{% endif %}

View file

@ -1,6 +1,6 @@
{% if IS_LOADING != "yes" and DISABLE_DEFAULT_SERVER == "no" +%}
root /usr/share/bunkerweb/core/misc/files;
location / {
try_files /default.html =404;
}
{% if IS_LOADING != "yes" and DISABLE_DEFAULT_SERVER == "no" +%}
root /usr/share/bunkerweb/core/misc/files;
location / {
try_files /default.html =404;
}
{% endif %}

View file

@ -1,11 +0,0 @@
{% if REDIRECT_HTTP_TO_HTTPS == "yes" +%}
if ($scheme = http) {
return 301 https://$host$request_uri;
}
{% elif AUTO_REDIRECT_HTTP_TO_HTTPS == "yes" +%}
{% if AUTO_LETS_ENCRYPT == "yes" or USE_CUSTOM_SSL == "yes" or GENERATE_SELF_SIGNED_SSL == "yes" +%}
if ($scheme = http) {
return 301 https://$host$request_uri;
}
{% endif +%}
{% endif +%}

View file

@ -1,64 +0,0 @@
#!/usr/bin/env python3
from json import dumps
from os import getenv, sep
from os.path import join
from pathlib import Path
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
from typing import Any, Dict
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from Database import Database # type: ignore
from logger import setup_logger # type: ignore
from jobs import cache_file, is_cached_file # type: ignore
from requests import post
logger = setup_logger("ANONYMOUS-REPORT", getenv("LOG_LEVEL", "INFO"))
status = 0
if getenv("SEND_ANONYMOUS_REPORT", "yes") != "yes":
logger.info("Skipping the sending of anonymous report (disabled)")
sys_exit(status)
anonymous_report_path = Path(sep, "var", "cache", "bunkerweb", "anonymous_report")
anonymous_report_path.mkdir(parents=True, exist_ok=True)
tmp_anonymous_report_path = Path(sep, "var", "tmp", "bunkerweb", "anonymous_report")
tmp_anonymous_report_path.mkdir(parents=True, exist_ok=True)
try:
db = Database(logger, sqlalchemy_string=getenv("DATABASE_URI", None), pool=False)
if is_cached_file(anonymous_report_path.joinpath("last_report.json"), "day", db):
logger.info("Skipping the sending of anonymous report (already sent today)")
sys_exit(0)
# ? Get version and integration of BunkerWeb
data: Dict[str, Any] = db.get_metadata()
data["integration"] = data["integration"].lower()
data["database"] = db.database_uri.split(":")[0].split("+")[0]
data["service_number"] = str(len(getenv("SERVER_NAME", "").split(" ")))
data["use_ui"] = getenv("USE_UI", "no")
if data["use_ui"] == "no":
for server in getenv("SERVER_NAME", "").split(" "):
if getenv(f"{server}_USE_UI", "no") == "yes":
data["use_ui"] = "yes"
break
data["external_plugins"] = [plugin["id"] for plugin in db.get_plugins(external=True)]
tmp_anonymous_report_path.joinpath("last_report.json").write_text(dumps(data, indent=4), encoding="utf-8")
response = post("https://api.bunkerweb.io/data", json=data, headers={"User-Agent": f"BunkerWeb/{data['version']}"}, allow_redirects=True, timeout=10)
response.raise_for_status()
cached, err = cache_file(tmp_anonymous_report_path.joinpath("last_report.json"), anonymous_report_path.joinpath("last_report.json"), None, db)
except SystemExit as e:
status = e.code
except:
status = 2
logger.error(f"Exception while running anonymous-report.py :\n{format_exc()}")
sys_exit(status)

View file

@ -39,7 +39,7 @@ try:
"-nodes",
"-x509",
"-newkey",
"rsa:4096",
"ed25519",
"-keyout",
str(cert_path.joinpath("cert.key")),
"-out",

View file

@ -7,6 +7,7 @@ local misc = class("misc", plugin)
local ngx = ngx
local HTTP_NOT_ALLOWED = ngx.HTTP_NOT_ALLOWED
local HTTP_BAD_REQUEST = ngx.HTTP_BAD_REQUEST
local HTTP_MOVED_PERMANENTLY = ngx.HTTP_MOVED_PERMANENTLY
local regex_match = utils.regex_match
function misc:initialize(ctx)
@ -15,6 +16,21 @@ function misc:initialize(ctx)
end
function misc:access()
-- Check if we need to redirect to HTTPS
if
self.ctx.bw.scheme == "http"
and (
(self.ctx.bw.https_configured == "yes" and self.variables["AUTO_REDIRECT_HTTP_TO_HTTPS"] == "yes")
or self.variables["REDIRECT_HTTP_TO_HTTPS"] == "yes"
)
then
return self:ret(
true,
"redirect to HTTPS",
HTTP_MOVED_PERMANENTLY,
"https://" .. self.ctx.bw.http_host .. self.ctx.bw.request_uri
)
end
-- Check if method is valid
local method = self.ctx.bw.request_method
if not method or not regex_match(method, "^[A-Z]+$") then
@ -23,10 +39,10 @@ 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
self:set_metric("counters", "failed_method", 1)
return self:ret(true, "method " .. method .. " is not allowed", HTTP_NOT_ALLOWED)
end

View file

@ -158,15 +158,6 @@
"regex": "^(403|444)$",
"type": "select",
"select": ["403", "444"]
},
"SEND_ANONYMOUS_REPORT": {
"context": "global",
"default": "yes",
"help": "Send anonymous report to BunkerWeb maintainers.",
"id": "send-anonymous-report",
"label": "Send anonymous report",
"regex": "^(yes|no)$",
"type": "check"
}
},
"jobs": [
@ -181,12 +172,6 @@
"file": "update-check.py",
"every": "day",
"reload": false
},
{
"name": "anonymous-report",
"file": "anonymous-report.py",
"every": "day",
"reload": false
}
]
}

View file

@ -1,118 +1,66 @@
{% 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>
<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>
<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"
>
DEFAULT SERVER DISABLED
</p>
<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">
total
</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
role="img"
class="dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle bg-orange-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.55] leading-none text-lg relative fill-white"
>
<path
d="M4.08 5.227A3 3 0 0 1 6.979 3H17.02a3 3 0 0 1 2.9 2.227l2.113 7.926A5.228 5.228 0 0 0 18.75 12H5.25a5.228 5.228 0 0 0-3.284 1.153L4.08 5.227Z"
/>
<path
fill-rule="evenodd"
d="M5.25 13.5a3.75 3.75 0 1 0 0 7.5h13.5a3.75 3.75 0 1 0 0-7.5H5.25Zm10.5 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm3.75-.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
clip-rule="evenodd"
/>
</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"
>
DISALLOWED METHODS
</p>
<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">
count
</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="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
<script>
{% extends "base.html" %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<div class="core-layout">
<!-- info-->
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text"></p>
</div>
</div>
<!-- end info -->
</div>
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">DEFAULT SERVER DISABLED</p>
<h5 data-count-server-disabled class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content info">total</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container orange">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="scale-[0.55] core-card-metrics-svg">
<path d="M4.08 5.227A3 3 0 0 1 6.979 3H17.02a3 3 0 0 1 2.9 2.227l2.113 7.926A5.228 5.228 0 0 0 18.75 12H5.25a5.228 5.228 0 0 0-3.284 1.153L4.08 5.227Z" />
<path fill-rule="evenodd" d="M5.25 13.5a3.75 3.75 0 1 0 0 7.5h13.5a3.75 3.75 0 1 0 0-7.5H5.25Zm10.5 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm3.75-.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" clip-rule="evenodd" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">DISALLOWED METHODS</p>
<h5 data-count-disallowed-methods class="core-card-title"></h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content info">count</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container lime">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-small core-card-metrics-svg">
<path d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z" />
</svg>
</div>
<!-- end icon -->
</div>
<script nonce="{{script_nonce}}">
// Use SetupPlugin class that is on static/js/plugins/setup.js
const setPlugin = new SetupPlugin({
info: {
@ -131,6 +79,29 @@
type: "text",
},
});
</script>
</div>
</script>
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">This plugin need to be activated to get metrics.</p>
</div>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -66,7 +66,7 @@ SecAuditLog /var/log/bunkerweb/modsec_audit.log
# include OWASP CRS configurations
{% if USE_MODSECURITY_CRS == "yes" %}
include /usr/share/bunkerweb/core/modsecurity/files/crs-setup.conf
include /usr/share/bunkerweb/core/modsecurity/files/crs-setup-v{{ MODSECURITY_CRS_VERSION }}.conf
# custom CRS configurations before loading rules (e.g. exclusions)
{% if is_custom_conf("/etc/bunkerweb/configs/modsec-crs") %}
@ -100,7 +100,7 @@ SecRule ENV:is_whitelisted "yes" "id:1000,phase:1,allow,nolog,ctl:ruleEngine=Off
{% endif +%}
# include OWASP CRS rules
include /usr/share/bunkerweb/core/modsecurity/files/coreruleset/rules/*.conf
include /usr/share/bunkerweb/core/modsecurity/files/coreruleset-v{{ MODSECURITY_CRS_VERSION }}/rules/*.conf
{% endif +%}
# custom rules after loading the CRS

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