docs: Installationsanleitung, gehärtete systemd-Unit und Beispielkonfiguration
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
This commit is contained in:
parent
2d223c5823
commit
4b287f9b1b
4 changed files with 633 additions and 0 deletions
121
deploy/config.example.yaml
Normal file
121
deploy/config.example.yaml
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Beispielkonfiguration für das VPN-Konfig-Portal.
|
||||
#
|
||||
# Diese Datei enthält Zugangsdaten. Rechte setzen:
|
||||
# chown root:vpnportal /etc/vpnportal/config.yaml
|
||||
# chmod 640 /etc/vpnportal/config.yaml
|
||||
# Das Portal startet nicht, wenn die Rechte weiter gefasst sind.
|
||||
#
|
||||
# Die Datei wird strikt gelesen: ein Tippfehler in einem Feldnamen führt zum
|
||||
# Startabbruch mit klarer Meldung. Prüfen mit:
|
||||
# vpnportal check --config /etc/vpnportal/config.yaml
|
||||
|
||||
portal:
|
||||
# Adresse, auf der das Portal lauscht. HTTPS ist Pflicht.
|
||||
listen: "0.0.0.0:8443"
|
||||
|
||||
# Serverzertifikat und privater Schlüssel des Portals.
|
||||
# Der Schlüssel muss 0600 sein, sonst startet das Portal nicht.
|
||||
tls_cert: "/etc/vpnportal/portal.crt"
|
||||
tls_key: "/etc/vpnportal/portal.key"
|
||||
|
||||
# Lebensdauer einer Anmeldung. Absolut, nicht verlängerbar. Default: 10m
|
||||
session_ttl: "10m"
|
||||
|
||||
# White-Label: Titel im Browser-Tab und in der Kopfzeile.
|
||||
title: "VPN-Portal Firma GmbH"
|
||||
|
||||
# Optionales Logo in der Kopfzeile (PNG oder SVG).
|
||||
# logo_file: "/etc/vpnportal/logo.png"
|
||||
|
||||
# Wird angezeigt, wenn für einen Benutzer kein Zertifikat hinterlegt ist.
|
||||
support_contact: "it-helpdesk@firma.de"
|
||||
|
||||
# Optionaler Versionscheck. Default: false.
|
||||
# Eingeschaltet baut das Portal beim Start eine ausgehende HTTPS-Verbindung
|
||||
# zur Release-URL auf ("telefoniert nach Hause"). Read-only, kein Auto-Update.
|
||||
# update_check: false
|
||||
|
||||
opnsense:
|
||||
url: "https://fw01.firma.local"
|
||||
|
||||
# API-Zugangsdaten eines dedizierten Benutzers, der AUSSCHLIESSLICH das
|
||||
# Privileg "VPN: OpenVPN Client Export" hat. Niemals einen Admin-Key.
|
||||
api_key: "BITTE-ERSETZEN"
|
||||
api_secret: "BITTE-ERSETZEN"
|
||||
# alternativ als Datei (kompatibel mit systemd LoadCredential, Ansible Vault):
|
||||
# api_key_file: "/run/credentials/vpnportal.service/opnsense-api-key"
|
||||
# api_secret_file: "/run/credentials/vpnportal.service/opnsense-api-secret"
|
||||
# oder als Umgebungsvariable:
|
||||
# VPNPORTAL_OPNSENSE_API_KEY, VPNPORTAL_OPNSENSE_API_SECRET
|
||||
# Rangfolge: Umgebungsvariable > *_file > Wert in dieser Datei.
|
||||
|
||||
# Interne CA, falls die Firewall ein eigenes Zertifikat verwendet.
|
||||
# ca_file: "/etc/vpnportal/firma-ca.pem"
|
||||
|
||||
# NUR für Tests: deaktiviert die TLS-Prüfung zur Firewall.
|
||||
# Erzeugt bei jedem Start eine Warnung. Für LDAP gibt es das bewusst nicht.
|
||||
# insecure_skip_verify: false
|
||||
|
||||
ad:
|
||||
# AD-Domäne. Der Base-DN wird daraus abgeleitet (DC=firma,DC=local).
|
||||
domain: "firma.local"
|
||||
|
||||
# Optionaler Override, z. B. um auf eine OU einzuschränken.
|
||||
# base_dn: "OU=Mitarbeiter,DC=firma,DC=local"
|
||||
|
||||
# Domain Controller mit Failover in dieser Reihenfolge.
|
||||
# HOSTNAMEN, keine IP-Adressen: das LDAPS-Zertifikat wird gegen den
|
||||
# Hostnamen geprüft. IP-Adressen werden abgelehnt.
|
||||
servers:
|
||||
- "dc01.firma.local"
|
||||
- "dc02.firma.local"
|
||||
|
||||
# LDAPS ist Default. Für StartTLS auf 389:
|
||||
# port: 389
|
||||
# tls_mode: "starttls"
|
||||
# port: 636
|
||||
# tls_mode: "ldaps"
|
||||
|
||||
# Unprivilegierter Domain-Benutzer genügt — er muss nur lesen dürfen.
|
||||
bind_user: "svc-vpnportal@firma.local"
|
||||
bind_password: "BITTE-ERSETZEN"
|
||||
# alternativ: bind_password_file oder VPNPORTAL_AD_BIND_PASSWORD
|
||||
# bind_password_file: "/run/credentials/vpnportal.service/ad-bind-password"
|
||||
|
||||
# Nur Mitglieder dieser Gruppe erhalten Zugang. Verschachtelte Gruppen
|
||||
# werden aufgelöst. Der Gruppen-DN wird beim Start ermittelt und gecacht.
|
||||
vpn_group: "VPN-Users"
|
||||
|
||||
# Interne CA für LDAPS. Für LDAP gibt es keine Insecure-Option.
|
||||
# ca_file: "/etc/vpnportal/firma-ca.pem"
|
||||
|
||||
# Zeitlimit pro Domain Controller, danach wird der nächste versucht.
|
||||
# timeout: "8s"
|
||||
|
||||
matching:
|
||||
# Wie der Zertifikats-CN dem Benutzer zugeordnet wird.
|
||||
# {username} ist der aus dem AD gelesene sAMAccountName in Kleinschreibung.
|
||||
# Der Vergleich ignoriert Groß-/Kleinschreibung.
|
||||
cn_pattern: "{username}"
|
||||
# Beispiel für Bestandsumgebungen mit E-Mail-artigen CNs:
|
||||
# cn_pattern: "{username}@firma.de"
|
||||
|
||||
# Notnagel für Bestandsumgebungen, die sich nicht in ein Template fassen
|
||||
# lassen. Schließt cn_pattern aus. {username} wird vor dem Kompilieren
|
||||
# quotiert eingesetzt.
|
||||
# ACHTUNG: eine zu weit gefasste Regex kann fremde Zertifikate zuordnen.
|
||||
# cn_regex: "^(vpn-)?{username}(-[0-9]+)?$"
|
||||
|
||||
logging:
|
||||
# debug, info, warn, error. Im Normalbetrieb NICHT debug: auf debug werden
|
||||
# die verglichenen CNs protokolliert.
|
||||
level: "info"
|
||||
|
||||
# Das Audit-Log enthält personenbezogene Daten. Verzeichnis höchstens 0750,
|
||||
# Datei wird mit 0600 angelegt.
|
||||
audit_log: "/var/log/vpnportal/audit.log"
|
||||
|
||||
# Eingebaute größenbasierte Rotation (schont SD-Karten).
|
||||
max_size_mb: 50
|
||||
max_backups: 5
|
||||
compress: true
|
||||
65
deploy/vpnportal.service
Normal file
65
deploy/vpnportal.service
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
[Unit]
|
||||
Description=VPN-Konfig-Portal für OPNsense
|
||||
Documentation=file:/usr/share/doc/vpnportal/README.md
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/vpnportal serve --config /etc/vpnportal/config.yaml
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
# Dedizierter, zustandsloser Dienstbenutzer. Das Portal hält keinen Zustand
|
||||
# auf der Platte außer dem Audit-Log, deshalb genügt DynamicUser.
|
||||
# Alternativ einen festen Systembenutzer anlegen und User=/Group= setzen —
|
||||
# siehe README, Abschnitt "Installation".
|
||||
DynamicUser=yes
|
||||
|
||||
# Audit-Log: systemd legt /var/log/vpnportal an und übergibt es dem Dienst.
|
||||
# In der config.yaml dann: audit_log: "/var/log/vpnportal/audit.log"
|
||||
LogsDirectory=vpnportal
|
||||
LogsDirectoryMode=0750
|
||||
|
||||
# Secrets werden nicht als Umgebungsvariablen übergeben, sondern als Dateien
|
||||
# unter $CREDENTIALS_DIRECTORY. In der config.yaml dann z. B.:
|
||||
# ad:
|
||||
# bind_password_file: "/run/credentials/vpnportal.service/ad-bind-password"
|
||||
# opnsense:
|
||||
# api_secret_file: "/run/credentials/vpnportal.service/opnsense-api-secret"
|
||||
LoadCredential=ad-bind-password:/etc/vpnportal/secrets/ad-bind-password
|
||||
LoadCredential=opnsense-api-secret:/etc/vpnportal/secrets/opnsense-api-secret
|
||||
|
||||
# ---------- Härtung ----------
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectClock=yes
|
||||
ProtectProc=invisible
|
||||
ProcSubset=pid
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
RestrictAddressFamilies=AF_INET AF_INET6
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallFilter=~@privileged @resources
|
||||
CapabilityBoundingSet=
|
||||
ReadOnlyPaths=/etc/vpnportal
|
||||
UMask=0077
|
||||
|
||||
# Soll das Portal direkt auf 443 lauschen, stattdessen:
|
||||
# AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
# Besser: auf 8443 lauschen lassen und davor per nftables umleiten.
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Add table
Add a link
Reference in a new issue