Files
certvia/prisma/import-managed.ts
T
msolarczekandClaude Opus 4.8 e2b6dee788 Richtlinien Phase 2a: verwaltete Register-Tabellen + Anwender-Handbuch (§7b, §9.7)
Vier neue, in die Bibliothek integrierte Dokumente (öffnen als interaktive Popups):

- Krypto-Register (VA-07): editierbare Tabelle der eingesetzten Verschlüsselung
  mit Ablaufüberwachung (läuft-ab/abgelaufen-Markierung), Baseline-Bezug
  (BL-CRY-…), voller CRUD (add/edit/delete).
- Risiko-Bewertungsmatrix (R03/VA-09): zentrale Pflegestelle mit FB-80-04-Defaults
  — 4×4-Heatmap (Schadensausmaß × EW), Risikoklassen + Akzeptanzinstanz
  (Niedrig≤3 / Mittel≤6 / Hoch≤9 / Kritisch≥12, editierbar), EW-Skala,
  7 Schadenskategorien. (Rückkopplung in die 5×5-Heatmap der Risikoanalyse folgt.)
- Klassifizierungs-Handhabungsmatrix (R02/VA-08): AA-80-20-Defaults, 4 Schutzklassen
  × 14 Aspekte mit eskalierenden Regeln; Zellen inline editierbar, Aspekt/Klasse
  hinzufügbar.
- Anwender-Handbuch (§9.7): kuratierte Themen (Passwörter/MFA, Zugriffsrechte,
  Klassifizierung, mobiles Arbeiten, E-Mail, Vorfälle) in verständlicher Sprache;
  Werte via {{VARIABLE}}-Templating → automatisch synchron zur Baseline; Deep-Links
  in die Quellabschnitte (z. B. R08 §4.1.2).

Zusätzlich: Richtlinien/Verfahren, die eine verwaltete Tabelle einbetten
(R09/VA-07, R03/VA-09, R02/VA-08), zeigen im Lesemodus einen „Vollständige Tabelle
öffnen"-Callout auf das jeweilige Register (§7b).

Datenmodell: CryptoEntry, ClassificationClass/HandlingAspect/HandlingRule,
RiskMatrixClass/RiskEwLevel/RiskDamageDimension, HandbookTopic (+ RLS).
Server-Actions mit RBAC (policy:write) + Audit-Log. Seed aus FB-80-04/AA-80-20.

Verifiziert im Browser: alle vier Register gerendert, Ablauf-/Farb-/Deep-Link-Logik,
Krypto-CRUD (add + delete) end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:55:46 +02:00

172 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PrismaClient } from "@prisma/client";
/**
* Seed der verwalteten Register-Tabellen (§7b) und des Anwender-Handbuchs (§9.7).
* Defaults aus den Auftraggeber-Vorgaben FB-80-04 (Risikomatrix) und
* AA-80-20 (Klassifizierung/Handhabung). Idempotent pro Mandant.
*/
const NOW = "2026-07-07T00:00:00.000Z";
const daysFrom = (base: string, d: number) => new Date(new Date(base).getTime() + d * 86400000);
// Klassifizierungs-Handhabungsmatrix (AA-80-20) — Reihenfolge: Offen · Intern · Vertraulich · Streng vertraulich
const CLASSES = [
{ name: "Offen", description: "Öffentlich; keine Schutzanforderungen." },
{ name: "Intern", description: "Nur für Mitarbeitende; geringer Schutzbedarf." },
{ name: "Vertraulich", description: "Begrenzter Personenkreis; hoher Schutzbedarf." },
{ name: "Streng vertraulich", description: "Namentlich Berechtigte; sehr hoher Schutzbedarf." },
];
// [Offen, Intern, Vertraulich, Streng vertraulich]
const ASPECTS: { name: string; category: string; rules: [string, string, string, string] }[] = [
{ name: "Kennzeichnung (Papier/elektronisch)", category: "Kennzeichnung", rules: ["nicht nötig", "„Intern“ empfohlen", "„Vertraulich“ verpflichtend", "„Streng vertraulich“ + Bearbeiter"] },
{ name: "Vervielfältigung", category: "Handhabung", rules: ["frei", "im Bedarfsfall", "nur mit Freigabe", "nur mit Freigabe, protokolliert"] },
{ name: "Weitergabe intern", category: "Weitergabe", rules: ["frei", "an Mitarbeitende", "an Berechtigte, need-to-know", "namentlich Berechtigte, dokumentiert"] },
{ name: "Postweg / Versand", category: "Weitergabe", rules: ["Standard", "Standard", "verschlossen, persönlich/Einschreiben", "Einschreiben eigenhändig, Empfangsbestätigung"] },
{ name: "E-Mail", category: "Weitergabe", rules: ["ohne Auflagen", "intern ohne Auflagen", "extern verschlüsselt (BL-CRY-04)", "immer verschlüsselt, nur an Berechtigte"] },
{ name: "Fax intern/extern", category: "Weitergabe", rules: ["zulässig", "zulässig", "nur mit Ankündigung/Abholung", "unzulässig"] },
{ name: "Mobile Datenträger", category: "Speicherung", rules: ["zulässig", "nur freigegebene", "nur verschlüsselt (BL-CRY-03)", "nur verschlüsselt, freigegeben, protokolliert"] },
{ name: "Internet / Blogs / Social Media", category: "Weitergabe", rules: ["zulässig", "kein interner Bezug", "unzulässig", "unzulässig"] },
{ name: "Entsorgung Papier", category: "Entsorgung", rules: ["Altpapier", "Datentonne", "Aktenvernichter ≥ P-4", "Aktenvernichter ≥ P-5, protokolliert"] },
{ name: "Löschung elektronisch / Hardware", category: "Entsorgung", rules: ["normal löschen", "normal löschen", "sicheres Löschen (BL-DEL-01)", "sicheres Löschen/Vernichten, Nachweis"] },
{ name: "Speicherung (IT-Systeme)", category: "Speicherung", rules: ["beliebig", "interne Systeme", "zugriffsbeschränkte Ablagen", "verschlüsselt, streng zugriffsbeschränkt"] },
{ name: "Homeoffice / mobiles Arbeiten", category: "Nutzung", rules: ["zulässig", "zulässig", "nur mit Sichtschutz, gesperrt bei Abwesenheit", "nur nach Freigabe, keine Papierform"] },
{ name: "Öffentlicher Bereich / Dienstreise", category: "Nutzung", rules: ["zulässig", "diskret", "Sichtschutz, nicht unbeaufsichtigt", "unzulässig (kritische Länder)"] },
{ name: "Verbale Weitergabe", category: "Weitergabe", rules: ["frei", "unter Kollegen", "nur in geschützter Umgebung", "nur namentlich Berechtigte, abhörsicher"] },
];
// Risiko-Bewertungsmatrix (FB-80-04): Schadensausmaß(14) × EW(14) = 116
const RISK_CLASSES = [
{ name: "Niedrig", maxScore: 3, acceptance: "wird akzeptiert", tone: "ok" },
{ name: "Mittel", maxScore: 6, acceptance: "Maßnahmen; Akzeptanz durch Risk Owner", tone: "warn" },
{ name: "Hoch", maxScore: 9, acceptance: "Akzeptanz durch Risikomanagement-Gremium", tone: "orange" },
{ name: "Kritisch", maxScore: 16, acceptance: "Akzeptanz durch Geschäftsführung", tone: "risk" },
];
const EW_LEVELS = [
{ level: 1, label: "Unwahrscheinlich", definition: "Ereignis in absehbarer Zeit kaum zu erwarten (seltener als alle 5 Jahre)." },
{ level: 2, label: "Gelegentlich", definition: "Ereignis denkbar, aber nicht häufig (ca. alle 15 Jahre)." },
{ level: 3, label: "Wahrscheinlich", definition: "Ereignis tritt voraussichtlich ein (mehrmals pro Jahr)." },
{ level: 4, label: "Sehr wahrscheinlich", definition: "Ereignis tritt regelmäßig/häufig ein (monatlich oder öfter)." },
];
const DAMAGE_DIMS: { name: string; levels: Record<string, string> }[] = [
{ name: "Gesetzes-/Vertragsverstöße", levels: { "1": "unerheblich", "2": "geringfügige Verstöße", "3": "erhebliche Verstöße/Bußgelder", "4": "gravierende Rechtsfolgen/Straftatbestand" } },
{ name: "Datenschutz", levels: { "1": "kein Personenbezug", "2": "geringe Betroffenheit", "3": "sensible Daten betroffen", "4": "massenhaft sensible Daten / hohe Betroffenheit" } },
{ name: "Persönliche Unversehrtheit", levels: { "1": "keine", "2": "leichte Beeinträchtigung", "3": "Gesundheitsgefahr", "4": "Gefahr für Leib und Leben" } },
{ name: "Aufgabenerfüllung", levels: { "1": "keine Einschränkung", "2": "geringe Einschränkung", "3": "erhebliche Einschränkung", "4": "Kernaufgaben nicht erfüllbar" } },
{ name: "Innen-/Außenwirkung (Reputation)", levels: { "1": "keine", "2": "intern begrenzt", "3": "öffentlich wahrnehmbar", "4": "nachhaltiger Reputationsschaden" } },
{ name: "Störungs-/Ausfallzeit", levels: { "1": "< 1 Std.", "2": "bis 1 Tag", "3": "bis 1 Woche", "4": "> 1 Woche" } },
{ name: "Finanzielle Auswirkungen", levels: { "1": "bis 10 T€", "2": "bis 50 T€", "3": "bis 200 T€", "4": "über 500 T€" } },
];
// Krypto-Register (VA-07) — Demo-Datensätze inkl. Ablaufüberwachung
const CRYPTO = [
{ dienst: "TLS Web-Portal (portal.gefim.example)", schluessel: "RSA 3072 · Let's Encrypt", algorithmus: "TLS 1.3 / AES-256", ablaufOffset: 45, verantwortlich: "IT-Leitung", speicherort: "Reverse Proxy", baselineRef: "BL-CRY-01" },
{ dienst: "VPN-Gateway", schluessel: "ECDSA P-256 · interne CA", algorithmus: "IKEv2 / AES-256-GCM", ablaufOffset: 12, verantwortlich: "IT-Leitung", speicherort: "Firewall/HSM", baselineRef: "BL-CRY-02" },
{ dienst: "Festplattenverschlüsselung Notebooks", schluessel: "BitLocker · TPM 2.0", algorithmus: "AES-256-XTS", ablaufOffset: null, verantwortlich: "IT-Betrieb", speicherort: "MDM-Recovery", baselineRef: "BL-CRY-03" },
{ dienst: "S/MIME Zertifikate Geschäftsführung", schluessel: "RSA 2048 · öffentliche CA", algorithmus: "S/MIME", ablaufOffset: -8, verantwortlich: "IT-Leitung", speicherort: "Smartcard", baselineRef: "BL-CRY-04" },
];
// Anwender-Handbuch (§9.7) — kuratierte Themen, Werte via {{VARIABLE}} synchron zur Baseline
const HANDBOOK: { category: string; title: string; bodyMd: string; sourceRefs: string[] }[] = [
{
category: "Zugang & Passwörter", title: "Passwörter & Mehr-Faktor-Anmeldung",
bodyMd: "**Das Wichtigste kurz:**\n\n- Nutze Passwörter mit **mindestens {{PW_MIN_LENGTH}} Zeichen** ({{PW_COMPLEXITY}}).\n- Aktiviere **Mehr-Faktor-Authentifizierung (MFA)** über {{TECH_MFA}} für {{MFA_SCOPE}}.\n- Gib Passwörter **nie** weiter und nutze für jeden Dienst ein eigenes.\n- Bei Verdacht auf Kompromittierung: Passwort sofort ändern und melden.",
sourceRefs: ["R08#4.1.2"],
},
{
category: "Zugang & Passwörter", title: "Zugriffsrechte nur was du brauchst",
bodyMd: "- Zugriff erhältst du **nach dem Minimalprinzip** (need-to-know) über {{TOOL_TICKET}}.\n- Nicht mehr benötigte Rechte werden entzogen; deine Rechte werden regelmäßig **überprüft (Rezertifizierung, {{RECERT_FREQ}})**.\n- Sperre deinen Bildschirm bei jedem Verlassen des Arbeitsplatzes.",
sourceRefs: ["R08#4.2.1"],
},
{
category: "Umgang mit Informationen", title: "Daten richtig klassifizieren & handhaben",
bodyMd: "- Behandle Informationen gemäß ihrer **Schutzklasse** (Offen · Intern · Vertraulich · Streng vertraulich).\n- **Vertrauliche** Inhalte extern nur **verschlüsselt** versenden.\n- Dokumente nicht offen liegen lassen (Clean Desk); vertrauliche Ausdrucke sicher vernichten.",
sourceRefs: ["R02"],
},
{
category: "Sicheres Arbeiten", title: "Mobiles Arbeiten & Homeoffice",
bodyMd: "- Nutze nur **freigegebene Geräte** (verwaltet über {{TECH_MDM}}).\n- Achte auf **Sichtschutz** in der Öffentlichkeit; keine vertraulichen Gespräche in Bahn/Café.\n- Verbinde dich extern nur über {{TECH_VPN}} mit MFA.",
sourceRefs: ["R06"],
},
{
category: "Sicheres Arbeiten", title: "E-Mail, Internet & Schadsoftware",
bodyMd: "- Öffne **keine unerwarteten Anhänge/Links**; im Zweifel nachfragen.\n- Melde verdächtige E-Mails (Phishing) über den Meldeweg.\n- {{TECH_MALWARE}} schützt deine Geräte deaktiviere den Schutz nie.",
sourceRefs: ["R10", "R09"],
},
{
category: "Vorfälle", title: "Sicherheitsvorfall? So meldest du ihn",
bodyMd: "**Sofort melden** bei Verdacht auf Vorfall, Datenverlust oder verlorenem Gerät:\n\n- an die {{ROLE_ISB}} bzw. den definierten Meldeweg.\n- Lieber einmal zu viel melden schnelle Meldung begrenzt den Schaden.\n- Nichts vertuschen, nichts eigenmächtig „reparieren“.",
sourceRefs: ["R04"],
},
];
export async function importManaged(prisma: PrismaClient, tenantId: string) {
// Idempotent: alte Datensätze pro Mandant entfernen
await prisma.handlingRule.deleteMany({ where: { tenantId } });
await prisma.handlingAspect.deleteMany({ where: { tenantId } });
await prisma.classificationClass.deleteMany({ where: { tenantId } });
await prisma.cryptoEntry.deleteMany({ where: { tenantId } });
await prisma.riskMatrixClass.deleteMany({ where: { tenantId } });
await prisma.riskEwLevel.deleteMany({ where: { tenantId } });
await prisma.riskDamageDimension.deleteMany({ where: { tenantId } });
await prisma.handbookTopic.deleteMany({ where: { tenantId } });
// Register-/Handbuch-Dokumente in der Bibliothek (interaktiv gerendert)
const registerDocs = [
{ code: "CRYPTO", type: "REGISTER" as const, title: "Verschlüsselungsmechanismen-Register", orderIdx: 902, intro: "Verwaltetes Register der eingesetzten Verschlüsselung (VA-07). Ablaufüberwachung aktiv." },
{ code: "RISKMATRIX", type: "REGISTER" as const, title: "Risiko-Bewertungsmatrix", orderIdx: 903, intro: "Zentrale Bewertungsmatrix Schadensausmaß × Eintrittswahrscheinlichkeit (R03 / VA-09), Defaults nach FB-80-04." },
{ code: "CLASSIFICATION", type: "REGISTER" as const, title: "Klassifizierung & Handhabungsmatrix", orderIdx: 904, intro: "Schutzklassen und Handhabungsregeln (R02 / VA-08), Defaults nach AA-80-20." },
{ code: "HANDBUCH", type: "HANDBUCH" as const, title: "Anwender-Handbuch Informationssicherheit", orderIdx: 950, intro: "Kompakte Zusammenfassung der wichtigsten Regeln für Mitarbeitende mit Deep-Links in die Quelldokumente." },
];
for (const d of registerDocs) {
await prisma.policyDocument.upsert({
where: { tenantId_code: { tenantId, code: d.code } },
update: { title: d.title, type: d.type, orderIdx: d.orderIdx, rawMarkdown: `# ${d.title}\n\n${d.intro}` },
create: { tenantId, code: d.code, type: d.type, title: d.title, version: "1.0", status: "FREIGEGEBEN", orderIdx: d.orderIdx, rawMarkdown: `# ${d.title}\n\n${d.intro}` },
});
}
// Krypto-Register
for (let i = 0; i < CRYPTO.length; i++) {
const c = CRYPTO[i];
await prisma.cryptoEntry.create({
data: {
tenantId, dienst: c.dienst, schluessel: c.schluessel, algorithmus: c.algorithmus,
ablaufdatum: c.ablaufOffset === null ? null : daysFrom(NOW, c.ablaufOffset),
verantwortlich: c.verantwortlich, speicherort: c.speicherort, baselineRef: c.baselineRef, orderIdx: i,
},
});
}
// Klassifizierung + Handhabungsmatrix
const classIds: string[] = [];
for (let i = 0; i < CLASSES.length; i++) {
const cl = await prisma.classificationClass.create({ data: { tenantId, name: CLASSES[i].name, description: CLASSES[i].description, orderIdx: i } });
classIds.push(cl.id);
}
for (let a = 0; a < ASPECTS.length; a++) {
const asp = await prisma.handlingAspect.create({ data: { tenantId, name: ASPECTS[a].name, category: ASPECTS[a].category, orderIdx: a } });
for (let c = 0; c < classIds.length; c++) {
await prisma.handlingRule.create({ data: { tenantId, classId: classIds[c], aspectId: asp.id, text: ASPECTS[a].rules[c] } });
}
}
// Risiko-Bewertungsmatrix
for (let i = 0; i < RISK_CLASSES.length; i++) {
const r = RISK_CLASSES[i];
await prisma.riskMatrixClass.create({ data: { tenantId, name: r.name, maxScore: r.maxScore, acceptance: r.acceptance, tone: r.tone, orderIdx: i } });
}
for (const ew of EW_LEVELS) await prisma.riskEwLevel.create({ data: { tenantId, level: ew.level, label: ew.label, definition: ew.definition } });
for (let i = 0; i < DAMAGE_DIMS.length; i++) {
await prisma.riskDamageDimension.create({ data: { tenantId, name: DAMAGE_DIMS[i].name, levels: DAMAGE_DIMS[i].levels, orderIdx: i } });
}
// Anwender-Handbuch
for (let i = 0; i < HANDBOOK.length; i++) {
const h = HANDBOOK[i];
await prisma.handbookTopic.create({ data: { tenantId, category: h.category, title: h.title, bodyMd: h.bodyMd, sourceRefs: h.sourceRefs, orderIdx: i } });
}
return { crypto: CRYPTO.length, classes: CLASSES.length, aspects: ASPECTS.length, damage: DAMAGE_DIMS.length, handbook: HANDBOOK.length };
}