Zum Inhalt

Plugins

BunkerWeb verfügt über ein Pluginsystem, das es ermöglicht, neue Funktionen einfach hinzuzufügen. Sobald ein Plugin installiert ist, können Sie es über zusätzliche, vom Plugin definierte Einstellungen verwalten.

Offizielle Plugins

Hier ist die Liste der "offiziellen" Plugins, die wir pflegen (weitere Informationen finden Sie im Repository bunkerweb-plugins):

Name Version Beschreibung Link
ClamAV 1.9 Scannt hochgeladene Dateien automatisch mit der ClamAV-Antiviren-Engine und lehnt die Anfrage ab, wenn eine Datei als bösartig erkannt wird. bunkerweb-plugins/clamav
Coraza 1.9 Überprüft Anfragen mit der Coraza WAF (Alternative zu ModSecurity). bunkerweb-plugins/coraza
Discord 1.9 Sendet Sicherheitsbenachrichtigungen über einen Webhook an einen Discord-Kanal. bunkerweb-plugins/discord
Slack 1.9 Sendet Sicherheitsbenachrichtigungen über einen Webhook an einen Slack-Kanal. bunkerweb-plugins/slack
VirusTotal 1.9 Scannt hochgeladene Dateien automatisch mit der VirusTotal-API und lehnt die Anfrage ab, wenn eine Datei als bösartig erkannt wird. bunkerweb-plugins/virustotal
WebHook 1.9 Sendet Sicherheitsbenachrichtigungen über einen Webhook an einen benutzerdefinierten HTTP-Endpunkt. bunkerweb-plugins/webhook

Wie man ein Plugin verwendet

Automatisch

Wenn Sie externe Plugins schnell installieren möchten, können Sie die Einstellung EXTERNAL_PLUGIN_URLS verwenden. Sie akzeptiert eine durch Leerzeichen getrennte Liste von URLs, die jeweils auf ein komprimiertes (zip-Format) Archiv mit einem oder mehreren Plugins verweisen.

Sie können den folgenden Wert verwenden, wenn Sie die offiziellen Plugins automatisch installieren möchten: EXTERNAL_PLUGIN_URLS=https://github.com/bunkerity/bunkerweb-plugins/archive/refs/tags/v1.9.zip

Manuell

Der erste Schritt besteht darin, das Plugin zu installieren, indem Sie seine Dateien in den entsprechenden Datenordner plugins legen. Das Verfahren hängt von Ihrer Integration ab:

Bei Verwendung der Docker-Integration müssen Plugins in das Volume gemountet werden, das auf /data/plugins im Scheduler-Container verweist.

Als Erstes müssen Sie den Plugins-Ordner erstellen:

mkdir -p ./bw-data/plugins

Dann können Sie die Plugins Ihrer Wahl in diesen Ordner legen:

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* ./bw-data/plugins
Verwendung eines lokalen Ordners für persistente Daten

Der Scheduler läuft als unprivilegierter Benutzer mit UID 101 und GID 101 im Container. Der Grund dafür ist die Sicherheit: Im Falle einer ausgenutzten Schwachstelle hat der Angreifer keine vollen Root-Rechte (UID/GID 0). Aber es gibt einen Nachteil: Wenn Sie einen lokalen Ordner für die persistenten Daten verwenden, müssen Sie die richtigen Berechtigungen festlegen, damit der unprivilegierte Benutzer Daten darin schreiben kann. Etwas wie das Folgende sollte ausreichen:

mkdir bw-data && \
chown root:101 bw-data && \
chmod 770 bw-data

Alternativ, wenn der Ordner bereits existiert:

chown -R root:101 bw-data && \
chmod -R 770 bw-data

Wenn Sie Docker im rootless-Modus oder podman verwenden, werden UIDs und GIDs im Container auf andere auf dem Host abgebildet. Sie müssen zuerst Ihre anfängliche subuid und subgid überprüfen:

grep ^$(whoami): /etc/subuid && \
grep ^$(whoami): /etc/subgid

Wenn Sie beispielsweise einen Wert von 100000 haben, ist die abgebildete UID/GID 100100 (100000 + 100):

mkdir bw-data && \
sudo chgrp 100100 bw-data && \
chmod 770 bw-data

Oder wenn der Ordner bereits existiert:

sudo chgrp -R 100100 bw-data && \
chmod -R 770 bw-data

Dann können Sie das Volume beim Starten Ihres Docker-Stacks mounten:

services:
...
  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.5
    volumes:
      - ./bw-data:/data
...

Bei Verwendung der Docker-Autoconf-Integration müssen Plugins in das Volume gemountet werden, das auf /data/plugins im Scheduler-Container verweist.

Als Erstes müssen Sie den Plugins-Ordner erstellen:

mkdir -p ./bw-data/plugins

Dann können Sie die Plugins Ihrer Wahl in diesen Ordner legen:

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* ./bw-data/plugins

Da der Scheduler als unprivilegierter Benutzer mit UID und GID 101 läuft, müssen Sie die Berechtigungen bearbeiten:

chown -R 101:101 ./bw-data

Dann können Sie das Volume beim Starten Ihres Docker-Stacks mounten:

services:
...
  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.5
    volumes:
      - ./bw-data:/data
...

Veraltet

Die Swarm-Integration ist veraltet und wird in einer zukünftigen Version entfernt. Bitte erwägen Sie stattdessen die Verwendung der Kubernetes-Integration.

Weitere Informationen finden Sie in der Swarm-Integrationsdokumentation.

Bei Verwendung der Swarm-Integration müssen Plugins in das Volume gemountet werden, das auf /data/plugins im Scheduler-Container verweist.

Swarm-Volume

Die Konfiguration eines Swarm-Volumes, das bestehen bleibt, wenn der Scheduler-Dienst auf verschiedenen Knoten ausgeführt wird, wird in dieser Dokumentation nicht behandelt. Wir gehen davon aus, dass Sie einen freigegebenen Ordner auf /shared auf allen Knoten gemountet haben.

Als Erstes müssen Sie den Plugins-Ordner erstellen:

mkdir -p /shared/bw-plugins

Dann können Sie die Plugins Ihrer Wahl in diesen Ordner legen:

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* /shared/bw-plugins

Da der Scheduler als unprivilegierter Benutzer mit UID und GID 101 läuft, müssen Sie die Berechtigungen bearbeiten:

chown -R 101:101 /shared/bw-plugins

Dann können Sie das Volume beim Starten Ihres Swarm-Stacks mounten:

services:
...
  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.5
    volumes:
      - /shared/bw-plugins:/data/plugins
...

Bei Verwendung der Kubernetes-Integration müssen Plugins in das Volume gemountet werden, das auf /data/plugins im Scheduler-Container verweist.

Als Erstes müssen Sie einen PersistentVolumeClaim deklarieren, der unsere Plugin-Daten enthalten wird:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-bunkerweb-plugins
spec:
  accessModes:
    - ReadWriteOnce
resources:
  requests:
    storage: 5Gi

Sie können nun den Volume-Mount und einen Init-Container hinzufügen, um das Volume automatisch bereitzustellen:

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.6.5
          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

Bei Verwendung der Linux-Integration müssen Plugins in den Ordner /etc/bunkerweb/plugins geschrieben werden:

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* /etc/bunkerweb/plugins && \
chown -R nginx:nginx /etc/bunkerweb/plugins

Ein Plugin schreiben

Struktur

Bestehende Plugins

Wenn die Dokumentation nicht ausreicht, können Sie sich den bestehenden Quellcode der offiziellen Plugins und der Kern-Plugins ansehen (bereits in BunkerWeb enthalten, aber technisch gesehen sind es Plugins).

Wie eine Plugin-Struktur aussieht:

plugin /
    confs / conf_type / conf_name.conf
    ui / actions.py
         hooks.py
         template.html
         blueprints / <blueprint_file(s)>
              templates / <blueprint_template(s)>
    jobs / my-job.py
    templates / my-template.json
          my-template / configs / conf_type / conf_name.conf
    plugin.lua
    plugin.json

  • conf_name.conf : Fügen Sie benutzerdefinierte NGINX-Konfigurationen (als Jinja2-Vorlagen) hinzu.

  • actions.py : Skript, das auf dem Flask-Server ausgeführt wird. Dieses Skript wird in einem Flask-Kontext ausgeführt und gibt Ihnen Zugriff auf Bibliotheken und Dienstprogramme wie jinja2 und requests.

  • hooks.py : Benutzerdefinierte Python-Datei, die Flask-Hooks enthält und beim Laden des Plugins ausgeführt wird.

  • template.html : Benutzerdefinierte Plugin-Seite, die über die Benutzeroberfläche aufgerufen wird.

  • blueprints-Ordner (innerhalb von ui): Dieser Ordner wird verwendet, um bestehende Flask-Blueprints zu überschreiben oder neue zu erstellen. Darin können Sie Blueprint-Dateien und einen optionalen templates-Unterordner für Blueprint-spezifische Vorlagen einschließen.

  • jobs py-Datei : Benutzerdefinierte Python-Dateien, die als Jobs vom Scheduler ausgeführt werden.

  • my-template.json : Fügen Sie benutzerdefinierte Vorlagen hinzu, um die Standardwerte von Einstellungen zu überschreiben und benutzerdefinierte Konfigurationen einfach anzuwenden.

  • plugin.lua : Code, der auf NGINX mit dem NGINX LUA-Modul ausgeführt wird.

  • plugin.json : Metadaten, Einstellungen und Jobdefinitionen für Ihr Plugin.

Erste Schritte

Der erste Schritt ist die Erstellung eines Ordners, der das Plugin enthalten wird:

mkdir myplugin && \
cd myplugin

Metadaten

Eine Datei namens plugin.json, die im Stammverzeichnis des Plugin-Ordners geschrieben wird, muss Metadaten über das Plugin enthalten. Hier ist ein Beispiel:

{
  "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"
    }
  ]
}

Hier sind die Details der Felder:

Feld Obligatorisch Typ Beschreibung
id ja string Interne ID für das Plugin: muss unter anderen Plugins (einschließlich "Kern"-Plugins) eindeutig sein und darf nur Kleinbuchstaben enthalten.
name ja string Name Ihres Plugins.
description ja string Beschreibung Ihres Plugins.
version ja string Version Ihres Plugins.
stream ja string Informationen zur Stream-Unterstützung: no, yes oder partial.
settings ja dict Liste der Einstellungen Ihres Plugins.
jobs nein list Liste der Jobs Ihres Plugins.
bwcli nein dict Ordnet CLI-Befehlsnamen den in dem 'bwcli'-Verzeichnis des Plugins gespeicherten Dateien zu, um CLI-Plugins verfügbar zu machen.

Jede Einstellung hat die folgenden Felder (der Schlüssel ist die ID der in einer Konfiguration verwendeten Einstellungen):

Feld Obligatorisch Typ Beschreibung
context ja string Kontext der Einstellung: multisite oder global.
default ja string Der Standardwert der Einstellung.
help ja string Hilfetext zum Plugin (wird in der Web-UI angezeigt).
id ja string Interne ID, die von der Web-UI für HTML-Elemente verwendet wird.
label ja string Label, das von der Web-UI angezeigt wird.
regex ja string Der Regex, der zur Validierung des vom Benutzer angegebenen Werts verwendet wird.
type ja string Der Typ des Feldes: text, check, select oder password.
multiple nein string Eindeutige ID zur Gruppierung mehrerer Einstellungen mit Zahlen als Suffix.
select nein list Liste der möglichen Zeichenfolgenwerte, wenn type select ist.

Jeder Job hat die folgenden Felder:

Feld Obligatorisch Typ Beschreibung
name ja string Name des Jobs.
file ja string Name der Datei im Jobs-Ordner.
every ja string Häufigkeit der Job-Planung: minute, hour, day, week oder once (keine Häufigkeit, nur einmal vor der (Neu-)Generierung der Konfiguration).

CLI-Befehle

Plugins können das 'bwcli'-Tool mit benutzerdefinierten Befehlen erweitern, die unter 'bwcli plugin ...' ausgeführt werden:

  1. Fügen Sie ein 'bwcli'-Verzeichnis in Ihrem Plugin hinzu und legen Sie eine Datei pro Befehl ab (zum Beispiel 'bwcli/list.py'). Die CLI fügt den Plugin-Pfad zu 'sys.path' hinzu, bevor die Datei ausgeführt wird.
  2. Deklarieren Sie die Befehle im optionalen 'bwcli'-Abschnitt von 'plugin.json', indem Sie jeden Befehlsnamen seinem ausführbaren Dateinamen zuordnen.
{
  "bwcli": {
    "list": "list.py",
    "save": "save.py"
  }
}

Der Scheduler stellt die deklarierten Befehle automatisch bereit, sobald das Plugin installiert ist. Core-Plugins wie 'backup' in 'src/common/core/backup' folgen demselben Muster.

Konfigurationen

Sie können benutzerdefinierte NGINX-Konfigurationen hinzufügen, indem Sie einen Ordner namens confs mit Inhalt ähnlich den benutzerdefinierten Konfigurationen hinzufügen. Jeder Unterordner in confs enthält jinja2-Vorlagen, die generiert und im entsprechenden Kontext (http, server-http, default-server-http, stream, server-stream, modsec, modsec-crs, crs-plugins-before und crs-plugins-after) geladen werden.

Hier ist ein Beispiel für eine Konfigurationsvorlagendatei im Ordner confs/server-http namens example.conf:

location /setting {
  default_type 'text/plain';
    content_by_lua_block {
        ngx.say('{{ DUMMY_SETTING }}')
    }
}

{{ DUMMY_SETTING }} wird durch den Wert von DUMMY_SETTING ersetzt, der vom Benutzer des Plugins ausgewählt wurde.

Vorlagen

Weitere Informationen finden Sie in der Vorlagendokumentation.

LUA

Hauptskript

Im Hintergrund verwendet BunkerWeb das NGINX LUA-Modul, um Code innerhalb von NGINX auszuführen. Plugins, die Code ausführen müssen, müssen eine Lua-Datei im Stammverzeichnis des Plugin-Ordners bereitstellen, die den id-Wert von plugin.json als Namen verwendet. Hier ist ein Beispiel namens myplugin.lua:

local class     = require "middleclass"
local plugin    = require "bunkerweb.plugin"
local utils     = require "bunkerweb.utils"


local myplugin = class("myplugin", plugin)


function myplugin:initialize(ctx)
    plugin.initialize(self, "myplugin", ctx)
    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

Die deklarierten Funktionen werden automatisch in bestimmten Kontexten aufgerufen. Hier sind die Details zu jeder Funktion:

Funktion Kontext Beschreibung Rückgabewert
init init_by_lua Wird aufgerufen, wenn NGINX gerade gestartet wurde oder einen Neuladebefehl erhalten hat. Der typische Anwendungsfall besteht darin, Daten vorzubereiten, die von Ihrem Plugin verwendet werden. ret, msg
  • ret (boolean): true, wenn kein Fehler, sonst false
  • msg (string): Erfolgs- oder Fehlermeldung
set set_by_lua Wird vor jeder vom Server empfangenen Anfrage aufgerufen. Der typische Anwendungsfall ist die Berechnung vor der Zugriffsphase. ret, msg
  • ret (boolean): true, wenn kein Fehler, sonst false
  • msg (string): Erfolgs- oder Fehlermeldung
access access_by_lua Wird bei jeder vom Server empfangenen Anfrage aufgerufen. Der typische Anwendungsfall besteht darin, hier die Sicherheitsüberprüfungen durchzuführen und die Anfrage bei Bedarf abzulehnen. ret, msg,status,redirect
  • ret (boolean): true, wenn kein Fehler, sonst false
  • msg (string): Erfolgs- oder Fehlermeldung
  • status (number): unterbricht den aktuellen Prozess und gibt den HTTP-Status zurück
  • redirect (URL): wenn gesetzt, wird auf die angegebene URL umgeleitet
log log_by_lua Wird aufgerufen, wenn eine Anfrage abgeschlossen ist (und bevor sie in die Zugriffsprotokolle geschrieben wird). Der typische Anwendungsfall besteht darin, beispielsweise Statistiken zu erstellen oder Zähler zu berechnen. ret, msg
  • ret (boolean): true, wenn kein Fehler, sonst false
  • msg (string): Erfolgs- oder Fehlermeldung
log_default log_by_lua Dasselbe wie log, wird aber nur auf dem Standardserver aufgerufen. ret, msg
  • ret (boolean): true, wenn kein Fehler, sonst false
  • msg (string): Erfolgs- oder Fehlermeldung
preread preread_by_lua Ähnlich der access-Funktion, aber für den Stream-Modus. ret, msg,status
  • ret (boolean): true, wenn kein Fehler, sonst false
  • msg (string): Erfolgs- oder Fehlermeldung
  • status (number): unterbricht den aktuellen Prozess und gibt den Status zurück
log_stream log_by_lua Ähnlich der log-Funktion, aber für den Stream-Modus. ret, msg
  • ret (boolean): true, wenn kein Fehler, sonst false
  • msg (string): Erfolgs- oder Fehlermeldung

Bibliotheken

Alle Direktiven aus dem NGINX LUA-Modul und dem NGINX Stream LUA-Modul sind verfügbar. Darüber hinaus können Sie die in BunkerWeb enthaltenen LUA-Bibliotheken verwenden: siehe dieses Skript für die vollständige Liste.

Wenn Sie zusätzliche Bibliotheken benötigen, können Sie diese in den Stammordner des Plugins legen und darauf zugreifen, indem Sie ihnen Ihre Plugin-ID voranstellen. Hier ist ein Beispiel für eine Datei namens mylibrary.lua:

local _M = {}

_M.dummy = function ()
    return "dummy"
end

return _M

Und hier ist, wie Sie sie aus der Datei myplugin.lua verwenden können:

local mylibrary = require "myplugin.mylibrary"

...

mylibrary.dummy()

...

Helfer

Einige Helfermodule bieten allgemeine nützliche Helfer:

  • self.variables: ermöglicht den Zugriff auf und die Speicherung von Plugin-Attributen
  • self.logger: druckt Protokolle
  • bunkerweb.utils: verschiedene nützliche Funktionen
  • bunkerweb.datastore: greift auf die globalen gemeinsamen Daten auf einer Instanz zu (Schlüssel-Wert-Speicher)
  • bunkerweb.clusterstore: greift auf einen Redis-Datenspeicher zu, der zwischen BunkerWeb-Instanzen geteilt wird (Schlüssel-Wert-Speicher)

Um auf die Funktionen zuzugreifen, müssen Sie zuerst die Module erfordern:

local utils       = require "bunkerweb.utils"
local datastore   = require "bunkerweb.datastore"
local clustestore = require "bunkerweb.clustertore"

Einen Einstellungswert abrufen:

local myvar = self.variables["DUMMY_SETTING"]
if not myvar then
    self.logger:log(ngx.ERR, "kann Einstellung DUMMY_SETTING nicht abrufen")
else
    self.logger:log(ngx.NOTICE, "DUMMY_SETTING = " .. value)
end

Etwas im lokalen Cache speichern:

local ok, err = self.datastore:set("plugin_myplugin_something", "somevalue")
if not ok then
    self.logger:log(ngx.ERR, "kann plugin_myplugin_something nicht im Datenspeicher speichern: " .. err)
else
    self.logger:log(ngx.NOTICE, "plugin_myplugin_something erfolgreich im Datenspeicher gespeichert")
end

Überprüfen, ob eine IP-Adresse global ist:

local ret, err = utils.ip_is_global(ngx.ctx.bw.remote_addr)
if ret == nil then
    self.logger:log(ngx.ERR, "Fehler beim Überprüfen, ob die IP " .. ngx.ctx.bw.remote_addr .. " global ist oder nicht: " .. err)
elseif not ret then
    self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " ist nicht global")
else
    self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " ist global")
end

Weitere Beispiele

Wenn Sie die vollständige Liste der verfügbaren Funktionen sehen möchten, können Sie sich die Dateien im lua-Verzeichnis des Repositorys ansehen.

Jobs

BunkerWeb verwendet einen internen Job-Scheduler für periodische Aufgaben wie die Erneuerung von Zertifikaten mit Certbot, das Herunterladen von Blacklists, das Herunterladen von MMDB-Dateien usw. Sie können Aufgaben Ihrer Wahl hinzufügen, indem Sie sie in einen Unterordner namens jobs legen und sie in der Metadatendatei plugin.json auflisten. Vergessen Sie nicht, die Ausführungsberechtigungen für alle hinzuzufügen, um Probleme zu vermeiden, wenn ein Benutzer Ihr Plugin klont und installiert.

Plugin-Seite

Alles, was mit der Web-UI zu tun hat, befindet sich im Unterordner ui, wie wir im vorherigen Strukturabschnitt gesehen haben.

Voraussetzungen

Wenn Sie eine Plugin-Seite erstellen möchten, benötigen Sie zwei Dateien:

  • template.html, das mit einem GET /plugins/<plugin_id> erreichbar ist.

  • actions.py, wo Sie Skripte und Logik mit einem POST /plugins/<plugin_id> hinzufügen können. Beachten Sie, dass diese Datei eine Funktion mit demselben Namen wie das Plugin benötigt, um zu funktionieren. Diese Datei wird auch dann benötigt, wenn die Funktion leer ist.

Grundlegendes Beispiel

Jinja 2-Vorlage

Die Datei template.html ist eine Jinja2-Vorlage. Weitere Informationen finden Sie in der Jinja2-Dokumentation.

Wir können die Datei actions.py beiseite legen und nur die Vorlage in einer GET-Situation verwenden. Die Vorlage kann auf den App-Kontext und Bibliotheken zugreifen, sodass Sie Jinja-, Request- oder Flask-Dienstprogramme verwenden können.

Sie können beispielsweise die Anforderungsargumente in Ihrer Vorlage wie folgt abrufen:

<p>Anforderungsargumente: {{ request.args.get() }}.</p>

Actions.py

CSRF-Token

Bitte beachten Sie, dass jede Formularübermittlung durch ein CSRF-Token geschützt ist. Sie müssen das folgende Snippet in Ihre Formulare einfügen:

<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />

Sie können Ihre Plugin-Seite mit zusätzlichen Skripten mit der Datei actions.py erweitern, wenn Sie einen POST /plugins/<plugin_id> senden.

Sie haben standardmäßig zwei Funktionen in actions.py:

pre_render-Funktion

Dies ermöglicht es Ihnen, Daten abzurufen, wenn Sie die Vorlage GETen, und die Daten mit der in Jinja verfügbaren pre_render-Variable zu verwenden, um Inhalte dynamischer anzuzeigen.

def pre_render(**kwargs)
  return <pre_render_data>

BunkerWeb sendet Ihnen diese Art von Antwort:

return jsonify({"status": "ok|ko", "code" : XXX, "data": <pre_render_data>}), 200

<plugin_id>-Funktion

Dies ermöglicht es Ihnen, Daten abzurufen, wenn Sie einen POST vom Vorlagenendpunkt ausführen, der in AJAX verwendet werden muss.

def myplugin(**kwargs)
  return <plugin_id_data>

BunkerWeb sendet Ihnen diese Art von Antwort:

return jsonify({"message": "ok", "data": <plugin_id_data>}), 200

Was Sie von action.py aus zugreifen können

Hier sind die Argumente, die an die Funktionen von action.py übergeben und darauf zugegriffen werden:

function(app=app, args=request.args.to_dict() or request.json or None)

Verfügbare Python-Bibliotheken

Die Web-UI von BunkerWeb enthält eine Reihe vorinstallierter Python-Bibliotheken, die Sie in der actions.py-Datei Ihres Plugins oder anderen UI-bezogenen Skripten verwenden können. Diese sind sofort verfügbar und erfordern keine zusätzlichen Installationen.

Hier ist die vollständige Liste der enthaltenen Bibliotheken:

  • bcrypt - Passwort-Hashing-Bibliothek
  • biscuit-python - Biscuit-Authentifizierungstoken
  • certbot - ACME-Client für Let's Encrypt
  • Flask - Web-Framework
  • Flask-Login - Benutzersitzungsverwaltung
  • Flask-Session[cachelib] - Serverseitige Sitzungsspeicherung
  • Flask-WTF - Formularbehandlung und CSRF-Schutz
  • gunicorn[gthread] - WSGI-HTTP-Server
  • pillow - Bildverarbeitung
  • psutil - System- und Prozess-Dienstprogramme
  • python_dateutil - Datums- und Uhrzeit-Dienstprogramme
  • qrcode - QR-Code-Generierung
  • regex - Erweiterte reguläre Ausdrücke
  • urllib3 - HTTP-Client
  • user_agents - User-Agent-Parsing

Verwendung von Bibliotheken in Ihrem Plugin

Um diese Bibliotheken in Ihrer actions.py-Datei zu importieren und zu verwenden, verwenden Sie einfach die standardmäßige Python-import-Anweisung. Zum Beispiel:

from flask import request
import bcrypt
Externe Bibliotheken

Wenn Sie Bibliotheken benötigen, die nicht oben aufgeführt sind, installieren Sie sie im ui-Ordner Ihres Plugins und importieren Sie sie mit der klassischen import-Anweisung. Stellen Sie die Kompatibilität mit der vorhandenen Umgebung sicher, um Konflikte zu vermeiden.

Einige Beispiele

  • Abrufen von übermittelten Formulardaten
from flask import request

def myplugin(**kwargs) :
    my_form_value = request.form["my_form_input"]
  return my_form_value
  • Zugriff auf die App-Konfiguration

action.py

from flask import request

def pre_render(**kwargs) :
    config = kwargs['app'].config["CONFIG"].get_config(methods=False)
  return config

Vorlage

<!-- Metadaten + Konfiguration -->
<div>{{ pre_render }}</div>

Hooks.py

Diese Dokumentation beschreibt die Lebenszyklus-Hooks, die zur Verwaltung verschiedener Phasen einer Anfrage innerhalb der Anwendung verwendet werden. Jeder Hook ist mit einer bestimmten Phase verbunden.

Diese Hooks werden vor der Verarbeitung einer eingehenden Anfrage ausgeführt. Sie werden normalerweise für Vorverarbeitungsaufgaben wie Authentifizierung, Validierung oder Protokollierung verwendet.

Wenn der Hook ein Antwortobjekt zurückgibt, überspringt Flask die Anforderungsbehandlung und gibt die Antwort direkt zurück. Dies kann nützlich sein, um die Anforderungsverarbeitungspipeline kurzzuschließen.

Beispiel:

from flask import request, Response

def before_request():
    print("Before-request: Validating request...", flush=True)
    # Hier Authentifizierung, Validierung oder Protokollierung durchführen
    if not is_valid_request(request): # Wir befinden uns im App-Kontext
        return Response("Invalid request!", status=400)

def is_valid_request(request):
    # Dummy-Validierungslogik
    return "user" in request

Diese Hooks werden nach der Verarbeitung der Anfrage ausgeführt. Sie sind ideal für Nachverarbeitungsaufgaben wie Bereinigung, zusätzliche Protokollierung oder das Ändern der Antwort, bevor sie zurückgesendet wird.

Sie erhalten das Antwortobjekt als Argument und können es ändern, bevor sie es zurückgeben. Der erste after_request-Hook, der eine Antwort zurückgibt, wird als endgültige Antwort verwendet.

Beispiel:

from flask import request

def after_request(response):
    print("After-request: Logging response...", flush=True)
    # Hier Protokollierung, Bereinigung oder Antwortänderungen durchführen
    log_response(response)
    return response

def log_response(response):
    # Dummy-Protokollierungslogik
    print("Response logged:", response, flush=True)

Diese Hooks werden aufgerufen, wenn der Anforderungskontext abgebaut wird. Diese Hooks werden verwendet, um Ressourcen freizugeben oder Fehler zu behandeln, die während des Lebenszyklus der Anfrage aufgetreten sind.

Beispiel:

def teardown_request(error=None):
    print("Teardown-request: Cleaning up resources...", flush=True)
    # Hier Bereinigung, Ressourcenfreigabe oder Fehlerbehandlung durchführen
    if error:
        handle_error(error)
    cleanup_resources()

def handle_error(error):
    # Dummy-Fehlerbehandlungslogik
    print("Error encountered:", error, flush=True)

def cleanup_resources():
    # Dummy-Ressourcenbereinigungslogik
    print("Resources have been cleaned up.", flush=True)

Diese Hooks werden verwendet, um zusätzlichen Kontext in Vorlagen oder Ansichten einzufügen. Sie bereichern den Laufzeitkontext, indem sie allgemeine Daten (wie Benutzerinformationen oder Konfigurationseinstellungen) an die Vorlagen übergeben.

Wenn ein Kontextprozessor ein Wörterbuch zurückgibt, werden die Schlüssel und Werte dem Kontext für alle Vorlagen hinzugefügt. Dies ermöglicht es Ihnen, Daten über mehrere Ansichten oder Vorlagen hinweg zu teilen.

Beispiel:

def context_processor() -> dict:
    print("Context-processor: Injecting context data...", flush=True)
    # Ein Wörterbuch mit Kontextdaten für Vorlagen/Ansichten zurückgeben
    return {
        "current_user": "John Doe",
        "app_version": "1.0.0",
        "feature_flags": {"new_ui": True}
    }

Dieses Lebenszyklus-Hook-Design bietet einen modularen und systematischen Ansatz zur Verwaltung verschiedener Aspekte des Lebenszyklus einer Anfrage:

  • Modularität: Jeder Hook ist für eine bestimmte Phase verantwortlich, wodurch sichergestellt wird, dass die Belange getrennt sind.
  • Wartbarkeit: Entwickler können Hook-Implementierungen einfach hinzufügen, ändern oder entfernen, ohne andere Teile des Lebenszyklus der Anfrage zu beeinträchtigen.
  • Erweiterbarkeit: Das Framework ist flexibel und ermöglicht zusätzliche Hooks oder Erweiterungen, wenn sich die Anwendungsanforderungen ändern.

Durch die klare Definition der Verantwortlichkeiten jedes Hooks und der zugehörigen Protokollierungspräfixe stellt das System sicher, dass jede Phase der Anforderungsverarbeitung transparent und verwaltbar ist.

Blueprints

In Flask dienen Blueprints als modularer Weg, um verwandte Komponenten – wie Ansichten, Vorlagen und statische Dateien – innerhalb Ihrer Anwendung zu organisieren. Sie ermöglichen es Ihnen, Funktionen logisch zu gruppieren und können verwendet werden, um neue Abschnitte Ihrer App zu erstellen oder bestehende zu überschreiben.

Erstellen eines Blueprints

Um einen Blueprint zu definieren, erstellen Sie eine Instanz der Blueprint-Klasse und geben dabei dessen Namen und Importpfad an. Anschließend definieren Sie Routen und Ansichten, die mit diesem Blueprint verbunden sind.

Beispiel: Definieren eines neuen Blueprints

from os.path import dirname
from flask import Blueprint, render_template

# Den Blueprint definieren
my_blueprint = Blueprint('my_blueprint', __name__, template_folder=dirname(__file__) + '/templates') # Der template_folder wird gesetzt, um Konflikte mit dem ursprünglichen Blueprint zu vermeiden

# Eine Route innerhalb des Blueprints definieren
@my_blueprint.route('/my_blueprint')
def my_blueprint_page():
    return render_template('my_blueprint.html')

In diesem Beispiel wird ein Blueprint namens my_blueprint erstellt und eine Route /my_blueprint darin definiert.

Überschreiben eines bestehenden Blueprints

Blueprints können auch bestehende überschreiben, um Funktionalität zu ändern oder zu erweitern. Stellen Sie dazu sicher, dass der neue Blueprint denselben Namen hat wie der, den Sie überschreiben, und registrieren Sie ihn nach dem Original.

Beispiel: Überschreiben eines bestehenden Blueprints

from os.path import dirname
from flask import Flask, Blueprint

# Ursprünglicher Blueprint
instances = Blueprint('instances', __name__, template_folder=dirname(__file__) + '/templates') # Der template_folder wird gesetzt, um Konflikte mit dem ursprünglichen Blueprint zu vermeiden

@instances.route('/instances')
def override_instances():
    return "Meine neue Instanzen-Seite"

In diesem Szenario wird beim Aufrufen der URL /instances "Meine neue Instanzen-Seite" angezeigt, da der zuletzt registrierte instances-Blueprint den ursprünglichen instances-Blueprint überschreibt.

Zum Überschreiben

Seien Sie vorsichtig beim Überschreiben bestehender Blueprints, da dies das Verhalten der Anwendung beeinträchtigen kann. Stellen Sie sicher, dass die Änderungen den Anforderungen der Anwendung entsprechen und keine unerwarteten Nebenwirkungen verursachen.

Alle bestehenden Routen werden aus dem ursprünglichen Blueprint entfernt, sodass Sie sie bei Bedarf neu implementieren müssen.

Namenskonventionen

Wichtig

Stellen Sie sicher, dass der Name des Blueprints mit dem Namen der Blueprint-Variable übereinstimmt, andernfalls wird er nicht als gültiger Blueprint betrachtet und nicht registriert.

Für Konsistenz und Klarheit ist es ratsam, die folgenden Namenskonventionen zu befolgen:

  • Blueprint-Namen: Verwenden Sie kurze, nur aus Kleinbuchstaben bestehende Namen. Unterstriche können zur Lesbarkeit verwendet werden, z. B. user_auth.

  • Dateinamen: Passen Sie den Dateinamen an den Blueprint-Namen an und stellen Sie sicher, dass er nur aus Kleinbuchstaben mit Unterstrichen besteht, z. B. user_auth.py.

Diese Praxis steht im Einklang mit den Namenskonventionen für Python-Module und hilft, eine klare Projektstruktur beizubehalten.

Beispiel: Blueprint- und Dateibenennung

plugin /
    ui / blueprints / user_auth.py
                      templates / user_auth.html

In dieser Struktur enthält user_auth.py den user_auth-Blueprint und user_auth.html ist die zugehörige Vorlage, die den empfohlenen Namenskonventionen entspricht.