Quelltext des aktuellen Standes
<?php
/**
* PDF-Body des Compliance-/Audit-Berichts.
* Variablen: $appName, $profile, $profileLabel, $facts (array), $sections (array), $date
* Wird von ComplianceController::buildPdfHtml() per ob_start/include gerendert.
*/
$e = static fn($v) => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
$yesNo = static fn(bool $b, string $yes = 'aktiv', string $no = 'inaktiv') => $b
? '<span class="ok">' . $yes . '</span>'
: '<span class="warn">' . $no . '</span>';
$hc = $facts['hashchain'];
?>
<!-- Deckblatt -->
<div class="cover">
<h1><?= $this->te('Compliance- & Audit-Nachweis') ?></h1>
<div class="sub"><?= $e($appName) ?></div>
<div class="sub" style="font-size:11pt;color:#555;"><?= $e($profileLabel) ?></div>
<div class="meta">
<?= $this->t('Erstellt am {datum} von {person}', ['datum' => $e($facts['generated_at']), 'person' => $e($facts['generated_by'])]) ?><br>
<?= $this->t('Version {version} · PHP {php}', ['version' => $e($facts['app_version']), 'php' => $e($facts['php_version'])]) ?><br>
<?= $this->t('Vertraulich – nur für interne Verwendung und berechtigte Prüfer') ?>
</div>
</div>
<!-- 1. Zweck & Geltungsbereich -->
<h2><?= $this->te('1. Zweck & Geltungsbereich') ?></h2>
<p>
<?= $this->t('Dieses Dokument weist nach, welche Anforderungen aus <strong>ISO/IEC 27001:2022 (Annex A)</strong>, <strong>BSI IT-Grundschutz</strong>, <strong>DSGVO</strong> und den <strong>GoBD</strong> (für die Wirtschaftsprüfung) durch {anwendung} unterstützt werden, mit welcher Funktion und wo der Nachweis im System liegt. Es ist als Beilage für interne Audits und die jährliche Wirtschaftsprüfung gedacht.', ['anwendung' => $e($appName)]) ?>
</p>
<div class="disclaimer">
<?= $this->t('<strong>Hinweis:</strong> ISO 27001 zertifiziert ein <em>Managementsystem (ISMS)</em>, kein einzelnes Tool. {anwendung} liefert technische Nachweise für einzelne Controls; Richtlinien, Risikoanalyse, Auftragsverarbeitungsverträge sowie die Sicherung von {anwendung} selbst (Datenbank + Datei-Storage, inkl. getestetem Restore) liegen in der Verantwortung der betreibenden Organisation.', ['anwendung' => $e($appName)]) ?>
</div>
<!-- 2. Systemüberblick -->
<h2><?= $this->te('2. Systemüberblick (Live-Kennzahlen)') ?></h2>
<table class="facts">
<tr><td class="k"><?= $this->te('Anwendung / Version') ?></td><td><?= $e($facts['app_name']) ?> · <?= $e($facts['app_version']) ?></td></tr>
<tr><td class="k"><?= $this->te('Basis-URL') ?></td><td><?= $e($facts['app_url'] ?: 'n/a') ?></td></tr>
<tr><td class="k"><?= $this->te('Zeitzone') ?></td><td><?= $e($facts['timezone']) ?></td></tr>
<tr><td class="k"><?= $this->te('Debug-Modus (Produktion: aus)') ?></td><td><?= $facts['app_debug'] ? '<span class="bad">AN – in Produktion deaktivieren!</span>' : '<span class="ok">aus</span>' ?></td></tr>
<tr><td class="k"><?= $this->te('Aktive Module') ?></td><td><?= $this->te('{aktiv} von {gesamt}', ['aktiv' => (int)$facts['modules_active_count'], 'gesamt' => (int)$facts['modules_total_count']]) ?></td></tr>
</table>
<h3><?= $this->te('2.1 Audit-Log') ?></h3>
<table class="facts">
<tr><td class="k"><?= $this->te('Protokollierte Ereignisse gesamt') ?></td><td><?= $facts['audit_total'] !== null ? (int)$facts['audit_total'] : 'n/a' ?></td></tr>
<tr><td class="k"><?= $this->te('Zeitraum') ?></td><td><?= $e($facts['audit_first'] ?: 'n/a') ?> – <?= $e($facts['audit_last'] ?: 'n/a') ?></td></tr>
<tr><td class="k"><?= $this->te('davon fehlgeschlagene Aktionen') ?></td><td><?= $facts['audit_failures'] !== null ? (int)$facts['audit_failures'] : 'n/a' ?></td></tr>
<tr><td class="k"><?= $this->te('Hash-Ketten-Verifikation (Stichprobe)') ?></td><td>
<?php if (!$hc['available']): ?>
<span class="warn"><?= $this->te('nicht automatisch geprüft – manuell unter /admin/audit') ?></span>
<?php elseif ($hc['valid']): ?>
<span class="ok"><?= $this->te('gültig') ?></span> <?= $this->te('({geprueft} von {gesamt} jüngsten Einträgen geprüft)', ['geprueft' => (int)$hc['checked'], 'gesamt' => (int)$hc['total']]) ?>
<?php else: ?>
<span class="bad"><?= $this->te('Abweichung erkannt – bitte /admin/audit prüfen') ?></span>
<?php endif; ?>
</td></tr>
<tr><td class="k"><?= $this->te('Aufbewahrungs-Policy') ?></td><td><?= $this->te('{anzahl} Jahre (Einträge unveränderlich: DB-Trigger gegen UPDATE/DELETE)', ['anzahl' => (int)$facts['retention_years']]) ?></td></tr>
</table>
<h3><?= $this->te('2.2 Zugriff & Authentifizierung') ?></h3>
<table class="facts">
<tr><td class="k"><?= $this->te('Benutzer (gesamt / aktiv)') ?></td><td><?= $facts['users_total'] !== null ? (int)$facts['users_total'] : 'n/a' ?> / <?= $facts['users_active'] !== null ? (int)$facts['users_active'] : 'n/a' ?></td></tr>
<tr><td class="k"><?= $this->te('MFA aktiviert') ?></td><td><?= $this->t('{totp} mit TOTP, {webauthn} mit WebAuthn/FIDO2', ['totp' => (int)$facts['mfa_totp'], 'webauthn' => (int)$facts['mfa_webauthn']]) ?></td></tr>
<tr><td class="k"><?= $this->te('Definierte Rollen') ?></td><td><?= $facts['roles_total'] !== null ? (int)$facts['roles_total'] : 'n/a' ?></td></tr>
<tr><td class="k"><?= $this->te('Lokaler Login') ?></td><td><?= $yesNo($facts['local_login'], 'erlaubt', 'deaktiviert') ?></td></tr>
<tr><td class="k"><?= $this->te('Microsoft 365 / Entra SSO') ?></td><td><?= $yesNo($facts['sso_azure'], 'konfiguriert', 'nicht konfiguriert') ?></td></tr>
</table>
<h3><?= $this->te('2.3 Sicherheitskonfiguration') ?></h3>
<table class="facts">
<tr><td class="k"><?= $this->te('Passwort-Hashing') ?></td><td><?= $e($facts['pw_algo']) ?></td></tr>
<tr><td class="k"><?= $this->te('Verschlüsselung (Vault/Felder)') ?></td><td><?= $e($facts['enc_algo']) ?></td></tr>
<tr><td class="k"><?= $this->te('Session-Lebensdauer / ID-Erneuerung') ?></td><td><?= $this->te('{dauer} min / alle {erneuerung} min', ['dauer' => (int)$facts['session_minutes'], 'erneuerung' => (int)$facts['regen_minutes']]) ?></td></tr>
<tr><td class="k"><?= $this->te('CSRF-Token-Lebensdauer') ?></td><td><?= $this->te('{minuten} min', ['minuten' => (int)$facts['csrf_minutes']]) ?></td></tr>
<tr><td class="k"><?= $this->te('Rate-Limiting') ?></td><td><?= $yesNo($facts['rate_limit']) ?></td></tr>
<tr><td class="k"><?= $this->t('HSTS / CSP') ?></td><td><?= $yesNo($facts['hsts'], 'an', 'aus') ?> / <?= $yesNo($facts['csp'], 'an', 'aus') ?></td></tr>
<tr><td class="k"><?= $this->te('Max. Upload-Größe') ?></td><td><?= (int)$facts['max_upload_mb'] ?> MB</td></tr>
</table>
<h3><?= $this->te('2.4 Optionale Schutzfunktionen (im Backend aktivierbar)') ?></h3>
<?php $mfaLabel = $facts['mfa_enforce'] ? 'aktiv (' . ($facts['mfa_scope'] === 'all' ? 'alle lokalen Konten' : 'nur Administratoren') . ')' : 'inaktiv'; ?>
<table class="facts">
<tr><td class="k"><?= $this->te('Passwort-Richtlinie') ?></td><td><?= $yesNo($facts['pw_policy']) ?></td></tr>
<tr><td class="k"><?= $this->te('Account-Lockout (Brute-Force-Schutz)') ?></td><td><?= $yesNo($facts['lockout']) ?></td></tr>
<tr><td class="k"><?= $this->te('MFA-Erzwingung (lokale Konten)') ?></td><td><?= $yesNo($facts['mfa_enforce'], $mfaLabel, $mfaLabel) ?></td></tr>
<tr><td class="k"><?= $this->te('Audit-Integritätsprüfung (periodisch)') ?></td><td><?= $yesNo($facts['integrity_check']) ?></td></tr>
<tr><td class="k"><?= $this->te('SIEM-/Log-Weiterleitung') ?></td><td><?= $yesNo($facts['siem_forward']) ?></td></tr>
<tr><td class="k"><?= $this->te('Audit-Aufbewahrungs-Purge') ?></td><td><?= $yesNo($facts['audit_purge']) ?></td></tr>
</table>
<!-- 3. Nachweis-Matrix -->
<h2 style="page-break-before:always;"><?= $this->te('3. Nachweis-Matrix (Anforderung → Funktion → Nachweis)') ?></h2>
<?php $n = 0; foreach ($sections as $section): $n++; ?>
<h3>3.<?= $n ?> <?= $e($section['title']) ?></h3>
<table>
<thead>
<tr>
<th scope="col" style="width:24%;"><?= $this->te('Norm / Anforderung') ?></th>
<th scope="col" style="width:20%;"><?= $this->te('Thema') ?></th>
<th scope="col" style="width:34%;"><?= $this->t('Abdeckung in') ?> <?= $e($appName) ?></th>
<th scope="col" style="width:22%;"><?= $this->te('Nachweis / Ort') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($section['rows'] as $row): ?>
<tr>
<td class="ref"><?= $e($row['ref']) ?></td>
<td><?= $e($row['topic']) ?></td>
<td><?= $e($row['function']) ?></td>
<td><?= $e($row['evidence']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach; ?>
<!-- 4. Prüfanleitung -->
<h2 style="page-break-before:always;"><?= $this->te('4. Prüfanleitung für Auditoren') ?></h2>
<p><?= $this->te('Mit den folgenden Schritten lässt sich die Wirksamkeit der zentralen Kontrolle (unveränderliches Audit-Log) unmittelbar nachweisen:') ?></p>
<table>
<thead><tr><th scope="col" style="width:5%;">#</th><th scope="col"><?= $this->te('Schritt') ?></th><th scope="col" style="width:30%;"><?= $this->te('Erwartetes Ergebnis') ?></th></tr></thead>
<tbody>
<tr><td>1</td><td><?= $this->te('Teständerung an einer Dokumentationsseite oder einem Asset vornehmen') ?></td><td><?= $this->te('Änderung wird gespeichert') ?></td></tr>
<tr><td>2</td><td><?= $this->t('<code>/admin/audit</code> öffnen und den Eintrag suchen') ?></td><td><?= $this->te('Eintrag erscheint mit Wer/Was/Wann') ?></td></tr>
<tr><td>3</td><td><?= $this->te('Detailansicht öffnen, Alt-/Neu-Wert vergleichen') ?></td><td><?= $this->te('Vorher/Nachher dokumentiert') ?></td></tr>
<tr><td>4</td><td><code>/admin/audit</code> <?= $this->te('→ „Integrität prüfen" ausführen') ?></td><td><?= $this->te('Hash-Kette: gültig') ?></td></tr>
<tr><td>5</td><td><?= $this->te('Direkt in der DB versuchen, einen Audit-Eintrag zu ändern/löschen') ?></td><td><?= $this->te('schlägt fehl (DB-Trigger)') ?></td></tr>
<tr><td>6</td><td><?= $this->te('Audit-Log als CSV/PDF exportieren') ?></td><td><?= $this->te('maschinen-/prüfbarer Export') ?></td></tr>
</tbody>
</table>
<div class="note">
<?= $this->t('<strong>Weiterführende Dokumente:</strong> docs/AUDIT.md (Audit-Log & Hash-Chain), docs/SECURITY.md & docs/SECURITY-HARDENING.md (Härtung), docs/COMPLIANCE.md (vollständige Control-Matrix), docs/OPEN_SOURCE_LICENSES.md (Drittanbieter & externe Dienste). Systemhandbuch: /admin/manual.') ?>
</div>
<div class="disclaimer">
<?= $this->te('Dieses Dokument bildet den Stand zum Erstellungszeitpunkt ab und ersetzt keine Bewertung durch die Zertifizierungsstelle bzw. den Abschlussprüfer. Der verbindliche Prüfumfang ergibt sich aus der jeweiligen Norm und dem ISMS-/Prüfungs-Scope der Organisation.') ?>
</div>