OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

security.php

9 KB
<?php

declare(strict_types=1);

/**
 * Sicherheitskonfiguration
 *
 * Zentrale Sicherheitseinstellungen für das gesamte System.
 * SICHERHEITSKRITISCH: Diese Werte direkt auf die Sicherheit des Systems aus.
 */

return [
    // Session-Konfiguration
    'session' => [
        'name' => 'ITDOCS_SESSION',
        'lifetime' => (int) ($_ENV['SESSION_LIFETIME'] ?? 28800), // 8 Stunden
        'path' => '/',
        'domain' => '', // Automatisch aus Host
        'secure' => filter_var($_ENV['SESSION_SECURE'] ?? true, FILTER_VALIDATE_BOOLEAN),
        'httponly' => true,
        // WICHTIG: Lax statt Strict für OAuth-Callbacks
        // Strict verhindert, dass Session-Cookies bei Redirects von externen Seiten gesendet werden
        'samesite' => 'Lax',
        'regenerate_interval' => 1800, // Session-ID alle 30 Minuten erneuern
        // Eigenes Session-Verzeichnis: verhindert, dass der System-Cron (Debian/Ubuntu)
        // Session-Dateien löscht, bevor die konfigurierte Lifetime abgelaufen ist.
        'save_path' => dirname(__DIR__) . '/storage/sessions',
    ],

    // CSRF-Schutz
    'csrf' => [
        'enabled' => true,
        'token_name' => '_csrf',
        'header_name' => 'X-CSRF-Token',
        'lifetime' => (int) ($_ENV['CSRF_TOKEN_LIFETIME'] ?? 28800), // 8 Stunden (gleich wie Session-Lifetime)
        'per_form' => true, // Separate Tokens pro Formular
    ],

    // Rate Limiting
    'rate_limit' => [
        'enabled' => true,
        'requests' => (int) ($_ENV['RATE_LIMIT_REQUESTS'] ?? 1000),
        'window' => (int) ($_ENV['RATE_LIMIT_WINDOW'] ?? 60), // Sekunden
        'by' => 'ip', // oder 'user'
        // Spezielle Limits für sensible Endpunkte
        'endpoints' => [
            'login' => ['requests' => 15, 'window' => 60],
            // LDAP-Login (AD-Bind) muss dieselbe Drossel wie der lokale Login haben,
            // sonst wären dort ~1000 Passwortversuche/Minute möglich.
            'auth/ldap' => ['requests' => 15, 'window' => 60],
            'mfa' => ['requests' => 5, 'window' => 300],
            'auth/mfa' => ['requests' => 5, 'window' => 300],
            // Muster werden gegen den Pfad geprüft (Präfix oder enthalten) – sie
            // müssen daher echten Pfadbestandteilen entsprechen, nicht Sprechnamen.
            'password/' => ['requests' => 10, 'window' => 300],
            'upload/image' => ['requests' => 50, 'window' => 60],
            'files/upload' => ['requests' => 50, 'window' => 60],
            'export' => ['requests' => 50, 'window' => 60],
        ],
    ],

    // Content Security Policy
    'csp' => [
        'enabled' => true,
        'directives' => [
            'default-src' => "'self'",
            'script-src' => "'self' 'unsafe-inline'",
            'style-src' => "'self' 'unsafe-inline'",
            'img-src' => "'self' data: blob: https:",
            'font-src' => "'self'",
            'connect-src' => "'self'",
            'frame-ancestors' => "'self'",
            'form-action' => "'self'",
            'base-uri' => "'self'",
        ],
    ],

    // Zusätzliche HTTP-Sicherheitsheader
    'headers' => [
        'X-Content-Type-Options' => 'nosniff',
        'X-Frame-Options' => 'SAMEORIGIN',
        // Hinweis: X-XSS-Protection wurde bewusst entfernt – der Header ist in
        // modernen Browsern deaktiviert/abgekündigt und kann in Altbrowsern sogar
        // Schaden anrichten. Maßgeblich ist die Content-Security-Policy.
        'Referrer-Policy' => 'strict-origin-when-cross-origin',
        'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()',
    ],

    // HSTS (nur bei HTTPS aktivieren)
    'hsts' => [
        'enabled' => true,
        'max_age' => 31536000, // 1 Jahr
        'include_subdomains' => true,
        'preload' => false,
    ],

    // Passwort-Hashing
    'password' => [
        'algorithm' => PASSWORD_ARGON2ID,
        'options' => [
            'memory_cost' => 65536,
            'time_cost' => 4,
            'threads' => 3,
        ],
    ],

    // Passwort-Richtlinie (optional, im Backend pflegbar; Default AUS).
    // Greift beim Anlegen/Setzen lokaler Passwörter. Ist 'enabled' = false,
    // gelten nur die bestehenden Basis-Mindestlängen der jeweiligen Maske.
    'password_policy' => [
        'enabled'           => false,
        'min_length'        => 12,
        'require_uppercase' => false,
        'require_lowercase' => false,
        'require_number'    => false,
        'require_special'   => false,
    ],

    // Account-Lockout nach zu vielen Fehl-Logins (optional; Default AUS).
    // Zählt fehlgeschlagene lokale Logins je Benutzerkonto innerhalb des
    // Zeitfensters und sperrt das Konto danach temporär.
    'lockout' => [
        'enabled'         => false,
        'max_attempts'    => 5,   // Fehlversuche bis zur Sperre
        'window_minutes'  => 15,  // Zeitfenster, in dem Fehlversuche zählen
        'lockout_minutes' => 15,  // Dauer der temporären Sperre
    ],

    // SIEM-Weiterleitung der Audit-Events (optional; Default AUS).
    // Ein Hintergrund-Task schickt neue Audit-Einträge an ein externes SIEM /
    // zentrales Logsystem – per HTTP-Webhook und/oder UDP-Syslog. 'min_risk'
    // begrenzt auf Ereignisse ab dieser Risikostufe (low|medium|high|critical).
    'siem' => [
        'enabled'     => false,
        'webhook_url' => '',
        'syslog_host' => '',
        'syslog_port' => 514,
        'min_risk'    => 'low',
    ],

    // Periodische Integritätsprüfung der Audit-Hash-Kette (optional; Default AUS).
    // Schlägt die Prüfung fehl, werden Administratoren in-App benachrichtigt und
    // ein kritisches Audit-Ereignis erzeugt (das ggf. ans SIEM weitergeleitet wird).
    'integrity_check' => [
        // Standardmäßig AN: Die nächtliche Prüfung der Audit-Hash-Kette meldet
        // Manipulationen früh. Sie ist günstig (nur die letzten `sample` Einträge)
        // und alarmiert nur bei erkannten Integritätsfehlern. Abschaltbar unter
        // /admin/settings/security. Bestehende Instanzen mit gespeicherter
        // Einstellung behalten ihren Wert (Runtime-Override greift vor Default).
        'enabled' => true,
        'sample'  => 1000, // Anzahl der zuletzt geprüften Einträge
    ],

    // MFA-Erzwingung (optional; Default AUS). Betrifft nur LOKALE Konten –
    // SSO/LDAP-MFA ist Sache des Identity-Providers. Ist 'enforce' aktiv, werden
    // betroffene Nutzer ohne eingerichtete MFA-Methode (TOTP/WebAuthn) nach dem
    // Login zur MFA-Einrichtung geleitet, bis sie eine Methode hinterlegt haben.
    // scope: 'admins' = nur globale Administratoren, 'all' = alle lokalen Konten.
    'mfa' => [
        'enforce' => false,
        'scope'   => 'admins',
    ],

    // Verschlüsselung
    'encryption' => [
        'algorithm' => 'aes-256-gcm',
        'key' => $_ENV['STORAGE_ENCRYPTION_KEY'] ?? '',
    ],

    // File Upload Sicherheit
    'upload' => [
        'max_size' => (int) ($_ENV['MAX_UPLOAD_SIZE'] ?? 52428800), // 50 MB
        'allowed_mimes' => [
            // Dokumente
            'application/pdf',
            'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'application/vnd.ms-excel',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'application/vnd.ms-powerpoint',
            'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'text/plain',
            'text/csv',
            // Bilder
            'image/jpeg',
            'image/png',
            'image/gif',
            'image/webp',
            'image/svg+xml',
            // Archive
            'application/zip',
            'application/x-7z-compressed',
        ],
        'denied_extensions' => [
            'php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'phps',
            'exe', 'bat', 'cmd', 'sh', 'bash',
            'js', 'vbs', 'wsf', 'wsh',
            'msi', 'dll', 'com', 'scr',
            'htaccess', 'htpasswd',
        ],
    ],

    // IP-Whitelist für Admin-Bereich (optional)
    'admin_ip_whitelist' => [
        'enabled' => false,
        'ips' => [],
    ],

    // Audit-Einstellungen
    'audit' => [
        'retention_years' => (int) ($_ENV['AUDIT_RETENTION_YEARS'] ?? 10),
        // Optionaler, im Backend aktivierbarer Retention-Purge (Default AUS):
        // löscht Audit-Einträge älter als 'retention_years'. Die Hash-Kette der
        // behaltenen Einträge bleibt über einen gespeicherten Anker prüfbar.
        'purge_enabled' => false,
        'log_request_body' => false, // Datenschutz
        'log_response_body' => false,
        'sensitive_fields' => ['password', 'token', 'secret', 'key'],
    ],
];