Quelltext des aktuellen Standes
# Externe Erreichbarkeit
> Anleitung zur sicheren Bereitstellung von OpenNIT für externen Zugriff
---
## Inhaltsverzeichnis
- [Übersicht](#übersicht)
- [Sicherheitspflicht-Checkliste](#sicherheitspflicht-checkliste)
- [Architekturoptionen](#architekturoptionen)
- [HTTPS / TLS konfigurieren](#https--tls-konfigurieren)
- [Rate Limiting aktivieren](#rate-limiting-aktivieren)
- [MFA erzwingen](#mfa-erzwingen)
- [Reverse Proxy konfigurieren](#reverse-proxy-konfigurieren)
- [Firewall-Regeln](#firewall-regeln)
- [Admin-Bereich schützen](#admin-bereich-schützen)
- [Azure AD Single Sign-On](#azure-ad-single-sign-on)
- [Monitoring & Alerting](#monitoring--alerting)
- [VPN-Alternative](#vpn-alternative)
---
## Übersicht
OpenNIT kann sicher extern erreichbar gemacht werden – also über das Internet zugänglich. Die Anwendung ist grundsätzlich dafür ausgelegt (HTTPS-Unterstützung, OAuth2, MFA). Ohne die richtigen Konfigurationsmaßnahmen entstehen jedoch vermeidbare Risiken.
**Empfohlene Architektur:**
```
Internet
│
▼
[Firewall / WAF] → Nur Port 443 nach außen
│
▼
[Reverse Proxy] → Nginx/Apache mit Rate Limiting, TLS Termination
│
▼
[PHP-FPM / Applikation] → Nur localhost
│
▼
[MySQL/MariaDB] → Nur localhost, kein Remote-Zugriff
│
▼
[Storage] → Außerhalb webroot
```
---
## Sicherheitspflicht-Checkliste
Die folgenden Maßnahmen sind **verpflichtend** vor Aktivierung des externen Zugriffs:
- [ ] **HTTPS** mit gültigem TLS-Zertifikat (kein self-signed für Produktion)
- [ ] **`SESSION_SECURE=true`** in `.env`
- [ ] **`APP_DEBUG=false`** in `.env`
- [ ] **`install.php`** aus `public/` entfernt
- [ ] **Rate Limiting** aktiviert
- [ ] **MFA** für alle Benutzer erzwungen
- [ ] **Firewall**: nur Port 443 von außen offen
- [ ] **Datenbank**: kein externer Zugriff
- [ ] **Storage-Pfad**: außerhalb des Webroots
---
## Architekturoptionen
### Option 1: Direkter Server (einfachste Variante)
```
Internet → Server (HTTPS:443) → PHP-Anwendung
```
Geeignet für: Kleine Teams, einfache Setups.
Vorteile: Wenig Konfigurationsaufwand.
Nachteile: Kein dedizierter Reverse Proxy, weniger Flexibilität.
### Option 2: Reverse Proxy (empfohlen)
```
Internet → Nginx/Apache (Reverse Proxy) → PHP-FPM (localhost)
```
Geeignet für: Produktive Umgebungen.
Vorteile: Rate Limiting, Caching, Header-Manipulation, separates Logging.
### Option 3: Nur via VPN (sicherste Variante)
```
Internet → VPN-Gateway → Internes Netz → Server
```
Geeignet für: Maximale Sicherheitsanforderungen.
Vorteile: Kein direkter Internetzugang, minimale Angriffsfläche.
Nachteile: VPN-Client für alle Nutzer notwendig.
---
## HTTPS / TLS konfigurieren
### Let's Encrypt (Empfehlung für öffentliche Server)
```bash
# Certbot installieren
sudo apt install certbot python3-certbot-nginx
# Zertifikat ausstellen (setzt laufenden Nginx voraus)
sudo certbot --nginx -d docs.example.com
# Automatische Erneuerung (läuft bereits via systemd timer)
sudo systemctl status certbot.timer
# Manuelle Erneuerung testen
sudo certbot renew --dry-run
```
### TLS-Härtung (Nginx)
```nginx
# Starke TLS-Konfiguration
ssl_protocols TLSv1.2 TLSv1.3;
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;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
```
### Bewertung testen
- SSL Labs: https://www.ssllabs.com/ssltest/ (Ziel: A oder A+)
- Mozilla Observatory: https://observatory.mozilla.org/
---
## Rate Limiting aktivieren
### Option A: In der Anwendung (`.env`)
```ini
# .env
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=60
```
Und in `config/security.php`:
```php
'rate_limit' => [
'enabled' => true, // ← Von false auf true ändern
'requests' => (int) ($_ENV['RATE_LIMIT_REQUESTS'] ?? 100),
'window' => (int) ($_ENV['RATE_LIMIT_WINDOW'] ?? 60),
...
],
```
### Option B: Im Nginx Reverse Proxy (robuster)
```nginx
# Rate Limiting Zonen definieren (in nginx.conf http-Block)
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=general:10m rate=100r/m;
# In der Server-Konfiguration:
server {
# Login-Endpunkt stark begrenzen
location = /auth/login {
limit_req zone=login burst=5 nodelay;
limit_req_status 429;
try_files $uri $uri/ /index.php?$query_string;
}
# API-Endpunkte
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
try_files $uri $uri/ /index.php?$query_string;
}
# Allgemein
location / {
limit_req zone=general burst=50 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
}
```
> **Hinweis – Asset-Sync-Endpoint:** `POST /api/assets/sync` (PowerShell-Agent)
> bringt bereits einen eigenen, anwendungsseitigen Schutz mit: Bearer-Token,
> Timestamp-Prüfung, Replay-Schutz und ein pro API-Key konfigurierbares
> Rate-Limit (Admin → Asset-Sync, Default 300/Minute). Bei großen Flotten, die
> sich einen Key teilen, dieses Limit anheben – nicht das Nginx-`api`-Limit zu
> niedrig setzen, sonst werden legitime Clients blockiert.
>
> Ist das **Software-Inventar** aktiv und die Erfassung über den Agenten
> eingeschaltet, meldet derselbe Endpunkt zusätzlich die installierten
> Programme (Feld `software`). Die Zahl der Einträge je Gerät ist begrenzt;
> gelesen wird ausschliesslich, was maschinenweit installiert ist (HKLM) –
> nicht, was einzelne Anmeldenamen für sich installiert haben.
---
## MFA erzwingen
### TOTP (Google Authenticator / Authy)
1. Admin-Oberfläche aufrufen: `/admin/settings`
2. Sicherheitseinstellungen → "MFA für alle Benutzer erzwingen" aktivieren
3. Bestehende Benutzer werden beim nächsten Login zur MFA-Einrichtung aufgefordert
### WebAuthn/FIDO2 (Hardware-Sicherheitsschlüssel)
1. Admin-Oberfläche: `/admin/settings` → WebAuthn aktivieren
2. Benutzer können Hardware-Keys (YubiKey, etc.) registrieren
3. Empfohlen für Admin-Konten
### Empfohlene MFA-Richtlinie
| Benutzertyp | Empfehlung |
|-------------|------------|
| Administratoren | WebAuthn (Hardware-Key) oder TOTP |
| Redakteure (extern) | TOTP Pflicht |
| Lesezugriff (extern) | TOTP empfohlen |
| Nur intern (VPN) | Optional |
---
## Reverse Proxy konfigurieren
### Vollständige Nginx-Konfiguration mit Sicherheitshärtung
```nginx
# /etc/nginx/sites-available/it-docs
# Rate Limiting Zonen
limit_req_zone $binary_remote_addr zone=it_docs_login:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=it_docs_general:10m rate=200r/m;
# HTTP → HTTPS Weiterleitung
server {
listen 80;
server_name docs.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name docs.example.com;
root /var/www/it-docs/public;
index index.php;
# TLS
ssl_certificate /etc/letsencrypt/live/docs.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/docs.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# Sicherheitsheader
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Server-Info verstecken
server_tokens off;
more_clear_headers Server;
# Allgemeines Rate Limiting
limit_req zone=it_docs_general burst=100 nodelay;
# URL-Rewriting
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Login: strenge Rate-Begrenzung
location ~ ^/auth/(login|callback) {
limit_req zone=it_docs_login burst=3 nodelay;
limit_req_status 429;
try_files $uri $uri/ /index.php?$query_string;
}
# PHP
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
# PHP-Fehler nicht an Browser senden
fastcgi_param PHP_VALUE "display_errors=0";
# Timeouts
fastcgi_read_timeout 60;
fastcgi_send_timeout 60;
}
# Sensible Verzeichnisse sperren
location ~ ^/(src|config|database|bin|templates|storage)/ {
deny all;
return 403;
}
# .env und andere Dotfiles sperren
location ~ /\. {
deny all;
return 403;
}
# Static Assets cachen
location ~* \.(css|js|png|jpg|gif|ico|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
# Logs
access_log /var/log/nginx/it-docs-access.log combined;
error_log /var/log/nginx/it-docs-error.log warn;
}
```
---
## Firewall-Regeln
### UFW (Ubuntu Firewall)
```bash
# Standard: alles blockieren
sudo ufw default deny incoming
sudo ufw default allow outgoing
# SSH nur von bekannten IPs (eigene IP anpassen)
sudo ufw allow from 203.0.113.10 to any port 22 comment "SSH Admin"
# HTTPS von überall (externe Erreichbarkeit)
sudo ufw allow 443/tcp comment "HTTPS"
# HTTP für Let's Encrypt Renewal
sudo ufw allow 80/tcp comment "HTTP (Let's Encrypt)"
# Firewall aktivieren
sudo ufw enable
sudo ufw status verbose
```
> **Wichtig:** Port 3306 (MySQL), 9000 (PHP-FPM) und 8080 dürfen **nicht** von außen erreichbar sein.
### iptables (Alternativ)
```bash
# Nur Port 443 und 22 öffnen
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.10 -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -j DROP
```
---
## Admin-Bereich schützen
### IP-Whitelist in der Anwendung
`config/security.php`:
```php
'admin_ip_whitelist' => [
'enabled' => true, // ← Aktivieren
'ips' => [
'10.0.0.0/8', // Internes Netz
'192.168.0.0/16', // Lokales Netz
'203.0.113.10', // Admin-Home-IP (VPN-Exit)
],
],
```
### Zusätzlich: Nginx-Level-Schutz
```nginx
# Admin-Bereich auf interne IPs beschränken
location /admin {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
allow 203.0.113.10;
deny all;
try_files $uri $uri/ /index.php?$query_string;
}
```
---
## Azure AD Single Sign-On
Für Unternehmen mit Microsoft Azure AD (Entra ID) bietet sich SSO an:
### Azure App-Registrierung
1. Azure Portal → "App-Registrierungen" → "Neue Registrierung"
2. Name: "OpenNIT"
3. Redirect URI: `https://docs.example.com/auth/callback`
4. Client-Secret erstellen
5. API-Berechtigung hinzufügen: `openid`, `profile`, `email`, `User.Read`
### `.env`-Konfiguration
```ini
AZURE_TENANT_ID=<Ihre-Tenant-ID>
AZURE_CLIENT_ID=<App-Client-ID>
AZURE_CLIENT_SECRET=<Client-Secret>
AZURE_REDIRECT_URI=https://docs.example.com/auth/callback
```
### Vorteile von Azure AD SSO
- Benutzer verwenden ihre bestehenden Unternehmens-Credentials
- MFA bereits durch Azure AD Policy erzwungen (keine separate Konfiguration)
- Automatische Benutzersperrung bei Ausscheiden (Azure AD → App)
- Conditional Access Policies nutzbar (Geräte-Compliance, Standort)
---
## Monitoring & Alerting
### Fail2Ban (Brute-Force-Schutz)
```bash
sudo apt install fail2ban
# Jail für die Anwendung erstellen
sudo nano /etc/fail2ban/jail.d/it-docs.conf
```
```ini
[it-docs-login]
enabled = true
port = 443
filter = it-docs-login
logpath = /var/log/nginx/it-docs-access.log
maxretry = 10
bantime = 3600
findtime = 600
```
```bash
# Filter erstellen
sudo nano /etc/fail2ban/filter.d/it-docs-login.conf
```
```ini
[Definition]
failregex = ^<HOST> .* "POST /auth/login HTTP.* 200
ignoreregex =
```
```bash
sudo systemctl restart fail2ban
sudo fail2ban-client status it-docs-login
```
### Uptime-Monitoring
Empfohlene Tools:
- **Uptime Kuma** (self-hosted): Monitor auf `https://docs.example.com/`
- **Betteruptime / UptimeRobot**: Externer Check alle 5 Minuten
- **Zabbix**: Falls bereits im Einsatz
### Log-Monitoring
```bash
# Kritische Ereignisse in Audit-Log überwachen
# (über Admin-Oberfläche oder direktes SQL)
SELECT * FROM audit_log
WHERE risk_level IN ('high', 'critical')
AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY created_at DESC;
```
---
## VPN-Alternative
Wenn maximale Sicherheit gewünscht ist, anstatt direkter Internet-Exposition:
### WireGuard VPN
```bash
# WireGuard installieren
sudo apt install wireguard
# Server-Keys generieren
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
# Konfiguration
sudo nano /etc/wireguard/wg0.conf
```
```ini
[Interface]
Address = 10.100.0.1/24
ListenPort = 51820
PrivateKey = <server-private-key>
# Client (pro Benutzer)
[Peer]
PublicKey = <client-public-key>
AllowedIPs = 10.100.0.2/32
```
```bash
sudo systemctl enable --now wg-quick@wg0
# Firewall: WireGuard-Port öffnen
sudo ufw allow 51820/udp
```
**Zugriffspfad mit VPN:**
```
Benutzer → WireGuard VPN (51820/UDP) → 10.100.0.x → OpenNIT (nur intern)
```
Damit ist die Anwendung aus dem Internet **nicht direkt erreichbar** – nur über VPN.