OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

EXTERNAL_ACCESS.md

13 KB
# 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.