# Plugins BunkerWeb comes with a plugin system making it possible to easily add new features. Once a plugin is installed, you can manage it using additional settings defined by the plugin. ## Official plugins Here is the list of "official" plugins that we maintain (see the [bunkerweb-plugins](https://github.com/bunkerity/bunkerweb-plugins) repository for more information) : | Name | Version | Description | Link | | :------------: | :-----: | :------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------: | | **ClamAV** | 1.1 | Automatically scans uploaded files with the ClamAV antivirus engine and denies the request when a file is detected as malicious. | [bunkerweb-plugins/clamav](https://github.com/bunkerity/bunkerweb-plugins/tree/main/clamav) | | **Coraza** | 1.1 | Inspect requests using a the Coraza WAF (alternative of ModSecurity). | [bunkerweb-plugins/coraza](https://github.com/bunkerity/bunkerweb-plugins/tree/main/coraza) | | **CrowdSec** | 1.1 | CrowdSec bouncer for BunkerWeb. | [bunkerweb-plugins/crowdsec](https://github.com/bunkerity/bunkerweb-plugins/tree/main/crowdsec) | | **Discord** | 1.1 | Send security notifications to a Discord channel using a Webhook. | [bunkerweb-plugins/discord](https://github.com/bunkerity/bunkerweb-plugins/tree/main/discord) | | **Slack** | 1.1 | Send security notifications to a Slack channel using a Webhook. | [bunkerweb-plugins/slack](https://github.com/bunkerity/bunkerweb-plugins/tree/main/slack) | | **VirusTotal** | 1.1 | Automatically scans uploaded files with the VirusTotal API and denies the request when a file is detected as malicious. | [bunkerweb-plugins/virustotal](https://github.com/bunkerity/bunkerweb-plugins/tree/main/virustotal) | | **WebHook** | 1.1 | Send security notifications to a custom HTTP endpoint using a Webhook. | [bunkerweb-plugins/webhook](https://github.com/bunkerity/bunkerweb-plugins/tree/main/webhook) | ## How to use a plugin ### Automatic If you want to quickly install external plugins, you can use the `EXTERNAL_PLUGIN_URLS` setting. It takes a list of URLs, separated with space, pointing to compressed (zip format) archive containing one or more plugin(s). You can use the following value if you want to automatically install the official plugins : `EXTERNAL_PLUGIN_URLS=https://github.com/bunkerity/bunkerweb-plugins/archive/refs/tags/v1.1.zip` ### Manual The first step is to install the plugin by putting the plugin files inside the corresponding `plugins` data folder, the procedure depends on your integration : === "Docker" When using the [Docker integration](integrations.md#docker), plugins must be written to the volume mounted on `/data/plugins` into the scheduler container. The first thing to do is to create the plugins folder : ```shell mkdir -p ./bw-data/plugins ``` Then, you can drop the plugins of your choice into that folder : ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ 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 : ```shell chown -R 101:101 ./bw-data ``` Then you can mount the volume when starting your Docker stack : ```yaml version: '3.5' services: ... bw-scheduler: image: bunkerity/bunkerweb-scheduler:1.5.1 volumes: - ./bw-data:/data ... ``` === "Docker autoconf" When using the [Docker autoconf integration](integrations.md#docker-autoconf), plugins must be written to the volume mounted on `/data/plugins` into the scheduler container. The first thing to do is to create the plugins folder : ```shell mkdir -p ./bw-data/plugins ``` Then, you can drop the plugins of your choice into that folder : ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ 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 : ```shell chown -R 101:101 ./bw-data ``` Then you can mount the volume when starting your Docker stack : ```yaml version: '3.5' services: ... bw-scheduler: image: bunkerity/bunkerweb-scheduler:1.5.1 volumes: - ./bw-data:/data ... ``` === "Swarm" When using the [Swarm integration](integrations.md#swarm), plugins must be written to the volume mounted on `/data/plugins` into the scheduler container. !!! info "Swarm volume" Configuring a Swarm volume that will persist when the scheduler service is running on different nodes is not covered is in this documentation. We will assume that you have a shared folder mounted on `/shared` accross all nodes. The first thing to do is to create the plugins folder : ```shell mkdir -p /shared/bw-plugins ``` Then, you can drop the plugins of your choice into that folder : ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ cp -rp ./bunkerweb-plugins/* /shared/bw-plugins ``` Because the scheduler runs as an unprivileged user with UID and GID 101, you will need to edit the permissions : ```shell chown -R 101:101 /shared/bw-plugins ``` Then you can mount the volume when starting your Swarm stack : ```yaml version: '3.5' services: ... bw-scheduler: image: bunkerity/bunkerweb-scheduler:1.5.1 volumes: - /shared/bw-plugins:/data/plugins ... ``` === "Kubernetes" When using the [Kubernetes integration](integrations.md#kubernetes), plugins must be written to the volume mounted on `/data/plugins` into the scheduler container. The fist thing to do is to declare a [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) that will contain our plugins data : ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-bunkerweb-plugins spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi ``` You can now add the volume mount and an init containers to automatically provision the volume : ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: bunkerweb-scheduler spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: bunkerweb-scheduler template: metadata: labels: app: bunkerweb-scheduler spec: serviceAccountName: sa-bunkerweb containers: - name: bunkerweb-scheduler image: bunkerity/bunkerweb-scheduler:1.5.1 imagePullPolicy: Always env: - name: KUBERNETES_MODE value: "yes" - name: "DATABASE_URI" value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" volumeMounts: - mountPath: "/data/plugins" name: vol-plugins initContainers: - name: bunkerweb-scheduler-init image: alpine/git command: ["/bin/sh", "-c"] args: ["git clone https://github.com/bunkerity/bunkerweb-plugins /data/plugins && chown -R 101:101 /data/plugins"] volumeMounts: - mountPath: "/data/plugins" name: vol-plugins volumes: - name: vol-plugins persistentVolumeClaim: claimName: pvc-bunkerweb-plugins ``` === "Linux" When using the [Linux integration](integrations.md#linux), plugins must be written to the `/etc/bunkerweb/plugins` folder : ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ cp -rp ./bunkerweb-plugins/* /etc/bunkerweb/plugins && \ chown -R nginx:nginx /etc/bunkerweb/plugins ``` === "Ansible" When using the [Ansible integration](integrations.md#ansible), you can use the `plugins` variable to set a local folder containing your plugins that will be copied to your BunkerWeb instances. Let's assume that you have plugins inside the `bunkerweb-plugins` folder : ```shell git clone https://github.com/bunkerity/bunkerweb-plugins ``` In your Ansible inventory, you can use the `plugins` variable to set the path of plugins folder : ```ini [mybunkers] 192.168.0.42 ... custom_plugins="{{ playbook_dir }}/bunkerweb-plugins" ``` Or alternatively, in your playbook file : ```yaml - hosts: all become: true vars: - custom_plugins: "{{ playbook_dir }}/bunkerweb-plugins" roles: - bunkerity.bunkerweb ``` Run the playbook : ```shell ansible-playbook -i inventory.yml playbook.yml ``` === "Vagrant" When using the [Vagrant integration](integrations.md#vagrant), plugins must be written to the `/etc/bunkerweb/plugins` folder (you will need to do a `vagrant ssh` first) : ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ cp -rp ./bunkerweb-plugins/* /etc/bunkerweb/plugins ``` ## Writing a plugin !!! tip "Existing plugins" If the documentation is not enough, you can have a look at the existing source code of [official plugins](https://github.com/bunkerity/bunkerweb-plugins) and the [core plugins](https://github.com/bunkerity/bunkerweb/tree/v1.5.1/src/common/core) (already included in BunkerWeb but they are plugins, technically speaking). The first step is to create a folder that will contain the plugin : ```shell mkdir myplugin && \ cd myplugin ``` ### Metadata A file named **plugin.json** and written at the root of the plugin folder must contain metadata about the plugin. Here is an example : ```json { "id": "myplugin", "name": "My Plugin", "description": "Just an example plugin.", "version": "1.0", "stream": "partial", "settings": { "DUMMY_SETTING": { "context": "multisite", "default": "1234", "help": "Here is the help of the setting.", "id": "dummy-id", "label": "Dummy setting", "regex": "^.*$", "type": "text" } }, "jobs": [ { "name": "my-job", "file": "my-job.py", "every": "hour" } ] } ``` Here are the details of the fields : | Field | Mandatory | Type | Description | | :-----------: | :-------: | :----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | yes | string | Internal ID for the plugin : must be unique among other plugins (including "core" ones) and contain only lowercase chars. | | `name` | yes | string | Name of your plugin. | | `description` | yes | string | Description of your plugin. | | `version` | yes | string | Version of your plugin. | | `stream` | yes | string | Information about stream support : `no`, `yes` or `partial`. | `settings` | yes | dict | List of the settings of your plugin. | | `jobs` | no | list | List of the jobs of your plugin. | Each setting has the following fields (the key is the ID of the settings used in a configuration) : | Field | Mandatory | Type | Description | | :--------: | :-------: | :----: | :----------------------------------------------------------- | | `context` | yes | string | Context of the setting : `multisite` or `global`. | | `default` | yes | string | The default value of the setting. | | `help` | yes | string | Help text about the plugin (shown in web UI). | | `id` | yes | string | Internal ID used by the web UI for HTML elements. | | `label` | yes | string | Label shown by the web UI. | | `regex` | yes | string | The regex used to validate the value provided by the user. | | `type` | yes | string | The type of the field : `text`, `check`, `select` or `password`. | | `multiple` | no | string | Unique ID to group multiple settings with numbers as suffix. | | `select` | no | list | List of possible string values when `type` is `select`. | Each job has the following fields : | Field | Mandatory | Type | Description | | :-----: | :-------: | :----: | :-------------------------------------------------------------------------------------------------------------------------------------- | | `name` | yes | string | Name of the job. | | `file` | yes | string | Name of the file inside the jobs folder. | | `every` | yes | string | Job scheduling frequency : `minute`, `hour`, `day`, `week` or `once` (no frequency, only once before (re)generating the configuration). | ### Configurations You can add custom NGINX configurations by adding a folder named **confs** with content similar to the [custom configurations](quickstart-guide.md#custom-configurations). Each subfolder inside the **confs** will contain [jinja2](https://jinja.palletsprojects.com) templates that will be generated and loaded at the corresponding context (`http`, `server-http`, `default-server-http`, `stream` and `server-stream`). Here is an example for a configuration template file inside the **confs/server-http** folder named **example.conf** : ```conf location /setting { default_type 'text/plain'; content_by_lua_block { ngx.say('{{ DUMMY_SETTING }}') } } ``` `{{ DUMMY_SETTING }}` will be replaced by the value of the `DUMMY_SETTING` chosen by the user of the plugin. ### LUA #### Main script Under the hood, BunkerWeb is using the [NGINX LUA module](https://github.com/openresty/lua-nginx-module) to execute code within NGINX. Plugins that need to execute code must provide a lua file at the root directory of the plugin folder using the `id` value of **plugin.json** as its name. Here is an example named **myplugin.lua** : ```lua local class = require "middleclass" local plugin = require "bunkerweb.plugin" local utils = require "bunkerweb.utils" local myplugin = class("myplugin", plugin) function myplugin:initialize() plugin.initialize(self, "myplugin") self.dummy = "dummy" end function myplugin:init() self.logger:log(ngx.NOTICE, "init called") return self:ret(true, "success") end function myplugin:set() self.logger:log(ngx.NOTICE, "set called") return self:ret(true, "success") end function myplugin:access() self.logger:log(ngx.NOTICE, "access called") return self:ret(true, "success") end function myplugin:log() self.logger:log(ngx.NOTICE, "log called") return self:ret(true, "success") end function myplugin:log_default() self.logger:log(ngx.NOTICE, "log_default called") return self:ret(true, "success") end function myplugin:preread() self.logger:log(ngx.NOTICE, "preread called") return self:ret(true, "success") end function myplugin:log_stream() self.logger:log(ngx.NOTICE, "log_stream called") return self:ret(true, "success") end return myplugin ``` The declared functions are automatically called during specific contexts. Here are the details of each function : | Function | Context | Description | Return value | | :------: | :--------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `init` | [init_by_lua](https://github.com/openresty/lua-nginx-module#init_by_lua) | Called when NGINX just started or received a reload order. the typical use case is to prepare any data that will be used by your plugin. | `ret`, `msg`