Remove duplicate compliance and DSGVO admin pages that have been superseded by the unified SDK pipeline. Update navigation, sidebar, roles, and module registry to reflect the new structure. Add DSFA corpus API proxy and source-policy components. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1138 lines
58 KiB
TypeScript
1138 lines
58 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Security Dashboard - DevSecOps
|
|
*
|
|
* Security scan results, vulnerability tracking, and compliance status
|
|
* Migrated from old admin (/admin/security)
|
|
*/
|
|
|
|
import { useEffect, useState, useCallback } from 'react'
|
|
import { PagePurpose } from '@/components/common/PagePurpose'
|
|
import { DevOpsPipelineSidebarResponsive } from '@/components/infrastructure/DevOpsPipelineSidebar'
|
|
|
|
interface ToolStatus {
|
|
name: string
|
|
installed: boolean
|
|
version: string | null
|
|
last_run: string | null
|
|
last_findings: number
|
|
}
|
|
|
|
interface Finding {
|
|
id: string
|
|
tool: string
|
|
severity: string
|
|
title: string
|
|
message: string | null
|
|
file: string | null
|
|
line: number | null
|
|
found_at: string
|
|
}
|
|
|
|
interface SeveritySummary {
|
|
critical: number
|
|
high: number
|
|
medium: number
|
|
low: number
|
|
info: number
|
|
total: number
|
|
}
|
|
|
|
interface HistoryItem {
|
|
timestamp: string
|
|
title: string
|
|
description: string
|
|
status: string
|
|
}
|
|
|
|
type ScanType = 'secrets' | 'sast' | 'deps' | 'containers' | 'sbom' | 'all'
|
|
|
|
interface MonitoringMetric {
|
|
name: string
|
|
value: number
|
|
unit: string
|
|
status: 'ok' | 'warning' | 'critical'
|
|
trend: 'up' | 'down' | 'stable'
|
|
}
|
|
|
|
interface ActiveAlert {
|
|
id: string
|
|
severity: 'critical' | 'high' | 'medium' | 'low'
|
|
title: string
|
|
source: string
|
|
timestamp: string
|
|
acknowledged: boolean
|
|
}
|
|
|
|
export default function SecurityDashboardPage() {
|
|
const [tools, setTools] = useState<ToolStatus[]>([])
|
|
const [findings, setFindings] = useState<Finding[]>([])
|
|
const [summary, setSummary] = useState<SeveritySummary>({ critical: 0, high: 0, medium: 0, low: 0, info: 0, total: 0 })
|
|
const [history, setHistory] = useState<HistoryItem[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [scanning, setScanning] = useState<string | null>(null)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [activeTab, setActiveTab] = useState<'overview' | 'findings' | 'tools' | 'history' | 'monitoring'>('overview')
|
|
const [monitoringMetrics, setMonitoringMetrics] = useState<MonitoringMetric[]>([])
|
|
const [activeAlerts, setActiveAlerts] = useState<ActiveAlert[]>([])
|
|
const [severityFilter, setSeverityFilter] = useState<string | null>(null)
|
|
const [toolFilter, setToolFilter] = useState<string | null>(null)
|
|
const [showFullDocs, setShowFullDocs] = useState(false)
|
|
const [isInitialLoad, setIsInitialLoad] = useState(true)
|
|
const [scanMessage, setScanMessage] = useState<string | null>(null)
|
|
const [lastScanTime, setLastScanTime] = useState<string | null>(null)
|
|
|
|
const fetchData = useCallback(async (showLoadingSpinner = false) => {
|
|
// Only show loading spinner on initial load, not on auto-refresh
|
|
if (showLoadingSpinner) {
|
|
setLoading(true)
|
|
}
|
|
setError(null)
|
|
|
|
try {
|
|
const [toolsRes, findingsRes, summaryRes, historyRes] = await Promise.all([
|
|
fetch('/api/v1/security/tools'),
|
|
fetch('/api/v1/security/findings'),
|
|
fetch('/api/v1/security/summary'),
|
|
fetch('/api/v1/security/history'),
|
|
])
|
|
|
|
if (toolsRes.ok) {
|
|
setTools(await toolsRes.json())
|
|
}
|
|
if (findingsRes.ok) {
|
|
setFindings(await findingsRes.json())
|
|
}
|
|
if (summaryRes.ok) {
|
|
setSummary(await summaryRes.json())
|
|
}
|
|
if (historyRes.ok) {
|
|
setHistory(await historyRes.json())
|
|
}
|
|
|
|
// Fetch monitoring data
|
|
const [metricsRes, alertsRes] = await Promise.all([
|
|
fetch('/api/v1/security/monitoring/metrics'),
|
|
fetch('/api/v1/security/monitoring/alerts'),
|
|
])
|
|
|
|
if (metricsRes.ok) {
|
|
setMonitoringMetrics(await metricsRes.json())
|
|
} else {
|
|
// Default metrics if API not available
|
|
setMonitoringMetrics([
|
|
{ name: 'API Latency', value: 45, unit: 'ms', status: 'ok', trend: 'stable' },
|
|
{ name: 'Auth Failures', value: 3, unit: '/h', status: 'ok', trend: 'down' },
|
|
{ name: 'Rate Limit Hits', value: 12, unit: '/h', status: 'warning', trend: 'up' },
|
|
{ name: 'Failed Logins', value: 0, unit: '/24h', status: 'ok', trend: 'stable' },
|
|
{ name: 'SSL Expiry', value: 45, unit: 'days', status: 'ok', trend: 'down' },
|
|
{ name: 'Open Ports', value: 8, unit: '', status: 'ok', trend: 'stable' },
|
|
])
|
|
}
|
|
if (alertsRes.ok) {
|
|
setActiveAlerts(await alertsRes.json())
|
|
} else {
|
|
setActiveAlerts([])
|
|
}
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Verbindung zum Backend fehlgeschlagen')
|
|
} finally {
|
|
setLoading(false)
|
|
setIsInitialLoad(false)
|
|
}
|
|
}, [])
|
|
|
|
// Initial load only - runs once
|
|
useEffect(() => {
|
|
fetchData(true) // Show loading spinner on initial load
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [])
|
|
|
|
// Auto-refresh every 60 seconds (without loading spinner)
|
|
useEffect(() => {
|
|
const interval = setInterval(() => fetchData(false), 60000)
|
|
return () => clearInterval(interval)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [])
|
|
|
|
const scanTypeLabels: Record<ScanType, string> = {
|
|
secrets: 'Secrets (Gitleaks)',
|
|
sast: 'SAST (Semgrep + Bandit)',
|
|
deps: 'Dependencies (Trivy + Grype)',
|
|
containers: 'Container',
|
|
sbom: 'SBOM (Syft)',
|
|
all: 'Full Scan',
|
|
}
|
|
|
|
const runScan = async (scanType: ScanType) => {
|
|
console.log(`Starting scan: ${scanType}`)
|
|
setScanning(scanType)
|
|
setError(null)
|
|
setScanMessage(`${scanTypeLabels[scanType]} wird gestartet...`)
|
|
|
|
try {
|
|
const response = await fetch(`/api/v1/security/scan/${scanType}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
|
|
console.log(`Scan response status: ${response.status}`)
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text()
|
|
console.error(`Scan error: ${errorText}`)
|
|
throw new Error(`Scan fehlgeschlagen: ${response.status} - ${errorText}`)
|
|
}
|
|
|
|
const result = await response.json()
|
|
console.log('Scan result:', result)
|
|
|
|
// Show success message
|
|
setScanMessage(`${scanTypeLabels[scanType]} laeuft im Hintergrund. Ergebnisse werden in wenigen Sekunden aktualisiert.`)
|
|
setLastScanTime(new Date().toLocaleTimeString('de-DE'))
|
|
|
|
// Refresh data after scan completes
|
|
setTimeout(() => {
|
|
fetchData(false)
|
|
setScanMessage(null)
|
|
}, 5000)
|
|
setTimeout(() => fetchData(false), 15000)
|
|
setTimeout(() => fetchData(false), 30000)
|
|
} catch (err) {
|
|
console.error('Scan error:', err)
|
|
setError(err instanceof Error ? err.message : 'Scan fehlgeschlagen - Pruefe Browser-Konsole')
|
|
setScanMessage(null)
|
|
} finally {
|
|
setScanning(null)
|
|
}
|
|
}
|
|
|
|
const getSeverityBadge = (severity: string) => {
|
|
const base = 'px-3 py-1 rounded-full text-xs font-semibold uppercase'
|
|
switch (severity.toUpperCase()) {
|
|
case 'CRITICAL':
|
|
return `${base} bg-red-100 text-red-800`
|
|
case 'HIGH':
|
|
return `${base} bg-orange-100 text-orange-800`
|
|
case 'MEDIUM':
|
|
return `${base} bg-yellow-100 text-yellow-800`
|
|
case 'LOW':
|
|
return `${base} bg-green-100 text-green-800`
|
|
default:
|
|
return `${base} bg-blue-100 text-blue-800`
|
|
}
|
|
}
|
|
|
|
const getStatusBadge = (installed: boolean) => {
|
|
return installed
|
|
? 'px-2 py-1 rounded text-xs font-semibold bg-green-100 text-green-800'
|
|
: 'px-2 py-1 rounded text-xs font-semibold bg-red-100 text-red-800'
|
|
}
|
|
|
|
const getHistoryStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'success':
|
|
return 'bg-green-500'
|
|
case 'warning':
|
|
return 'bg-yellow-500'
|
|
case 'error':
|
|
return 'bg-red-500'
|
|
default:
|
|
return 'bg-slate-400'
|
|
}
|
|
}
|
|
|
|
const getOverallStatus = () => {
|
|
if (summary.critical > 0) {
|
|
return { label: 'Critical Issues', color: 'bg-red-100 text-red-800' }
|
|
}
|
|
if (summary.high > 0) {
|
|
return { label: 'High Issues', color: 'bg-orange-100 text-orange-800' }
|
|
}
|
|
if (summary.medium > 0) {
|
|
return { label: 'Warnings', color: 'bg-yellow-100 text-yellow-800' }
|
|
}
|
|
return { label: 'Secure', color: 'bg-green-100 text-green-800' }
|
|
}
|
|
|
|
const filteredFindings = findings.filter(f => {
|
|
if (severityFilter && f.severity.toUpperCase() !== severityFilter.toUpperCase()) return false
|
|
if (toolFilter && f.tool.toLowerCase() !== toolFilter.toLowerCase()) return false
|
|
return true
|
|
})
|
|
|
|
const toolDescriptions: Record<string, { icon: string; desc: string }> = {
|
|
gitleaks: { icon: '🔑', desc: 'Secrets Detection in Git History' },
|
|
semgrep: { icon: '🔍', desc: 'Static Application Security Testing (SAST)' },
|
|
bandit: { icon: '🐍', desc: 'Python Security Linter' },
|
|
trivy: { icon: '🔒', desc: 'Container & Filesystem Vulnerability Scanner' },
|
|
grype: { icon: '🐛', desc: 'Vulnerability Scanner for Dependencies' },
|
|
syft: { icon: '📦', desc: 'SBOM Generator (CycloneDX/SPDX)' },
|
|
}
|
|
|
|
// Map tool names to backend scan types
|
|
const toolToScanType: Record<string, ScanType> = {
|
|
gitleaks: 'secrets',
|
|
semgrep: 'sast',
|
|
bandit: 'sast',
|
|
trivy: 'deps',
|
|
grype: 'deps',
|
|
syft: 'sbom',
|
|
}
|
|
|
|
const status = getOverallStatus()
|
|
|
|
return (
|
|
<div>
|
|
<PagePurpose
|
|
title="Security Dashboard"
|
|
purpose="DevSecOps Dashboard mit Echtzeit-Scan-Ergebnissen. Ueberwachen Sie Secrets, Schwachstellen und Abhaengigkeiten. Fuehren Sie Security-Scans manuell aus oder integrieren Sie sie in Ihre CI/CD-Pipeline."
|
|
audience={['DevOps', 'Security', 'Entwickler']}
|
|
gdprArticles={['Art. 32 (Sicherheit der Verarbeitung)']}
|
|
architecture={{
|
|
services: ['Gitleaks', 'Semgrep', 'Bandit', 'Trivy', 'Grype', 'Syft'],
|
|
databases: ['JSON Reports'],
|
|
}}
|
|
relatedPages={[
|
|
{ name: 'SBOM', href: '/infrastructure/sbom', description: 'Software Bill of Materials' },
|
|
{ name: 'Middleware', href: '/infrastructure/middleware', description: 'API Gateway & Rate Limiting' },
|
|
{ name: 'Controls', href: '/sdk/controls', description: 'Security Controls' },
|
|
]}
|
|
collapsible={true}
|
|
defaultCollapsed={true}
|
|
/>
|
|
|
|
{/* DevOps Pipeline Sidebar */}
|
|
<DevOpsPipelineSidebarResponsive currentTool="security" />
|
|
|
|
{/* Header with Status */}
|
|
<div className="bg-white rounded-xl border border-slate-200 p-6 mb-6">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<div className="flex items-center gap-4">
|
|
<h2 className="text-xl font-semibold text-slate-900">Security Status</h2>
|
|
<span className={`px-3 py-1 rounded-full text-sm font-semibold ${status.color}`}>
|
|
{status.label}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<span className="flex items-center gap-2 text-sm text-slate-500">
|
|
<span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
|
Auto-Refresh aktiv
|
|
</span>
|
|
<button
|
|
onClick={() => runScan('all')}
|
|
disabled={scanning !== null}
|
|
className={`px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2 ${
|
|
scanning === 'all'
|
|
? 'bg-orange-200 text-orange-800'
|
|
: 'bg-orange-600 text-white hover:bg-orange-700 disabled:opacity-50'
|
|
}`}
|
|
>
|
|
{scanning === 'all' ? (
|
|
<>
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-orange-700" />
|
|
<span>Full Scan laeuft...</span>
|
|
</>
|
|
) : (
|
|
'Full Scan starten'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Severity Summary */}
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
|
<div className="border-l-4 border-red-500 bg-slate-50 rounded-r-lg p-4">
|
|
<div className="text-3xl font-bold text-red-600">{summary.critical}</div>
|
|
<div className="text-sm text-slate-600">Critical</div>
|
|
</div>
|
|
<div className="border-l-4 border-orange-500 bg-slate-50 rounded-r-lg p-4">
|
|
<div className="text-3xl font-bold text-orange-600">{summary.high}</div>
|
|
<div className="text-sm text-slate-600">High</div>
|
|
</div>
|
|
<div className="border-l-4 border-yellow-500 bg-slate-50 rounded-r-lg p-4">
|
|
<div className="text-3xl font-bold text-yellow-600">{summary.medium}</div>
|
|
<div className="text-sm text-slate-600">Medium</div>
|
|
</div>
|
|
<div className="border-l-4 border-green-500 bg-slate-50 rounded-r-lg p-4">
|
|
<div className="text-3xl font-bold text-green-600">{summary.low}</div>
|
|
<div className="text-sm text-slate-600">Low</div>
|
|
</div>
|
|
<div className="border-l-4 border-blue-500 bg-slate-50 rounded-r-lg p-4">
|
|
<div className="text-3xl font-bold text-blue-600">{summary.info}</div>
|
|
<div className="text-sm text-slate-600">Info</div>
|
|
</div>
|
|
<div className="border-l-4 border-slate-400 bg-slate-50 rounded-r-lg p-4">
|
|
<div className="text-3xl font-bold text-slate-700">{summary.total}</div>
|
|
<div className="text-sm text-slate-600">Total</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden mb-6">
|
|
<div className="flex border-b border-slate-200">
|
|
{(['overview', 'findings', 'tools', 'history', 'monitoring'] as const).map(tab => (
|
|
<button
|
|
key={tab}
|
|
onClick={() => setActiveTab(tab)}
|
|
className={`px-6 py-3 text-sm font-medium transition-colors ${
|
|
activeTab === tab
|
|
? 'bg-orange-50 text-orange-700 border-b-2 border-orange-600'
|
|
: 'text-slate-600 hover:bg-slate-50'
|
|
}`}
|
|
>
|
|
{tab === 'overview' && 'Uebersicht'}
|
|
{tab === 'findings' && `Findings (${summary.total})`}
|
|
{tab === 'tools' && 'Tools'}
|
|
{tab === 'history' && 'Historie'}
|
|
{tab === 'monitoring' && 'Monitoring'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="p-6">
|
|
{/* Scan Status Message */}
|
|
{scanMessage && (
|
|
<div className="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg text-blue-700 flex items-center gap-3">
|
|
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600" />
|
|
<div>
|
|
<span className="font-medium">{scanMessage}</span>
|
|
{lastScanTime && (
|
|
<span className="text-sm text-blue-500 ml-2">(gestartet um {lastScanTime})</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{loading ? (
|
|
<div className="flex justify-center py-12">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-orange-600" />
|
|
</div>
|
|
) : (
|
|
<>
|
|
{/* Overview Tab */}
|
|
{activeTab === 'overview' && (
|
|
<div className="space-y-6">
|
|
{/* Tools Grid */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">DevSecOps Tools</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{tools.map(tool => {
|
|
const info = toolDescriptions[tool.name.toLowerCase()] || { icon: '🔧', desc: 'Security Tool' }
|
|
return (
|
|
<div key={tool.name} className="bg-slate-50 rounded-lg p-4 border border-slate-200">
|
|
<div className="flex justify-between items-start mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xl">{info.icon}</span>
|
|
<span className="font-semibold text-slate-900">{tool.name}</span>
|
|
</div>
|
|
<span className={getStatusBadge(tool.installed)}>
|
|
{tool.installed ? 'Installiert' : 'Nicht installiert'}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-slate-600 mb-3">{info.desc}</p>
|
|
<div className="flex justify-between items-center text-xs text-slate-500">
|
|
<span>{tool.version || '-'}</span>
|
|
<span>Letzter Scan: {tool.last_run || 'Nie'}</span>
|
|
</div>
|
|
<button
|
|
onClick={() => runScan(toolToScanType[tool.name.toLowerCase()] || 'all')}
|
|
disabled={scanning !== null || !tool.installed}
|
|
className={`mt-3 w-full px-3 py-1.5 text-sm border rounded transition-colors flex items-center justify-center gap-2 ${
|
|
scanning === toolToScanType[tool.name.toLowerCase()]
|
|
? 'bg-orange-100 border-orange-300 text-orange-700'
|
|
: 'bg-white border-slate-300 hover:bg-slate-50 disabled:opacity-50'
|
|
}`}
|
|
>
|
|
{scanning === toolToScanType[tool.name.toLowerCase()] ? (
|
|
<>
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-orange-600" />
|
|
<span>Scan laeuft...</span>
|
|
</>
|
|
) : (
|
|
'Scan starten'
|
|
)}
|
|
</button>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Recent Findings */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Aktuelle Findings</h3>
|
|
{findings.length === 0 ? (
|
|
<div className="text-center py-8 text-slate-500">
|
|
<span className="text-4xl block mb-2">🎉</span>
|
|
Keine Findings gefunden. Das ist gut!
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="border-b border-slate-200">
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Severity</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Tool</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Finding</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Datei</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Gefunden</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{findings.slice(0, 10).map((finding, idx) => (
|
|
<tr key={`${finding.id}-${idx}`} className="border-b border-slate-100 hover:bg-slate-50">
|
|
<td className="py-3 px-4">
|
|
<span className={getSeverityBadge(finding.severity)}>{finding.severity}</span>
|
|
</td>
|
|
<td className="py-3 px-4 text-sm text-slate-600">{finding.tool}</td>
|
|
<td className="py-3 px-4 text-sm text-slate-900">{finding.title}</td>
|
|
<td className="py-3 px-4 text-sm text-slate-500 font-mono">{finding.file || '-'}</td>
|
|
<td className="py-3 px-4 text-sm text-slate-500">
|
|
{finding.found_at ? new Date(finding.found_at).toLocaleString('de-DE', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
}) : '-'}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
{findings.length > 10 && (
|
|
<button
|
|
onClick={() => setActiveTab('findings')}
|
|
className="mt-4 text-sm text-orange-600 hover:text-orange-700"
|
|
>
|
|
Alle {findings.length} Findings anzeigen →
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Findings Tab */}
|
|
{activeTab === 'findings' && (
|
|
<div>
|
|
{/* Filters */}
|
|
<div className="flex gap-2 mb-4 flex-wrap">
|
|
<button
|
|
onClick={() => setSeverityFilter(null)}
|
|
className={`px-3 py-1 rounded-full text-sm ${!severityFilter ? 'bg-orange-600 text-white' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'}`}
|
|
>
|
|
Alle
|
|
</button>
|
|
{['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'].map(sev => (
|
|
<button
|
|
key={sev}
|
|
onClick={() => setSeverityFilter(sev)}
|
|
className={`px-3 py-1 rounded-full text-sm ${severityFilter === sev ? 'bg-orange-600 text-white' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'}`}
|
|
>
|
|
{sev}
|
|
</button>
|
|
))}
|
|
<span className="mx-2 border-l border-slate-300" />
|
|
{['gitleaks', 'semgrep', 'bandit', 'trivy', 'grype'].map(t => (
|
|
<button
|
|
key={t}
|
|
onClick={() => setToolFilter(toolFilter === t ? null : t)}
|
|
className={`px-3 py-1 rounded-full text-sm capitalize ${toolFilter === t ? 'bg-orange-600 text-white' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'}`}
|
|
>
|
|
{t}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{filteredFindings.length === 0 ? (
|
|
<div className="text-center py-8 text-slate-500">
|
|
Keine Findings mit diesem Filter gefunden.
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="border-b border-slate-200">
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Severity</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Tool</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Finding</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Datei</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Zeile</th>
|
|
<th className="text-left py-3 px-4 text-xs font-semibold text-slate-500 uppercase">Gefunden</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filteredFindings.map((finding, idx) => (
|
|
<tr key={`${finding.id}-${idx}`} className="border-b border-slate-100 hover:bg-slate-50">
|
|
<td className="py-3 px-4">
|
|
<span className={getSeverityBadge(finding.severity)}>{finding.severity}</span>
|
|
</td>
|
|
<td className="py-3 px-4 text-sm text-slate-600">{finding.tool}</td>
|
|
<td className="py-3 px-4">
|
|
<div className="text-sm text-slate-900">{finding.title}</div>
|
|
{finding.message && (
|
|
<div className="text-xs text-slate-500 mt-1">{finding.message}</div>
|
|
)}
|
|
</td>
|
|
<td className="py-3 px-4 text-sm text-slate-500 font-mono max-w-xs truncate">
|
|
{finding.file || '-'}
|
|
</td>
|
|
<td className="py-3 px-4 text-sm text-slate-500">{finding.line || '-'}</td>
|
|
<td className="py-3 px-4 text-sm text-slate-500">
|
|
{finding.found_at ? new Date(finding.found_at).toLocaleDateString('de-DE') : '-'}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Tools Tab */}
|
|
{activeTab === 'tools' && (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{tools.map(tool => {
|
|
const info = toolDescriptions[tool.name.toLowerCase()] || { icon: '🔧', desc: 'Security Tool' }
|
|
return (
|
|
<div key={tool.name} className="bg-white border border-slate-200 rounded-lg p-6">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-1">
|
|
<span className="text-2xl">{info.icon}</span>
|
|
<h3 className="text-lg font-semibold text-slate-900">{tool.name}</h3>
|
|
</div>
|
|
<p className="text-sm text-slate-600">{info.desc}</p>
|
|
</div>
|
|
<span className={getStatusBadge(tool.installed)}>
|
|
{tool.installed ? 'Installiert' : 'Nicht installiert'}
|
|
</span>
|
|
</div>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-500">Version:</span>
|
|
<span className="font-mono">{tool.version || '-'}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-500">Letzter Scan:</span>
|
|
<span>{tool.last_run || 'Nie'}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-500">Findings:</span>
|
|
<span className="font-semibold">{tool.last_findings}</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2 mt-4">
|
|
<button
|
|
onClick={() => runScan(toolToScanType[tool.name.toLowerCase()] || 'all')}
|
|
disabled={scanning !== null || !tool.installed}
|
|
className={`flex-1 px-4 py-2 rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2 ${
|
|
scanning === toolToScanType[tool.name.toLowerCase()]
|
|
? 'bg-orange-200 text-orange-800'
|
|
: 'bg-orange-600 text-white hover:bg-orange-700 disabled:opacity-50'
|
|
}`}
|
|
>
|
|
{scanning === toolToScanType[tool.name.toLowerCase()] ? (
|
|
<>
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-orange-700" />
|
|
<span>Scan laeuft...</span>
|
|
</>
|
|
) : (
|
|
'Scan starten'
|
|
)}
|
|
</button>
|
|
<button
|
|
onClick={() => window.open(`/api/v1/security/reports/${tool.name.toLowerCase()}`, '_blank')}
|
|
className="px-4 py-2 border border-slate-300 text-slate-700 rounded-lg text-sm font-medium hover:bg-slate-50 transition-colors"
|
|
>
|
|
Report
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{/* History Tab */}
|
|
{activeTab === 'history' && (
|
|
<div className="space-y-4">
|
|
{history.length === 0 ? (
|
|
<div className="text-center py-8 text-slate-500">
|
|
Keine Scan-Historie vorhanden.
|
|
</div>
|
|
) : (
|
|
<div className="relative">
|
|
<div className="absolute left-4 top-0 bottom-0 w-0.5 bg-slate-200" />
|
|
{history.map((item, idx) => (
|
|
<div key={idx} className="relative pl-10 pb-6">
|
|
<div className={`absolute left-2.5 w-3 h-3 rounded-full ${getHistoryStatusColor(item.status)}`} />
|
|
<div className="bg-slate-50 rounded-lg p-4 border border-slate-200">
|
|
<div className="flex justify-between items-start mb-1">
|
|
<span className="font-semibold text-slate-900">{item.title}</span>
|
|
<span className="text-xs text-slate-500">
|
|
{new Date(item.timestamp).toLocaleString('de-DE')}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-slate-600">{item.description}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Monitoring Tab */}
|
|
{activeTab === 'monitoring' && (
|
|
<div className="space-y-6">
|
|
{/* Real-time Metrics */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
|
<svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
Security Metriken
|
|
</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
|
{monitoringMetrics.map((metric, idx) => (
|
|
<div
|
|
key={idx}
|
|
className={`rounded-lg p-4 border ${
|
|
metric.status === 'critical' ? 'bg-red-50 border-red-200' :
|
|
metric.status === 'warning' ? 'bg-yellow-50 border-yellow-200' :
|
|
'bg-green-50 border-green-200'
|
|
}`}
|
|
>
|
|
<div className="flex justify-between items-start mb-2">
|
|
<span className="text-xs text-slate-600">{metric.name}</span>
|
|
<span className={`text-xs ${
|
|
metric.trend === 'up' ? 'text-red-600' :
|
|
metric.trend === 'down' ? 'text-green-600' :
|
|
'text-slate-500'
|
|
}`}>
|
|
{metric.trend === 'up' ? '↑' : metric.trend === 'down' ? '↓' : '→'}
|
|
</span>
|
|
</div>
|
|
<div className={`text-2xl font-bold ${
|
|
metric.status === 'critical' ? 'text-red-700' :
|
|
metric.status === 'warning' ? 'text-yellow-700' :
|
|
'text-green-700'
|
|
}`}>
|
|
{metric.value}
|
|
<span className="text-sm font-normal ml-1">{metric.unit}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Active Alerts */}
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
|
<svg className="w-5 h-5 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
</svg>
|
|
Aktive Alerts
|
|
{activeAlerts.filter(a => !a.acknowledged).length > 0 && (
|
|
<span className="ml-2 px-2 py-0.5 bg-red-500 text-white text-xs rounded-full">
|
|
{activeAlerts.filter(a => !a.acknowledged).length}
|
|
</span>
|
|
)}
|
|
</h3>
|
|
{activeAlerts.length === 0 ? (
|
|
<div className="text-center py-8 bg-green-50 rounded-lg border border-green-200">
|
|
<span className="text-4xl block mb-2">✓</span>
|
|
<span className="text-green-700">Keine aktiven Security-Alerts</span>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{activeAlerts.map((alert) => (
|
|
<div
|
|
key={alert.id}
|
|
className={`flex items-center justify-between p-4 rounded-lg border ${
|
|
alert.severity === 'critical' ? 'bg-red-50 border-red-200' :
|
|
alert.severity === 'high' ? 'bg-orange-50 border-orange-200' :
|
|
alert.severity === 'medium' ? 'bg-yellow-50 border-yellow-200' :
|
|
'bg-blue-50 border-blue-200'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<span className={`px-2 py-1 text-xs font-semibold rounded uppercase ${
|
|
alert.severity === 'critical' ? 'bg-red-100 text-red-800' :
|
|
alert.severity === 'high' ? 'bg-orange-100 text-orange-800' :
|
|
alert.severity === 'medium' ? 'bg-yellow-100 text-yellow-800' :
|
|
'bg-blue-100 text-blue-800'
|
|
}`}>
|
|
{alert.severity}
|
|
</span>
|
|
<div>
|
|
<div className="font-medium text-slate-900">{alert.title}</div>
|
|
<div className="text-xs text-slate-500">
|
|
{alert.source} • {new Date(alert.timestamp).toLocaleString('de-DE')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{!alert.acknowledged && (
|
|
<button className="px-3 py-1 text-xs bg-white border border-slate-300 rounded hover:bg-slate-50">
|
|
Bestaetigen
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Security Overview Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="bg-slate-50 rounded-lg p-4 border border-slate-200">
|
|
<h4 className="font-medium text-slate-900 mb-3 flex items-center gap-2">
|
|
<svg className="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
</svg>
|
|
Authentifizierung
|
|
</h4>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">Aktive Sessions</span>
|
|
<span className="font-medium">24</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">Fehlgeschlagene Logins (24h)</span>
|
|
<span className="font-medium text-green-600">0</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">2FA-Quote</span>
|
|
<span className="font-medium text-green-600">100%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-slate-50 rounded-lg p-4 border border-slate-200">
|
|
<h4 className="font-medium text-slate-900 mb-3 flex items-center gap-2">
|
|
<svg className="w-4 h-4 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
SSL/TLS
|
|
</h4>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">Zertifikate</span>
|
|
<span className="font-medium">5 aktiv</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">Naechster Ablauf</span>
|
|
<span className="font-medium text-green-600">45 Tage</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">TLS Version</span>
|
|
<span className="font-medium">1.3</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-slate-50 rounded-lg p-4 border border-slate-200">
|
|
<h4 className="font-medium text-slate-900 mb-3 flex items-center gap-2">
|
|
<svg className="w-4 h-4 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
Firewall
|
|
</h4>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">Blockierte IPs (24h)</span>
|
|
<span className="font-medium">12</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">Rate Limit Hits</span>
|
|
<span className="font-medium text-yellow-600">7</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">WAF Status</span>
|
|
<span className="font-medium text-green-600">Aktiv</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Link to CI/CD for pipeline monitoring */}
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
<div>
|
|
<div className="font-medium text-blue-900">Pipeline Security</div>
|
|
<div className="text-sm text-blue-700">Security-Scans in CI/CD Pipelines und Container-Status</div>
|
|
</div>
|
|
</div>
|
|
<a
|
|
href="/infrastructure/ci-cd"
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors"
|
|
>
|
|
CI/CD Dashboard →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Documentation Section */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<div className="p-6">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
|
|
<svg className="w-5 h-5 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
Security Dokumentation
|
|
</h3>
|
|
<button
|
|
onClick={() => setShowFullDocs(!showFullDocs)}
|
|
className="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors flex items-center gap-2 text-sm font-medium"
|
|
>
|
|
<svg className={`w-4 h-4 transition-transform ${showFullDocs ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
{showFullDocs ? 'Weniger anzeigen' : 'Vollstaendige Dokumentation'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Short Description */}
|
|
<div className="prose prose-slate max-w-none">
|
|
<p className="text-slate-600">
|
|
Das Security Dashboard bietet einen zentralen Ueberblick ueber alle DevSecOps-Aktivitaeten.
|
|
Es integriert 6 Security-Tools fuer umfassende Code- und Infrastruktur-Sicherheit:
|
|
Secrets Detection, Static Analysis (SAST), Dependency Scanning und SBOM-Generierung.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Tool Quick Reference */}
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3 mt-4">
|
|
<div className="bg-red-50 p-3 rounded-lg text-center">
|
|
<span className="text-lg">🔑</span>
|
|
<p className="text-xs font-medium text-red-800 mt-1">Gitleaks</p>
|
|
<p className="text-xs text-red-600">Secrets</p>
|
|
</div>
|
|
<div className="bg-blue-50 p-3 rounded-lg text-center">
|
|
<span className="text-lg">🔍</span>
|
|
<p className="text-xs font-medium text-blue-800 mt-1">Semgrep</p>
|
|
<p className="text-xs text-blue-600">SAST</p>
|
|
</div>
|
|
<div className="bg-yellow-50 p-3 rounded-lg text-center">
|
|
<span className="text-lg">🐍</span>
|
|
<p className="text-xs font-medium text-yellow-800 mt-1">Bandit</p>
|
|
<p className="text-xs text-yellow-600">Python</p>
|
|
</div>
|
|
<div className="bg-purple-50 p-3 rounded-lg text-center">
|
|
<span className="text-lg">🔒</span>
|
|
<p className="text-xs font-medium text-purple-800 mt-1">Trivy</p>
|
|
<p className="text-xs text-purple-600">Container</p>
|
|
</div>
|
|
<div className="bg-green-50 p-3 rounded-lg text-center">
|
|
<span className="text-lg">🐛</span>
|
|
<p className="text-xs font-medium text-green-800 mt-1">Grype</p>
|
|
<p className="text-xs text-green-600">Dependencies</p>
|
|
</div>
|
|
<div className="bg-orange-50 p-3 rounded-lg text-center">
|
|
<span className="text-lg">📦</span>
|
|
<p className="text-xs font-medium text-orange-800 mt-1">Syft</p>
|
|
<p className="text-xs text-orange-600">SBOM</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Full Documentation (Expandable) */}
|
|
{showFullDocs && (
|
|
<div className="mt-6 bg-slate-50 rounded-lg p-6 border border-slate-200">
|
|
<div className="prose prose-slate max-w-none prose-headings:text-slate-900 prose-p:text-slate-600 prose-li:text-slate-600">
|
|
|
|
<h3>1. Security Tools Uebersicht</h3>
|
|
|
|
<h4>🔑 Gitleaks - Secrets Detection</h4>
|
|
<p>Durchsucht die gesamte Git-Historie nach versehentlich eingecheckten Secrets wie API-Keys, Passwoertern und Tokens.</p>
|
|
<ul>
|
|
<li><strong>Scan-Bereich:</strong> Git-Historie, Commits, Branches</li>
|
|
<li><strong>Erkannte Secrets:</strong> AWS Keys, GitHub Tokens, Private Keys, Passwoerter</li>
|
|
<li><strong>Ausgabe:</strong> JSON-Report mit Fundstelle, Commit-Hash, Autor</li>
|
|
</ul>
|
|
|
|
<h4>🔍 Semgrep - Static Application Security Testing</h4>
|
|
<p>Fuehrt regelbasierte statische Code-Analyse durch, um Sicherheitsluecken und Anti-Patterns zu finden.</p>
|
|
<ul>
|
|
<li><strong>Unterstuetzte Sprachen:</strong> Python, JavaScript, TypeScript, Go, Java</li>
|
|
<li><strong>Regelsets:</strong> OWASP Top 10, CWE, Security Best Practices</li>
|
|
<li><strong>Findings:</strong> SQL Injection, XSS, Path Traversal, Insecure Deserialization</li>
|
|
</ul>
|
|
|
|
<h4>🐍 Bandit - Python Security Linter</h4>
|
|
<p>Spezialisierter Security-Linter fuer Python-Code mit Fokus auf haeufige Sicherheitsprobleme.</p>
|
|
<ul>
|
|
<li><strong>Checks:</strong> Hardcoded Passwords, SQL Injection, Shell Injection</li>
|
|
<li><strong>Severity Levels:</strong> LOW, MEDIUM, HIGH</li>
|
|
<li><strong>Confidence:</strong> LOW, MEDIUM, HIGH</li>
|
|
</ul>
|
|
|
|
<h4>🔒 Trivy - Container & Filesystem Scanner</h4>
|
|
<p>Scannt Container-Images und Dateisysteme auf bekannte Schwachstellen (CVEs).</p>
|
|
<ul>
|
|
<li><strong>Scan-Typen:</strong> Container Images, Filesystems, Git Repositories</li>
|
|
<li><strong>Datenbanken:</strong> NVD, GitHub Advisory, Alpine SecDB, RedHat OVAL</li>
|
|
<li><strong>Ausgabe:</strong> CVE-ID, Severity, Fixed Version, Description</li>
|
|
</ul>
|
|
|
|
<h4>🐛 Grype - Dependency Vulnerability Scanner</h4>
|
|
<p>Analysiert Software-Abhaengigkeiten auf bekannte Sicherheitsluecken.</p>
|
|
<ul>
|
|
<li><strong>Package Manager:</strong> npm, pip, go mod, Maven, Gradle</li>
|
|
<li><strong>Input:</strong> SBOM (CycloneDX/SPDX), Lockfiles, Container Images</li>
|
|
<li><strong>Matching:</strong> CPE-basiert, Package URL (purl)</li>
|
|
</ul>
|
|
|
|
<h4>📦 Syft - SBOM Generator</h4>
|
|
<p>Erstellt Software Bill of Materials (SBOM) fuer Compliance und Supply-Chain-Security.</p>
|
|
<ul>
|
|
<li><strong>Formate:</strong> CycloneDX (JSON/XML), SPDX, Syft JSON</li>
|
|
<li><strong>Erfassung:</strong> Packages, Lizenzen, Versionen, Checksums</li>
|
|
<li><strong>Compliance:</strong> NIS2, ISO 27001, DSGVO Art. 32</li>
|
|
</ul>
|
|
|
|
<h3>2. Severity-Klassifizierung</h3>
|
|
<table className="min-w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b">
|
|
<th className="text-left py-2">Severity</th>
|
|
<th className="text-left py-2">CVSS Score</th>
|
|
<th className="text-left py-2">Reaktionszeit</th>
|
|
<th className="text-left py-2">Beispiele</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr className="border-b"><td className="py-2"><span className="px-2 py-0.5 bg-red-100 text-red-800 rounded text-xs font-semibold">CRITICAL</span></td><td>9.0 - 10.0</td><td>Sofort (24h)</td><td>RCE, Auth Bypass, Exposed Secrets</td></tr>
|
|
<tr className="border-b"><td className="py-2"><span className="px-2 py-0.5 bg-orange-100 text-orange-800 rounded text-xs font-semibold">HIGH</span></td><td>7.0 - 8.9</td><td>1-3 Tage</td><td>SQL Injection, XSS, Path Traversal</td></tr>
|
|
<tr className="border-b"><td className="py-2"><span className="px-2 py-0.5 bg-yellow-100 text-yellow-800 rounded text-xs font-semibold">MEDIUM</span></td><td>4.0 - 6.9</td><td>1-2 Wochen</td><td>Information Disclosure, CSRF</td></tr>
|
|
<tr className="border-b"><td className="py-2"><span className="px-2 py-0.5 bg-green-100 text-green-800 rounded text-xs font-semibold">LOW</span></td><td>0.1 - 3.9</td><td>Naechster Sprint</td><td>Minor Info Leak, Best Practice</td></tr>
|
|
<tr><td className="py-2"><span className="px-2 py-0.5 bg-blue-100 text-blue-800 rounded text-xs font-semibold">INFO</span></td><td>0.0</td><td>Optional</td><td>Empfehlungen, Hinweise</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>3. Scan-Workflow</h3>
|
|
<pre className="bg-slate-800 text-slate-100 p-4 rounded-lg overflow-x-auto text-sm">
|
|
{`┌─────────────────────────────────────────────────────────────┐
|
|
│ Security Scan Pipeline │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ 1. Secrets Detection (Gitleaks) │
|
|
│ └── Scannt Git-Historie nach API-Keys & Credentials │
|
|
│ ↓ │
|
|
│ 2. Static Analysis (Semgrep + Bandit) │
|
|
│ └── Code-Analyse auf Sicherheitsluecken │
|
|
│ ↓ │
|
|
│ 3. Dependency Scan (Trivy + Grype) │
|
|
│ └── CVE-Check aller Abhaengigkeiten │
|
|
│ ↓ │
|
|
│ 4. SBOM Generation (Syft) │
|
|
│ └── Software Bill of Materials erstellen │
|
|
│ ↓ │
|
|
│ 5. Report & Dashboard │
|
|
│ └── Ergebnisse aggregieren und visualisieren │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘`}
|
|
</pre>
|
|
|
|
<h3>4. Remediation-Strategien</h3>
|
|
|
|
<h4>Bei Secrets-Findings:</h4>
|
|
<ol>
|
|
<li>Secret sofort rotieren (neue API-Keys, Passwoerter)</li>
|
|
<li>Git-Historie bereinigen (BFG Repo-Cleaner oder git filter-branch)</li>
|
|
<li>Betroffene Systeme auf unauthorisierte Zugriffe pruefen</li>
|
|
<li>Secret-Scanning in Pre-Commit-Hooks aktivieren</li>
|
|
</ol>
|
|
|
|
<h4>Bei SAST-Findings:</h4>
|
|
<ol>
|
|
<li>Finding-Details und betroffene Code-Stelle analysieren</li>
|
|
<li>Empfohlene Fix-Strategie aus Semgrep-Dokumentation anwenden</li>
|
|
<li>Unit-Tests fuer den Fix schreiben</li>
|
|
<li>Code-Review durch Security-erfahrenen Entwickler</li>
|
|
</ol>
|
|
|
|
<h4>Bei Dependency-Vulnerabilities:</h4>
|
|
<ol>
|
|
<li>Pruefen ob ein Patch/Update verfuegbar ist</li>
|
|
<li>Abhaengigkeit auf gepatchte Version aktualisieren</li>
|
|
<li>Falls kein Patch: Workaround oder Alternative evaluieren</li>
|
|
<li>Temporaer: WAF-Regel als Mitigation</li>
|
|
</ol>
|
|
|
|
<h3>5. CI/CD Integration</h3>
|
|
<p>Security-Scans sind in die Gitea Actions Pipeline integriert:</p>
|
|
<ul>
|
|
<li><strong>Pre-Commit:</strong> Gitleaks (lokale Secrets-Pruefung)</li>
|
|
<li><strong>Pull Request:</strong> Semgrep, Bandit, Trivy (Blocking bei Critical)</li>
|
|
<li><strong>Main Branch:</strong> Full Scan + SBOM-Update</li>
|
|
<li><strong>Nightly:</strong> Dependency-Update-Check</li>
|
|
</ul>
|
|
|
|
<h3>6. Compliance-Mapping</h3>
|
|
<table className="min-w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b">
|
|
<th className="text-left py-2">Regulation</th>
|
|
<th className="text-left py-2">Artikel</th>
|
|
<th className="text-left py-2">Erfuellt durch</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr className="border-b"><td className="py-2">DSGVO</td><td>Art. 32</td><td>Alle Security-Scans, Vulnerability Management</td></tr>
|
|
<tr className="border-b"><td className="py-2">NIS2</td><td>Art. 21</td><td>SBOM, Supply-Chain-Security, Incident Response</td></tr>
|
|
<tr className="border-b"><td className="py-2">ISO 27001</td><td>A.12.6</td><td>Vulnerability Management, Patch Management</td></tr>
|
|
<tr><td className="py-2">OWASP</td><td>Top 10</td><td>SAST (Semgrep), Secrets Detection</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>7. API-Endpunkte</h3>
|
|
<table className="min-w-full text-sm font-mono">
|
|
<thead>
|
|
<tr className="border-b">
|
|
<th className="text-left py-2">Methode</th>
|
|
<th className="text-left py-2">Endpoint</th>
|
|
<th className="text-left py-2 font-sans">Beschreibung</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr className="border-b"><td className="py-2"><span className="bg-blue-100 text-blue-700 px-1 rounded">GET</span></td><td>/api/v1/security/tools</td><td className="font-sans">Tool-Status abrufen</td></tr>
|
|
<tr className="border-b"><td className="py-2"><span className="bg-blue-100 text-blue-700 px-1 rounded">GET</span></td><td>/api/v1/security/findings</td><td className="font-sans">Alle Findings abrufen</td></tr>
|
|
<tr className="border-b"><td className="py-2"><span className="bg-blue-100 text-blue-700 px-1 rounded">GET</span></td><td>/api/v1/security/summary</td><td className="font-sans">Severity-Zusammenfassung</td></tr>
|
|
<tr className="border-b"><td className="py-2"><span className="bg-blue-100 text-blue-700 px-1 rounded">GET</span></td><td>/api/v1/security/history</td><td className="font-sans">Scan-Historie</td></tr>
|
|
<tr className="border-b"><td className="py-2"><span className="bg-green-100 text-green-700 px-1 rounded">POST</span></td><td>/api/v1/security/scan/all</td><td className="font-sans">Full Scan starten</td></tr>
|
|
<tr><td className="py-2"><span className="bg-green-100 text-green-700 px-1 rounded">POST</span></td><td>/api/v1/security/scan/[tool]</td><td className="font-sans">Einzelnes Tool scannen</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|