Paperless NGX fremgehostet
Einleitung
Was tun, wenn ich keine Server zuhause betreiben aber trotzdem Paperless-NGX nutzen möchte? Nun, es gibt Anbieter, die Paperless NGX hosten und das als Dienstleistung anbieten. Ich möchte aber eine Paperless-NGX-Installation bei einem Hoster aufsetzen und sie ansonsten selbst betreiben. Dabei sind folgende Merkmale gewünscht:
- Hohe Autonomie bei Konfiguration und Nutzung
- Hohe Sicherheit
- Schutz gegenüber unbefugten Zugriffen und Manipulationen
- Schutz vor Datenverlust
- Geringe Kosten
Folgende Funktionalitäten sind gewünscht:
- Nutzung über eine gesicherte Weboberfläche
- Scannen direkt in Paperless-NGX
- Ablage von Dokumenten auch über ein dafür vorgesehenes Verzeichnis
Lösungsdesign
Die Lösung, die ich auch in einem Youtube-Video beschrieben habe, besteht aus einem Server, der bei einem Hoster angemietet wird. Auf diesem läuft zunächst Wireguard, um eine VPN-Verbindung zu Clients im lokalen Netzwerk aufzubauen. Das wird ausschließlich für die Administration des Systems benötigt. Normale Userzugriffe auf Paperless-NGX werden später über die öffentlich zugängliche Domain stattfinden. Dann läuft auf dem Server unter Docker der Reverse Proxy traefik, Paperless-NGX und die Datensicherungslösung Duplicati. Zum Schutz vor unbefugten Zugriffen und Manipulationen kommen restriktive Firewalleinstellungen zum Einsatz und mit fail2ban wird ein Schutz vor Brute Force Attacken realisiert. Die Datensicherung wird später auf einen Cloud-Speicher durchgeführt; natürlich verschlüsselt.
Zutaten
Man nehme:
- Einen günstigen virtuellen Server bei einem Hoster
- Eine (Sub)-Domain, die aus dem Internet erreichbar ist
- Günstigen Cloudspeicher, der per SFTP angesprochen werden kann
Hosting
Ich werde hier keine Empfehlungen für oder gegen das eine oder andere Hosting-Angebot aussprechen. Die Angebote der verschiedenen Hoster unterscheiden sich hinsichtlich der Speichermengen, der Systemressourcen, der Bandbreite, mit der die Server ans Internet angebunden sind und auch hinsichtlich der jeweils angebotenen Services (mit oder ohne Firewall vor dem Server, mit oder ohne Snapshots, Nutzung eigener Bootmedien oder ausschließlich vom Anbieter bereit gestellter Medien...). Hier muss man sich überlegen, was man braucht und möchte. Meine Paperless-NGX-Installationen kommen in der Regel mit 2 Prozessorkernen und 4 GB RAM aus. Beim Massenspeicher muss man überlegen, wie viel man vielleicht braucht. Wenn man davon ausgeht, am Anfang nicht sehr viel zu brauchen, dann mögen vielleicht 32 GB Massenspeicher ausreichen. Wenn der aber irgendwann knapp wird, dann ist ein Umzug auf einen besser ausgestatteten Server zumindest nicht unaufwändig. Für den Anfang sollte aber eine Kapazität von etwa 80 GB reichen.
Für diese recht geringen Anforderungen werden von den meisten Hostern (z.B. Hetzner, Ionos, Netcup oder Strato) Produkte in der Preisklasse um die 4 - 5 € pro Monat angeboten. Für die Datensicherung empfehle ich einen Cloudspeicher bei einem anderen Anbieter, als dem bei dem der Server gemietet wird. Da die Sicherungen verschlüsselt übertragen und gespeichert werden, ist das aus Sicherheits- und Datenschutzgesichtspunkten her unbedenklich. Solche Speicherlösungen werden für 3 - 5 € im Monat angeboten (z.B. die Hetzner Storage-Box für 3,20 € für 1 TB oder das Strato HiDrive Start für 3,5 € für 500 GB). Somit summieren sich die monatlichen laufenden Kosten für die vorgesehene Lösung auf 7 - 10 € im Monat.
Absicherung des Servers
Die Basisinstallation des Servers beschreibe ich hier nicht, weil sich das zum Einen von Hoster zu Hoster geringfügig unterscheidet und zum anderen recht selbst erklärend ist. Die folgenden Schritte basieren auf einem Ubuntu Linux Server oder einem Debian, jeweils in aktueller Long Term Service Version. Ich beschreibe hier die Absicherung des Servers. Zur Basisabsicherung des Servers s. hier: https://tutorials.kernke.koeln/sicherheit/server-absichern.html. Wer seinem Provider seine Daten nicht anvertrauen möchte, kann zusätzlich eine verschlüsselte Partition einrichten. Darauf verzichte ich hier, weil ich Angebote von zertifizierten Providern in Deutschland nutze.
Unprivilegierten User anlegen
Wenn man Bootmedien bzw. entsprechende Templates von Providern für das Betriebssystem des Server nutzt, dann wird bei der Basisinstallation oftmals nur ein Root-User angelegt und kein normaler User ohne Administrationsprivilegien. Die folgenden Anweisungen gehen jeweils davon aus, dass ein normaler User genutzt wird. Wenn der noch nicht existiert, muss man sich also erst einmal als root anmelden und einen unprivilegierten User anlegen. Das macht man mit dem Befehl:
adduser --shell /bin/bash [Benutzername]
usermod -aG sudo [Benutzername]
Installation Fail2Ban
Zunächst installiere ich auf Servern, die ins Internet exponiert werden immer fail2ban und schütze das System auf diese Weise vor Brute Force Attacken gegen die SSH-Schnittstelle. Die Installation geht einfach mit dem Befehl
sudo apt install fail2ban
Konfiguration der Firewall(s)
Wenn der Hoster eine Firewall anbietet, die bereits zwischen dem Server und dem Internet arbeitet, dann sollte diese auf jeden Fall genutzt werden. Alles, was abgefangen wird, bevor es überhaupt den Server erreicht, belastet natürlich nicht die Systemressourcen unseres Servers. Aber auch wenn es eine solche Firewall des Hosters gibt, sollte noch eine Firewall auf dem Server eingesetzt werden. IT-Sicherheit basiert immer auf dem Prinzip der möglichst vielen Schichten. Ich verwende die ufw, die uncomplicated firewall. Soweit sie noch nicht auf dem Server installiert ist, hole ich das mit
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw allow 51820
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow from 100.11.0.0/24 to any port 22 proto tcp
sudo ufw allow from 100.11.0.0/24 to any port 8080 proto tcp
sudo ufw allow from 100.11.0.0/24 to any port 8200 proto tcp
sudo ufw allow from 100.11.0.0/24 to any port 445 proto tcp
sudo ufw enable
Konfiguration Fail2Ban für ufw
Wenn die ufw unbefugte Zugriffe auf den Server unterbindet, dann kann man sehr schnell sehen, dass es massenhaft Zugriffsversuche aus dem Netz gibt. Fail2Ban kann diese Zugriffsversuche bereits eine Ebene früher abfangen und die ufw damit entlasten. Dazu definiere ich einen entsprechenden Jail
sudo nano /etc/fail2ban/jail.d/ufw.conf
[ufw-block]
enabled = true
logpath = /var/log/ufw.log
# Der Filter sucht nach dem UFW-Block-Muster in den Log-Zeilen
filter = ufw-block
# Aktionsdefinition: UFW (iptables) zum Blockieren verwenden
banaction = ufw
maxretry = 3 ; Maximale Versuche (UFW-Blocks) in der Findtime
findtime = 600 ; Zeitraum in Sekunden (10 Minuten)
bantime = 3600 ; Dauer der Sperre in Sekunden (1 Stunde)
sudo nano /etc/fail2ban/filter.d/ufw-block.conf
[INCLUDES]
before = common.conf
[Definition]
# Die RegEx sucht nach den typischen Kernel-Log-Einträgen mit UFW BLOCK,
# gefolgt von der Quell-IP-Adresse (SRC=) als Hostname-Feld.
failregex = \[UFW BLOCK\].*SRC=
ignoreregex =
# Fail2Ban action configuration file for ufw
#
# You are required to run "ufw enable" before this will have any effect.
#
# The insert position should be appropriate to block the required traffic.
# A number after an allow rule to the application won't be of much use.
[Definition]
actionstart =
actionstop =
actioncheck =
# ufw does "quickly process packets for which we already have a connection" in before.rules,
# therefore all related sockets should be closed
# actionban is using `ss` to do so, this only handles IPv4 and IPv6.
actionban = if [ -n "<application>" ] && ufw app info "<application>"
then
ufw <add> <blocktype> from <ip> to <destination> app "<application>" comment "<comment>"
else
ufw <add> <blocktype> from <ip> to <destination> comment "<comment>"
fi
<kill>
actionunban = if [ -n "<application>" ] && ufw app info "<application>"
then
ufw delete <blocktype> from <ip> to <destination> app "<application>"
else
ufw delete <blocktype> from <ip> to <destination>
fi
# Option: kill-mode
# Notes.: can be set to ss or conntrack (may be extended later with other modes) to immediately drop all connections from banned IP, default empty (no kill)
# Example: banaction = ufw[kill-mode=ss]
kill-mode =
# intern conditional parameter used to provide killing mode after ban:
_kill_ =
_kill_ss = ss -K dst "[<ip>]"
_kill_conntrack = conntrack -D -s "<ip>"
# Option: kill
# Notes.: can be used to specify custom killing feature, by default depending on option kill-mode
# Examples: banaction = ufw[kill='ss -K "( sport = :http || sport = :https )" dst "[<ip>]"']
# banaction = ufw[kill='cutter "<ip>"']
kill = <_kill_<kill-mode>>
[Init]
# Option: add
# Notes.: can be set to "insert 1" to insert a rule at certain position (here 1):
add = prepend
# Option: blocktype
# Notes.: reject or deny
blocktype = reject
# Option: destination
# Notes.: The destination address to block in the ufw rule
destination = any
# Option: application
# Notes.: application from sudo ufw app list
application =
# Option: comment
# Notes.: comment for rule added by fail2ban
comment = by Fail2Ban after <failures> attempts against <name>
# DEV NOTES:
#
# Author: Guilhem Lettron
# Enhancements: Daniel Black
sudo systemctl restart fail2ban
sudo fail2ban-client status ufw-block
Status for the jail: ufw-block
|- Filter
| |- Currently failed: 34
| |- Total failed: 5551
| `- Journal matches:
`- Actions
|- Currently banned: 20
|- Total banned: 157
`- Banned IP list: 78.128.114.86 106.55.180.162 167.94.138.38 79.124.62.126 79.124.62.134 167.94.138.187 79.124.58.18 167.94.146.49 185.242.226.76 193.163.125.181 206.168.34.199 162.142.125.212 82.156.52.230 167.94.138.196 78.128.114.126 199.45.155.93 206.168.34.198 162.142.125.217 213.209.143.88 167.94.138.43
Installation und Konfiguration der Software
Installation und Konfiguration von Wireguard
Wireguard ist eine leichtgewichtige und effiziente VPN-Lösung. Ein VPN, also ein virtuelles privates Netzwerk stellt eine Möglichkeit dar, innerhalb eines öffentlichen Netzwerks oder sogar über Netzwerkgrenzen hinweg mittels Verschlüsselungstechnologie ein abgeschottetes Netzwerk zu etablieren. Das ist ein starkes Sicherheitsfeature, um Zugriffe auf sensible Dienste und Daten abzusichern. Zunächst installiere ich wireguard mit
sudo apt install wireguard
net.ipv4.ip_forward=1
Dann wechsele ich in den Root-Modus, weil ich in den geschützten Ordner /etc/wireguard wechseln und außerdem mehrere Befehle mit Adminrechten ausführen muss.
sudo su
cd /etc/wireguard
umask 077; wg genkey | tee privatekey | wg pubkey > publickey
cat privatekey
cat publickey
chmod 600 privatekey
nano wg10011.conf
[Interface]
Address = 100.11.0.1/32
SaveConfig = true
PostUp = iptables -A FORWARD -i wg10011 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg10011 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens18 -j MASQUERADE
ListenPort = 51820
PrivateKey = Private Key des Servers
ip a
wg-quick up wg10011
systemctl enable wg-quick@wg1011
Zur Einrichtung des Wireguard-Tunnels auf dem Client wechsele ich in die Linux-Konsole dieses Clients und installiere auch dort, wenn nicht bereits geschehen Wireguard, erstelle das Schlüsselpaar und setze die entsprechenden Dateirechte auf den privaten Schlüssel:
sudo apt install wireguard
sudo su
cd /etc/wireguard
umask 077; wg genkey | tee privatekey | wg pubkey > publickey
cat privatekey
cat publickey
sudo nano /etc/wireguard/wg10011.conf
[Interface]
PrivateKey = {privater Schlüssel des Clients}
Address = 100.11.0.36/32
[Peer]
PublicKey = {öffentlicher Schlüssel des Servers}
Endpoint = {IP-Adresse des Servers}:51820
AllowedIPs = 100.11.0.0/24
PersistentKeepalive = 25
wg-quick up wg10011
systemctl enable wg-quick@wg10011
exit
sudo wg set wg1011 peer {öffentlicher Schlüssel des Clients} allowed-ips 100.11.0.36/32
wg-quick strip wg10011 > /tmp/wg10011_new.conf
wg syncconf wg10011 /tmp/wg10011_new.conf
rm /tmp/wg10011_new.conf
- wg-quick strip: Entfernt die PostUp/PostDown-Befehle für den Export, da wg syncconf nur die reinen WireGuard-Parameter (Keys, IPs) abgleicht.
- wg syncconf: Vergleicht die laufende Konfiguration mit der Datei und fügt neue Peers hinzu oder löscht entfernte, ohne bestehende Tunnel zu trennen.
ping 100.11.0.36
ping 100.11.0.1
sudo ufw status numbered
[xx] 22 ALLOW IN Anywhere
sudo ufw delete 2
sudo ufw enable
ssh -l andreas 100.11.0.1
Installation Docker
Da die mit den gängigen Linux-Distributionen mitgelieferten Docker-Implementierungen nicht immer ganz kompatibel mit den auf Github bereitgestellten Containern ist, installiere ich Docker und Docker Compose aus den Originalquellen. Dazu habe ich mir ein kleines Script geschrieben:
# Shell-Skript zur Installation von Docker (mit sudo ausführen:
#!/bin/bash
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
apt-get remove $pkg;
done
apt-get update
apt-get install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl start docker
systemctl enable docker
usermod -aG docker "$SUDO_USER"
chmod +x installdocker.sh
sudo ./installdocker.sh
docker ps
Installation traefik Reverse Proxy
Der Reverse Proxy traefik wird ganz einfach mit docker und docker-compose installiert. Ich erstelle zunächst einen Ordner traefik und wechsele in selbigen
mkdir traefik && cd traefik
nano docker-compose.yml
services:
traefik:
image: traefik:latest
container_name: traefik
network_mode: host
restart: unless-stopped
command:
# Entrypoints definieren (Ports, auf denen Traefik lauscht)
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.forwardedheaders.trustedIPs=0.0.0.0/0
- --entrypoints.websecure.forwardedheaders.trustedIPs=0.0.0.0/0
- --api.dashboard=true
- --entrypoints.traefik.address=10.11.0.1:8080
- --providers.providersThrottleDuration=0
# Provider: Traefik soll Docker-Labels überwachen
- --providers.docker=true
- --providers.docker.exposedbydefault=false # Nur Container mit 'traefik.enable=true' beachten
- --api.insecure=true
# HTTPS-Zertifikate automatisch von Let's Encrypt holen
- --certificatesresolvers.letsencrypt.acme.email=mail@sechzig-veedel.de
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
ports:
- "8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # Lesezugriff auf Docker-Events
- /home/andreas/traefik/traefik-data:/letsencrypt
volumes:
traefik-data:
Mit "api.dashboard=true" aktiviere ich das Dashboard von trafik und Port 8080, der für das Web-Dashboard von traefik genutzt wird, wird an die IP-Adresse aus dem VPN gebunden.
Dann kann ich traefik starten mit:
docker compose up -d
docker ps
Installation und Konfiguration Paperless-NGX
Auch Paperless-NGX wird mit docker und docker-compose aufgesetzt. Zunächst wechsele ich aus dem Verzeichnis traefik wieder zurück in mein Homeshare
cd ..
mkdir paperless-ngx && cd paperless-ngx
nano docker-compose.env
PAPERLESS_URL=https://paperless.domain.de
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
PAPERLESS_SECRET_KEY='V<_dddxkOW[.l,VF{:%cQAT|$uAIZY)n;MWH/LU?MJ_hBV7HGb%7KhY)+dSethRvWSV'
nano docker-compose.yml
services:
broker:
image: docker.io/library/redis:8
restart: unless-stopped
volumes:
- redisdata:/data
db:
image: docker.io/library/postgres:17
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped
depends_on:
- db
- broker
- gotenberg
- tika
labels:
# 1. Traefik aktivieren
- "traefik.enable=true"
# 2. Router definieren (Definiert die Domain und das Protokoll)
- "traefik.http.routers.paperless.entrypoints=websecure" # Nur über 443
- "traefik.http.routers.paperless.rule=Host(`paperless.domain.de`)" # Ihre Subdomain
- "traefik.http.routers.paperless.tls=true"
- "traefik.http.routers.paperless.tls.certresolver=letsencrypt" # Nutze den oben definierten Resolver
# 3. HTTP-zu-HTTPS-Umleitung (optional, aber empfohlen)
- "traefik.http.routers.paperless-insecure.entrypoints=web" # Lausche auf Port 80
- "traefik.http.routers.paperless-insecure.rule=Host(`paperless.domain.de`)"
- "traefik.http.routers.paperless-insecure.middlewares=redirect-to-https"
# 4. Middleware (Die Weiterleitung selbst)
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.paperless-realip.headers.customrequestheaders.X-Forwarded-For={client.ip}"
- "traefik.http.routers.paperless.middlewares=paperless-realip@docker"
# 5. Service definieren (Wo Traefik die Anfrage hinschicken soll)
- "traefik.http.services.paperless.loadbalancer.server.port=8000"
volumes:
- /home/andreas/paperless-ngx/data:/usr/src/paperless/data
- /home/andreas/paperless-ngx/media:/usr/src/paperless/media
- /home/andreas/paperless-ngx/export:/usr/src/paperless/export
- /home/andreas/paperless-ngx/consume:/usr/src/paperless/consume
env_file: docker-compose.env
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
TRUSTED_PROXIES: "172.18.0.0/16" # das Docker-Subnetz
USE_X_FORWARDED_FOR: True
PAPERLESS_ALLOWED_HOSTS: '*'
PAPERLESS_LOG_IP_ADDRESS_HEADER: X-Forwarded-For
PAPERLESS_LOGLEVEL: DEBUG
gotenberg:
image: docker.io/gotenberg/gotenberg:8.20
restart: unless-stopped
command:
- "gotenberg"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: docker.io/apache/tika:latest
restart: unless-stopped
volumes:
pgdata:
redisdata:
Die gemounteten Volumes beginnen hier immer mit "/home/andreas/paperless-ngx". Ein symbolischer Pfad auf das Home-Share ("~/) würde im Docker-Container nicht aufgelöst werden können. Wenn der Nutzer, in dessen Kontext das Ganze läuft, nicht "andreas" heißt, dann muss das natürlich angepasst werden.
Unter Environment sind noch die Parameter
TRUSTED_PROXIES: "172.18.0.0/16" # das Docker-Subnetz
USE_X_FORWARDED_FOR: True
PAPERLESS_ALLOWED_HOSTS: '*'
PAPERLESS_LOG_IP_ADDRESS_HEADER: X-Forwarded-For
PAPERLESS_LOGLEVEL: DEBUG
Die restlichen Parameter sind Standard. Diese Konfiguration kann dann ebenfalls mit
docker compose up -d
docker ps
docker logs paperless-ngx-webserver-1
Absicherung Paperless-NGX mit Fail2Ban
Noch ist der Server nicht vor Brute Force Attacken, also dem massenhaften, ggf. automatisierten Versuch, Benutzernamen und Passworte zu erraten, gesichert. Einen wirksamen Schutz vor Brute Force Attacken bieten eine Multifaktor-Authentifizierung, also die Anmeldung an dem System mit mindestens zwei Authentifizierungsfaktoren, nach dem Prinzip "Kennen und Haben". Allerdings muss das im jeweiligen Anwender*innen-Kontext eingerichtet werden und man kann die Anwender*innen nicht zwingen, das zu tun. Darum ist meines Erachtens zusätzlich eine serverseitige Absicherung erforderlich. Das bietet fail2ban. Ganz am Anfang hatte ich ja fail2ban installiert, ohne weitere Konfigurationen daran vorzunehmen. Auf diese Weise sichert fail2ban zunächst die SSH-Schnittstelle gegen Brute Force Attacken ab, aber nicht mehr. Nach der Firewall-Konfiguration habe ich fail2ban auf die ufw-Protokolle angesetzt und nun setze ich es auf die Anmeldung an Paperless-NGX an. Paperless-NGX speichert Logfiles in der Datei .../data/log/paperless.log. Das data-Verzeichnis habe ich in der docker-compose.yml auf den Pfad /home/andreas/paperless-ngx gemounted. Ich erstelle nun eine Filterdatei, die dieses Logfile auswertet und dort verzeichnete fehlerhafte Anmeldeversuche erkennt. Dazu erstelle ich die Datei /etc/fail2ban/filter.d/paperless-block.conf
sudo nano /etc/fail2ban/filter.d/paperless-block.conf
[INCLUDES]
before = common.conf
[Definition]
# Paperless-Zeitstempel sind nicht nötig
datepattern =
# Regex für fehlgeschlagene Logins
failregex = \[paperless\.auth\]\s*Login failed for user `.*` from private IP ``\.
ignoreregex =
sudo nano /etc/fail2ban/jail.d/paperless.conf
[paperless]
enabled = true
filter = paperless-block
# Verwende die Multiport-Action für Container-Netz
banaction = paperless-reject
# Ports innerhalb des Docker-Netzes blockieren, Host bleibt unberuehrt
port = 443
# Logfile, das vom Host erreichbar ist
logpath = /home/andreas/paperless-ngx/data/log/paperless.log
backend = polling
# auch hier IPs vom Bann ausschliessen
ignoreip =
maxretry = 3
findtime = 600
bantime = 3600
Der Parameter logpath ist selbsterklärend. Wichtig ist danach der Parameter "backend = polling". Der bewirkt, dass tatsächlich dieses Logfile ausgewertet wird und nicht irgendwelche Systemprotokolle. Dann könnte ich auch hier noch einmal IP-Adressen vom Blockieren ausschließen. Der Parameter maxretry definiert natürlich die Anzahl der erlaubten Versuche. Findtime definiert den Suchzeitraum in Millisekunden, also den Zeitraum innerhalb dessen die Fehlerversuche gezählt werden. Hier habe ich mit 600 eine Minute definiert. Wenn ich innerhalb einer Minute dreimal versuche, mich mit falschen Credentials anzumelden, werde ich gesperrt. Bots, die durch das Netz schwirren, unternehmen sehr viele Zugriffsversuche in sehr kurzer Zeit und werden schnell erkannt. Wenn ich mich beim Anmelden zweimal vertippt habe, dann warte ich eine Minute und kann gefahrlos einen dritten Versuch unternehmen. Die bantime, also die Zeitspanne einer Sperrung beträgt hier 3600 Millisekunden, also eine Stunde. Diese Werte kann man nach Belieben ändern.
Nun muss ich aber auch noch die Banaction definieren. Das mache ich in der Datei /etc/fail2ban/action.d/docker-iptables.conf
sudo nano /etc/fail2ban/action.d/paperless-reject.conf
[Definition]
actionstart =
actionstop =
actioncheck =
# Nutze die INPUT Chain für den Host-Modus, um den Traffic am Host abzufangen
# Der REJECT sorgt für eine sofortige Ablehnung.
actionban = iptables -I INPUT -p tcp -s --dport 443 -j REJECT --reject-with icmp-port-unreachable
actionunban = iptables -D INPUT -p tcp -s --dport 443 -j REJECT --reject-with icmp-port-unreachable
sudo systemctl restart fail2ban
sudo fail2ban-client status paperless
Scannen
Der Consume-Ordner der Paperless-Installation ist nur über SFTP und nur innerhalb des VPN erreichbar. Mit gängigen Scannern wird man wohl nicht einrichten können, dass in dieser Weise in den Consume-Ordner gescannt wird. Der beste Weg, direkt in das System zu scannen, ist daher Scan2Email am Scanner einzurichten und in Paperless-NGX entsprechende eMailkontoeinstellungen und eMailregeln zu definieren. Wenn der Scanner aber nicht in der Lage ist, an eine eMail-Adresse zu scannen, dann muss man über einen Umweg in den consume-Ordner scannen, wenn man die Digitalisate nicht zu Fuß übertragen möchte. Hierzu gebe ich den consume-Ordner mittels Samba frei. Dann mounte ich den freigegebenen Ordner an meinem Client und gebe diesen wiederum im Netzwerk frei. Das ist zugegebenermaßen kein besonders eleganter Weg. Aber er funktioniert und ein besserer fällt mir nicht ein.
Samba kann ganz einfach mit dem Befehl
sudo apt install samba
sudo smbpasswd -a [username]
sudo nano /etc/samba/smb.conf
[scans]
path = /home/[username]/paperless-ngx/consume
read only = no
guest ok = no
sudo systemctl daemon-reload
sudo systemctl restart smbd.service
sudo nano /etc/fstab
//100.11.0.1/scans /home/andreas/scans cifs _netdev,x-systemd.requires=wg-quick@wg10011.service,credentials=/pfad/zu/deiner/.smbcredentials,uid=1000,gid=1000 0 0
sudo nano /root/.smbcredentials
username=[username]
password=[smb-password]
sudo chown root:root /root/.smbcredentials
sudo chmod 600 /root/.smbcredentials
mkdir scans
sudo apt install cifs-utils
sudo mount scans
sudo mount -a
sudo nano /etc/samba/smb.conf
[scans] path = /home/[username]/scans
read only = no
guest ok = no
sudo systemctl daemon-reload
sudo systemctl restart smbd.service
Datensicherung
Wenn ich dem System Dokumente anvertraue, die ich vielleicht ansonsten nicht mehr aufbewahre, dann muss ich mich natürlich um eine zuverlässige Datensicherung kümmern. Hierfür bietet sich das Open Source Tool Duplicati an. Es lässt sich bequem über Docker installieren und bietet verschlüsselte, inkrementelle Backups auf verschiedenste Sicherungsziele an. Die Sicherungen auf dem selben System abzulegen, auf dem auch schon die Daten liegen, ist natürlich komplett sinnfrei. Daher brauche ich ein externes Sicherungsziel. Hier nutze ich, wie eingangs erklärt, Speicherlösungen bei Hosting-Anbietern. Die Einrichtung der Speichers bei externen Hostern führe ich hier nicht aus, weil es viele verschiedene Anbieter und Konfigurationsoberflächen gibt. Im Ergebnis setze ich im Folgenden voraus, dass es einen Speicher gibt, der per SFTP (SSH) angesprochen werden kann.
Export der Inhalte und Metadaten
Paperless-NGX bietet mit dem document-exporter eine komfortable Möglichkeit, alle Dokumente, Datenbankinhalte und Strukturen zu exportieren. Das Ergebnis dieses Exports kann dann gesichert werden und damit ist auch eine Wiederherstellung des Systems jederzeit möglich. Der document-exporter wird mit
docker exec -it paperless-ngx-webserver-1 document_exporter /usr/src/paperless/export
crontab -e
no crontab for andreas - using an empty one
Select an editor. To change later, run 'select-editor'.
1. /bin/nano <---- easiest
2. /usr/bin/vim.basic
3. /usr/bin/vim.tiny
4. /bin/ed
Choose 1-4 [1]: 1
0 7 * * * docker exec -it paperless-ngx-webserver-1 document_exporter /usr/src/paperless/export
Duplicati installieren
Auch Duplicati installiere ich per Docker und Docker-Compose. Dazu erstelle ich ein Verzeichnis duplicati und wechsele in selbiges:
mkdir ~/duplicati && cd ~/duplicati
nano docker-compose.yml
services:
duplicati:
image: lscr.io/linuxserver/duplicati:latest
container_name: duplicati
environment:
- PUID=0
- PGID=0
- TZ=Europe/Berlin
- SETTINGS_ENCRYPTION_KEY=strenggeheimerundkomplexerkey
- DUPLICATI__WEBSERVICE_PASSWORD=strenggeheimesundkomplexespasswort
volumes:
- ./config:/config
- /home/andreas/paperless-ngx/export:/paperless-export
- /home/andreas/sicherungsverzeichnis1:/source1
ports:
- 10.11.0.1:8200:8200
restart: unless-stopped
Hier habe ich das Exportverzeichnis von Paperless-NGX als Volume gemountet und zusätzlich ein Verzeichnis namens "sicherungsverzeichnis1". Hier kann man beliebige Verzeichnisse angeben, die dann später von der Weboberfläche aus und für den Dockercontainer erreichbar sind. Das zweite Verzeichnis habe ich angegeben, damit ich später aus duplicati heraus einzelne Files an anderer Stelle zurücksichern kann, als dem Ursprungspfad. Mit der Angabe unter "ports" binde ich die Duplicati-Weboberfläche vor allem an die VPN-Adresse.
Duplicati starte ich nun mit
docker compose up -d
Sicherung in Duplicati einrichten
Wenn ich mich an der Weboberfläche von Duplicati anmelde, dann moniert mein Browser natürlich die unsichere Verbindung. Das kann ich aber getrost ignorieren, weil ich mich ja innerhalb meines VPN bewege. Der Dialog für das Hinzufügen eines neuen Backup-Jobs ist größtenteils selbst erklärend.
Bei den Backup-Destinationen wähle ich natürlich SSH aus. Auf der nächsten Seite muss ich dann die Zugangsdaten für den externen Speicher eingeben. Bei einer Storage-Box von Hetzner ist das z.B. accountname.your-storagebox.de, ansonsten könnte das eine IP-Adresse eines entfernten Servers oder auch eine Domain sein. Der Standard-Port für SSH ist 22, bei der Storage-Box von Hetzner ist es 23. Auf der Firewall habe ich ausgehenden Traffik nicht begrenzt. Darum brauche ich mir keine Gedanken zu machen, ob ich einen Port auf der Firewall öffnen muss. Ordnerpfad ist natürlich der Pfad, in den auf dem entfernten System gesichert werden soll. Den hier angegebenen Ordner muss es im Ziel schon geben, sonst läuft die Sicherung auf einen Fehler. Das war in früheren Versionen von Duplicati noch anders. Da wurde man dann gefragt, ob der Ordner angelegt werden soll.. Username ist natürlich der Username in dessen Berechtigungskontext auf dem Zielsystem die Sicherungen abgelegt werden sollen.
Die Autentifizierungsmethode wählt man mit "Erweiterte Option hinzufügen" aus. Hier reicht eine Authentifizierung mit Passwort aus. Duplicati unterstützt auch SSH-Key-Authentifizierung. Allerdings werden nur Schlüssel im PEM-Format erlaubt und die Einbindung ist auch recht kompliziert. Da die Passwortübertragung verschlüsselt erfolgt, mache ich es mir einfach und wähle Passwortauthentifizierung aus. Neben dem Passwort prüft Duplicati den Fingerprint der Gegenseite. Den erwartet es als MD5-Hash. Da es je nach Hosting-Anbieter nicht ganz einfach ist, den Fingerprint der Gegenseite im MD5-Format zu bekommen, wende ich einen kleinen Trick an. Ich füge zunächst unter "Erweiterte Option hinzufügen" "SSH Fingerprint" hinzu und schreibe in das entsprechende Feld irgend etwas hinein. Später, wenn ich mit diesen unsinnigen Daten zu sichern versuche, erhalte ich eine Fehlermeldung, in der mir der korrekte Fingerprint angezeigt wird. Dazu komme ich später noch. Ich klicke also nicht auf "Test destination", sondern erst einmal auf Weiter. Hier wähle ich die zu sichernden Daten aus. In meinem Fall habe ich ja den Export-Ordner von Paperless-NGX in das Verzeichnis "source" gemountet und darum wähle ich das natürlich aus.
Die Parameter auf der Seite Zeitplan sind selbsterklärend. Ich stelle meine Sicherungen auf z.B. 8:00 Uhr morgens ein, weil der Export ja jeden Morgen um 7:00 Uhr läuft. Auf der Seite Optionen brauche ich bei der Zielvolumengröße nichts zu ändern. Interessanter sind die Retention-Regeln, die ich unter "Sicherungsaufbewahrung" einstellen kann. Alle Sicherungen immer aufzubewahren ist natürlich nicht sinnvoll.
Fazit
Nun habe ich also einen Server bei einem Hoster, auf dem Paperless-NGX läuft. Die Weboberfläche von Paperless-NGX ist aus dem Internet über eine (Sub-)Domain erreichbar und mit einem gültigen SSL-Zertifikat von Let's Encrypt abgesichert. Die SSL-Absicherung und die Verbindung mit dem Zertifikat werden durch einen Reverse-Proxy besorgt. Paperless-NGX ist ausschließlich über den Reverse-Proxy ansprechbar. Somit kann die Absicherung mit SSL niemand umgehen. Die Remote-Konsole des Servers sowie die Weboberflächen des Reverse Proxys und von Duplicati sind ausschließlich über das VPN erreichbar. Die Firewall blockiert unerlaubte Zugriffe aus dem Netz und fail2ban sperrt IP-Adressen aus, wenn sie mehr als dreimal versuchen, mit falschen Zugangsdaten auf die Remoteshell oder Paperless-NGX zuzugreifen. Die Datensicherung erfolgt auf ein System bei einem anderen Hoster, also auch in einem anderen Rechenzentrum. Die Sicherungen finden verschlüsselt, komprimiert und inkrementell statt. Auch der Übertragungsweg der Sicherungsdaten ist verschlüsselt.
Damit habe ich ein System geschaffen, das eine sichere und zuverlässige Verarbeitung meiner Dokumente ermöglicht, ohne dass ich zuhause Server dafür betreiben muss. Die laufenden Kosten für die beiden Betreiber belaufen sich auf knapp 10,00 Euro im Monat. Die in der Einleitung genannten Ziele sind alle erreicht.