OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

AUDIT.md

15 KB
# Audit-Dokumentation

> OpenNIT – Revisionssichere Audit-Protokollierung für Compliance und Zertifizierungen

---

## Inhaltsverzeichnis

- [Überblick](#überblick)
- [Technische Implementierung](#technische-implementierung)
- [Erfasste Ereignisse](#erfasste-ereignisse)
- [Hash-Chain-Verifikation](#hash-chain-verifikation)
- [Audit-Log einsehen](#audit-log-einsehen)
- [Compliance-Nachweise](#compliance-nachweise)
- [Aufbewahrungsfristen](#aufbewahrungsfristen)
- [Export und Reporting](#export-und-reporting)
- [Datenbankstruktur](#datenbankstruktur)

---

## Überblick

Das System implementiert einen **unveränderlichen, revisionssicheren Audit-Trail**, der alle Benutzeraktionen und Systemereignisse lückenlos protokolliert. Durch eine kryptografische Hash-Chain wird sichergestellt, dass keine Einträge nachträglich verändert oder gelöscht werden können, ohne dass dies erkannt wird.

**Geeignet für folgende Compliance-Rahmenwerke:**
- ISO/IEC 27001 (Informationssicherheits-Management)
- BSI IT-Grundschutz
- SOC 2 Type II
- DSGVO / GDPR (Nachvollziehbarkeit von Datenzugriffen)
- NIS2-Richtlinie

---

## Technische Implementierung

### Unveränderlichkeit durch Datenbank-Trigger

Der Audit-Log ist durch Datenbank-Trigger gegen Manipulationen geschützt:

```sql
-- Kein UPDATE auf audit_log erlaubt
CREATE TRIGGER prevent_audit_update
BEFORE UPDATE ON audit_log
FOR EACH ROW
BEGIN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = 'Audit-Log-Einträge können nicht geändert werden';
END;

-- Kein DELETE auf audit_log erlaubt
CREATE TRIGGER prevent_audit_delete
BEFORE DELETE ON audit_log
FOR EACH ROW
BEGIN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = 'Audit-Log-Einträge können nicht gelöscht werden';
END;
```

Selbst bei direktem Datenbankzugriff ist eine Manipulation der Audit-Einträge nicht möglich.

### Hash-Chain-Prinzip

Jeder Audit-Eintrag enthält den kryptografischen Hash des vorherigen Eintrags:

```
Eintrag 1: hash(Daten₁ + "genesis")           → H₁
Eintrag 2: hash(Daten₂ + H₁)                  → H₂
Eintrag 3: hash(Daten₃ + H₂)                  → H₃
   ...
Eintrag N: hash(DatenN + H(N-1))               → HN
```

**Manipulationserkennung:** Wird ein historischer Eintrag verändert, stimmt sein Hash nicht mehr mit dem Verkettungswert des nachfolgenden Eintrags überein. Die Verifikationsfunktion erkennt die Unterbrechung der Kette.

#### Serialisierung der Felder

Signiert wird nicht der Datensatz, sondern eine Zeichenkette aus seinen Feldwerten. Damit die Signatur den Inhalt belegt, muss aus dieser Zeichenkette genau eine Aufteilung in Felder hervorgehen. Jedem Feld geht deshalb seine Byte-Länge voraus:

```
8:audit-v2 36:<event_id> 26:<timestamp> 1:<user_id> …
```

Zuvor wurden die Felder mit `|` verkettet. Da mehrere Felder frei wählbaren Text tragen (Objektname, die JSON-Spalten), ließen sich dieselben Bytes anders auf die Felder verteilen, ohne die Signatur zu verändern – für einen Angreifer mit Schreibzugriff auf die Datenbank genau die Lücke, gegen die die Kette schützt.

#### Format-Grenzen

Bestehende Einträge bleiben nachrechenbar: Jede Umstellung des Verfahrens hinterlässt eine mit dem Anwendungsschlüssel signierte Grenze. Unterhalb gilt das frühere Verfahren, oberhalb ausschließlich das neue – ein Eintrag lässt sich also nicht nachträglich auf ein schwächeres Verfahren zurückstufen. Die Grenzen selbst sind ohne den Schlüssel nicht verschiebbar.

| Tabelle | Umstellung |
|---|---|
| `audit_hmac_anchor` | SHA-256 → HMAC-SHA-256 |
| `audit_canonical_anchor` | JSON-Felder in kanonischer Schreibweise |
| `audit_field_anchor` | Felder längenpräfixiert statt mit `|` verkettet |

### Zeitstempel

- Präzision: **Mikrosekunden** (`DATETIME(6)`)
- Zeitzone: UTC (konfigurierbar)
- Manipulationsschutz: Teil der Hash-Berechnung

---

## Erfasste Ereignisse

### Authentifizierung & Zugriff

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `auth.login.success` | Erfolgreicher Login | low |
| `auth.login.failed` | Fehlgeschlagener Login | medium |
| `auth.logout` | Abmeldung | low |
| `auth.mfa.setup` | MFA eingerichtet | medium |
| `auth.mfa.verified` | MFA-Code verifiziert | low |
| `auth.mfa.failed` | MFA-Code fehlgeschlagen | medium |
| `auth.session.expired` | Session abgelaufen | low |
| `auth.oauth.login` | Azure AD Login | low |

### Benutzerverwaltung

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `user.created` | Benutzer angelegt | medium |
| `user.updated` | Benutzerdaten geändert | medium |
| `user.deleted` | Benutzer gelöscht | high |
| `user.password_changed` | Passwort geändert | medium |
| `user.role_assigned` | Rolle zugewiesen | high |
| `user.role_removed` | Rolle entzogen | high |
| `user.locked` | Konto gesperrt | high |
| `user.unlocked` | Konto entsperrt | medium |

### Dokumentation

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `page.created` | Seite erstellt | low |
| `page.updated` | Seite bearbeitet | low |
| `page.deleted` | Seite gelöscht | medium |
| `page.version.restored` | Version wiederhergestellt | medium |
| `topic.created` | Thema erstellt | low |
| `topic.deleted` | Thema gelöscht | medium |

### Dateiverwaltung

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `file.uploaded` | Datei hochgeladen | low |
| `file.downloaded` | Datei heruntergeladen | low |
| `file.deleted` | Datei gelöscht | medium |

### Assets & Inventar

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `asset.created` | Asset angelegt | low |
| `asset.updated` | Asset bearbeitet | low |
| `asset.deleted` | Asset gelöscht | medium |
| `asset.handover` | Gerät übergeben | medium |
| `asset.warranty.synced` | Garantiedaten aktualisiert (manuell oder per Scheduler, Quelle wird mitprotokolliert) | low |
| `asset.attachment.added` | Datei-Anhang hinzugefügt | low |
| `asset.attachment.deleted` | Datei-Anhang gelöscht | medium |
| `asset.exported` | Inventarliste als CSV exportiert (mit Datensatzanzahl) | low |
| `asset.hw_sync` | Hardware-Daten per PowerShell-Agent synchronisiert | low |
| `device.unmatched` | Gerät vom Sync erkannt, aber keinem Asset zugeordnet (inkl. Grund) | low |
| `asset.sync_settings_updated` | Sync-Einstellungen (z. B. Rate-Limit) geändert | medium |

> Hinweis: Die Spalte „Ereignis" zeigt die fachliche Aktion. Technisch werden die
> Aktionstypen mit Unterstrich gespeichert (z. B. `asset_warranty_synced`).

### Verträge

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `contract.created` | Vertrag angelegt | medium |
| `contract.updated` | Vertrag bearbeitet | medium |
| `contract.deleted` | Vertrag gelöscht | high |

### Passwort-Vault

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `vault.unlocked` | Vault entsperrt | low |
| `vault.entry.created` | Vault-Eintrag erstellt | medium |
| `vault.entry.accessed` | Vault-Eintrag abgerufen | medium |
| `vault.entry.updated` | Vault-Eintrag bearbeitet | medium |
| `vault.entry.deleted` | Vault-Eintrag gelöscht | high |

### Systemverwaltung

| Ereignis | Beschreibung | Risikostufe |
|---------|-------------|-------------|
| `admin.settings.updated` | Systemeinstellungen geändert | critical |
| `admin.role.created` | Rolle erstellt | high |
| `admin.role.updated` | Rolle bearbeitet | high |
| `admin.role.deleted` | Rolle gelöscht | critical |
| `admin.export.created` | Datenexport erstellt | medium |
| `audit.verification.run` | Hash-Chain verifiziert | low |
| `audit.integrity.failed` | Integritätsverletzung erkannt | critical |

---

## Erfasste Datenpunkte

Jeder Audit-Eintrag enthält:

| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `id` | BIGINT | Eindeutige ID des Eintrags |
| `timestamp` | DATETIME(6) | Zeitstempel (Mikrosekunden, UTC) |
| `user_id` | INT | ID des handelnden Benutzers |
| `username` | VARCHAR | Benutzername (denormalisiert) |
| `session_id` | VARCHAR | Session-ID der Aktion |
| `ip_address` | VARCHAR | IP-Adresse des Clients |
| `user_agent` | TEXT | Browser/Client-Kennung |
| `action_category` | VARCHAR | Kategorie (auth, user, page, ...) |
| `action_type` | VARCHAR | Spezifische Aktion |
| `object_type` | VARCHAR | Betroffener Objekttyp |
| `object_id` | INT | ID des betroffenen Objekts |
| `object_name` | VARCHAR | Name/Bezeichnung des Objekts |
| `old_value` | JSON | Alter Wert (vor Änderung) |
| `new_value` | JSON | Neuer Wert (nach Änderung) |
| `risk_level` | ENUM | low / medium / high / critical |
| `result` | ENUM | success / failure |
| `details` | TEXT | Zusätzliche Informationen |
| `checksum` | VARCHAR(64) | SHA-256-Hash (Hash-Chain) |

**Sensible Felder werden maskiert** und nie im Klartext gespeichert:
- `password`, `token`, `secret`, `key` → `[REDACTED]`

---

## Hash-Chain-Verifikation

### Manuelle Verifikation (Admin-Oberfläche)

1. Als Administrator anmelden
2. `/admin/audit` aufrufen
3. "Integrität prüfen" klicken
4. Das System verifiziert die gesamte Hash-Chain

**Ergebnis:**
- ✅ Grün: Alle Einträge integer, keine Manipulation erkannt
- ❌ Rot: Unterbrechung der Kette erkannt (Zeitpunkt und betroffener Bereich werden angezeigt)

### Automatische Verifikation

Empfehlung: Tägliche Verifikation als Cron-Job:

```bash
# Cron: Tägliche Hash-Chain-Verifikation um 6 Uhr
0 6 * * * www-data php /var/www/it-docs/bin/verify-audit-chain.php \
    >> /var/log/it-docs-audit-verify.log 2>&1
```

### Direktes SQL (für unabhängige Prüfung)

```sql
-- Alle Audit-Einträge des letzten Tages
SELECT
    id,
    timestamp,
    username,
    ip_address,
    action_category,
    action_type,
    object_type,
    object_name,
    risk_level,
    result
FROM audit_log
WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 1 DAY)
ORDER BY timestamp DESC;

-- Kritische Ereignisse der letzten 7 Tage
SELECT *
FROM audit_log
WHERE risk_level IN ('high', 'critical')
  AND timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY timestamp DESC;

-- Fehlgeschlagene Anmeldeversuche
SELECT
    ip_address,
    COUNT(*) AS attempts,
    MAX(timestamp) AS last_attempt
FROM audit_log
WHERE action_type = 'auth.login.failed'
  AND timestamp >= DATE_SUB(NOW(), INTERVAL 1 DAY)
GROUP BY ip_address
ORDER BY attempts DESC;

-- Aktivitäten eines bestimmten Benutzers
SELECT *
FROM audit_log
WHERE user_id = 42
ORDER BY timestamp DESC
LIMIT 100;
```

---

## Audit-Log einsehen

### Admin-Oberfläche

URL: `/admin/audit`

**Filtermöglichkeiten:**
- Zeitraum (Von / Bis)
- Benutzer
- IP-Adresse
- Aktionskategorie
- Aktionstyp
- Risikostufe
- Ergebnis (Erfolg / Fehler)

**Ansichten:**
- Listenansicht mit Paginierung
- Detailansicht eines Eintrags (Alt-Wert / Neu-Wert)
- Hash-Chain-Verifikation

### Zugriffsrechte

Benötigte Capability: `view_audit`

Standardmäßig nur für Administratoren verfügbar. Kann gezielt an Compliance-Beauftragte vergeben werden ohne vollen Admin-Zugriff.

---

## Compliance-Nachweise

### ISO 27001 – A.8.15 Protokollierung

Das System erfüllt folgende Anforderungen:
- ✅ Protokollierung von Benutzeraktivitäten (Anhang A.8.15)
- ✅ Schutz von Protokollinformationen vor Manipulation (A.8.15.2)
- ✅ Protokollierung von Administrator- und Operatoraktivitäten (A.8.15.3)
- ✅ Synchronisation von Uhren (A.8.17, via NTP)

**Nachweisdokument für Auditor:**
1. `/admin/audit` öffnen
2. Zeitraum auswählen
3. Export als PDF oder CSV
4. Hash-Chain-Verifikationsstatus dokumentieren

### DSGVO Art. 5 (1) f – Integrität und Vertraulichkeit

- ✅ Alle Datenzugriffe werden protokolliert (wer hat wann welche Daten gesehen/geändert)
- ✅ Sensible Felder werden maskiert (kein Klartext-Passwort im Log)
- ✅ Zugriff auf Audit-Log ist selbst auditiert

### BSI-Grundschutz – OPS.1.1.5 Protokollierung

- ✅ Protokollierung sicherheitsrelevanter Ereignisse
- ✅ Schutz der Protokolldaten
- ✅ Regelmäßige Auswertung (via Admin-Oberfläche)

---

## Aufbewahrungsfristen

### Konfiguration

```ini
# .env
AUDIT_RETENTION_YEARS=10
```

Standard: **10 Jahre** (übertrifft die gesetzlichen Mindestanforderungen in DE/EU).

### Gesetzliche Anforderungen (Deutschland)

| Regelwerk | Anforderung | Empfehlung |
|-----------|-------------|------------|
| GoBD (Handels-/Steuerrecht) | 6 / 10 Jahre | 10 Jahre |
| DSGVO | Zweckgebunden | 10 Jahre (für Sicherheitsnachweise) |
| BSI IT-Grundschutz | Risikobewertung | 3-10 Jahre |
| ISO 27001 | Nach Policy | Mindestens 3 Jahre |

---

## Export und Reporting

### Über die Admin-Oberfläche

1. `/admin/audit` aufrufen
2. Filterkriterien setzen
3. "Export" → Format wählen:
   - **CSV**: Maschinenlesbar, für eigene Auswertungen
   - **PDF**: Für Auditoren und Dokumentation

### Automatisiertes Reporting

Empfehlung: Monatlichen Audit-Report erstellen und archivieren:

```bash
# Monatlicher Audit-Report (1. jeden Monats, 7 Uhr)
0 7 1 * * www-data php /var/www/it-docs/bin/audit-report.php \
    --period=last-month \
    --format=pdf \
    --output=/var/backups/it-docs/audit-reports/ \
    >> /var/log/it-docs-audit-report.log 2>&1
```

---

## Datenbankstruktur

```sql
CREATE TABLE audit_log (
    id           BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    timestamp    DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
    user_id      INT UNSIGNED NULL,
    username     VARCHAR(255) NULL,
    session_id   VARCHAR(128) NULL,
    ip_address   VARCHAR(45) NOT NULL,
    user_agent   TEXT NULL,

    action_category VARCHAR(64) NOT NULL,
    action_type     VARCHAR(128) NOT NULL,
    object_type     VARCHAR(64) NULL,
    object_id       INT UNSIGNED NULL,
    object_name     VARCHAR(255) NULL,

    old_value    JSON NULL,
    new_value    JSON NULL,
    risk_level   ENUM('low','medium','high','critical') NOT NULL DEFAULT 'low',
    result       ENUM('success','failure') NOT NULL DEFAULT 'success',
    details      TEXT NULL,

    checksum     VARCHAR(64) NOT NULL,

    INDEX idx_timestamp (timestamp),
    INDEX idx_user_id (user_id),
    INDEX idx_ip_address (ip_address),
    INDEX idx_action_type (action_type),
    INDEX idx_risk_level (risk_level),
    INDEX idx_result (result)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Manipulation durch Trigger verhindern
-- (Details in der initialen Migrationsdatei)
```

---

## Für den Auditor: Schnellreferenz

Folgende Informationen sind für externe Auditoren relevant:

**System-Information:**
- Audit-Log: `/admin/audit`
- Hash-Chain-Verifikation: `/admin/audit` → "Integrität prüfen"
- Datenbankschema: `database/migrations/001_initial_schema.sql`
- Aufbewahrungsdauer: konfiguriert in `.env` (`AUDIT_RETENTION_YEARS`)

**Nachweis der Unveränderlichkeit:**
1. Datenbank-Trigger verhindern UPDATE/DELETE
2. Hash-Chain erkennt nachträgliche Manipulationen
3. Audit-Log-Zugriff selbst ist auditiert

**Nachweis der Vollständigkeit:**
- Alle authentifizierten Benutzeraktionen werden erfasst
- Fehlgeschlagene Aktionen werden ebenfalls protokolliert
- Systemereignisse (Konfigurationsänderungen) sind eingeschlossen

**Testverfahren für Auditoren:**
1. Teständerung an einer Dokumentationsseite vornehmen
2. Audit-Log auf den Eintrag prüfen (sollte sofort erscheinen)
3. Alt-Wert und Neu-Wert im Eintrag vergleichen
4. Hash-Chain-Verifikation ausführen → muss ✅ zeigen
5. Versuch, einen Eintrag zu löschen → muss fehlschlagen (DB-Trigger)