diff --git a/admin-compliance/app/(sdk)/sdk/gci/page.tsx b/admin-compliance/app/(sdk)/sdk/gci/page.tsx new file mode 100644 index 0000000..8eed1fe --- /dev/null +++ b/admin-compliance/app/(sdk)/sdk/gci/page.tsx @@ -0,0 +1,693 @@ +'use client' + +import React, { useState, useEffect, useCallback } from 'react' +import { + GCIResult, + GCIBreakdown, + GCIHistoryResponse, + GCIMatrixResponse, + NIS2Score, + ISOGapAnalysis, + WeightProfile, + MaturityLevel, + MATURITY_INFO, + getScoreColor, + getScoreRingColor, +} from '@/lib/sdk/gci/types' +import { + getGCIScore, + getGCIBreakdown, + getGCIHistory, + getGCIMatrix, + getNIS2Score, + getISOGapAnalysis, + getWeightProfiles, +} from '@/lib/sdk/gci/api' + +// ============================================================================= +// TYPES +// ============================================================================= + +type TabId = 'overview' | 'breakdown' | 'nis2' | 'iso' | 'matrix' | 'audit' + +interface Tab { + id: TabId + label: string +} + +const TABS: Tab[] = [ + { id: 'overview', label: 'Uebersicht' }, + { id: 'breakdown', label: 'Breakdown' }, + { id: 'nis2', label: 'NIS2' }, + { id: 'iso', label: 'ISO 27001' }, + { id: 'matrix', label: 'Matrix' }, + { id: 'audit', label: 'Audit Trail' }, +] + +// ============================================================================= +// HELPER COMPONENTS +// ============================================================================= + +function TabNavigation({ tabs, activeTab, onTabChange }: { tabs: Tab[]; activeTab: TabId; onTabChange: (tab: TabId) => void }) { + return ( +
+ +
+ ) +} + +function ScoreCircle({ score, size = 144, label }: { score: number; size?: number; label?: string }) { + const radius = (size / 2) - 12 + const circumference = 2 * Math.PI * radius + const strokeDashoffset = circumference - (score / 100) * circumference + + return ( +
+ + + + +
+ {score.toFixed(1)} + {label && {label}} +
+
+ ) +} + +function MaturityBadge({ level }: { level: MaturityLevel }) { + const info = MATURITY_INFO[level] || MATURITY_INFO.HIGH_RISK + return ( + + {info.label} + + ) +} + +function AreaScoreBar({ name, score, weight }: { name: string; score: number; weight: number }) { + return ( +
+
+ {name} + {score.toFixed(1)}% +
+
+
+
+
Gewichtung: {(weight * 100).toFixed(0)}%
+
+ ) +} + +function LoadingSpinner() { + return ( +
+
+
+ ) +} + +function ErrorMessage({ message, onRetry }: { message: string; onRetry?: () => void }) { + return ( +
+

{message}

+ {onRetry && ( + + )} +
+ ) +} + +// ============================================================================= +// TAB: OVERVIEW +// ============================================================================= + +function OverviewTab({ gci, history, profiles, selectedProfile, onProfileChange }: { + gci: GCIResult + history: GCIHistoryResponse | null + profiles: WeightProfile[] + selectedProfile: string + onProfileChange: (p: string) => void +}) { + return ( +
+ {/* Profile Selector */} + {profiles.length > 0 && ( +
+ + +
+ )} + + {/* Main Score */} +
+
+ +
+
+

Gesamt-Compliance-Index

+
+ + + Berechnet: {new Date(gci.calculated_at).toLocaleString('de-DE')} + +
+
+

+ {MATURITY_INFO[gci.maturity_level]?.description || ''} +

+
+
+
+ + {/* Area Scores */} +
+

Regulierungsbereiche

+
+ {gci.area_scores.map(area => ( + + ))} +
+
+ + {/* History Chart (simplified) */} + {history && history.snapshots.length > 0 && ( +
+

Verlauf

+
+ {history.snapshots.map((snap, i) => ( +
+ {snap.score.toFixed(0)} +
+ + {new Date(snap.calculated_at).toLocaleDateString('de-DE', { month: 'short' })} + +
+ ))} +
+
+ )} + + {/* Adjustments */} +
+
+
Kritikalitaets-Multiplikator
+
{gci.criticality_multiplier.toFixed(2)}x
+
+
+
Incident-Korrektur
+
+ {gci.incident_adjustment > 0 ? '+' : ''}{gci.incident_adjustment.toFixed(1)} +
+
+
+
+ ) +} + +// ============================================================================= +// TAB: BREAKDOWN +// ============================================================================= + +function BreakdownTab({ breakdown }: { breakdown: GCIBreakdown | null; loading: boolean }) { + if (!breakdown) return + + return ( +
+ {/* Level 1: Modules */} +
+

Level 1: Modul-Scores

+
+ + + + + + + + + + + + + + {breakdown.level1_modules.map(m => ( + + + + + + + + + + ))} + +
ModulKategorieZugewiesenAbgeschlossenRaw ScoreValiditaetFinal
{m.module_name} + + {m.category} + + {m.assigned}{m.completed}{(m.raw_score * 100).toFixed(1)}%{(m.validity_factor * 100).toFixed(0)}% + {(m.final_score * 100).toFixed(1)}% +
+
+
+ + {/* Level 2: Areas */} +
+

Level 2: Regulierungsbereiche (risikogewichtet)

+
+ {breakdown.level2_areas.map(area => ( +
+
+

{area.area_name}

+ + {area.area_score.toFixed(1)}% + +
+
+ {area.modules.map(m => ( +
+ {m.module_name} + {(m.final_score * 100).toFixed(0)}% (w:{m.risk_weight.toFixed(1)}) +
+ ))} +
+
+ ))} +
+
+
+ ) +} + +// ============================================================================= +// TAB: NIS2 +// ============================================================================= + +function NIS2Tab({ nis2 }: { nis2: NIS2Score | null }) { + if (!nis2) return + + return ( +
+ {/* NIS2 Overall */} +
+
+ +
+

NIS2 Compliance Score

+

+ Network and Information Security Directive 2 (EU 2022/2555) +

+
+
+
+ + {/* NIS2 Areas */} +
+

NIS2 Bereiche

+
+ {nis2.areas.map(area => ( + + ))} +
+
+ + {/* NIS2 Roles */} + {nis2.role_scores && nis2.role_scores.length > 0 && ( +
+

Rollen-Compliance

+
+ {nis2.role_scores.map(role => ( +
+
{role.role_name}
+
+ + {(role.completion_rate * 100).toFixed(0)}% + + + {role.modules_completed}/{role.modules_required} Module + +
+
+
+
+
+ ))} +
+
+ )} +
+ ) +} + +// ============================================================================= +// TAB: ISO 27001 +// ============================================================================= + +function ISOTab({ iso }: { iso: ISOGapAnalysis | null }) { + if (!iso) return + + return ( +
+ {/* Coverage Overview */} +
+
+ +
+

ISO 27001:2022 Gap-Analyse

+
+
+
{iso.covered_full}
+
Voll abgedeckt
+
+
+
{iso.covered_partial}
+
Teilweise
+
+
+
{iso.not_covered}
+
Nicht abgedeckt
+
+
+
+
+
+ + {/* Category Summaries */} +
+

Kategorien

+
+ {iso.category_summaries.map(cat => { + const coveragePercent = cat.total_controls > 0 + ? ((cat.covered_full + cat.covered_partial * 0.5) / cat.total_controls) * 100 + : 0 + return ( +
+
+ {cat.category_id}: {cat.category_name} + + {cat.covered_full}/{cat.total_controls} Controls + +
+
+
+
+
+
+ ) + })} +
+
+ + {/* Gaps */} + {iso.gaps && iso.gaps.length > 0 && ( +
+

+ Offene Gaps ({iso.gaps.length}) +

+
+ {iso.gaps.map(gap => ( +
+ + {gap.priority} + +
+
{gap.control_id}: {gap.control_name}
+
{gap.recommendation}
+
+
+ ))} +
+
+ )} +
+ ) +} + +// ============================================================================= +// TAB: MATRIX +// ============================================================================= + +function MatrixTab({ matrix }: { matrix: GCIMatrixResponse | null }) { + if (!matrix || !matrix.matrix) return + + const regulations = matrix.matrix.length > 0 ? Object.keys(matrix.matrix[0].regulations) : [] + + return ( +
+
+

Compliance-Matrix (Rollen x Regulierungen)

+
+ + + + + {regulations.map(r => ( + + ))} + + + + + + {matrix.matrix.map(entry => ( + + + {regulations.map(r => ( + + ))} + + + + ))} + +
Rolle{r}GesamtModule
{entry.role_name} + + {entry.regulations[r].toFixed(0)}% + + + + {entry.overall_score.toFixed(0)}% + + + {entry.completed_modules}/{entry.required_modules} +
+
+
+
+ ) +} + +// ============================================================================= +// TAB: AUDIT TRAIL +// ============================================================================= + +function AuditTab({ gci }: { gci: GCIResult }) { + return ( +
+
+

+ Audit Trail - Berechnung GCI {gci.gci_score.toFixed(1)} +

+

+ Jeder Schritt der GCI-Berechnung ist nachvollziehbar und prueffaehig dokumentiert. +

+
+ {gci.audit_trail.map((entry, i) => ( +
+
+
+
+ {entry.factor} + + {entry.value > 0 ? '+' : ''}{entry.value.toFixed(2)} + +
+

{entry.description}

+
+
+ ))} +
+
+
+ ) +} + +// ============================================================================= +// MAIN PAGE +// ============================================================================= + +export default function GCIPage() { + const [activeTab, setActiveTab] = useState('overview') + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const [gci, setGCI] = useState(null) + const [breakdown, setBreakdown] = useState(null) + const [history, setHistory] = useState(null) + const [matrix, setMatrix] = useState(null) + const [nis2, setNIS2] = useState(null) + const [iso, setISO] = useState(null) + const [profiles, setProfiles] = useState([]) + const [selectedProfile, setSelectedProfile] = useState('default') + + const loadData = useCallback(async (profile?: string) => { + setLoading(true) + setError(null) + try { + const [gciRes, historyRes, profilesRes] = await Promise.all([ + getGCIScore(profile), + getGCIHistory(), + getWeightProfiles(), + ]) + setGCI(gciRes) + setHistory(historyRes) + setProfiles(profilesRes.profiles || []) + } catch (err: any) { + setError(err.message || 'Fehler beim Laden der GCI-Daten') + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + loadData(selectedProfile) + }, [selectedProfile, loadData]) + + // Lazy-load tab data + useEffect(() => { + if (activeTab === 'breakdown' && !breakdown && gci) { + getGCIBreakdown(selectedProfile).then(setBreakdown).catch(() => {}) + } + if (activeTab === 'nis2' && !nis2) { + getNIS2Score().then(setNIS2).catch(() => {}) + } + if (activeTab === 'iso' && !iso) { + getISOGapAnalysis().then(setISO).catch(() => {}) + } + if (activeTab === 'matrix' && !matrix) { + getGCIMatrix().then(setMatrix).catch(() => {}) + } + }, [activeTab, breakdown, nis2, iso, matrix, gci, selectedProfile]) + + const handleProfileChange = (profile: string) => { + setSelectedProfile(profile) + setBreakdown(null) // reset breakdown to reload + } + + return ( +
+ {/* Header */} +
+
+

Gesamt-Compliance-Index (GCI)

+

+ 4-stufiges, mathematisch fundiertes Compliance-Scoring +

+
+ +
+ + {/* Tabs */} + + + {/* Content */} + {error && loadData(selectedProfile)} />} + + {loading && !gci ? ( + + ) : gci ? ( +
+ {activeTab === 'overview' && ( + + )} + {activeTab === 'breakdown' && ( + + )} + {activeTab === 'nis2' && } + {activeTab === 'iso' && } + {activeTab === 'matrix' && } + {activeTab === 'audit' && } +
+ ) : null} +
+ ) +} diff --git a/admin-compliance/app/api/sdk/v1/gci/[[...path]]/route.ts b/admin-compliance/app/api/sdk/v1/gci/[[...path]]/route.ts new file mode 100644 index 0000000..65b7724 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/gci/[[...path]]/route.ts @@ -0,0 +1,89 @@ +/** + * GCI API Proxy - Catch-all route + * Proxies all /api/sdk/v1/gci/* requests to ai-compliance-sdk backend + */ + +import { NextRequest, NextResponse } from 'next/server' + +const SDK_BACKEND_URL = process.env.SDK_API_URL || 'http://ai-compliance-sdk:8090' + +async function proxyRequest( + request: NextRequest, + pathSegments: string[] | undefined, + method: string +) { + const pathStr = pathSegments?.join('/') || '' + const searchParams = request.nextUrl.searchParams.toString() + const basePath = `${SDK_BACKEND_URL}/sdk/v1/gci` + const url = pathStr + ? `${basePath}/${pathStr}${searchParams ? `?${searchParams}` : ''}` + : `${basePath}${searchParams ? `?${searchParams}` : ''}` + + try { + const headers: HeadersInit = { + 'Content-Type': 'application/json', + } + + const headerNames = ['authorization', 'x-tenant-id', 'x-user-id', 'x-namespace-id', 'x-tenant-slug'] + for (const name of headerNames) { + const value = request.headers.get(name) + if (value) { + headers[name] = value + } + } + + const fetchOptions: RequestInit = { + method, + headers, + signal: AbortSignal.timeout(30000), + } + + if (method === 'POST' || method === 'PUT') { + const body = await request.text() + if (body) { + fetchOptions.body = body + } + } + + const response = await fetch(url, fetchOptions) + + if (!response.ok) { + const errorText = await response.text() + let errorJson + try { + errorJson = JSON.parse(errorText) + } catch { + errorJson = { error: errorText } + } + return NextResponse.json( + { error: `Backend Error: ${response.status}`, ...errorJson }, + { status: response.status } + ) + } + + const data = await response.json() + return NextResponse.json(data) + } catch (error) { + console.error('GCI API proxy error:', error) + return NextResponse.json( + { error: 'Verbindung zum SDK Backend fehlgeschlagen' }, + { status: 503 } + ) + } +} + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ path?: string[] }> } +) { + const { path } = await params + return proxyRequest(request, path, 'GET') +} + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ path?: string[] }> } +) { + const { path } = await params + return proxyRequest(request, path, 'POST') +} diff --git a/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx b/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx index dd8f7e3..7ac5b94 100644 --- a/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx +++ b/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx @@ -561,6 +561,20 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP isActive={pathname === '/sdk/reporting'} collapsed={collapsed} /> + + + + + } + label="GCI Score" + isActive={pathname === '/sdk/gci'} + collapsed={collapsed} + /> (path: string, options?: RequestInit): Promise { + const res = await fetch(`${BASE_URL}${path}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + 'X-Tenant-ID': typeof window !== 'undefined' + ? (localStorage.getItem('bp-tenant-id') || 'default') + : 'default', + ...options?.headers, + }, + }) + + if (!res.ok) { + const error = await res.json().catch(() => ({ error: res.statusText })) + throw new Error(error.error || `API Error: ${res.status}`) + } + + return res.json() +} + +/** GCI Score abrufen */ +export async function getGCIScore(profile?: string): Promise { + const params = profile ? `?profile=${profile}` : '' + return apiFetch(`/score${params}`) +} + +/** Detailliertes 4-Level Breakdown abrufen */ +export async function getGCIBreakdown(profile?: string): Promise { + const params = profile ? `?profile=${profile}` : '' + return apiFetch(`/score/breakdown${params}`) +} + +/** GCI History abrufen */ +export async function getGCIHistory(): Promise { + return apiFetch('/score/history') +} + +/** Compliance Matrix abrufen */ +export async function getGCIMatrix(): Promise { + return apiFetch('/matrix') +} + +/** Audit Trail abrufen */ +export async function getGCIAuditTrail(profile?: string): Promise<{ tenant_id: string; gci_score: number; audit_trail: any[] }> { + const params = profile ? `?profile=${profile}` : '' + return apiFetch(`/audit-trail${params}`) +} + +/** Gewichtungsprofile abrufen */ +export async function getWeightProfiles(): Promise<{ profiles: WeightProfile[] }> { + return apiFetch<{ profiles: WeightProfile[] }>('/profiles') +} + +/** NIS2 Score abrufen */ +export async function getNIS2Score(): Promise { + return apiFetch('/nis2/score') +} + +/** NIS2 Rollen auflisten */ +export async function getNIS2Roles(): Promise<{ roles: NIS2Role[]; total: number }> { + return apiFetch<{ roles: NIS2Role[]; total: number }>('/nis2/roles') +} + +/** NIS2 Rolle zuweisen */ +export async function assignNIS2Role(roleId: string, userId: string): Promise { + return apiFetch('/nis2/roles/assign', { + method: 'POST', + body: JSON.stringify({ role_id: roleId, user_id: userId }), + }) +} + +/** ISO Gap-Analyse abrufen */ +export async function getISOGapAnalysis(): Promise { + return apiFetch('/iso/gap-analysis') +} + +/** ISO Mappings abrufen */ +export async function getISOMappings(category?: string): Promise { + const params = category ? `?category=${category}` : '' + return apiFetch(`/iso/mappings${params}`) +} diff --git a/admin-compliance/lib/sdk/gci/types.ts b/admin-compliance/lib/sdk/gci/types.ts new file mode 100644 index 0000000..50486b6 --- /dev/null +++ b/admin-compliance/lib/sdk/gci/types.ts @@ -0,0 +1,246 @@ +/** + * GCI (Gesamt-Compliance-Index) Types + * TypeScript definitions for the 4-level compliance scoring model + */ + +// ============================================================================= +// MATURITY LEVELS +// ============================================================================= + +export type MaturityLevel = 'OPTIMIZED' | 'MANAGED' | 'DEFINED' | 'REACTIVE' | 'HIGH_RISK' + +export const MATURITY_INFO: Record = { + OPTIMIZED: { label: 'Optimiert', color: 'text-green-700', bgColor: 'bg-green-100', borderColor: 'border-green-300', description: 'Kontinuierliche Verbesserung, proaktive Compliance' }, + MANAGED: { label: 'Gesteuert', color: 'text-blue-700', bgColor: 'bg-blue-100', borderColor: 'border-blue-300', description: 'Messbare Prozesse, regelmaessige Reviews' }, + DEFINED: { label: 'Definiert', color: 'text-yellow-700', bgColor: 'bg-yellow-100', borderColor: 'border-yellow-300', description: 'Dokumentierte Prozesse, erste Strukturen' }, + REACTIVE: { label: 'Reaktiv', color: 'text-orange-700', bgColor: 'bg-orange-100', borderColor: 'border-orange-300', description: 'Ad-hoc Massnahmen, wenig Struktur' }, + HIGH_RISK: { label: 'Hohes Risiko', color: 'text-red-700', bgColor: 'bg-red-100', borderColor: 'border-red-300', description: 'Erheblicher Handlungsbedarf, Compliance-Luecken' }, +} + +// ============================================================================= +// LEVEL 1: MODULE SCORE +// ============================================================================= + +export interface ModuleScore { + module_id: string + module_name: string + assigned: number + completed: number + raw_score: number + validity_factor: number + final_score: number + risk_weight: number + category: string +} + +// ============================================================================= +// LEVEL 2: RISK-WEIGHTED AREA SCORE +// ============================================================================= + +export interface RiskWeightedScore { + area_id: string + area_name: string + modules: ModuleScore[] + weighted_sum: number + total_weight: number + area_score: number +} + +// ============================================================================= +// LEVEL 3: REGULATION AREA SCORE +// ============================================================================= + +export interface RegulationAreaScore { + regulation_id: string + regulation_name: string + score: number + weight: number + weighted_score: number + module_count: number + completed_count: number +} + +// ============================================================================= +// LEVEL 4: GCI RESULT +// ============================================================================= + +export interface AuditEntry { + timestamp: string + factor: string + description: string + value: number + impact: 'positive' | 'negative' | 'neutral' +} + +export interface GCIResult { + tenant_id: string + gci_score: number + maturity_level: MaturityLevel + maturity_label: string + calculated_at: string + profile: string + area_scores: RegulationAreaScore[] + criticality_multiplier: number + incident_adjustment: number + audit_trail: AuditEntry[] +} + +export interface GCIBreakdown extends GCIResult { + level1_modules: ModuleScore[] + level2_areas: RiskWeightedScore[] +} + +// ============================================================================= +// GCI HISTORY +// ============================================================================= + +export interface GCISnapshot { + tenant_id: string + score: number + maturity_level: MaturityLevel + area_scores: Record + calculated_at: string +} + +export interface GCIHistoryResponse { + tenant_id: string + snapshots: GCISnapshot[] + total: number +} + +// ============================================================================= +// COMPLIANCE MATRIX +// ============================================================================= + +export interface ComplianceMatrixEntry { + role: string + role_name: string + regulations: Record + overall_score: number + required_modules: number + completed_modules: number +} + +export interface GCIMatrixResponse { + tenant_id: string + matrix: ComplianceMatrixEntry[] +} + +// ============================================================================= +// NIS2 +// ============================================================================= + +export interface NIS2Role { + id: string + name: string + description: string + mandatory_modules: string[] + priority: number +} + +export interface NIS2AreaScore { + area_id: string + area_name: string + weight: number + score: number + weighted_score: number +} + +export interface NIS2RoleScore { + role_id: string + role_name: string + assigned_users: number + completion_rate: number + modules_completed: number + modules_required: number +} + +export interface NIS2Score { + tenant_id: string + overall_score: number + maturity_level: string + areas: NIS2AreaScore[] + role_scores: NIS2RoleScore[] + calculated_at: string +} + +// ============================================================================= +// ISO 27001 +// ============================================================================= + +export interface ISOControl { + id: string + name: string + description: string + category_id: string + category_name: string + control_type: string + is_critical: boolean + sdk_modules: string[] +} + +export interface ISOGap { + control_id: string + control_name: string + category: string + status: string + priority: string + recommendation: string +} + +export interface ISOCategorySummary { + category_id: string + category_name: string + total_controls: number + covered_full: number + covered_partial: number + not_covered: number +} + +export interface ISOGapAnalysis { + tenant_id: string + total_controls: number + covered_full: number + covered_partial: number + not_covered: number + coverage_percent: number + category_summaries: ISOCategorySummary[] + gaps: ISOGap[] + calculated_at: string +} + +// ============================================================================= +// WEIGHT PROFILES +// ============================================================================= + +export interface WeightProfile { + id: string + name: string + description: string + weights: Record +} + +// ============================================================================= +// HELPERS +// ============================================================================= + +export function getScoreColor(score: number): string { + if (score >= 80) return 'text-green-600' + if (score >= 60) return 'text-yellow-600' + if (score >= 40) return 'text-orange-600' + return 'text-red-600' +} + +export function getScoreBgColor(score: number): string { + if (score >= 80) return 'bg-green-500' + if (score >= 60) return 'bg-yellow-500' + if (score >= 40) return 'bg-orange-500' + return 'bg-red-500' +} + +export function getScoreRingColor(score: number): string { + if (score >= 80) return '#22c55e' + if (score >= 60) return '#eab308' + if (score >= 40) return '#f97316' + return '#ef4444' +} diff --git a/ai-compliance-sdk/cmd/server/main.go b/ai-compliance-sdk/cmd/server/main.go index 87c69b2..46068d7 100644 --- a/ai-compliance-sdk/cmd/server/main.go +++ b/ai-compliance-sdk/cmd/server/main.go @@ -27,6 +27,7 @@ import ( "github.com/breakpilot/ai-compliance-sdk/internal/vendor" "github.com/breakpilot/ai-compliance-sdk/internal/workshop" "github.com/breakpilot/ai-compliance-sdk/internal/portfolio" + "github.com/breakpilot/ai-compliance-sdk/internal/gci" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgxpool" @@ -124,6 +125,10 @@ func main() { industryHandlers := handlers.NewIndustryHandlers() dsbHandlers := handlers.NewDSBHandlers(dsbStore) + // Initialize GCI engine and handlers + gciEngine := gci.NewEngine() + gciHandlers := handlers.NewGCIHandlers(gciEngine) + // Initialize middleware rbacMiddleware := rbac.NewMiddleware(rbacService, policyEngine) @@ -652,6 +657,29 @@ func main() { dsbRoutes.POST("/assignments/:id/communications", dsbHandlers.CreateCommunication) dsbRoutes.GET("/assignments/:id/communications", dsbHandlers.ListCommunications) } + + // GCI routes - Gesamt-Compliance-Index + gciRoutes := v1.Group("/gci") + { + // Core GCI endpoints + gciRoutes.GET("/score", gciHandlers.GetScore) + gciRoutes.GET("/score/breakdown", gciHandlers.GetScoreBreakdown) + gciRoutes.GET("/score/history", gciHandlers.GetHistory) + gciRoutes.GET("/matrix", gciHandlers.GetMatrix) + gciRoutes.GET("/audit-trail", gciHandlers.GetAuditTrail) + gciRoutes.GET("/profiles", gciHandlers.GetWeightProfiles) + + // NIS2 sub-routes + gciRoutes.GET("/nis2/score", gciHandlers.GetNIS2Score) + gciRoutes.GET("/nis2/roles", gciHandlers.ListNIS2Roles) + gciRoutes.POST("/nis2/roles/assign", gciHandlers.AssignNIS2Role) + + // ISO 27001 sub-routes + gciRoutes.GET("/iso/gap-analysis", gciHandlers.GetISOGapAnalysis) + gciRoutes.GET("/iso/mappings", gciHandlers.ListISOMappings) + gciRoutes.GET("/iso/mappings/:controlId", gciHandlers.GetISOMapping) + } + } // Create HTTP server diff --git a/ai-compliance-sdk/internal/api/handlers/gci_handlers.go b/ai-compliance-sdk/internal/api/handlers/gci_handlers.go new file mode 100644 index 0000000..538b8d3 --- /dev/null +++ b/ai-compliance-sdk/internal/api/handlers/gci_handlers.go @@ -0,0 +1,188 @@ +package handlers + +import ( + "net/http" + + "github.com/breakpilot/ai-compliance-sdk/internal/gci" + "github.com/breakpilot/ai-compliance-sdk/internal/rbac" + "github.com/gin-gonic/gin" +) + +type GCIHandlers struct { + engine *gci.Engine +} + +func NewGCIHandlers(engine *gci.Engine) *GCIHandlers { + return &GCIHandlers{engine: engine} +} + +// GetScore returns the GCI score for the current tenant +// GET /sdk/v1/gci/score +func (h *GCIHandlers) GetScore(c *gin.Context) { + tenantID := rbac.GetTenantID(c).String() + profile := c.DefaultQuery("profile", "default") + + result := h.engine.Calculate(tenantID, profile) + c.JSON(http.StatusOK, result) +} + +// GetScoreBreakdown returns the detailed 4-level GCI breakdown +// GET /sdk/v1/gci/score/breakdown +func (h *GCIHandlers) GetScoreBreakdown(c *gin.Context) { + tenantID := rbac.GetTenantID(c).String() + profile := c.DefaultQuery("profile", "default") + + breakdown := h.engine.CalculateBreakdown(tenantID, profile) + c.JSON(http.StatusOK, breakdown) +} + +// GetHistory returns historical GCI snapshots for trend analysis +// GET /sdk/v1/gci/score/history +func (h *GCIHandlers) GetHistory(c *gin.Context) { + tenantID := rbac.GetTenantID(c).String() + + history := h.engine.GetHistory(tenantID) + c.JSON(http.StatusOK, gin.H{ + "tenant_id": tenantID, + "snapshots": history, + "total": len(history), + }) +} + +// GetMatrix returns the compliance matrix (roles x regulations) +// GET /sdk/v1/gci/matrix +func (h *GCIHandlers) GetMatrix(c *gin.Context) { + tenantID := rbac.GetTenantID(c).String() + + matrix := h.engine.GetMatrix(tenantID) + c.JSON(http.StatusOK, gin.H{ + "tenant_id": tenantID, + "matrix": matrix, + }) +} + +// GetAuditTrail returns the audit trail for the latest GCI calculation +// GET /sdk/v1/gci/audit-trail +func (h *GCIHandlers) GetAuditTrail(c *gin.Context) { + tenantID := rbac.GetTenantID(c).String() + profile := c.DefaultQuery("profile", "default") + + result := h.engine.Calculate(tenantID, profile) + c.JSON(http.StatusOK, gin.H{ + "tenant_id": tenantID, + "gci_score": result.GCIScore, + "audit_trail": result.AuditTrail, + }) +} + +// GetNIS2Score returns the NIS2-specific compliance score +// GET /sdk/v1/gci/nis2/score +func (h *GCIHandlers) GetNIS2Score(c *gin.Context) { + tenantID := rbac.GetTenantID(c).String() + + score := gci.CalculateNIS2Score(tenantID) + c.JSON(http.StatusOK, score) +} + +// ListNIS2Roles returns available NIS2 responsibility roles +// GET /sdk/v1/gci/nis2/roles +func (h *GCIHandlers) ListNIS2Roles(c *gin.Context) { + roles := gci.ListNIS2Roles() + c.JSON(http.StatusOK, gin.H{ + "roles": roles, + "total": len(roles), + }) +} + +// AssignNIS2Role assigns a NIS2 role to a user (stub - returns mock) +// POST /sdk/v1/gci/nis2/roles/assign +func (h *GCIHandlers) AssignNIS2Role(c *gin.Context) { + var req struct { + RoleID string `json:"role_id" binding:"required"` + UserID string `json:"user_id" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + role, found := gci.GetNIS2Role(req.RoleID) + if !found { + c.JSON(http.StatusNotFound, gin.H{"error": "NIS2 role not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "status": "assigned", + "role": role, + "user_id": req.UserID, + }) +} + +// GetISOGapAnalysis returns the ISO 27001 gap analysis +// GET /sdk/v1/gci/iso/gap-analysis +func (h *GCIHandlers) GetISOGapAnalysis(c *gin.Context) { + tenantID := rbac.GetTenantID(c).String() + + analysis := gci.CalculateISOGapAnalysis(tenantID) + c.JSON(http.StatusOK, analysis) +} + +// ListISOMappings returns all ISO 27001 control mappings +// GET /sdk/v1/gci/iso/mappings +func (h *GCIHandlers) ListISOMappings(c *gin.Context) { + category := c.Query("category") + + if category != "" { + controls := gci.GetISOControlsByCategory(category) + c.JSON(http.StatusOK, gin.H{ + "controls": controls, + "total": len(controls), + "category": category, + }) + return + } + + categories := []string{"A.5", "A.6", "A.7", "A.8"} + result := make(map[string][]gci.ISOControl) + total := 0 + for _, cat := range categories { + controls := gci.GetISOControlsByCategory(cat) + if len(controls) > 0 { + result[cat] = controls + total += len(controls) + } + } + + c.JSON(http.StatusOK, gin.H{ + "categories": result, + "total": total, + }) +} + +// GetISOMapping returns a single ISO control by ID +// GET /sdk/v1/gci/iso/mappings/:controlId +func (h *GCIHandlers) GetISOMapping(c *gin.Context) { + controlID := c.Param("controlId") + + control, found := gci.GetISOControlByID(controlID) + if !found { + c.JSON(http.StatusNotFound, gin.H{"error": "ISO control not found"}) + return + } + + c.JSON(http.StatusOK, control) +} + +// GetWeightProfiles returns available weighting profiles +// GET /sdk/v1/gci/profiles +func (h *GCIHandlers) GetWeightProfiles(c *gin.Context) { + profiles := []string{"default", "nis2_relevant", "ki_nutzer"} + result := make([]gci.WeightProfile, 0, len(profiles)) + for _, id := range profiles { + result = append(result, gci.GetProfile(id)) + } + c.JSON(http.StatusOK, gin.H{ + "profiles": result, + }) +} diff --git a/ai-compliance-sdk/internal/gci/engine.go b/ai-compliance-sdk/internal/gci/engine.go new file mode 100644 index 0000000..1599f8b --- /dev/null +++ b/ai-compliance-sdk/internal/gci/engine.go @@ -0,0 +1,371 @@ +package gci + +import ( + "fmt" + "math" + "time" +) + +// Engine calculates the GCI score +type Engine struct{} + +// NewEngine creates a new GCI calculation engine +func NewEngine() *Engine { + return &Engine{} +} + +// Calculate computes the full GCI result for a tenant +func (e *Engine) Calculate(tenantID string, profileID string) *GCIResult { + now := time.Now() + profile := GetProfile(profileID) + auditTrail := []AuditEntry{} + + // Step 1: Get module data (mock for now) + modules := MockModuleData(tenantID) + certDates := MockCertificateData() + + // Step 2: Calculate Level 1 - Module Scores with validity + for i := range modules { + m := &modules[i] + if m.Assigned > 0 { + m.RawScore = float64(m.Completed) / float64(m.Assigned) * 100.0 + } + // Apply validity factor + if validUntil, ok := certDates[m.ModuleID]; ok { + m.ValidityFactor = CalculateValidityFactor(validUntil, now) + } else { + m.ValidityFactor = 1.0 // No certificate tracking = assume valid + } + m.FinalScore = m.RawScore * m.ValidityFactor + + if m.ValidityFactor < 1.0 { + auditTrail = append(auditTrail, AuditEntry{ + Timestamp: now, + Factor: "validity_decay", + Description: fmt.Sprintf("Modul '%s': Gueltigkeitsfaktor %.2f (Zertifikat laeuft ab/abgelaufen)", m.ModuleName, m.ValidityFactor), + Value: m.ValidityFactor, + Impact: "negative", + }) + } + } + + // Step 3: Calculate Level 2 - Risk-Weighted Scores per area + areaModules := map[string][]ModuleScore{ + "dsgvo": {}, + "nis2": {}, + "iso27001": {}, + "ai_act": {}, + } + for _, m := range modules { + if _, ok := areaModules[m.Category]; ok { + areaModules[m.Category] = append(areaModules[m.Category], m) + } + } + + level2Areas := []RiskWeightedScore{} + areaNames := map[string]string{ + "dsgvo": "DSGVO", + "nis2": "NIS2", + "iso27001": "ISO 27001", + "ai_act": "EU AI Act", + } + + for areaID, mods := range areaModules { + rws := RiskWeightedScore{ + AreaID: areaID, + AreaName: areaNames[areaID], + Modules: mods, + } + for _, m := range mods { + rws.WeightedSum += m.FinalScore * m.RiskWeight + rws.TotalWeight += m.RiskWeight + } + if rws.TotalWeight > 0 { + rws.AreaScore = rws.WeightedSum / rws.TotalWeight + } + level2Areas = append(level2Areas, rws) + } + + // Step 4: Calculate Level 3 - Regulation Area Scores + areaScores := []RegulationAreaScore{} + for _, rws := range level2Areas { + weight := profile.Weights[rws.AreaID] + completedCount := 0 + for _, m := range rws.Modules { + if m.Completed >= m.Assigned && m.Assigned > 0 { + completedCount++ + } + } + ras := RegulationAreaScore{ + RegulationID: rws.AreaID, + RegulationName: rws.AreaName, + Score: math.Round(rws.AreaScore*100) / 100, + Weight: weight, + WeightedScore: rws.AreaScore * weight, + ModuleCount: len(rws.Modules), + CompletedCount: completedCount, + } + areaScores = append(areaScores, ras) + + auditTrail = append(auditTrail, AuditEntry{ + Timestamp: now, + Factor: "area_score", + Description: fmt.Sprintf("Bereich '%s': Score %.1f, Gewicht %.0f%%", rws.AreaName, rws.AreaScore, weight*100), + Value: rws.AreaScore, + Impact: "neutral", + }) + } + + // Step 5: Calculate raw GCI + rawGCI := 0.0 + totalWeight := 0.0 + for _, ras := range areaScores { + rawGCI += ras.WeightedScore + totalWeight += ras.Weight + } + if totalWeight > 0 { + rawGCI = rawGCI / totalWeight + } + + // Step 6: Apply Criticality Multiplier + criticalityMult := calculateCriticalityMultiplier(modules) + auditTrail = append(auditTrail, AuditEntry{ + Timestamp: now, + Factor: "criticality_multiplier", + Description: fmt.Sprintf("Kritikalitaetsmultiplikator: %.3f", criticalityMult), + Value: criticalityMult, + Impact: func() string { + if criticalityMult < 1.0 { + return "negative" + } + return "neutral" + }(), + }) + + // Step 7: Apply Incident Adjustment + openInc, critInc := MockIncidentData() + incidentAdj := calculateIncidentAdjustment(openInc, critInc) + auditTrail = append(auditTrail, AuditEntry{ + Timestamp: now, + Factor: "incident_adjustment", + Description: fmt.Sprintf("Vorfallsanpassung: %.3f (%d offen, %d kritisch)", incidentAdj, openInc, critInc), + Value: incidentAdj, + Impact: "negative", + }) + + // Step 8: Final GCI + finalGCI := rawGCI * criticalityMult * incidentAdj + finalGCI = math.Max(0, math.Min(100, math.Round(finalGCI*10)/10)) + + // Step 9: Determine Maturity Level + maturity := determineMaturityLevel(finalGCI) + + auditTrail = append(auditTrail, AuditEntry{ + Timestamp: now, + Factor: "final_gci", + Description: fmt.Sprintf("GCI-Endergebnis: %.1f → Reifegrad: %s", finalGCI, MaturityLabels[maturity]), + Value: finalGCI, + Impact: "neutral", + }) + + return &GCIResult{ + TenantID: tenantID, + GCIScore: finalGCI, + MaturityLevel: maturity, + MaturityLabel: MaturityLabels[maturity], + CalculatedAt: now, + Profile: profileID, + AreaScores: areaScores, + CriticalityMult: criticalityMult, + IncidentAdj: incidentAdj, + AuditTrail: auditTrail, + } +} + +// CalculateBreakdown returns the full 4-level breakdown +func (e *Engine) CalculateBreakdown(tenantID string, profileID string) *GCIBreakdown { + result := e.Calculate(tenantID, profileID) + modules := MockModuleData(tenantID) + certDates := MockCertificateData() + now := time.Now() + + // Recalculate module scores for the breakdown + for i := range modules { + m := &modules[i] + if m.Assigned > 0 { + m.RawScore = float64(m.Completed) / float64(m.Assigned) * 100.0 + } + if validUntil, ok := certDates[m.ModuleID]; ok { + m.ValidityFactor = CalculateValidityFactor(validUntil, now) + } else { + m.ValidityFactor = 1.0 + } + m.FinalScore = m.RawScore * m.ValidityFactor + } + + // Build Level 2 areas + areaModules := map[string][]ModuleScore{} + for _, m := range modules { + areaModules[m.Category] = append(areaModules[m.Category], m) + } + + areaNames := map[string]string{"dsgvo": "DSGVO", "nis2": "NIS2", "iso27001": "ISO 27001", "ai_act": "EU AI Act"} + level2 := []RiskWeightedScore{} + for areaID, mods := range areaModules { + rws := RiskWeightedScore{AreaID: areaID, AreaName: areaNames[areaID], Modules: mods} + for _, m := range mods { + rws.WeightedSum += m.FinalScore * m.RiskWeight + rws.TotalWeight += m.RiskWeight + } + if rws.TotalWeight > 0 { + rws.AreaScore = rws.WeightedSum / rws.TotalWeight + } + level2 = append(level2, rws) + } + + return &GCIBreakdown{ + GCIResult: *result, + Level1Modules: modules, + Level2Areas: level2, + } +} + +// GetHistory returns historical GCI snapshots +func (e *Engine) GetHistory(tenantID string) []GCISnapshot { + // Add current score to history + result := e.Calculate(tenantID, "default") + history := MockGCIHistory(tenantID) + current := GCISnapshot{ + TenantID: tenantID, + Score: result.GCIScore, + MaturityLevel: result.MaturityLevel, + AreaScores: make(map[string]float64), + CalculatedAt: result.CalculatedAt, + } + for _, as := range result.AreaScores { + current.AreaScores[as.RegulationID] = as.Score + } + history = append(history, current) + return history +} + +// GetMatrix returns the compliance matrix (roles x regulations) +func (e *Engine) GetMatrix(tenantID string) []ComplianceMatrixEntry { + modules := MockModuleData(tenantID) + + roles := []struct { + ID string + Name string + }{ + {"management", "Geschaeftsfuehrung"}, + {"it_security", "IT-Sicherheit / CISO"}, + {"data_protection", "Datenschutz / DSB"}, + {"hr", "Personalwesen"}, + {"general", "Allgemeine Mitarbeiter"}, + } + + // Define which modules are relevant per role + roleModules := map[string][]string{ + "management": {"dsgvo-grundlagen", "nis2-management", "ai-governance", "iso-isms"}, + "it_security": {"nis2-risikomanagement", "nis2-incident-response", "iso-zugangssteuerung", "iso-kryptografie", "ai-hochrisiko"}, + "data_protection": {"dsgvo-grundlagen", "dsgvo-betroffenenrechte", "dsgvo-tom", "dsgvo-dsfa", "dsgvo-auftragsverarbeitung"}, + "hr": {"dsgvo-grundlagen", "dsgvo-betroffenenrechte", "nis2-management"}, + "general": {"dsgvo-grundlagen", "nis2-risikomanagement", "ai-risikokategorien", "ai-transparenz"}, + } + + moduleMap := map[string]ModuleScore{} + for _, m := range modules { + moduleMap[m.ModuleID] = m + } + + entries := []ComplianceMatrixEntry{} + for _, role := range roles { + entry := ComplianceMatrixEntry{ + Role: role.ID, + RoleName: role.Name, + Regulations: map[string]float64{}, + } + + regScores := map[string][]float64{} + requiredModuleIDs := roleModules[role.ID] + entry.RequiredModules = len(requiredModuleIDs) + + for _, modID := range requiredModuleIDs { + if m, ok := moduleMap[modID]; ok { + score := 0.0 + if m.Assigned > 0 { + score = float64(m.Completed) / float64(m.Assigned) * 100 + } + regScores[m.Category] = append(regScores[m.Category], score) + if m.Completed >= m.Assigned && m.Assigned > 0 { + entry.CompletedModules++ + } + } + } + + totalScore := 0.0 + count := 0 + for reg, scores := range regScores { + sum := 0.0 + for _, s := range scores { + sum += s + } + avg := sum / float64(len(scores)) + entry.Regulations[reg] = math.Round(avg*10) / 10 + totalScore += avg + count++ + } + if count > 0 { + entry.OverallScore = math.Round(totalScore/float64(count)*10) / 10 + } + + entries = append(entries, entry) + } + + return entries +} + +// Helper functions + +func calculateCriticalityMultiplier(modules []ModuleScore) float64 { + criticalModules := 0 + criticalLow := 0 + for _, m := range modules { + if m.RiskWeight >= 2.5 { + criticalModules++ + if m.FinalScore < 50 { + criticalLow++ + } + } + } + if criticalModules == 0 { + return 1.0 + } + // Reduce score if critical modules have low completion + ratio := float64(criticalLow) / float64(criticalModules) + return 1.0 - (ratio * 0.15) // max 15% reduction +} + +func calculateIncidentAdjustment(openIncidents, criticalIncidents int) float64 { + adj := 1.0 + // Each open incident reduces by 1% + adj -= float64(openIncidents) * 0.01 + // Each critical incident reduces by additional 3% + adj -= float64(criticalIncidents) * 0.03 + return math.Max(0.8, adj) // minimum 80% (max 20% reduction) +} + +func determineMaturityLevel(score float64) string { + switch { + case score >= 90: + return MaturityOptimized + case score >= 75: + return MaturityManaged + case score >= 60: + return MaturityDefined + case score >= 40: + return MaturityReactive + default: + return MaturityHighRisk + } +} diff --git a/ai-compliance-sdk/internal/gci/iso_gap_analysis.go b/ai-compliance-sdk/internal/gci/iso_gap_analysis.go new file mode 100644 index 0000000..9032f45 --- /dev/null +++ b/ai-compliance-sdk/internal/gci/iso_gap_analysis.go @@ -0,0 +1,188 @@ +package gci + +import "math" + +// ISOGapAnalysis represents the complete ISO 27001 gap analysis +type ISOGapAnalysis struct { + TenantID string `json:"tenant_id"` + TotalControls int `json:"total_controls"` + CoveredFull int `json:"covered_full"` + CoveredPartial int `json:"covered_partial"` + NotCovered int `json:"not_covered"` + CoveragePercent float64 `json:"coverage_percent"` + CategorySummaries []ISOCategorySummary `json:"category_summaries"` + ControlDetails []ISOControlDetail `json:"control_details"` + Gaps []ISOGap `json:"gaps"` +} + +// ISOControlDetail shows coverage status for a single control +type ISOControlDetail struct { + Control ISOControl `json:"control"` + CoverageLevel string `json:"coverage_level"` // full, partial, none + CoveredBy []string `json:"covered_by"` // module IDs + Score float64 `json:"score"` // 0-100 +} + +// ISOGap represents an identified gap in ISO coverage +type ISOGap struct { + ControlID string `json:"control_id"` + ControlName string `json:"control_name"` + Category string `json:"category"` + Priority string `json:"priority"` // high, medium, low + Recommendation string `json:"recommendation"` +} + +// CalculateISOGapAnalysis performs the ISO 27001 gap analysis +func CalculateISOGapAnalysis(tenantID string) *ISOGapAnalysis { + modules := MockModuleData(tenantID) + moduleMap := map[string]ModuleScore{} + for _, m := range modules { + moduleMap[m.ModuleID] = m + } + + // Build reverse mapping: control -> modules covering it + controlCoverage := map[string][]string{} + controlCoverageLevel := map[string]string{} + for _, mapping := range DefaultISOModuleMappings { + for _, controlID := range mapping.ISOControls { + controlCoverage[controlID] = append(controlCoverage[controlID], mapping.ModuleID) + // Use the highest coverage level + existingLevel := controlCoverageLevel[controlID] + if mapping.CoverageLevel == "full" || existingLevel == "" { + controlCoverageLevel[controlID] = mapping.CoverageLevel + } + } + } + + // Analyze each control + details := []ISOControlDetail{} + gaps := []ISOGap{} + coveredFull := 0 + coveredPartial := 0 + notCovered := 0 + + categoryCounts := map[string]*ISOCategorySummary{ + "A.5": {CategoryID: "A.5", CategoryName: "Organisatorische Massnahmen"}, + "A.6": {CategoryID: "A.6", CategoryName: "Personelle Massnahmen"}, + "A.7": {CategoryID: "A.7", CategoryName: "Physische Massnahmen"}, + "A.8": {CategoryID: "A.8", CategoryName: "Technologische Massnahmen"}, + } + + for _, control := range ISOControls { + coveredBy := controlCoverage[control.ID] + level := controlCoverageLevel[control.ID] + + if len(coveredBy) == 0 { + level = "none" + } + + // Calculate score based on module completion + score := 0.0 + if len(coveredBy) > 0 { + scoreSum := 0.0 + count := 0 + for _, modID := range coveredBy { + if m, ok := moduleMap[modID]; ok && m.Assigned > 0 { + scoreSum += float64(m.Completed) / float64(m.Assigned) * 100 + count++ + } + } + if count > 0 { + score = scoreSum / float64(count) + } + // Adjust for coverage level + if level == "partial" { + score *= 0.7 // partial coverage reduces effective score + } + } + + detail := ISOControlDetail{ + Control: control, + CoverageLevel: level, + CoveredBy: coveredBy, + Score: math.Round(score*10) / 10, + } + details = append(details, detail) + + // Count by category + cat := categoryCounts[control.CategoryID] + if cat != nil { + cat.TotalControls++ + switch level { + case "full": + coveredFull++ + cat.CoveredFull++ + case "partial": + coveredPartial++ + cat.CoveredPartial++ + default: + notCovered++ + cat.NotCovered++ + // Generate gap recommendation + gap := ISOGap{ + ControlID: control.ID, + ControlName: control.Name, + Category: control.Category, + Priority: determineGapPriority(control), + Recommendation: generateGapRecommendation(control), + } + gaps = append(gaps, gap) + } + } + } + + totalControls := len(ISOControls) + coveragePercent := 0.0 + if totalControls > 0 { + coveragePercent = math.Round(float64(coveredFull+coveredPartial)/float64(totalControls)*100*10) / 10 + } + + summaries := []ISOCategorySummary{} + for _, catID := range []string{"A.5", "A.6", "A.7", "A.8"} { + if cat, ok := categoryCounts[catID]; ok { + summaries = append(summaries, *cat) + } + } + + return &ISOGapAnalysis{ + TenantID: tenantID, + TotalControls: totalControls, + CoveredFull: coveredFull, + CoveredPartial: coveredPartial, + NotCovered: notCovered, + CoveragePercent: coveragePercent, + CategorySummaries: summaries, + ControlDetails: details, + Gaps: gaps, + } +} + +func determineGapPriority(control ISOControl) string { + // High priority for access, incident, and data protection controls + highPriority := map[string]bool{ + "A.5.15": true, "A.5.17": true, "A.5.24": true, "A.5.26": true, + "A.5.34": true, "A.8.2": true, "A.8.5": true, "A.8.7": true, + "A.8.10": true, "A.8.20": true, + } + if highPriority[control.ID] { + return "high" + } + // Medium for organizational and people controls + if control.CategoryID == "A.5" || control.CategoryID == "A.6" { + return "medium" + } + return "low" +} + +func generateGapRecommendation(control ISOControl) string { + recommendations := map[string]string{ + "organizational": "Erstellen Sie eine Richtlinie und weisen Sie Verantwortlichkeiten zu fuer: " + control.Name, + "people": "Implementieren Sie Schulungen und Prozesse fuer: " + control.Name, + "physical": "Definieren Sie physische Sicherheitsmassnahmen fuer: " + control.Name, + "technological": "Implementieren Sie technische Kontrollen fuer: " + control.Name, + } + if rec, ok := recommendations[control.Category]; ok { + return rec + } + return "Massnahmen implementieren fuer: " + control.Name +} diff --git a/ai-compliance-sdk/internal/gci/iso_mapping.go b/ai-compliance-sdk/internal/gci/iso_mapping.go new file mode 100644 index 0000000..8f1a8fa --- /dev/null +++ b/ai-compliance-sdk/internal/gci/iso_mapping.go @@ -0,0 +1,207 @@ +package gci + +// ISOControl represents an ISO 27001:2022 Annex A control +type ISOControl struct { + ID string `json:"id"` // e.g. "A.5.1" + Name string `json:"name"` + Category string `json:"category"` // organizational, people, physical, technological + CategoryID string `json:"category_id"` // A.5, A.6, A.7, A.8 + Description string `json:"description"` +} + +// ISOModuleMapping maps a course/module to ISO controls +type ISOModuleMapping struct { + ModuleID string `json:"module_id"` + ModuleName string `json:"module_name"` + ISOControls []string `json:"iso_controls"` // control IDs + CoverageLevel string `json:"coverage_level"` // full, partial, none +} + +// ISO 27001:2022 Annex A controls (representative selection) +var ISOControls = []ISOControl{ + // A.5 Organizational Controls (37 controls, showing key ones) + {ID: "A.5.1", Name: "Informationssicherheitsrichtlinien", Category: "organizational", CategoryID: "A.5", Description: "Informationssicherheitsleitlinie und themenspezifische Richtlinien"}, + {ID: "A.5.2", Name: "Rollen und Verantwortlichkeiten", Category: "organizational", CategoryID: "A.5", Description: "Definition und Zuweisung von Informationssicherheitsrollen"}, + {ID: "A.5.3", Name: "Aufgabentrennung", Category: "organizational", CategoryID: "A.5", Description: "Trennung von konfligierenden Aufgaben und Verantwortlichkeiten"}, + {ID: "A.5.4", Name: "Managementverantwortung", Category: "organizational", CategoryID: "A.5", Description: "Fuehrungskraefte muessen Sicherheitsrichtlinien einhalten und durchsetzen"}, + {ID: "A.5.5", Name: "Kontakt mit Behoerden", Category: "organizational", CategoryID: "A.5", Description: "Pflege von Kontakten zu relevanten Aufsichtsbehoerden"}, + {ID: "A.5.6", Name: "Kontakt mit Interessengruppen", Category: "organizational", CategoryID: "A.5", Description: "Kontakt zu Fachgruppen und Sicherheitsforen"}, + {ID: "A.5.7", Name: "Bedrohungsintelligenz", Category: "organizational", CategoryID: "A.5", Description: "Sammlung und Analyse von Bedrohungsinformationen"}, + {ID: "A.5.8", Name: "Informationssicherheit im Projektmanagement", Category: "organizational", CategoryID: "A.5", Description: "Integration von Sicherheit in Projektmanagement"}, + {ID: "A.5.9", Name: "Inventar der Informationswerte", Category: "organizational", CategoryID: "A.5", Description: "Inventarisierung und Verwaltung von Informationswerten"}, + {ID: "A.5.10", Name: "Zuleassige Nutzung", Category: "organizational", CategoryID: "A.5", Description: "Regeln fuer die zuleassige Nutzung von Informationswerten"}, + {ID: "A.5.11", Name: "Rueckgabe von Werten", Category: "organizational", CategoryID: "A.5", Description: "Rueckgabe von Werten bei Beendigung"}, + {ID: "A.5.12", Name: "Klassifizierung von Informationen", Category: "organizational", CategoryID: "A.5", Description: "Klassifizierungsschema fuer Informationen"}, + {ID: "A.5.13", Name: "Kennzeichnung von Informationen", Category: "organizational", CategoryID: "A.5", Description: "Kennzeichnung gemaess Klassifizierung"}, + {ID: "A.5.14", Name: "Informationsuebertragung", Category: "organizational", CategoryID: "A.5", Description: "Regeln fuer sichere Informationsuebertragung"}, + {ID: "A.5.15", Name: "Zugangssteuerung", Category: "organizational", CategoryID: "A.5", Description: "Zugangssteuerungsrichtlinie"}, + {ID: "A.5.16", Name: "Identitaetsmanagement", Category: "organizational", CategoryID: "A.5", Description: "Verwaltung des Lebenszyklus von Identitaeten"}, + {ID: "A.5.17", Name: "Authentifizierungsinformationen", Category: "organizational", CategoryID: "A.5", Description: "Verwaltung von Authentifizierungsinformationen"}, + {ID: "A.5.18", Name: "Zugriffsrechte", Category: "organizational", CategoryID: "A.5", Description: "Vergabe, Pruefung und Entzug von Zugriffsrechten"}, + {ID: "A.5.19", Name: "Informationssicherheit in Lieferantenbeziehungen", Category: "organizational", CategoryID: "A.5", Description: "Sicherheitsanforderungen an Lieferanten"}, + {ID: "A.5.20", Name: "Informationssicherheit in Lieferantenvereinbarungen", Category: "organizational", CategoryID: "A.5", Description: "Sicherheitsklauseln in Vertraegen"}, + {ID: "A.5.21", Name: "IKT-Lieferkette", Category: "organizational", CategoryID: "A.5", Description: "Management der IKT-Lieferkette"}, + {ID: "A.5.22", Name: "Ueberwachung von Lieferantenservices", Category: "organizational", CategoryID: "A.5", Description: "Ueberwachung und Pruefung von Lieferantenservices"}, + {ID: "A.5.23", Name: "Cloud-Sicherheit", Category: "organizational", CategoryID: "A.5", Description: "Informationssicherheit fuer Cloud-Dienste"}, + {ID: "A.5.24", Name: "Vorfallsmanagement - Planung", Category: "organizational", CategoryID: "A.5", Description: "Planung und Vorbereitung des Vorfallsmanagements"}, + {ID: "A.5.25", Name: "Vorfallsbeurteilung", Category: "organizational", CategoryID: "A.5", Description: "Beurteilung und Entscheidung ueber Sicherheitsereignisse"}, + {ID: "A.5.26", Name: "Vorfallsreaktion", Category: "organizational", CategoryID: "A.5", Description: "Reaktion auf Sicherheitsvorfaelle"}, + {ID: "A.5.27", Name: "Aus Vorfaellen lernen", Category: "organizational", CategoryID: "A.5", Description: "Lessons Learned aus Sicherheitsvorfaellen"}, + {ID: "A.5.28", Name: "Beweissicherung", Category: "organizational", CategoryID: "A.5", Description: "Identifikation und Sicherung von Beweisen"}, + {ID: "A.5.29", Name: "Informationssicherheit bei Stoerungen", Category: "organizational", CategoryID: "A.5", Description: "Sicherheit waehrend Stoerungen und Krisen"}, + {ID: "A.5.30", Name: "IKT-Bereitschaft fuer Business Continuity", Category: "organizational", CategoryID: "A.5", Description: "IKT-Bereitschaft zur Unterstuetzung der Geschaeftskontinuitaet"}, + {ID: "A.5.31", Name: "Rechtliche Anforderungen", Category: "organizational", CategoryID: "A.5", Description: "Einhaltung rechtlicher und vertraglicher Anforderungen"}, + {ID: "A.5.32", Name: "Geistige Eigentumsrechte", Category: "organizational", CategoryID: "A.5", Description: "Schutz geistigen Eigentums"}, + {ID: "A.5.33", Name: "Schutz von Aufzeichnungen", Category: "organizational", CategoryID: "A.5", Description: "Schutz von Aufzeichnungen vor Verlust und Manipulation"}, + {ID: "A.5.34", Name: "Datenschutz und PII", Category: "organizational", CategoryID: "A.5", Description: "Datenschutz und Schutz personenbezogener Daten"}, + {ID: "A.5.35", Name: "Unabhaengige Ueberpruefung", Category: "organizational", CategoryID: "A.5", Description: "Unabhaengige Ueberpruefung der Informationssicherheit"}, + {ID: "A.5.36", Name: "Richtlinienkonformitaet", Category: "organizational", CategoryID: "A.5", Description: "Einhaltung von Richtlinien und Standards"}, + {ID: "A.5.37", Name: "Dokumentierte Betriebsverfahren", Category: "organizational", CategoryID: "A.5", Description: "Dokumentation von Betriebsverfahren"}, + + // A.6 People Controls (8 controls) + {ID: "A.6.1", Name: "Ueberpruefen", Category: "people", CategoryID: "A.6", Description: "Hintergrundpruefungen vor der Einstellung"}, + {ID: "A.6.2", Name: "Beschaeftigungsbedingungen", Category: "people", CategoryID: "A.6", Description: "Sicherheitsanforderungen in Arbeitsvertraegen"}, + {ID: "A.6.3", Name: "Sensibilisierung und Schulung", Category: "people", CategoryID: "A.6", Description: "Awareness-Programme und Schulungen"}, + {ID: "A.6.4", Name: "Disziplinarverfahren", Category: "people", CategoryID: "A.6", Description: "Formales Disziplinarverfahren"}, + {ID: "A.6.5", Name: "Verantwortlichkeiten nach Beendigung", Category: "people", CategoryID: "A.6", Description: "Sicherheitspflichten nach Beendigung des Beschaeftigungsverhaeltnisses"}, + {ID: "A.6.6", Name: "Vertraulichkeitsvereinbarungen", Category: "people", CategoryID: "A.6", Description: "Vertraulichkeits- und Geheimhaltungsvereinbarungen"}, + {ID: "A.6.7", Name: "Remote-Arbeit", Category: "people", CategoryID: "A.6", Description: "Sicherheitsmassnahmen fuer Remote-Arbeit"}, + {ID: "A.6.8", Name: "Meldung von Sicherheitsereignissen", Category: "people", CategoryID: "A.6", Description: "Mechanismen zur Meldung von Sicherheitsereignissen"}, + + // A.7 Physical Controls (14 controls, showing key ones) + {ID: "A.7.1", Name: "Physische Sicherheitsperimeter", Category: "physical", CategoryID: "A.7", Description: "Definition physischer Sicherheitszonen"}, + {ID: "A.7.2", Name: "Physischer Zutritt", Category: "physical", CategoryID: "A.7", Description: "Zutrittskontrolle zu Sicherheitszonen"}, + {ID: "A.7.3", Name: "Sicherung von Bueros und Raeumen", Category: "physical", CategoryID: "A.7", Description: "Physische Sicherheit fuer Bueros und Raeume"}, + {ID: "A.7.4", Name: "Physische Sicherheitsueberwachung", Category: "physical", CategoryID: "A.7", Description: "Ueberwachung physischer Sicherheit"}, + {ID: "A.7.5", Name: "Schutz vor Umweltgefahren", Category: "physical", CategoryID: "A.7", Description: "Schutz gegen natuerliche und menschgemachte Gefahren"}, + {ID: "A.7.6", Name: "Arbeit in Sicherheitszonen", Category: "physical", CategoryID: "A.7", Description: "Regeln fuer das Arbeiten in Sicherheitszonen"}, + {ID: "A.7.7", Name: "Aufgeraemter Schreibtisch", Category: "physical", CategoryID: "A.7", Description: "Clean-Desk und Clear-Screen Richtlinie"}, + {ID: "A.7.8", Name: "Geraeteplatzierung", Category: "physical", CategoryID: "A.7", Description: "Platzierung und Schutz von Geraeten"}, + {ID: "A.7.9", Name: "Sicherheit von Geraeten ausserhalb", Category: "physical", CategoryID: "A.7", Description: "Sicherheit von Geraeten ausserhalb der Raeumlichkeiten"}, + {ID: "A.7.10", Name: "Speichermedien", Category: "physical", CategoryID: "A.7", Description: "Verwaltung von Speichermedien"}, + {ID: "A.7.11", Name: "Versorgungseinrichtungen", Category: "physical", CategoryID: "A.7", Description: "Schutz vor Ausfaellen der Versorgungseinrichtungen"}, + {ID: "A.7.12", Name: "Verkabelungssicherheit", Category: "physical", CategoryID: "A.7", Description: "Schutz der Verkabelung"}, + {ID: "A.7.13", Name: "Instandhaltung von Geraeten", Category: "physical", CategoryID: "A.7", Description: "Korrekte Instandhaltung von Geraeten"}, + {ID: "A.7.14", Name: "Sichere Entsorgung", Category: "physical", CategoryID: "A.7", Description: "Sichere Entsorgung oder Wiederverwendung"}, + + // A.8 Technological Controls (34 controls, showing key ones) + {ID: "A.8.1", Name: "Endbenutzergeraete", Category: "technological", CategoryID: "A.8", Description: "Sicherheit von Endbenutzergeraeten"}, + {ID: "A.8.2", Name: "Privilegierte Zugriffsrechte", Category: "technological", CategoryID: "A.8", Description: "Verwaltung privilegierter Zugriffsrechte"}, + {ID: "A.8.3", Name: "Informationszugangsbeschraenkung", Category: "technological", CategoryID: "A.8", Description: "Beschraenkung des Zugangs zu Informationen"}, + {ID: "A.8.4", Name: "Zugang zu Quellcode", Category: "technological", CategoryID: "A.8", Description: "Sicherer Zugang zu Quellcode"}, + {ID: "A.8.5", Name: "Sichere Authentifizierung", Category: "technological", CategoryID: "A.8", Description: "Sichere Authentifizierungstechnologien"}, + {ID: "A.8.6", Name: "Kapazitaetsmanagement", Category: "technological", CategoryID: "A.8", Description: "Ueberwachung und Anpassung der Kapazitaet"}, + {ID: "A.8.7", Name: "Schutz gegen Malware", Category: "technological", CategoryID: "A.8", Description: "Schutz vor Schadprogrammen"}, + {ID: "A.8.8", Name: "Management technischer Schwachstellen", Category: "technological", CategoryID: "A.8", Description: "Identifikation und Behebung von Schwachstellen"}, + {ID: "A.8.9", Name: "Konfigurationsmanagement", Category: "technological", CategoryID: "A.8", Description: "Sichere Konfiguration von Systemen"}, + {ID: "A.8.10", Name: "Datensicherung", Category: "technological", CategoryID: "A.8", Description: "Erstellen und Testen von Datensicherungen"}, + {ID: "A.8.11", Name: "Datenredundanz", Category: "technological", CategoryID: "A.8", Description: "Redundanz von Informationsverarbeitungseinrichtungen"}, + {ID: "A.8.12", Name: "Protokollierung", Category: "technological", CategoryID: "A.8", Description: "Aufzeichnung und Ueberwachung von Aktivitaeten"}, + {ID: "A.8.13", Name: "Ueberwachung von Aktivitaeten", Category: "technological", CategoryID: "A.8", Description: "Ueberwachung von Netzwerken und Systemen"}, + {ID: "A.8.14", Name: "Zeitsynchronisation", Category: "technological", CategoryID: "A.8", Description: "Synchronisation von Uhren"}, + {ID: "A.8.15", Name: "Nutzung privilegierter Hilfsprogramme", Category: "technological", CategoryID: "A.8", Description: "Einschraenkung privilegierter Hilfsprogramme"}, + {ID: "A.8.16", Name: "Softwareinstallation", Category: "technological", CategoryID: "A.8", Description: "Kontrolle der Softwareinstallation"}, + {ID: "A.8.17", Name: "Netzwerksicherheit", Category: "technological", CategoryID: "A.8", Description: "Sicherheit von Netzwerken"}, + {ID: "A.8.18", Name: "Netzwerksegmentierung", Category: "technological", CategoryID: "A.8", Description: "Segmentierung von Netzwerken"}, + {ID: "A.8.19", Name: "Webfilterung", Category: "technological", CategoryID: "A.8", Description: "Filterung des Webzugangs"}, + {ID: "A.8.20", Name: "Kryptografie", Category: "technological", CategoryID: "A.8", Description: "Einsatz kryptografischer Massnahmen"}, + {ID: "A.8.21", Name: "Sichere Entwicklung", Category: "technological", CategoryID: "A.8", Description: "Sichere Entwicklungslebenszyklus"}, + {ID: "A.8.22", Name: "Sicherheitsanforderungen bei Applikationen", Category: "technological", CategoryID: "A.8", Description: "Sicherheitsanforderungen bei Anwendungen"}, + {ID: "A.8.23", Name: "Sichere Systemarchitektur", Category: "technological", CategoryID: "A.8", Description: "Sicherheitsprinzipien in der Systemarchitektur"}, + {ID: "A.8.24", Name: "Sicheres Programmieren", Category: "technological", CategoryID: "A.8", Description: "Sichere Programmierpraktiken"}, + {ID: "A.8.25", Name: "Sicherheitstests", Category: "technological", CategoryID: "A.8", Description: "Sicherheitstests in der Entwicklung und Abnahme"}, + {ID: "A.8.26", Name: "Auslagerung der Entwicklung", Category: "technological", CategoryID: "A.8", Description: "Ueberwachung ausgelagerter Entwicklung"}, + {ID: "A.8.27", Name: "Trennung von Umgebungen", Category: "technological", CategoryID: "A.8", Description: "Trennung von Entwicklungs-, Test- und Produktionsumgebungen"}, + {ID: "A.8.28", Name: "Aenderungsmanagement", Category: "technological", CategoryID: "A.8", Description: "Formales Aenderungsmanagement"}, + {ID: "A.8.29", Name: "Sicherheitstests in der Abnahme", Category: "technological", CategoryID: "A.8", Description: "Durchfuehrung von Sicherheitstests vor Abnahme"}, + {ID: "A.8.30", Name: "Datenloeschung", Category: "technological", CategoryID: "A.8", Description: "Sichere Datenloeschung"}, + {ID: "A.8.31", Name: "Datenmaskierung", Category: "technological", CategoryID: "A.8", Description: "Techniken zur Datenmaskierung"}, + {ID: "A.8.32", Name: "Verhinderung von Datenverlust", Category: "technological", CategoryID: "A.8", Description: "DLP-Massnahmen"}, + {ID: "A.8.33", Name: "Testinformationen", Category: "technological", CategoryID: "A.8", Description: "Schutz von Testinformationen"}, + {ID: "A.8.34", Name: "Audit-Informationssysteme", Category: "technological", CategoryID: "A.8", Description: "Schutz von Audit-Tools und -systemen"}, +} + +// Default mappings: which modules cover which ISO controls +var DefaultISOModuleMappings = []ISOModuleMapping{ + { + ModuleID: "iso-isms", ModuleName: "ISMS Grundlagen", + ISOControls: []string{"A.5.1", "A.5.2", "A.5.3", "A.5.4", "A.5.35", "A.5.36"}, + CoverageLevel: "full", + }, + { + ModuleID: "iso-risikobewertung", ModuleName: "Risikobewertung", + ISOControls: []string{"A.5.7", "A.5.8", "A.5.9", "A.5.10", "A.5.12", "A.5.13"}, + CoverageLevel: "full", + }, + { + ModuleID: "iso-zugangssteuerung", ModuleName: "Zugangssteuerung", + ISOControls: []string{"A.5.15", "A.5.16", "A.5.17", "A.5.18", "A.8.2", "A.8.3", "A.8.5"}, + CoverageLevel: "full", + }, + { + ModuleID: "iso-kryptografie", ModuleName: "Kryptografie", + ISOControls: []string{"A.8.20", "A.8.21", "A.8.24"}, + CoverageLevel: "partial", + }, + { + ModuleID: "iso-physisch", ModuleName: "Physische Sicherheit", + ISOControls: []string{"A.7.1", "A.7.2", "A.7.3", "A.7.4", "A.7.5", "A.7.7", "A.7.8"}, + CoverageLevel: "full", + }, + { + ModuleID: "dsgvo-tom", ModuleName: "Technisch-Organisatorische Massnahmen", + ISOControls: []string{"A.5.34", "A.8.10", "A.8.12", "A.8.30", "A.8.31"}, + CoverageLevel: "partial", + }, + { + ModuleID: "nis2-incident-response", ModuleName: "NIS2 Incident Response", + ISOControls: []string{"A.5.24", "A.5.25", "A.5.26", "A.5.27", "A.5.28", "A.6.8"}, + CoverageLevel: "full", + }, + { + ModuleID: "nis2-supply-chain", ModuleName: "NIS2 Lieferkettensicherheit", + ISOControls: []string{"A.5.19", "A.5.20", "A.5.21", "A.5.22", "A.5.23"}, + CoverageLevel: "full", + }, + { + ModuleID: "nis2-risikomanagement", ModuleName: "NIS2 Risikomanagement", + ISOControls: []string{"A.5.29", "A.5.30", "A.8.6", "A.8.7", "A.8.8", "A.8.9"}, + CoverageLevel: "partial", + }, + { + ModuleID: "dsgvo-grundlagen", ModuleName: "DSGVO Grundlagen", + ISOControls: []string{"A.5.31", "A.5.34", "A.6.2", "A.6.3"}, + CoverageLevel: "partial", + }, +} + +// GetISOControlByID returns a control by its ID +func GetISOControlByID(id string) (ISOControl, bool) { + for _, c := range ISOControls { + if c.ID == id { + return c, true + } + } + return ISOControl{}, false +} + +// GetISOControlsByCategory returns all controls in a category +func GetISOControlsByCategory(categoryID string) []ISOControl { + var result []ISOControl + for _, c := range ISOControls { + if c.CategoryID == categoryID { + result = append(result, c) + } + } + return result +} + +// ISOCategorySummary provides a summary per ISO category +type ISOCategorySummary struct { + CategoryID string `json:"category_id"` + CategoryName string `json:"category_name"` + TotalControls int `json:"total_controls"` + CoveredFull int `json:"covered_full"` + CoveredPartial int `json:"covered_partial"` + NotCovered int `json:"not_covered"` +} diff --git a/ai-compliance-sdk/internal/gci/mock_data.go b/ai-compliance-sdk/internal/gci/mock_data.go new file mode 100644 index 0000000..bb8c074 --- /dev/null +++ b/ai-compliance-sdk/internal/gci/mock_data.go @@ -0,0 +1,74 @@ +package gci + +import "time" + +// MockModuleData provides fallback data when academy store is empty +func MockModuleData(tenantID string) []ModuleScore { + return []ModuleScore{ + // DSGVO modules + {ModuleID: "dsgvo-grundlagen", ModuleName: "DSGVO Grundlagen", Assigned: 25, Completed: 22, Category: "dsgvo", RiskWeight: 2.0}, + {ModuleID: "dsgvo-betroffenenrechte", ModuleName: "Betroffenenrechte", Assigned: 25, Completed: 18, Category: "dsgvo", RiskWeight: 2.5}, + {ModuleID: "dsgvo-tom", ModuleName: "Technisch-Organisatorische Massnahmen", Assigned: 20, Completed: 17, Category: "dsgvo", RiskWeight: 2.5}, + {ModuleID: "dsgvo-dsfa", ModuleName: "Datenschutz-Folgenabschaetzung", Assigned: 15, Completed: 10, Category: "dsgvo", RiskWeight: 2.0}, + {ModuleID: "dsgvo-auftragsverarbeitung", ModuleName: "Auftragsverarbeitung", Assigned: 20, Completed: 16, Category: "dsgvo", RiskWeight: 2.0}, + + // NIS2 modules + {ModuleID: "nis2-risikomanagement", ModuleName: "NIS2 Risikomanagement", Assigned: 15, Completed: 11, Category: "nis2", RiskWeight: 3.0}, + {ModuleID: "nis2-incident-response", ModuleName: "NIS2 Incident Response", Assigned: 15, Completed: 9, Category: "nis2", RiskWeight: 3.0}, + {ModuleID: "nis2-supply-chain", ModuleName: "NIS2 Lieferkettensicherheit", Assigned: 10, Completed: 6, Category: "nis2", RiskWeight: 2.0}, + {ModuleID: "nis2-management", ModuleName: "NIS2 Geschaeftsleitungspflicht", Assigned: 10, Completed: 8, Category: "nis2", RiskWeight: 3.0}, + + // ISO 27001 modules + {ModuleID: "iso-isms", ModuleName: "ISMS Grundlagen", Assigned: 20, Completed: 16, Category: "iso27001", RiskWeight: 2.0}, + {ModuleID: "iso-risikobewertung", ModuleName: "Risikobewertung", Assigned: 15, Completed: 12, Category: "iso27001", RiskWeight: 2.0}, + {ModuleID: "iso-zugangssteuerung", ModuleName: "Zugangssteuerung", Assigned: 20, Completed: 18, Category: "iso27001", RiskWeight: 2.0}, + {ModuleID: "iso-kryptografie", ModuleName: "Kryptografie", Assigned: 10, Completed: 7, Category: "iso27001", RiskWeight: 1.5}, + {ModuleID: "iso-physisch", ModuleName: "Physische Sicherheit", Assigned: 10, Completed: 9, Category: "iso27001", RiskWeight: 1.0}, + + // AI Act modules + {ModuleID: "ai-risikokategorien", ModuleName: "KI-Risikokategorien", Assigned: 15, Completed: 12, Category: "ai_act", RiskWeight: 2.5}, + {ModuleID: "ai-transparenz", ModuleName: "KI-Transparenzpflichten", Assigned: 15, Completed: 10, Category: "ai_act", RiskWeight: 2.0}, + {ModuleID: "ai-hochrisiko", ModuleName: "Hochrisiko-KI-Systeme", Assigned: 10, Completed: 6, Category: "ai_act", RiskWeight: 2.5}, + {ModuleID: "ai-governance", ModuleName: "KI-Governance", Assigned: 10, Completed: 7, Category: "ai_act", RiskWeight: 2.0}, + } +} + +// MockCertificateData provides mock certificate validity dates +func MockCertificateData() map[string]time.Time { + now := time.Now() + return map[string]time.Time{ + "dsgvo-grundlagen": now.AddDate(0, 8, 0), // valid 8 months + "dsgvo-betroffenenrechte": now.AddDate(0, 3, 0), // expiring in 3 months + "dsgvo-tom": now.AddDate(0, 10, 0), // valid + "dsgvo-dsfa": now.AddDate(0, -1, 0), // expired 1 month ago + "dsgvo-auftragsverarbeitung": now.AddDate(0, 6, 0), + "nis2-risikomanagement": now.AddDate(0, 5, 0), + "nis2-incident-response": now.AddDate(0, 2, 0), // expiring soon + "nis2-supply-chain": now.AddDate(0, -2, 0), // expired 2 months + "nis2-management": now.AddDate(0, 9, 0), + "iso-isms": now.AddDate(1, 0, 0), + "iso-risikobewertung": now.AddDate(0, 4, 0), + "iso-zugangssteuerung": now.AddDate(0, 11, 0), + "iso-kryptografie": now.AddDate(0, 1, 0), // expiring in 1 month + "iso-physisch": now.AddDate(0, 7, 0), + "ai-risikokategorien": now.AddDate(0, 6, 0), + "ai-transparenz": now.AddDate(0, 3, 0), + "ai-hochrisiko": now.AddDate(0, -3, 0), // expired 3 months + "ai-governance": now.AddDate(0, 5, 0), + } +} + +// MockIncidentData returns mock incident counts for adjustment +func MockIncidentData() (openIncidents int, criticalIncidents int) { + return 3, 1 +} + +// MockGCIHistory returns mock historical GCI snapshots +func MockGCIHistory(tenantID string) []GCISnapshot { + now := time.Now() + return []GCISnapshot{ + {TenantID: tenantID, Score: 58.2, MaturityLevel: MaturityReactive, AreaScores: map[string]float64{"dsgvo": 62, "nis2": 48, "iso27001": 60, "ai_act": 55}, CalculatedAt: now.AddDate(0, -3, 0)}, + {TenantID: tenantID, Score: 62.5, MaturityLevel: MaturityDefined, AreaScores: map[string]float64{"dsgvo": 65, "nis2": 55, "iso27001": 63, "ai_act": 58}, CalculatedAt: now.AddDate(0, -2, 0)}, + {TenantID: tenantID, Score: 67.8, MaturityLevel: MaturityDefined, AreaScores: map[string]float64{"dsgvo": 70, "nis2": 60, "iso27001": 68, "ai_act": 62}, CalculatedAt: now.AddDate(0, -1, 0)}, + } +} diff --git a/ai-compliance-sdk/internal/gci/models.go b/ai-compliance-sdk/internal/gci/models.go new file mode 100644 index 0000000..0f75779 --- /dev/null +++ b/ai-compliance-sdk/internal/gci/models.go @@ -0,0 +1,104 @@ +package gci + +import "time" + +// Level 1: Module Score +type ModuleScore struct { + ModuleID string `json:"module_id"` + ModuleName string `json:"module_name"` + Assigned int `json:"assigned"` + Completed int `json:"completed"` + RawScore float64 `json:"raw_score"` // completions/assigned + ValidityFactor float64 `json:"validity_factor"` // 0.0-1.0 + FinalScore float64 `json:"final_score"` // RawScore * ValidityFactor + RiskWeight float64 `json:"risk_weight"` // module criticality weight + Category string `json:"category"` // dsgvo, nis2, iso27001, ai_act +} + +// Level 2: Risk-weighted Module Score per regulation area +type RiskWeightedScore struct { + AreaID string `json:"area_id"` + AreaName string `json:"area_name"` + Modules []ModuleScore `json:"modules"` + WeightedSum float64 `json:"weighted_sum"` + TotalWeight float64 `json:"total_weight"` + AreaScore float64 `json:"area_score"` // WeightedSum / TotalWeight +} + +// Level 3: Regulation Area Score +type RegulationAreaScore struct { + RegulationID string `json:"regulation_id"` // dsgvo, nis2, iso27001, ai_act + RegulationName string `json:"regulation_name"` // Display name + Score float64 `json:"score"` // 0-100 + Weight float64 `json:"weight"` // regulation weight in GCI + WeightedScore float64 `json:"weighted_score"` // Score * Weight + ModuleCount int `json:"module_count"` + CompletedCount int `json:"completed_count"` +} + +// Level 4: GCI Result +type GCIResult struct { + TenantID string `json:"tenant_id"` + GCIScore float64 `json:"gci_score"` // 0-100 + MaturityLevel string `json:"maturity_level"` // Optimized, Managed, Defined, Reactive, HighRisk + MaturityLabel string `json:"maturity_label"` // German label + CalculatedAt time.Time `json:"calculated_at"` + Profile string `json:"profile"` // default, nis2_relevant, ki_nutzer + AreaScores []RegulationAreaScore `json:"area_scores"` + CriticalityMult float64 `json:"criticality_multiplier"` + IncidentAdj float64 `json:"incident_adjustment"` + AuditTrail []AuditEntry `json:"audit_trail"` +} + +// GCI Breakdown with all 4 levels +type GCIBreakdown struct { + GCIResult + Level1Modules []ModuleScore `json:"level1_modules"` + Level2Areas []RiskWeightedScore `json:"level2_areas"` +} + +// MaturityLevel constants +const ( + MaturityOptimized = "OPTIMIZED" + MaturityManaged = "MANAGED" + MaturityDefined = "DEFINED" + MaturityReactive = "REACTIVE" + MaturityHighRisk = "HIGH_RISK" +) + +// Maturity level labels (German) +var MaturityLabels = map[string]string{ + MaturityOptimized: "Optimiert", + MaturityManaged: "Gesteuert", + MaturityDefined: "Definiert", + MaturityReactive: "Reaktiv", + MaturityHighRisk: "Hohes Risiko", +} + +// AuditEntry for score transparency +type AuditEntry struct { + Timestamp time.Time `json:"timestamp"` + Factor string `json:"factor"` + Description string `json:"description"` + Value float64 `json:"value"` + Impact string `json:"impact"` // positive, negative, neutral +} + +// ComplianceMatrixEntry maps roles to regulations +type ComplianceMatrixEntry struct { + Role string `json:"role"` + RoleName string `json:"role_name"` + Regulations map[string]float64 `json:"regulations"` // regulation_id -> score + OverallScore float64 `json:"overall_score"` + RequiredModules int `json:"required_modules"` + CompletedModules int `json:"completed_modules"` +} + +// GCI History snapshot +type GCISnapshot struct { + TenantID string `json:"tenant_id"` + Score float64 `json:"score"` + MaturityLevel string `json:"maturity_level"` + AreaScores map[string]float64 `json:"area_scores"` + CalculatedAt time.Time `json:"calculated_at"` +} diff --git a/ai-compliance-sdk/internal/gci/nis2_roles.go b/ai-compliance-sdk/internal/gci/nis2_roles.go new file mode 100644 index 0000000..c75d134 --- /dev/null +++ b/ai-compliance-sdk/internal/gci/nis2_roles.go @@ -0,0 +1,118 @@ +package gci + +// NIS2Role defines a NIS2 role classification +type NIS2Role struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + MandatoryModules []string `json:"mandatory_modules"` + Priority int `json:"priority"` // 1=highest +} + +// NIS2RoleAssignment represents a user's NIS2 role +type NIS2RoleAssignment struct { + TenantID string `json:"tenant_id"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + RoleID string `json:"role_id"` + RoleName string `json:"role_name"` + AssignedAt string `json:"assigned_at"` +} + +// NIS2 role definitions +var NIS2Roles = map[string]NIS2Role{ + "N1": { + ID: "N1", + Name: "Geschaeftsleitung", + Description: "Leitungsorgane mit persoenlicher Haftung gemaess NIS2 Art. 20", + Priority: 1, + MandatoryModules: []string{ + "nis2-management", + "nis2-risikomanagement", + "dsgvo-grundlagen", + "iso-isms", + }, + }, + "N2": { + ID: "N2", + Name: "IT-Sicherheit / CISO", + Description: "Verantwortliche fuer IT-Sicherheit und Cybersecurity", + Priority: 2, + MandatoryModules: []string{ + "nis2-risikomanagement", + "nis2-incident-response", + "nis2-supply-chain", + "iso-zugangssteuerung", + "iso-kryptografie", + }, + }, + "N3": { + ID: "N3", + Name: "Kritische Funktionen", + Description: "Mitarbeiter in kritischen Geschaeftsprozessen", + Priority: 3, + MandatoryModules: []string{ + "nis2-risikomanagement", + "nis2-incident-response", + "dsgvo-tom", + "iso-zugangssteuerung", + }, + }, + "N4": { + ID: "N4", + Name: "Allgemeine Mitarbeiter", + Description: "Alle Mitarbeiter mit IT-Zugang", + Priority: 4, + MandatoryModules: []string{ + "nis2-risikomanagement", + "dsgvo-grundlagen", + "iso-isms", + }, + }, + "N5": { + ID: "N5", + Name: "Incident Response Team", + Description: "Mitglieder des IRT/CSIRT gemaess NIS2 Art. 21", + Priority: 2, + MandatoryModules: []string{ + "nis2-incident-response", + "nis2-risikomanagement", + "nis2-supply-chain", + "iso-zugangssteuerung", + "iso-kryptografie", + "iso-isms", + }, + }, +} + +// GetNIS2Role returns a NIS2 role by ID +func GetNIS2Role(roleID string) (NIS2Role, bool) { + r, ok := NIS2Roles[roleID] + return r, ok +} + +// ListNIS2Roles returns all NIS2 roles sorted by priority +func ListNIS2Roles() []NIS2Role { + roles := []NIS2Role{} + // Return in priority order + order := []string{"N1", "N2", "N5", "N3", "N4"} + for _, id := range order { + if r, ok := NIS2Roles[id]; ok { + roles = append(roles, r) + } + } + return roles +} + +// MockNIS2RoleAssignments returns mock role assignments +func MockNIS2RoleAssignments(tenantID string) []NIS2RoleAssignment { + return []NIS2RoleAssignment{ + {TenantID: tenantID, UserID: "user-001", UserName: "Dr. Schmidt", RoleID: "N1", RoleName: "Geschaeftsleitung", AssignedAt: "2025-06-01"}, + {TenantID: tenantID, UserID: "user-002", UserName: "M. Weber", RoleID: "N2", RoleName: "IT-Sicherheit / CISO", AssignedAt: "2025-06-01"}, + {TenantID: tenantID, UserID: "user-003", UserName: "S. Mueller", RoleID: "N5", RoleName: "Incident Response Team", AssignedAt: "2025-07-15"}, + {TenantID: tenantID, UserID: "user-004", UserName: "K. Fischer", RoleID: "N3", RoleName: "Kritische Funktionen", AssignedAt: "2025-08-01"}, + {TenantID: tenantID, UserID: "user-005", UserName: "L. Braun", RoleID: "N3", RoleName: "Kritische Funktionen", AssignedAt: "2025-08-01"}, + {TenantID: tenantID, UserID: "user-006", UserName: "A. Schwarz", RoleID: "N4", RoleName: "Allgemeine Mitarbeiter", AssignedAt: "2025-09-01"}, + {TenantID: tenantID, UserID: "user-007", UserName: "T. Wagner", RoleID: "N4", RoleName: "Allgemeine Mitarbeiter", AssignedAt: "2025-09-01"}, + } +} diff --git a/ai-compliance-sdk/internal/gci/nis2_scoring.go b/ai-compliance-sdk/internal/gci/nis2_scoring.go new file mode 100644 index 0000000..57b7468 --- /dev/null +++ b/ai-compliance-sdk/internal/gci/nis2_scoring.go @@ -0,0 +1,147 @@ +package gci + +import "math" + +// NIS2Score represents the NIS2-specific compliance score +type NIS2Score struct { + TenantID string `json:"tenant_id"` + OverallScore float64 `json:"overall_score"` + MaturityLevel string `json:"maturity_level"` + MaturityLabel string `json:"maturity_label"` + AreaScores []NIS2AreaScore `json:"area_scores"` + RoleCompliance []NIS2RoleScore `json:"role_compliance"` +} + +// NIS2AreaScore represents a NIS2 compliance area +type NIS2AreaScore struct { + AreaID string `json:"area_id"` + AreaName string `json:"area_name"` + Score float64 `json:"score"` + Weight float64 `json:"weight"` + ModuleIDs []string `json:"module_ids"` +} + +// NIS2RoleScore represents completion per NIS2 role +type NIS2RoleScore struct { + RoleID string `json:"role_id"` + RoleName string `json:"role_name"` + AssignedUsers int `json:"assigned_users"` + CompletionRate float64 `json:"completion_rate"` + MandatoryTotal int `json:"mandatory_total"` + MandatoryDone int `json:"mandatory_done"` +} + +// NIS2 scoring areas with weights +// NIS2Score = 25% Management + 25% Incident + 30% IT Security + 20% Supply Chain +var nis2Areas = []struct { + ID string + Name string + Weight float64 + ModuleIDs []string +}{ + { + ID: "management", Name: "Management & Governance", Weight: 0.25, + ModuleIDs: []string{"nis2-management", "dsgvo-grundlagen", "iso-isms"}, + }, + { + ID: "incident", Name: "Vorfallsbehandlung", Weight: 0.25, + ModuleIDs: []string{"nis2-incident-response"}, + }, + { + ID: "it_security", Name: "IT-Sicherheit", Weight: 0.30, + ModuleIDs: []string{"nis2-risikomanagement", "iso-zugangssteuerung", "iso-kryptografie"}, + }, + { + ID: "supply_chain", Name: "Lieferkettensicherheit", Weight: 0.20, + ModuleIDs: []string{"nis2-supply-chain", "dsgvo-auftragsverarbeitung"}, + }, +} + +// CalculateNIS2Score computes the NIS2-specific compliance score +func CalculateNIS2Score(tenantID string) *NIS2Score { + modules := MockModuleData(tenantID) + moduleMap := map[string]ModuleScore{} + for _, m := range modules { + moduleMap[m.ModuleID] = m + } + + areaScores := []NIS2AreaScore{} + totalWeighted := 0.0 + + for _, area := range nis2Areas { + areaScore := NIS2AreaScore{ + AreaID: area.ID, + AreaName: area.Name, + Weight: area.Weight, + ModuleIDs: area.ModuleIDs, + } + + scoreSum := 0.0 + count := 0 + for _, modID := range area.ModuleIDs { + if m, ok := moduleMap[modID]; ok { + if m.Assigned > 0 { + scoreSum += float64(m.Completed) / float64(m.Assigned) * 100 + } + count++ + } + } + if count > 0 { + areaScore.Score = math.Round(scoreSum/float64(count)*10) / 10 + } + totalWeighted += areaScore.Score * areaScore.Weight + areaScores = append(areaScores, areaScore) + } + + overallScore := math.Round(totalWeighted*10) / 10 + + // Calculate role compliance + roleAssignments := MockNIS2RoleAssignments(tenantID) + roleScores := calculateNIS2RoleScores(roleAssignments, moduleMap) + + return &NIS2Score{ + TenantID: tenantID, + OverallScore: overallScore, + MaturityLevel: determineMaturityLevel(overallScore), + MaturityLabel: MaturityLabels[determineMaturityLevel(overallScore)], + AreaScores: areaScores, + RoleCompliance: roleScores, + } +} + +func calculateNIS2RoleScores(assignments []NIS2RoleAssignment, moduleMap map[string]ModuleScore) []NIS2RoleScore { + // Count users per role + roleCounts := map[string]int{} + for _, a := range assignments { + roleCounts[a.RoleID]++ + } + + scores := []NIS2RoleScore{} + for roleID, role := range NIS2Roles { + rs := NIS2RoleScore{ + RoleID: roleID, + RoleName: role.Name, + AssignedUsers: roleCounts[roleID], + MandatoryTotal: len(role.MandatoryModules), + } + + completionSum := 0.0 + for _, modID := range role.MandatoryModules { + if m, ok := moduleMap[modID]; ok { + if m.Assigned > 0 { + rate := float64(m.Completed) / float64(m.Assigned) + completionSum += rate + if rate >= 0.8 { // 80%+ = considered done + rs.MandatoryDone++ + } + } + } + } + if rs.MandatoryTotal > 0 { + rs.CompletionRate = math.Round(completionSum/float64(rs.MandatoryTotal)*100*10) / 10 + } + scores = append(scores, rs) + } + + return scores +} diff --git a/ai-compliance-sdk/internal/gci/validity.go b/ai-compliance-sdk/internal/gci/validity.go new file mode 100644 index 0000000..5578f3d --- /dev/null +++ b/ai-compliance-sdk/internal/gci/validity.go @@ -0,0 +1,59 @@ +package gci + +import ( + "math" + "time" +) + +const ( + // GracePeriodDays is the number of days after expiry during which + // the certificate still contributes (with declining factor) + GracePeriodDays = 180 + + // DecayStartDays is how many days before expiry the linear decay begins + DecayStartDays = 180 +) + +// CalculateValidityFactor computes the validity factor for a certificate +// based on its expiry date. +// +// Rules: +// - Certificate not yet expiring (>6 months): factor = 1.0 +// - Certificate expiring within 6 months: linear decay from 1.0 to 0.5 +// - Certificate expired: linear decay from 0.5 to 0.0 over grace period +// - Certificate expired beyond grace period: factor = 0.0 +func CalculateValidityFactor(validUntil time.Time, now time.Time) float64 { + daysUntilExpiry := validUntil.Sub(now).Hours() / 24.0 + + if daysUntilExpiry > float64(DecayStartDays) { + // Not yet in decay window + return 1.0 + } + + if daysUntilExpiry > 0 { + // In pre-expiry decay window: linear from 1.0 to 0.5 + fraction := daysUntilExpiry / float64(DecayStartDays) + return 0.5 + 0.5*fraction + } + + // Certificate is expired + daysExpired := -daysUntilExpiry + if daysExpired > float64(GracePeriodDays) { + return 0.0 + } + + // In grace period: linear from 0.5 to 0.0 + fraction := 1.0 - (daysExpired / float64(GracePeriodDays)) + return math.Max(0, 0.5*fraction) +} + +// IsExpired returns true if the certificate is past its validity date +func IsExpired(validUntil time.Time, now time.Time) bool { + return now.After(validUntil) +} + +// IsExpiringSoon returns true if the certificate expires within the decay window +func IsExpiringSoon(validUntil time.Time, now time.Time) bool { + daysUntil := validUntil.Sub(now).Hours() / 24.0 + return daysUntil > 0 && daysUntil <= float64(DecayStartDays) +} diff --git a/ai-compliance-sdk/internal/gci/weights.go b/ai-compliance-sdk/internal/gci/weights.go new file mode 100644 index 0000000..7c50742 --- /dev/null +++ b/ai-compliance-sdk/internal/gci/weights.go @@ -0,0 +1,78 @@ +package gci + +// WeightProfile defines regulation weights for different compliance profiles +type WeightProfile struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Weights map[string]float64 `json:"weights"` // regulation_id -> weight (0.0-1.0) +} + +// Default weight profiles +var DefaultProfiles = map[string]WeightProfile{ + "default": { + ID: "default", + Name: "Standard", + Description: "Ausgewogenes Profil fuer allgemeine Compliance", + Weights: map[string]float64{ + "dsgvo": 0.30, + "nis2": 0.25, + "iso27001": 0.25, + "ai_act": 0.20, + }, + }, + "nis2_relevant": { + ID: "nis2_relevant", + Name: "NIS2-relevant", + Description: "Fuer Betreiber kritischer Infrastrukturen", + Weights: map[string]float64{ + "dsgvo": 0.25, + "nis2": 0.35, + "iso27001": 0.25, + "ai_act": 0.15, + }, + }, + "ki_nutzer": { + ID: "ki_nutzer", + Name: "KI-Nutzer", + Description: "Fuer Organisationen mit KI-Einsatz", + Weights: map[string]float64{ + "dsgvo": 0.25, + "nis2": 0.25, + "iso27001": 0.20, + "ai_act": 0.30, + }, + }, +} + +// ModuleRiskWeights defines risk criticality per module type +var ModuleRiskWeights = map[string]float64{ + "incident_response": 3.0, + "management_awareness": 3.0, + "data_protection": 2.5, + "it_security": 2.5, + "supply_chain": 2.0, + "risk_assessment": 2.0, + "access_control": 2.0, + "business_continuity": 2.0, + "employee_training": 1.5, + "documentation": 1.5, + "physical_security": 1.0, + "general": 1.0, +} + +// GetProfile returns a weight profile by ID, defaulting to "default" +func GetProfile(profileID string) WeightProfile { + if p, ok := DefaultProfiles[profileID]; ok { + return p + } + return DefaultProfiles["default"] +} + +// GetModuleRiskWeight returns the risk weight for a module category +func GetModuleRiskWeight(category string) float64 { + if w, ok := ModuleRiskWeights[category]; ok { + return w + } + return 1.0 +}