implemented LE and ACME and fixed some bugs
This commit is contained in:
441
docs/API_CHEATSHEET.md
Normal file
441
docs/API_CHEATSHEET.md
Normal file
@@ -0,0 +1,441 @@
|
||||
# Certigo Addon API Cheatsheet
|
||||
|
||||
## Base URL
|
||||
```
|
||||
http://localhost:8080/api
|
||||
```
|
||||
|
||||
Alle Endpunkte unterstützen CORS und akzeptieren OPTIONS-Requests für Preflight-Checks.
|
||||
|
||||
---
|
||||
|
||||
## System & Statistics
|
||||
|
||||
### GET /health
|
||||
Prüft den Systemstatus des Backends.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"message": "Backend ist erreichbar",
|
||||
"time": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl http://localhost:8080/api/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /stats
|
||||
Ruft Statistiken über die Anzahl der Spaces, FQDNs und CSRs ab.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"spaces": 5,
|
||||
"fqdns": 12,
|
||||
"csrs": 7
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl http://localhost:8080/api/stats
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spaces
|
||||
|
||||
### GET /spaces
|
||||
Ruft alle Spaces ab.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Mein Space",
|
||||
"description": "Beschreibung des Spaces",
|
||||
"createdAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl http://localhost:8080/api/spaces
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /spaces
|
||||
Erstellt einen neuen Space.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "Mein Space",
|
||||
"description": "Beschreibung des Spaces"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created`
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Mein Space",
|
||||
"description": "Beschreibung des Spaces",
|
||||
"createdAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/spaces \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Mein Space",
|
||||
"description": "Beschreibung des Spaces"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE /spaces/{id}
|
||||
Löscht einen Space.
|
||||
|
||||
**Query Parameters:**
|
||||
- `deleteFqdns` (optional, boolean): Wenn `true`, werden alle FQDNs des Spaces mitgelöscht.
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"message": "Space erfolgreich gelöscht"
|
||||
}
|
||||
```
|
||||
|
||||
**Fehler:**
|
||||
- `409 Conflict`: Space enthält noch FQDNs (nur wenn `deleteFqdns` nicht `true` ist)
|
||||
|
||||
**Beispiele:**
|
||||
```bash
|
||||
# Space ohne FQDNs löschen
|
||||
curl -X DELETE http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000
|
||||
|
||||
# Space mit allen FQDNs löschen
|
||||
curl -X DELETE "http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000?deleteFqdns=true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /spaces/{id}/fqdns/count
|
||||
Ruft die Anzahl der FQDNs für einen Space ab.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"count": 5
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns/count
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FQDNs
|
||||
|
||||
### GET /spaces/{id}/fqdns
|
||||
Ruft alle FQDNs für einen Space ab.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"spaceId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"fqdn": "example.com",
|
||||
"description": "Beschreibung des FQDN",
|
||||
"createdAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /spaces/{id}/fqdns
|
||||
Erstellt einen neuen FQDN innerhalb eines Spaces.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"fqdn": "example.com",
|
||||
"description": "Beschreibung des FQDN"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created`
|
||||
```json
|
||||
{
|
||||
"id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"spaceId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"fqdn": "example.com",
|
||||
"description": "Beschreibung des FQDN",
|
||||
"createdAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Fehler:**
|
||||
- `409 Conflict`: FQDN existiert bereits in diesem Space (case-insensitive)
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"fqdn": "example.com",
|
||||
"description": "Beschreibung des FQDN"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE /spaces/{id}/fqdns/{fqdnId}
|
||||
Löscht einen einzelnen FQDN.
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"message": "FQDN erfolgreich gelöscht"
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns/660e8400-e29b-41d4-a716-446655440000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE /spaces/{id}/fqdns
|
||||
Löscht alle FQDNs eines Spaces.
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"message": "Alle FQDNs erfolgreich gelöscht",
|
||||
"deletedCount": 5
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE /fqdns?confirm=true
|
||||
Löscht alle FQDNs aus allen Spaces. **WICHTIG:** Erfordert `confirm=true` Query-Parameter.
|
||||
|
||||
**Query Parameters:**
|
||||
- `confirm` (required, boolean): Muss `true` sein, um die Operation auszuführen.
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"message": "Alle FQDNs erfolgreich gelöscht",
|
||||
"deletedCount": 12
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8080/api/fqdns?confirm=true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSRs (Certificate Signing Requests)
|
||||
|
||||
### POST /spaces/{spaceId}/fqdns/{fqdnId}/csr
|
||||
Lädt einen CSR (Certificate Signing Request) im PEM-Format hoch.
|
||||
|
||||
**Request:** `multipart/form-data`
|
||||
- `csr` (file, required): CSR-Datei im PEM-Format (.pem oder .csr)
|
||||
- `spaceId` (string, required): ID des Spaces
|
||||
- `fqdn` (string, required): Name des FQDNs
|
||||
|
||||
**Response:** `201 Created`
|
||||
```json
|
||||
{
|
||||
"id": "770e8400-e29b-41d4-a716-446655440000",
|
||||
"fqdnId": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"spaceId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"fqdn": "example.com",
|
||||
"csrPem": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----",
|
||||
"subject": "CN=example.com",
|
||||
"publicKeyAlgorithm": "RSA",
|
||||
"signatureAlgorithm": "SHA256-RSA",
|
||||
"keySize": 2048,
|
||||
"dnsNames": ["example.com", "www.example.com"],
|
||||
"emailAddresses": ["admin@example.com"],
|
||||
"ipAddresses": ["192.168.1.1"],
|
||||
"uris": ["https://example.com"],
|
||||
"extensions": [
|
||||
{
|
||||
"id": "2.5.29.37",
|
||||
"oid": "2.5.29.37",
|
||||
"name": "X509v3 Extended Key Usage",
|
||||
"critical": false,
|
||||
"value": "301406082b0601050507030106082b06010505070302",
|
||||
"description": "TLS Web Server Authentication\n TLS Web Client Authentication",
|
||||
"purposes": ["TLS Web Server Authentication", "TLS Web Client Authentication"]
|
||||
}
|
||||
],
|
||||
"createdAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns/660e8400-e29b-41d4-a716-446655440000/csr \
|
||||
-F "csr=@/path/to/certificate.csr" \
|
||||
-F "spaceId=550e8400-e29b-41d4-a716-446655440000" \
|
||||
-F "fqdn=example.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /spaces/{spaceId}/fqdns/{fqdnId}/csr
|
||||
Ruft CSR(s) für einen FQDN ab.
|
||||
|
||||
**Query Parameters:**
|
||||
- `latest` (optional, boolean): Wenn `true`, wird nur der neueste CSR zurückgegeben. Standard: alle CSRs.
|
||||
|
||||
**Response (ohne `latest`):** `200 OK`
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "770e8400-e29b-41d4-a716-446655440000",
|
||||
"fqdnId": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"spaceId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"fqdn": "example.com",
|
||||
"csrPem": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----",
|
||||
"subject": "CN=example.com",
|
||||
"publicKeyAlgorithm": "RSA",
|
||||
"signatureAlgorithm": "SHA256-RSA",
|
||||
"keySize": 2048,
|
||||
"dnsNames": ["example.com"],
|
||||
"emailAddresses": [],
|
||||
"ipAddresses": [],
|
||||
"uris": [],
|
||||
"extensions": [...],
|
||||
"createdAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Response (mit `latest=true`):** `200 OK`
|
||||
```json
|
||||
{
|
||||
"id": "770e8400-e29b-41d4-a716-446655440000",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiele:**
|
||||
```bash
|
||||
# Alle CSRs abrufen
|
||||
curl http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns/660e8400-e29b-41d4-a716-446655440000/csr
|
||||
|
||||
# Nur neuesten CSR abrufen
|
||||
curl "http://localhost:8080/api/spaces/550e8400-e29b-41d4-a716-446655440000/fqdns/660e8400-e29b-41d4-a716-446655440000/csr?latest=true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extension Details
|
||||
|
||||
CSR Extensions werden automatisch geparst und in menschenlesbarem Format zurückgegeben:
|
||||
|
||||
### Bekannte Extension-Namen:
|
||||
- `X509v3 Key Usage`
|
||||
- `X509v3 Subject Alternative Name`
|
||||
- `X509v3 Basic Constraints`
|
||||
- `X509v3 Extended Key Usage`
|
||||
- `X509v3 CRL Distribution Points`
|
||||
- `X509v3 Certificate Policies`
|
||||
- `X509v3 Authority Key Identifier`
|
||||
- `X509v3 Subject Key Identifier`
|
||||
|
||||
### Extended Key Usage Werte:
|
||||
- `TLS Web Server Authentication`
|
||||
- `TLS Web Client Authentication`
|
||||
- `Code Signing`
|
||||
- `E-mail Protection`
|
||||
- `Time Stamping`
|
||||
- `OCSP Signing`
|
||||
- `IPsec End System`
|
||||
- `IPsec Tunnel`
|
||||
- `IPsec User`
|
||||
|
||||
---
|
||||
|
||||
## HTTP Status Codes
|
||||
|
||||
- `200 OK`: Erfolgreiche Anfrage
|
||||
- `201 Created`: Ressource erfolgreich erstellt
|
||||
- `400 Bad Request`: Ungültige Anfrage
|
||||
- `404 Not Found`: Ressource nicht gefunden
|
||||
- `409 Conflict`: Konflikt (z.B. FQDN existiert bereits)
|
||||
- `500 Internal Server Error`: Serverfehler
|
||||
|
||||
---
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
Bei Fehlern wird eine Fehlermeldung im Response-Body zurückgegeben:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Fehlermeldung"
|
||||
}
|
||||
```
|
||||
|
||||
Oder als Plain-Text:
|
||||
```
|
||||
Fehlermeldung
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CORS
|
||||
|
||||
Alle Endpunkte unterstützen CORS mit folgenden Headern:
|
||||
- `Access-Control-Allow-Origin: *`
|
||||
- `Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS`
|
||||
- `Access-Control-Allow-Headers: Content-Type`
|
||||
|
||||
---
|
||||
|
||||
## Hinweise
|
||||
|
||||
1. **UUIDs**: Alle IDs sind UUIDs im Format `550e8400-e29b-41d4-a716-446655440000`
|
||||
2. **Timestamps**: Alle Timestamps sind im RFC3339-Format (ISO 8601)
|
||||
3. **FQDN-Validierung**: FQDNs werden case-insensitive verglichen
|
||||
4. **CSR-Format**: CSRs müssen im PEM-Format vorliegen
|
||||
5. **Cascading Deletes**: Beim Löschen eines Spaces werden alle zugehörigen FQDNs und CSRs automatisch gelöscht (ON DELETE CASCADE)
|
||||
|
||||
865
docs/AUTO_RENEWAL_KONZEPT.md
Normal file
865
docs/AUTO_RENEWAL_KONZEPT.md
Normal file
@@ -0,0 +1,865 @@
|
||||
# Automatische Zertifikats-Erneuerung Konzept für Let's Encrypt
|
||||
|
||||
## 1. Übersicht
|
||||
|
||||
### 1.1 Ziel
|
||||
Implementierung einer automatischen Erneuerungsfunktion für Let's Encrypt (LE) Zertifikate, die ablaufende Zertifikate rechtzeitig erneuert, bevor sie ablaufen.
|
||||
|
||||
### 1.2 Anforderungen
|
||||
- **Proaktive Erneuerung**: Zertifikate werden erneuert, bevor sie ablaufen (z.B. 30 Tage vor Ablauf)
|
||||
- **Automatische Ausführung**: Läuft im Hintergrund ohne Benutzerinteraktion
|
||||
- **Fehlerbehandlung**: Robustes Error-Handling und Retry-Mechanismus
|
||||
- **Logging & Monitoring**: Umfassendes Logging für Nachverfolgbarkeit
|
||||
- **Konfigurierbarkeit**: Erneuerungs-Schwellenwerte und Intervalle konfigurierbar
|
||||
- **Berechtigungen**: Respektiert bestehende Permission-Systeme
|
||||
- **DNS-Validierung**: Automatische DNS-Challenge-Validierung vor Erneuerung
|
||||
|
||||
---
|
||||
|
||||
## 2. Architektur
|
||||
|
||||
### 2.1 Komponenten-Übersicht
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Auto-Renewal System │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │
|
||||
│ │ Scheduler │───>│ Certificate │───>│ Renewal │ │
|
||||
│ │ (Cron) │ │ Scanner │ │ Worker │ │
|
||||
│ └──────────────┘ └──────────────┘ └─────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ v v v │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │
|
||||
│ │ Config │ │ Database │ │ ACME │ │
|
||||
│ │ Manager │ │ Queries │ │ Client │ │
|
||||
│ └──────────────┘ └──────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │
|
||||
│ │ Logger │ │ Notifier │ │ Retry │ │
|
||||
│ │ Service │ │ Service │ │ Manager │ │
|
||||
│ └──────────────┘ └──────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 Workflow
|
||||
|
||||
```
|
||||
1. Scheduler startet (z.B. täglich um 02:00 Uhr)
|
||||
│
|
||||
├─> 2. Scanner identifiziert ablaufende Zertifikate
|
||||
│ (expires_at < now + renewal_threshold)
|
||||
│
|
||||
├─> 3. Für jedes Zertifikat:
|
||||
│ │
|
||||
│ ├─> 3.1 Prüfe ob Auto-Renewal aktiviert
|
||||
│ │
|
||||
│ ├─> 3.2 Prüfe ob bereits Erneuerung läuft
|
||||
│ │
|
||||
│ ├─> 3.3 Prüfe Berechtigungen (Space-Zugriff)
|
||||
│ │
|
||||
│ ├─> 3.4 Validiere DNS (CNAME Check)
|
||||
│ │
|
||||
│ ├─> 3.5 Erstelle Renewal-Job
|
||||
│ │
|
||||
│ └─> 3.6 Queue für Worker
|
||||
│
|
||||
├─> 4. Worker verarbeitet Jobs sequenziell
|
||||
│ │
|
||||
│ ├─> 4.1 Hole FQDN-Informationen
|
||||
│ │
|
||||
│ ├─> 4.2 Hole ACME-Provider-Konfiguration
|
||||
│ │
|
||||
│ ├─> 4.3 Rufe RequestCertificate() auf
|
||||
│ │
|
||||
│ ├─> 4.4 Speichere neues Zertifikat
|
||||
│ │
|
||||
│ ├─> 4.5 Markiere altes Zertifikat als "replaced"
|
||||
│ │
|
||||
│ └─> 4.6 Logge Erfolg/Fehler
|
||||
│
|
||||
└─> 5. Cleanup & Reporting
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Datenbank-Schema
|
||||
|
||||
### 3.1 Erweiterte Certificates-Tabelle
|
||||
|
||||
```sql
|
||||
-- Migration: Erweitere certificates-Tabelle um Auto-Renewal-Felder
|
||||
ALTER TABLE certificates ADD COLUMN auto_renewal_enabled BOOLEAN DEFAULT 1;
|
||||
ALTER TABLE certificates ADD COLUMN renewal_attempts INTEGER DEFAULT 0;
|
||||
ALTER TABLE certificates ADD COLUMN last_renewal_attempt DATETIME;
|
||||
ALTER TABLE certificates ADD COLUMN next_renewal_check DATETIME;
|
||||
ALTER TABLE certificates ADD COLUMN renewal_status TEXT; -- 'pending', 'in_progress', 'success', 'failed', 'disabled'
|
||||
ALTER TABLE certificates ADD COLUMN replaced_by_cert_id TEXT; -- ID des neuen Zertifikats
|
||||
ALTER TABLE certificates ADD COLUMN replaces_cert_id TEXT; -- ID des ersetzten Zertifikats
|
||||
```
|
||||
|
||||
### 3.2 Neue Tabelle: certificate_renewal_logs
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS certificate_renewal_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
certificate_id TEXT NOT NULL,
|
||||
fqdn_id TEXT NOT NULL,
|
||||
space_id TEXT NOT NULL,
|
||||
renewal_status TEXT NOT NULL, -- 'started', 'success', 'failed', 'skipped'
|
||||
renewal_reason TEXT, -- 'expiring_soon', 'manual', 'retry'
|
||||
error_message TEXT,
|
||||
old_expires_at DATETIME,
|
||||
new_expires_at DATETIME,
|
||||
new_certificate_id TEXT,
|
||||
renewal_duration_seconds INTEGER,
|
||||
trace_id TEXT,
|
||||
created_at DATETIME NOT NULL,
|
||||
FOREIGN KEY (certificate_id) REFERENCES certificates(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (fqdn_id) REFERENCES fqdns(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (space_id) REFERENCES spaces(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_renewal_logs_certificate_id ON certificate_renewal_logs(certificate_id);
|
||||
CREATE INDEX idx_renewal_logs_created_at ON certificate_renewal_logs(created_at);
|
||||
CREATE INDEX idx_renewal_logs_status ON certificate_renewal_logs(renewal_status);
|
||||
```
|
||||
|
||||
### 3.3 Neue Tabelle: renewal_config
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS renewal_config (
|
||||
id TEXT PRIMARY KEY DEFAULT 'global',
|
||||
enabled BOOLEAN DEFAULT 1,
|
||||
renewal_threshold_days INTEGER DEFAULT 30, -- Erneuere X Tage vor Ablauf
|
||||
check_interval_hours INTEGER DEFAULT 24, -- Wie oft prüfen (in Stunden)
|
||||
max_renewal_attempts INTEGER DEFAULT 3, -- Max. Versuche pro Zertifikat
|
||||
retry_delay_hours INTEGER DEFAULT 24, -- Wartezeit zwischen Retries
|
||||
notification_enabled BOOLEAN DEFAULT 0,
|
||||
notification_email TEXT,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- Initiale Konfiguration einfügen
|
||||
INSERT INTO renewal_config (id, enabled, renewal_threshold_days, check_interval_hours, max_renewal_attempts, retry_delay_hours, created_at, updated_at)
|
||||
VALUES ('global', 1, 30, 24, 3, 24, datetime('now'), datetime('now'));
|
||||
```
|
||||
|
||||
### 3.4 FQDN-Tabelle Erweiterung
|
||||
|
||||
```sql
|
||||
-- Optional: Pro-FQDN Auto-Renewal-Einstellungen
|
||||
ALTER TABLE fqdns ADD COLUMN auto_renewal_enabled BOOLEAN DEFAULT 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Konfiguration
|
||||
|
||||
### 4.1 Global Configuration (Environment Variables)
|
||||
|
||||
```bash
|
||||
# Auto-Renewal Einstellungen
|
||||
AUTO_RENEWAL_ENABLED=true
|
||||
AUTO_RENEWAL_THRESHOLD_DAYS=30
|
||||
AUTO_RENEWAL_CHECK_INTERVAL_HOURS=24
|
||||
AUTO_RENEWAL_SCHEDULE="0 2 * * *" # Cron-Format: Täglich um 02:00 Uhr
|
||||
AUTO_RENEWAL_MAX_ATTEMPTS=3
|
||||
AUTO_RENEWAL_RETRY_DELAY_HOURS=24
|
||||
|
||||
# Notifications
|
||||
AUTO_RENEWAL_NOTIFICATIONS_ENABLED=false
|
||||
AUTO_RENEWAL_NOTIFICATION_EMAIL=admin@example.com
|
||||
|
||||
# Concurrency
|
||||
AUTO_RENEWAL_MAX_CONCURRENT=1 # Anzahl paralleler Erneuerungen
|
||||
```
|
||||
|
||||
### 4.2 Per-FQDN Configuration
|
||||
|
||||
- **Default**: Auto-Renewal aktiviert für alle FQDNs
|
||||
- **Opt-out**: Pro FQDN deaktivierbar über `fqdns.auto_renewal_enabled`
|
||||
- **Opt-out**: Pro Zertifikat deaktivierbar über `certificates.auto_renewal_enabled`
|
||||
|
||||
---
|
||||
|
||||
## 5. Scheduler-Implementierung
|
||||
|
||||
### 5.1 Optionen
|
||||
|
||||
#### Option A: Go Cron Library (Empfohlen)
|
||||
```go
|
||||
import "github.com/robfig/cron/v3"
|
||||
|
||||
c := cron.New()
|
||||
c.AddFunc("0 2 * * *", func() {
|
||||
runCertificateRenewalScan()
|
||||
})
|
||||
c.Start()
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Einfach zu implementieren
|
||||
- Gut getestet
|
||||
- Cron-Format unterstützt
|
||||
|
||||
**Nachteile:**
|
||||
- Läuft nur im Backend-Prozess
|
||||
- Bei Neustart muss Scheduler neu gestartet werden
|
||||
|
||||
#### Option B: Separate Background Service
|
||||
```go
|
||||
// Separate Go-Routine die kontinuierlich läuft
|
||||
go func() {
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
runCertificateRenewalScan()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Einfacher zu debuggen
|
||||
- Keine externe Dependency
|
||||
|
||||
**Nachteile:**
|
||||
- Weniger flexibel als Cron
|
||||
- Muss selbst implementiert werden
|
||||
|
||||
#### Option C: System Cron Job
|
||||
```bash
|
||||
# /etc/cron.d/certigo-renewal
|
||||
0 2 * * * curl -X POST http://localhost:8080/api/internal/renewal/scan
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Unabhängig vom Backend-Prozess
|
||||
- Läuft auch wenn Backend neu gestartet wird
|
||||
|
||||
**Nachteile:**
|
||||
- Externe Dependency (curl)
|
||||
- Schwieriger zu debuggen
|
||||
- Benötigt separaten API-Endpunkt
|
||||
|
||||
**Empfehlung: Option A (Go Cron Library)**
|
||||
|
||||
---
|
||||
|
||||
## 6. Certificate Scanner
|
||||
|
||||
### 6.1 Query für ablaufende Zertifikate
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
c.id,
|
||||
c.fqdn_id,
|
||||
c.space_id,
|
||||
c.certificate_id,
|
||||
c.provider_id,
|
||||
c.expires_at,
|
||||
c.auto_renewal_enabled,
|
||||
c.renewal_status,
|
||||
c.renewal_attempts,
|
||||
c.last_renewal_attempt,
|
||||
f.fqdn,
|
||||
f.acme_email,
|
||||
f.acme_key_id,
|
||||
f.provider_id as fqdn_provider_id
|
||||
FROM certificates c
|
||||
INNER JOIN fqdns f ON c.fqdn_id = f.id
|
||||
WHERE
|
||||
-- Nur Leaf-Zertifikate (nicht Intermediate)
|
||||
c.is_intermediate = 0
|
||||
-- Nur Let's Encrypt Zertifikate (via certigo-acmeproxy)
|
||||
AND c.provider_id = 'certigo-acmeproxy'
|
||||
-- Nur gültige/ausgestellte Zertifikate
|
||||
AND c.status = 'issued'
|
||||
-- Auto-Renewal muss aktiviert sein
|
||||
AND (c.auto_renewal_enabled IS NULL OR c.auto_renewal_enabled = 1)
|
||||
AND (f.auto_renewal_enabled IS NULL OR f.auto_renewal_enabled = 1)
|
||||
-- Zertifikat läuft bald ab
|
||||
AND c.expires_at IS NOT NULL
|
||||
AND datetime(c.expires_at) <= datetime('now', '+' || ? || ' days')
|
||||
-- Keine laufende Erneuerung
|
||||
AND (c.renewal_status IS NULL OR c.renewal_status != 'in_progress')
|
||||
-- Nicht zu viele Versuche
|
||||
AND (c.renewal_attempts IS NULL OR c.renewal_attempts < ?)
|
||||
-- Retry-Delay eingehalten
|
||||
AND (
|
||||
c.last_renewal_attempt IS NULL
|
||||
OR datetime(c.last_renewal_attempt) <= datetime('now', '-' || ? || ' hours')
|
||||
)
|
||||
ORDER BY c.expires_at ASC;
|
||||
```
|
||||
|
||||
### 6.2 Filter-Logik
|
||||
|
||||
**Ausschluss-Kriterien:**
|
||||
1. ✅ Intermediate-Zertifikate (nur Leaf)
|
||||
2. ✅ Nur `certigo-acmeproxy` Provider
|
||||
3. ✅ Status = 'issued'
|
||||
4. ✅ Auto-Renewal aktiviert (Certificate + FQDN)
|
||||
5. ✅ `expires_at` innerhalb Threshold
|
||||
6. ✅ Keine laufende Erneuerung (`renewal_status != 'in_progress'`)
|
||||
7. ✅ Max. Versuche nicht überschritten
|
||||
8. ✅ Retry-Delay eingehalten
|
||||
|
||||
---
|
||||
|
||||
## 7. Renewal Worker
|
||||
|
||||
### 7.1 Renewal-Prozess
|
||||
|
||||
```go
|
||||
func renewCertificate(certID string, fqdnID string, spaceID string) error {
|
||||
traceID := generateTraceID()
|
||||
|
||||
// 1. Markiere als "in_progress"
|
||||
updateRenewalStatus(certID, "in_progress", traceID)
|
||||
|
||||
// 2. Hole FQDN-Informationen
|
||||
fqdn, err := getFQDN(fqdnID)
|
||||
if err != nil {
|
||||
logRenewalError(certID, traceID, "FQDN nicht gefunden", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Prüfe DNS (CNAME)
|
||||
if !validateDNSCNAME(fqdn.FQDN) {
|
||||
logRenewalError(certID, traceID, "DNS-CNAME nicht gültig", nil)
|
||||
return fmt.Errorf("DNS validation failed")
|
||||
}
|
||||
|
||||
// 4. Hole ACME-Provider-Konfiguration
|
||||
providerConfig, err := getACMEProviderConfig(fqdn.ProviderID)
|
||||
if err != nil {
|
||||
logRenewalError(certID, traceID, "Provider-Konfiguration nicht gefunden", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. Rufe RequestCertificate() auf
|
||||
result, err := RequestCertificate(
|
||||
fqdn.FQDN,
|
||||
fqdn.AcmeEmail,
|
||||
fqdnID,
|
||||
fqdn.AcmeKeyID,
|
||||
traceID,
|
||||
updateTokenFunc,
|
||||
cleanupTokenFunc,
|
||||
statusCallback,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
// 6a. Fehler: Erhöhe Versuche, setze Retry-Zeitpunkt
|
||||
incrementRenewalAttempts(certID)
|
||||
setNextRenewalCheck(certID, time.Now().Add(retryDelay))
|
||||
updateRenewalStatus(certID, "failed", traceID)
|
||||
logRenewalError(certID, traceID, "Erneuerung fehlgeschlagen", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 6b. Erfolg: Speichere neues Zertifikat
|
||||
newCertID, err := saveNewCertificate(result, fqdnID, spaceID)
|
||||
if err != nil {
|
||||
logRenewalError(certID, traceID, "Fehler beim Speichern", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 7. Verknüpfe alte und neue Zertifikate
|
||||
linkCertificates(certID, newCertID)
|
||||
|
||||
// 8. Markiere als erfolgreich
|
||||
updateRenewalStatus(certID, "success", traceID)
|
||||
logRenewalSuccess(certID, newCertID, traceID)
|
||||
|
||||
// 9. Optional: Benachrichtigung senden
|
||||
sendRenewalNotification(fqdn.FQDN, newCertID, traceID)
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Concurrency Control
|
||||
|
||||
**Sequenzielle Verarbeitung:**
|
||||
- Pro FQDN nur eine Erneuerung gleichzeitig
|
||||
- Pro Space max. N Erneuerungen parallel (konfigurierbar)
|
||||
- Global max. M Erneuerungen parallel (konfigurierbar)
|
||||
|
||||
**Implementierung:**
|
||||
```go
|
||||
// Semaphore für Concurrency Control
|
||||
var renewalSemaphore = make(chan struct{}, maxConcurrentRenewals)
|
||||
|
||||
func renewCertificateWithLock(certID string, fqdnID string, spaceID string) error {
|
||||
renewalSemaphore <- struct{}{} // Acquire
|
||||
defer func() { <-renewalSemaphore }() // Release
|
||||
|
||||
return renewCertificate(certID, fqdnID, spaceID)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Fehlerbehandlung & Retry
|
||||
|
||||
### 8.1 Fehler-Kategorien
|
||||
|
||||
| Fehler-Typ | Retry? | Max. Versuche | Beispiel |
|
||||
|-----------|--------|--------------|----------|
|
||||
| DNS-Validierung fehlgeschlagen | ✅ Ja | 3 | CNAME nicht gesetzt |
|
||||
| ACME-Provider-Fehler | ✅ Ja | 3 | Rate Limit erreicht |
|
||||
| Netzwerk-Fehler | ✅ Ja | 5 | Timeout, Connection Error |
|
||||
| Konfigurations-Fehler | ❌ Nein | 0 | Provider nicht konfiguriert |
|
||||
| Berechtigungs-Fehler | ❌ Nein | 0 | Kein Space-Zugriff |
|
||||
|
||||
### 8.2 Retry-Strategie
|
||||
|
||||
**Exponential Backoff:**
|
||||
```
|
||||
Versuch 1: Sofort
|
||||
Versuch 2: Nach 24 Stunden
|
||||
Versuch 3: Nach 48 Stunden
|
||||
Versuch 4+: Nach 72 Stunden
|
||||
```
|
||||
|
||||
**Oder: Fixed Delay**
|
||||
```
|
||||
Alle Retries: Nach X Stunden (konfigurierbar, Default: 24h)
|
||||
```
|
||||
|
||||
### 8.3 Fehler-Logging
|
||||
|
||||
```go
|
||||
type RenewalError struct {
|
||||
CertificateID string
|
||||
FQDN string
|
||||
ErrorType string // 'dns', 'acme', 'network', 'config'
|
||||
ErrorMessage string
|
||||
TraceID string
|
||||
Timestamp time.Time
|
||||
Attempt int
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Logging & Monitoring
|
||||
|
||||
### 9.1 Structured Logging
|
||||
|
||||
**Erfolgreiche Erneuerung:**
|
||||
```json
|
||||
{
|
||||
"event": "certificate_renewal_success",
|
||||
"trace_id": "abc123",
|
||||
"certificate_id": "cert-uuid",
|
||||
"fqdn": "example.com",
|
||||
"old_expires_at": "2025-02-15T10:00:00Z",
|
||||
"new_expires_at": "2025-05-15T10:00:00Z",
|
||||
"new_certificate_id": "new-cert-uuid",
|
||||
"duration_seconds": 45,
|
||||
"timestamp": "2025-01-15T02:05:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Fehlgeschlagene Erneuerung:**
|
||||
```json
|
||||
{
|
||||
"event": "certificate_renewal_failed",
|
||||
"trace_id": "abc123",
|
||||
"certificate_id": "cert-uuid",
|
||||
"fqdn": "example.com",
|
||||
"error_type": "dns_validation",
|
||||
"error_message": "CNAME record not found",
|
||||
"attempt": 1,
|
||||
"max_attempts": 3,
|
||||
"next_retry": "2025-01-16T02:00:00Z",
|
||||
"timestamp": "2025-01-15T02:05:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Audit Logs
|
||||
|
||||
**Integration in bestehendes Audit-System:**
|
||||
```go
|
||||
auditService.Track(ctx, "RENEW", "certificate", certID, "system", "auto-renewal", map[string]interface{}{
|
||||
"fqdn": fqdn,
|
||||
"old_expires_at": oldExpiresAt,
|
||||
"new_expires_at": newExpiresAt,
|
||||
"trace_id": traceID,
|
||||
}, ipAddress, userAgent)
|
||||
```
|
||||
|
||||
### 9.3 Metrics
|
||||
|
||||
**Zu tracken:**
|
||||
- Anzahl Erneuerungen pro Tag/Woche/Monat
|
||||
- Erfolgsrate (Erfolgreich / Gesamt)
|
||||
- Durchschnittliche Erneuerungsdauer
|
||||
- Anzahl fehlgeschlagener Erneuerungen
|
||||
- Anzahl Retries
|
||||
- Zertifikate die bald ablaufen (Warnung)
|
||||
|
||||
---
|
||||
|
||||
## 10. API-Endpunkte
|
||||
|
||||
### 10.1 Manuelle Erneuerung
|
||||
|
||||
**POST** `/api/spaces/{spaceId}/fqdns/{fqdnId}/certificates/{certId}/renew`
|
||||
|
||||
Manuell eine Erneuerung auslösen.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Erneuerung gestartet",
|
||||
"trace_id": "abc123",
|
||||
"estimated_completion": "2025-01-15T02:05:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 Erneuerungs-Status abrufen
|
||||
|
||||
**GET** `/api/spaces/{spaceId}/fqdns/{fqdnId}/certificates/{certId}/renewal-status`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"auto_renewal_enabled": true,
|
||||
"renewal_status": "success",
|
||||
"renewal_attempts": 1,
|
||||
"last_renewal_attempt": "2025-01-15T02:00:00Z",
|
||||
"next_renewal_check": "2025-01-16T02:00:00Z",
|
||||
"replaced_by_cert_id": "new-cert-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 Erneuerungs-Logs abrufen
|
||||
|
||||
**GET** `/api/spaces/{spaceId}/fqdns/{fqdnId}/certificates/{certId}/renewal-logs`
|
||||
|
||||
**Query Parameters:**
|
||||
- `limit` (optional): Anzahl Einträge (Default: 50)
|
||||
- `offset` (optional): Pagination Offset
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"logs": [
|
||||
{
|
||||
"id": "log-uuid",
|
||||
"renewal_status": "success",
|
||||
"renewal_reason": "expiring_soon",
|
||||
"old_expires_at": "2025-02-15T10:00:00Z",
|
||||
"new_expires_at": "2025-05-15T10:00:00Z",
|
||||
"new_certificate_id": "new-cert-uuid",
|
||||
"renewal_duration_seconds": 45,
|
||||
"trace_id": "abc123",
|
||||
"created_at": "2025-01-15T02:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 10,
|
||||
"limit": 50,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 10.4 Auto-Renewal konfigurieren
|
||||
|
||||
**PUT** `/api/spaces/{spaceId}/fqdns/{fqdnId}/certificates/{certId}/auto-renewal`
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
### 10.5 Global Configuration
|
||||
|
||||
**GET** `/api/internal/renewal/config`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"renewal_threshold_days": 30,
|
||||
"check_interval_hours": 24,
|
||||
"max_renewal_attempts": 3,
|
||||
"retry_delay_hours": 24
|
||||
}
|
||||
```
|
||||
|
||||
**PUT** `/api/internal/renewal/config`
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"renewal_threshold_days": 30,
|
||||
"check_interval_hours": 24,
|
||||
"max_renewal_attempts": 3,
|
||||
"retry_delay_hours": 24
|
||||
}
|
||||
```
|
||||
|
||||
### 10.6 Manueller Scan (für Testing)
|
||||
|
||||
**POST** `/api/internal/renewal/scan`
|
||||
|
||||
Löst einen manuellen Scan aus (nur für Admins).
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"certificates_found": 5,
|
||||
"certificates_queued": 3,
|
||||
"certificates_skipped": 2
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Frontend-Integration
|
||||
|
||||
### 11.1 UI-Komponenten
|
||||
|
||||
#### Auto-Renewal Toggle
|
||||
- **Ort**: Certificate Detail View
|
||||
- **Funktion**: Ein/Aus-Schalter für Auto-Renewal pro Zertifikat
|
||||
|
||||
#### Renewal Status Badge
|
||||
- **Ort**: Certificate List & Detail View
|
||||
- **Anzeige**:
|
||||
- 🟢 "Auto-Renewal aktiv" (wenn enabled)
|
||||
- 🟡 "Erneuerung läuft" (wenn in_progress)
|
||||
- 🔴 "Erneuerung fehlgeschlagen" (wenn failed)
|
||||
- ⚪ "Auto-Renewal deaktiviert" (wenn disabled)
|
||||
|
||||
#### Renewal History
|
||||
- **Ort**: Certificate Detail View
|
||||
- **Anzeige**: Tabelle mit Erneuerungs-Logs
|
||||
- **Spalten**: Datum, Status, Grund, Neue Ablaufzeit, Trace ID
|
||||
|
||||
#### Manuelle Erneuerung Button
|
||||
- **Ort**: Certificate Detail View
|
||||
- **Funktion**: "Jetzt erneuern" Button (falls Auto-Renewal deaktiviert)
|
||||
|
||||
#### Upcoming Renewals Dashboard
|
||||
- **Ort**: Dashboard/Overview
|
||||
- **Anzeige**: Liste von Zertifikaten die bald erneuert werden
|
||||
- **Filter**: Nach Space, FQDN, Ablaufdatum
|
||||
|
||||
### 11.2 Notifications (Optional)
|
||||
|
||||
**Email-Benachrichtigungen:**
|
||||
- Erfolgreiche Erneuerung
|
||||
- Fehlgeschlagene Erneuerung (nach max. Versuchen)
|
||||
- Warnung: Zertifikat läuft in X Tagen ab (falls Erneuerung fehlschlägt)
|
||||
|
||||
**In-App Notifications:**
|
||||
- Toast-Notification bei erfolgreicher Erneuerung
|
||||
- Alert bei fehlgeschlagener Erneuerung
|
||||
|
||||
---
|
||||
|
||||
## 12. Sicherheit & Berechtigungen
|
||||
|
||||
### 12.1 Berechtigungen
|
||||
|
||||
**Auto-Renewal ausführen:**
|
||||
- System-User (für automatische Erneuerungen)
|
||||
- Admin-User (für manuelle Erneuerungen)
|
||||
- User mit `FULL_ACCESS` auf Space (für manuelle Erneuerungen)
|
||||
|
||||
**Auto-Renewal konfigurieren:**
|
||||
- Admin-User
|
||||
- User mit `FULL_ACCESS` auf Space
|
||||
|
||||
**Erneuerungs-Logs anzeigen:**
|
||||
- Alle User mit Space-Zugriff (READ-Berechtigung)
|
||||
|
||||
### 12.2 Rate Limiting
|
||||
|
||||
**Let's Encrypt Rate Limits:**
|
||||
- 50 Certificates per Registered Domain per week
|
||||
- 300 New Orders per Account per 3 hours
|
||||
|
||||
**Schutz:**
|
||||
- Tracke Anzahl Erneuerungen pro FQDN
|
||||
- Verzögere Erneuerung wenn Rate Limit erreicht
|
||||
- Logge Warnung bei Rate Limit
|
||||
|
||||
---
|
||||
|
||||
## 13. Testing & Rollout
|
||||
|
||||
### 13.1 Test-Plan
|
||||
|
||||
**Phase 1: Unit Tests**
|
||||
- [ ] Certificate Scanner Query
|
||||
- [ ] Renewal Worker Logic
|
||||
- [ ] Retry-Mechanismus
|
||||
- [ ] Error-Handling
|
||||
|
||||
**Phase 2: Integration Tests**
|
||||
- [ ] End-to-End Erneuerung (mit Staging ACME)
|
||||
- [ ] Fehler-Szenarien (DNS-Fehler, Rate Limit)
|
||||
- [ ] Concurrency Tests
|
||||
|
||||
**Phase 3: Staging Tests**
|
||||
- [ ] Test mit echten Staging-Zertifikaten
|
||||
- [ ] Monitoring & Logging prüfen
|
||||
- [ ] Performance-Tests
|
||||
|
||||
**Phase 4: Production Rollout**
|
||||
- [ ] Feature Flag aktivieren
|
||||
- [ ] Monitoring aktivieren
|
||||
- [ ] Schrittweise Aktivierung (zuerst einzelne FQDNs)
|
||||
|
||||
### 13.2 Rollback-Plan
|
||||
|
||||
**Falls Probleme auftreten:**
|
||||
1. Auto-Renewal global deaktivieren (Config)
|
||||
2. Laufende Erneuerungen abbrechen (Status zurücksetzen)
|
||||
3. Manuelle Erneuerung weiterhin möglich
|
||||
|
||||
---
|
||||
|
||||
## 14. Monitoring & Alerting
|
||||
|
||||
### 14.1 Health Checks
|
||||
|
||||
**Endpoint:** `GET /api/health/renewal`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"last_scan": "2025-01-15T02:00:00Z",
|
||||
"next_scan": "2025-01-16T02:00:00Z",
|
||||
"certificates_pending": 2,
|
||||
"certificates_in_progress": 1,
|
||||
"certificates_failed": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 14.2 Alerts
|
||||
|
||||
**Zu überwachen:**
|
||||
- ❌ Auto-Renewal Service läuft nicht
|
||||
- ⚠️ Viele fehlgeschlagene Erneuerungen (> 10% in 24h)
|
||||
- ⚠️ Zertifikate laufen in < 7 Tagen ab (ohne Erneuerung)
|
||||
- ⚠️ Rate Limit erreicht
|
||||
- ⚠️ Scheduler läuft nicht (letzter Scan > 48h her)
|
||||
|
||||
---
|
||||
|
||||
## 15. Zukünftige Erweiterungen
|
||||
|
||||
### 15.1 Multi-Provider Support
|
||||
- Erneuerung für andere Provider (nicht nur Let's Encrypt)
|
||||
|
||||
### 15.2 Smart Scheduling
|
||||
- Erneuerung basierend auf Traffic-Patterns
|
||||
- Erneuerung außerhalb der Geschäftszeiten
|
||||
|
||||
### 15.3 Batch Renewals
|
||||
- Erneuerung mehrerer Zertifikate gleichzeitig (wenn möglich)
|
||||
|
||||
### 15.4 Webhook-Integration
|
||||
- Webhooks für erfolgreiche/fehlgeschlagene Erneuerungen
|
||||
- Integration mit externen Monitoring-Tools
|
||||
|
||||
### 15.5 Certificate Rotation
|
||||
- Automatische Rotation von Private Keys
|
||||
- Unterstützung für Key-Rollover
|
||||
|
||||
---
|
||||
|
||||
## 16. Abhängigkeiten
|
||||
|
||||
### 16.1 Backend (Go)
|
||||
|
||||
```go
|
||||
// Cron Scheduler
|
||||
github.com/robfig/cron/v3
|
||||
|
||||
// (Bereits vorhanden)
|
||||
// - ACME Client (acme_client.go)
|
||||
// - Certificate Parser (cert_parser.go)
|
||||
// - Logger (cert_logger.go)
|
||||
```
|
||||
|
||||
### 16.2 Frontend
|
||||
|
||||
Keine zusätzlichen Dependencies nötig.
|
||||
|
||||
---
|
||||
|
||||
## 17. Risiken & Mitigation
|
||||
|
||||
### 17.1 Risiken
|
||||
|
||||
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|
||||
|--------|-------------------|--------|------------|
|
||||
| Rate Limit erreicht | Mittel | Hoch | Rate Limit Tracking, Verzögerung |
|
||||
| DNS-Validierung fehlschlägt | Mittel | Hoch | DNS-Check vor Erneuerung, Retry |
|
||||
| ACME-Provider Downtime | Niedrig | Hoch | Retry-Mechanismus, Fallback |
|
||||
| Doppelte Erneuerung | Niedrig | Mittel | Status-Check, Locking |
|
||||
| Datenbank-Lock | Niedrig | Mittel | Transaktionen, Timeouts |
|
||||
|
||||
### 17.2 Best Practices
|
||||
|
||||
- ✅ Idempotenz: Erneuerung kann mehrfach ausgeführt werden ohne Probleme
|
||||
- ✅ Atomic Operations: Datenbank-Transaktionen für Konsistenz
|
||||
- ✅ Graceful Degradation: Bei Fehlern weiterhin manuelle Erneuerung möglich
|
||||
- ✅ Comprehensive Logging: Alle Schritte loggen für Debugging
|
||||
- ✅ Rate Limit Awareness: Respektiere Let's Encrypt Limits
|
||||
|
||||
---
|
||||
|
||||
## 18. Zusammenfassung
|
||||
|
||||
### 18.1 Vorteile
|
||||
|
||||
- **Automatisierung**: Keine manuelle Intervention nötig
|
||||
- **Zuverlässigkeit**: Zertifikate laufen nicht mehr ab
|
||||
- **Zeitersparnis**: Weniger manuelle Arbeit
|
||||
- **Sicherheit**: Immer gültige Zertifikate
|
||||
|
||||
### 18.2 Herausforderungen
|
||||
|
||||
- **Komplexität**: Zusätzliche Infrastruktur und Code
|
||||
- **Fehlerbehandlung**: Robustes Error-Handling erforderlich
|
||||
- **Rate Limits**: Let's Encrypt Limits beachten
|
||||
- **Testing**: Umfangreiche Tests erforderlich
|
||||
|
||||
### 18.3 Empfohlene Implementierungs-Reihenfolge
|
||||
|
||||
1. **Phase 1**: Datenbank-Schema & Grundfunktionalität
|
||||
2. **Phase 2**: Scanner & Worker
|
||||
3. **Phase 3**: Scheduler & Automation
|
||||
4. **Phase 4**: Frontend-Integration
|
||||
5. **Phase 5**: Monitoring & Alerting
|
||||
6. **Phase 6**: Notifications (Optional)
|
||||
|
||||
---
|
||||
|
||||
**Erstellt am**: 2025-01-XX
|
||||
**Version**: 1.0
|
||||
**Status**: Konzept - Noch nicht implementiert
|
||||
|
||||
198
docs/DB_COMMANDS.md
Normal file
198
docs/DB_COMMANDS.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# SQLite Datenbank-Befehle
|
||||
|
||||
## Verbindung zur Datenbank
|
||||
|
||||
Die Datenbank liegt in: `./backend/spaces.db`
|
||||
|
||||
### Verbindung herstellen:
|
||||
```bash
|
||||
cd /home/nick/Development/certigo-addon/backend
|
||||
sqlite3 spaces.db
|
||||
```
|
||||
|
||||
## Nützliche SQLite-Befehle
|
||||
|
||||
### Tabellen auflisten:
|
||||
```sql
|
||||
.tables
|
||||
```
|
||||
|
||||
### Schema einer Tabelle anzeigen:
|
||||
```sql
|
||||
.schema audit_logs
|
||||
```
|
||||
|
||||
### Alle Audit-Logs anzeigen:
|
||||
```sql
|
||||
SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT 10;
|
||||
```
|
||||
|
||||
### Anzahl der Audit-Logs:
|
||||
```sql
|
||||
SELECT COUNT(*) FROM audit_logs;
|
||||
```
|
||||
|
||||
### Letzte 5 Audit-Logs mit Details:
|
||||
```sql
|
||||
SELECT
|
||||
timestamp,
|
||||
username,
|
||||
action,
|
||||
resource_type,
|
||||
details
|
||||
FROM audit_logs
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 5;
|
||||
```
|
||||
|
||||
### Einen spezifischen Audit-Log anzeigen (nach ID):
|
||||
```sql
|
||||
SELECT * FROM audit_logs WHERE id = 'LOG_ID_HIER';
|
||||
```
|
||||
|
||||
### Einen spezifischen Audit-Log anzeigen (nach Timestamp):
|
||||
```sql
|
||||
SELECT * FROM audit_logs WHERE timestamp = '2025-11-20 16:20:00';
|
||||
```
|
||||
|
||||
### Audit-Logs nach Aktion filtern:
|
||||
```sql
|
||||
SELECT * FROM audit_logs WHERE action = 'CREATE' ORDER BY timestamp DESC;
|
||||
```
|
||||
|
||||
### Audit-Logs nach Ressourcentyp filtern:
|
||||
```sql
|
||||
SELECT * FROM audit_logs WHERE resource_type = 'user' ORDER BY timestamp DESC;
|
||||
```
|
||||
|
||||
### Audit-Logs nach Benutzer filtern:
|
||||
```sql
|
||||
SELECT * FROM audit_logs WHERE username = 'admin' ORDER BY timestamp DESC;
|
||||
```
|
||||
|
||||
### Vollständige Details eines Logs (formatiert):
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
timestamp,
|
||||
user_id,
|
||||
username,
|
||||
action,
|
||||
resource_type,
|
||||
resource_id,
|
||||
details,
|
||||
ip_address,
|
||||
user_agent
|
||||
FROM audit_logs
|
||||
WHERE id = 'LOG_ID_HIER';
|
||||
```
|
||||
|
||||
### Neueste Logs mit formatierten Details:
|
||||
```sql
|
||||
SELECT
|
||||
datetime(timestamp) as zeit,
|
||||
username,
|
||||
action,
|
||||
resource_type,
|
||||
json_extract(details, '$.message') as nachricht,
|
||||
ip_address
|
||||
FROM audit_logs
|
||||
ORDER BY datetime(timestamp) DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
### Logs der letzten Stunde:
|
||||
```sql
|
||||
SELECT * FROM audit_logs
|
||||
WHERE datetime(timestamp) >= datetime('now', '-1 hour')
|
||||
ORDER BY timestamp DESC;
|
||||
```
|
||||
|
||||
### Logs von heute:
|
||||
```sql
|
||||
SELECT * FROM audit_logs
|
||||
WHERE date(timestamp) = date('now')
|
||||
ORDER BY timestamp DESC;
|
||||
```
|
||||
|
||||
### Alle Tabellen auflisten:
|
||||
```sql
|
||||
SELECT name FROM sqlite_master WHERE type='table';
|
||||
```
|
||||
|
||||
### Prüfen ob audit_logs Tabelle existiert:
|
||||
```sql
|
||||
SELECT name FROM sqlite_master WHERE type='table' AND name='audit_logs';
|
||||
```
|
||||
|
||||
### Alle Spalten der audit_logs Tabelle:
|
||||
```sql
|
||||
PRAGMA table_info(audit_logs);
|
||||
```
|
||||
|
||||
### SQLite verlassen:
|
||||
```sql
|
||||
.quit
|
||||
```
|
||||
oder
|
||||
```sql
|
||||
.exit
|
||||
```
|
||||
|
||||
## Direkte Befehle (ohne interaktive Shell)
|
||||
|
||||
### Anzahl der Logs:
|
||||
```bash
|
||||
sqlite3 backend/spaces.db "SELECT COUNT(*) FROM audit_logs;"
|
||||
```
|
||||
|
||||
### Letzte 5 Logs:
|
||||
```bash
|
||||
sqlite3 backend/spaces.db "SELECT timestamp, username, action, resource_type, details FROM audit_logs ORDER BY timestamp DESC LIMIT 5;"
|
||||
```
|
||||
|
||||
### Prüfen ob Tabelle existiert:
|
||||
```bash
|
||||
sqlite3 backend/spaces.db "SELECT name FROM sqlite_master WHERE type='table' AND name='audit_logs';"
|
||||
```
|
||||
|
||||
### Alle Logs als CSV exportieren:
|
||||
```bash
|
||||
sqlite3 -header -csv backend/spaces.db "SELECT * FROM audit_logs ORDER BY timestamp DESC;" > audit_logs.csv
|
||||
```
|
||||
|
||||
### Einen spezifischen Log anzeigen (Beispiel):
|
||||
```bash
|
||||
sqlite3 backend/spaces.db "SELECT * FROM audit_logs WHERE id = '5d424293-14c1-48ef-a34c-5555d843d289';"
|
||||
```
|
||||
|
||||
### Neueste 10 Logs mit Details:
|
||||
```bash
|
||||
sqlite3 -header -column backend/spaces.db "SELECT timestamp, username, action, resource_type, details FROM audit_logs ORDER BY timestamp DESC LIMIT 10;"
|
||||
```
|
||||
|
||||
### Logs nach Aktion filtern:
|
||||
```bash
|
||||
sqlite3 -header -column backend/spaces.db "SELECT * FROM audit_logs WHERE action = 'CREATE' ORDER BY timestamp DESC LIMIT 10;"
|
||||
```
|
||||
|
||||
### Logs nach Benutzer filtern:
|
||||
```bash
|
||||
sqlite3 -header -column backend/spaces.db "SELECT * FROM audit_logs WHERE username = 'admin' ORDER BY timestamp DESC LIMIT 10;"
|
||||
```
|
||||
|
||||
### Logs von heute:
|
||||
```bash
|
||||
sqlite3 -header -column backend/spaces.db "SELECT * FROM audit_logs WHERE date(timestamp) = date('now') ORDER BY timestamp DESC;"
|
||||
```
|
||||
|
||||
### JSON-Details eines Logs formatiert anzeigen:
|
||||
```bash
|
||||
sqlite3 backend/spaces.db "SELECT json_pretty(details) FROM audit_logs WHERE id = 'LOG_ID_HIER';"
|
||||
```
|
||||
|
||||
**Hinweis**: Falls `json_pretty` nicht verfügbar ist, verwende stattdessen:
|
||||
```bash
|
||||
sqlite3 backend/spaces.db "SELECT details FROM audit_logs WHERE id = 'LOG_ID_HIER';" | python3 -m json.tool
|
||||
```
|
||||
|
||||
754
docs/OAUTH_KONZEPT.md
Normal file
754
docs/OAUTH_KONZEPT.md
Normal file
@@ -0,0 +1,754 @@
|
||||
# OAuth 2.0 Integration Konzept für Certigo
|
||||
|
||||
## 1. Übersicht
|
||||
|
||||
### 1.1 Ziel
|
||||
Integration von OAuth 2.0 als zusätzliche Authentifizierungsmethode neben dem bestehenden Basic Authentication System. Benutzer sollen sich mit externen OAuth-Providern (z.B. Google, Microsoft, GitHub) anmelden können.
|
||||
|
||||
### 1.2 Anforderungen
|
||||
- **Hybrides System**: OAuth und Basic Auth parallel unterstützen
|
||||
- **User Linking**: OAuth-Benutzer mit bestehenden lokalen Accounts verknüpfen können
|
||||
- **Automatische User-Erstellung**: Neue OAuth-Benutzer automatisch anlegen
|
||||
- **Berechtigungssystem**: OAuth-Benutzer in bestehendes Permission-System integrieren
|
||||
- **Session Management**: Sichere Session-Verwaltung für OAuth-Logins
|
||||
- **Multi-Provider**: Unterstützung mehrerer OAuth-Provider gleichzeitig
|
||||
|
||||
---
|
||||
|
||||
## 2. Architektur
|
||||
|
||||
### 2.1 OAuth Flow (Authorization Code Flow)
|
||||
|
||||
```
|
||||
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ Browser │ │ Frontend │ │ Backend │ │ OAuth │
|
||||
│ │ │ │ │ │ │ Provider │
|
||||
└────┬────┘ └────┬─────┘ └──────┬──────┘ └────┬─────┘
|
||||
│ │ │ │
|
||||
│ 1. Login Button │ │ │
|
||||
│──────────────────>│ │ │
|
||||
│ │ │ │
|
||||
│ │ 2. GET /api/oauth/{provider}/auth │
|
||||
│ │─────────────────────>│ │
|
||||
│ │ │ │
|
||||
│ │ │ 3. Redirect to OAuth │
|
||||
│ │ │ Authorization URL │
|
||||
│ │<─────────────────────│ │
|
||||
│ │ │ │
|
||||
│ 4. Redirect to │ │ │
|
||||
│ OAuth Provider │ │ │
|
||||
│<──────────────────│ │ │
|
||||
│ │ │ │
|
||||
│ 5. User Auth │ │ │
|
||||
│────────────────────────────────────────────────────────────────>│
|
||||
│ │ │ │
|
||||
│ 6. Callback with │ │ │
|
||||
│ Authorization │ │ │
|
||||
│ Code │ │ │
|
||||
│<────────────────────────────────────────────────────────────────│
|
||||
│ │ │ │
|
||||
│ 7. Callback to │ │ │
|
||||
│ /api/oauth/ │ │ │
|
||||
│ {provider}/ │ │ │
|
||||
│ callback │ │ │
|
||||
│──────────────────>│ │ │
|
||||
│ │ 8. POST /api/oauth/{provider}/callback │
|
||||
│ │ (code=xxx) │ │
|
||||
│ │─────────────────────>│ │
|
||||
│ │ │ │
|
||||
│ │ │ 9. Exchange Code for │
|
||||
│ │ │ Access Token │
|
||||
│ │ │──────────────────────>│
|
||||
│ │ │ │
|
||||
│ │ │ 10. Get User Info │
|
||||
│ │ │──────────────────────>│
|
||||
│ │ │ │
|
||||
│ │ │ 11. User Info │
|
||||
│ │ │<──────────────────────│
|
||||
│ │ │ │
|
||||
│ │ │ 12. Create/Update │
|
||||
│ │ │ User in DB │
|
||||
│ │ │ │
|
||||
│ │ │ 13. Create Session │
|
||||
│ │ │ │
|
||||
│ │ 14. Return Session │ │
|
||||
│ │ Token │ │
|
||||
│ │<─────────────────────│ │
|
||||
│ │ │ │
|
||||
│ 15. Store Session │ │ │
|
||||
│ & Redirect │ │ │
|
||||
│<──────────────────│ │ │
|
||||
│ │ │ │
|
||||
```
|
||||
|
||||
### 2.2 Komponenten
|
||||
|
||||
#### Backend
|
||||
- **OAuth Handler**: `/api/oauth/{provider}/auth` - Initiierung des OAuth Flows
|
||||
- **OAuth Callback Handler**: `/api/oauth/{provider}/callback` - Verarbeitung des Authorization Codes
|
||||
- **OAuth Provider Manager**: Verwaltung mehrerer OAuth-Provider
|
||||
- **Session Manager**: Verwaltung von OAuth-Sessions (JWT oder Session-Tokens)
|
||||
- **User Linking Service**: Verknüpfung von OAuth-Accounts mit lokalen Accounts
|
||||
|
||||
#### Frontend
|
||||
- **OAuth Login Component**: Buttons für verschiedene OAuth-Provider
|
||||
- **OAuth Callback Handler**: Verarbeitung des Redirects nach OAuth-Authentifizierung
|
||||
- **Session Storage**: Speicherung von Session-Tokens (HttpOnly Cookies bevorzugt)
|
||||
|
||||
---
|
||||
|
||||
## 3. Datenbank-Schema
|
||||
|
||||
### 3.1 Erweiterte Users-Tabelle
|
||||
|
||||
```sql
|
||||
-- Migration: Erweitere users-Tabelle um OAuth-Felder
|
||||
ALTER TABLE users ADD COLUMN auth_method TEXT DEFAULT 'basic'; -- 'basic' | 'oauth' | 'hybrid'
|
||||
ALTER TABLE users ADD COLUMN oauth_provider TEXT; -- 'google' | 'microsoft' | 'github' | NULL
|
||||
ALTER TABLE users ADD COLUMN oauth_provider_id TEXT; -- Externe User-ID vom OAuth-Provider
|
||||
ALTER TABLE users ADD COLUMN oauth_email TEXT; -- Email vom OAuth-Provider (kann von lokaler Email abweichen)
|
||||
ALTER TABLE users ADD COLUMN password_hash TEXT; -- NULL für reine OAuth-User
|
||||
```
|
||||
|
||||
### 3.2 Neue Tabelle: oauth_sessions
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS oauth_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
access_token TEXT, -- Verschlüsselt gespeichert
|
||||
refresh_token TEXT, -- Verschlüsselt gespeichert
|
||||
expires_at DATETIME NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
last_used_at DATETIME,
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_oauth_sessions_user_id ON oauth_sessions(user_id);
|
||||
CREATE INDEX idx_oauth_sessions_expires_at ON oauth_sessions(expires_at);
|
||||
```
|
||||
|
||||
### 3.3 Neue Tabelle: oauth_providers
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS oauth_providers (
|
||||
id TEXT PRIMARY KEY, -- 'google', 'microsoft', 'github'
|
||||
name TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT 1,
|
||||
client_id TEXT NOT NULL,
|
||||
client_secret TEXT NOT NULL, -- Verschlüsselt gespeichert
|
||||
authorization_url TEXT NOT NULL,
|
||||
token_url TEXT NOT NULL,
|
||||
user_info_url TEXT NOT NULL,
|
||||
scopes TEXT, -- JSON Array: ["openid", "email", "profile"]
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
### 3.4 Neue Tabelle: user_oauth_links
|
||||
|
||||
```sql
|
||||
-- Für Benutzer, die mehrere OAuth-Provider verknüpfen wollen
|
||||
CREATE TABLE IF NOT EXISTS user_oauth_links (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
provider_user_id TEXT NOT NULL,
|
||||
provider_email TEXT,
|
||||
linked_at DATETIME NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE(provider, provider_user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_user_oauth_links_user_id ON user_oauth_links(user_id);
|
||||
CREATE INDEX idx_user_oauth_links_provider ON user_oauth_links(provider, provider_user_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. OAuth Provider Konfiguration
|
||||
|
||||
### 4.1 Unterstützte Provider
|
||||
|
||||
#### Google OAuth 2.0
|
||||
- **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth`
|
||||
- **Token URL**: `https://oauth2.googleapis.com/token`
|
||||
- **User Info URL**: `https://www.googleapis.com/oauth2/v2/userinfo`
|
||||
- **Scopes**: `["openid", "email", "profile"]`
|
||||
|
||||
#### Microsoft Azure AD
|
||||
- **Authorization URL**: `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize`
|
||||
- **Token URL**: `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token`
|
||||
- **User Info URL**: `https://graph.microsoft.com/v1.0/me`
|
||||
- **Scopes**: `["openid", "email", "profile"]`
|
||||
|
||||
#### GitHub
|
||||
- **Authorization URL**: `https://github.com/login/oauth/authorize`
|
||||
- **Token URL**: `https://github.com/login/oauth/access_token`
|
||||
- **User Info URL**: `https://api.github.com/user`
|
||||
- **Scopes**: `["user:email"]`
|
||||
|
||||
### 4.2 Provider-Konfiguration (Environment Variables / Config File)
|
||||
|
||||
```yaml
|
||||
oauth:
|
||||
providers:
|
||||
google:
|
||||
enabled: true
|
||||
client_id: "${GOOGLE_CLIENT_ID}"
|
||||
client_secret: "${GOOGLE_CLIENT_SECRET}"
|
||||
redirect_uri: "http://localhost:5173/api/oauth/google/callback"
|
||||
scopes: ["openid", "email", "profile"]
|
||||
microsoft:
|
||||
enabled: true
|
||||
tenant: "${MICROSOFT_TENANT_ID}"
|
||||
client_id: "${MICROSOFT_CLIENT_ID}"
|
||||
client_secret: "${MICROSOFT_CLIENT_SECRET}"
|
||||
redirect_uri: "http://localhost:5173/api/oauth/microsoft/callback"
|
||||
scopes: ["openid", "email", "profile"]
|
||||
github:
|
||||
enabled: false
|
||||
client_id: "${GITHUB_CLIENT_ID}"
|
||||
client_secret: "${GITHUB_CLIENT_SECRET}"
|
||||
redirect_uri: "http://localhost:5173/api/oauth/github/callback"
|
||||
scopes: ["user:email"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API-Endpunkte
|
||||
|
||||
### 5.1 OAuth Initiation
|
||||
|
||||
**GET** `/api/oauth/{provider}/auth`
|
||||
|
||||
Initiert den OAuth Flow für einen bestimmten Provider.
|
||||
|
||||
**Query Parameters:**
|
||||
- `redirect_uri` (optional): Custom Redirect URI nach erfolgreichem Login
|
||||
|
||||
**Response:**
|
||||
- `302 Redirect` zur OAuth Provider Authorization URL
|
||||
|
||||
**Beispiel:**
|
||||
```
|
||||
GET /api/oauth/google/auth
|
||||
→ Redirect zu: https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&scope=...&response_type=code&state=...
|
||||
```
|
||||
|
||||
### 5.2 OAuth Callback
|
||||
|
||||
**GET** `/api/oauth/{provider}/callback`
|
||||
|
||||
Verarbeitet den Authorization Code vom OAuth-Provider.
|
||||
|
||||
**Query Parameters:**
|
||||
- `code`: Authorization Code vom Provider
|
||||
- `state`: CSRF Protection Token (optional, aber empfohlen)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "user@example.com",
|
||||
"email": "user@example.com",
|
||||
"isAdmin": false,
|
||||
"enabled": true,
|
||||
"authMethod": "oauth",
|
||||
"oauthProvider": "google"
|
||||
},
|
||||
"sessionToken": "jwt-token-here",
|
||||
"redirectTo": "/"
|
||||
}
|
||||
```
|
||||
|
||||
**Fehler-Response:**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Invalid authorization code",
|
||||
"errorCode": "OAUTH_INVALID_CODE"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 OAuth Logout
|
||||
|
||||
**POST** `/api/oauth/logout`
|
||||
|
||||
Beendet die OAuth-Session.
|
||||
|
||||
**Headers:**
|
||||
- `Authorization: Bearer {sessionToken}`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Logged out successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 OAuth Provider Status
|
||||
|
||||
**GET** `/api/oauth/providers`
|
||||
|
||||
Gibt eine Liste aller konfigurierten OAuth-Provider zurück.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"providers": [
|
||||
{
|
||||
"id": "google",
|
||||
"name": "Google",
|
||||
"enabled": true,
|
||||
"authUrl": "/api/oauth/google/auth"
|
||||
},
|
||||
{
|
||||
"id": "microsoft",
|
||||
"name": "Microsoft",
|
||||
"enabled": true,
|
||||
"authUrl": "/api/oauth/microsoft/auth"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.5 Link OAuth Account
|
||||
|
||||
**POST** `/api/oauth/link`
|
||||
|
||||
Verknüpft einen OAuth-Account mit einem bestehenden lokalen Account (für Hybrid-Auth).
|
||||
|
||||
**Headers:**
|
||||
- `Authorization: Basic {credentials}` (lokaler Account)
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"provider": "google",
|
||||
"code": "authorization-code-from-oauth"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OAuth account linked successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Session Management
|
||||
|
||||
### 6.1 Session-Token (JWT)
|
||||
|
||||
**Token-Struktur:**
|
||||
```json
|
||||
{
|
||||
"sub": "user-uuid",
|
||||
"auth_method": "oauth",
|
||||
"provider": "google",
|
||||
"exp": 1234567890,
|
||||
"iat": 1234567890,
|
||||
"session_id": "session-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**Token-Speicherung:**
|
||||
- **Backend**: In `oauth_sessions` Tabelle
|
||||
- **Frontend**: HttpOnly Cookie (bevorzugt) oder localStorage (Fallback)
|
||||
- **Lifetime**: 24 Stunden (konfigurierbar)
|
||||
- **Refresh**: Automatisches Refresh bei Ablauf (falls Refresh Token vorhanden)
|
||||
|
||||
### 6.2 Session-Validierung
|
||||
|
||||
**Middleware**: `oauthSessionMiddleware`
|
||||
|
||||
Prüft OAuth-Session-Token in folgenden Headers:
|
||||
1. `Authorization: Bearer {token}`
|
||||
2. Cookie: `oauth_session`
|
||||
|
||||
**Flow:**
|
||||
```
|
||||
Request → oauthSessionMiddleware → Check Token → Validate Session → Continue
|
||||
↓ Invalid
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. User Management
|
||||
|
||||
### 7.1 Automatische User-Erstellung
|
||||
|
||||
**Flow:**
|
||||
1. OAuth-Callback empfängt User-Info vom Provider
|
||||
2. Prüfe ob User mit `oauth_provider_id` existiert
|
||||
3. Falls nicht:
|
||||
- Erstelle neuen User
|
||||
- Setze `auth_method = 'oauth'`
|
||||
- Setze `oauth_provider` und `oauth_provider_id`
|
||||
- Setze `password_hash = NULL`
|
||||
- Setze `enabled = true` (oder konfigurierbar)
|
||||
- Setze `isAdmin = false`
|
||||
- Weise Standard-Berechtigungsgruppe zu (optional)
|
||||
4. Falls ja:
|
||||
- Update `last_login_at` (falls Feld existiert)
|
||||
- Update Session
|
||||
|
||||
### 7.2 User Linking (Hybrid Auth)
|
||||
|
||||
**Szenario**: Benutzer hat bereits lokalen Account, möchte OAuth hinzufügen
|
||||
|
||||
**Flow:**
|
||||
1. User loggt sich mit Basic Auth ein
|
||||
2. User klickt "Link Google Account"
|
||||
3. OAuth Flow wird initiiert
|
||||
4. Nach erfolgreicher OAuth-Auth:
|
||||
- Verknüpfe OAuth-Account mit lokalem Account
|
||||
- Setze `auth_method = 'hybrid'`
|
||||
- Erstelle Eintrag in `user_oauth_links`
|
||||
5. User kann sich nun mit beiden Methoden anmelden
|
||||
|
||||
### 7.3 User Mapping
|
||||
|
||||
**Email-basierte Verknüpfung:**
|
||||
- Falls OAuth-Email mit lokalem Account übereinstimmt → Auto-Link (optional, konfigurierbar)
|
||||
- Falls nicht → Neue User-Erstellung oder manuelle Verknüpfung erforderlich
|
||||
|
||||
---
|
||||
|
||||
## 8. Sicherheit
|
||||
|
||||
### 8.1 CSRF Protection
|
||||
|
||||
**State Parameter:**
|
||||
- Generiere zufälligen `state` Token bei OAuth-Initiation
|
||||
- Speichere in Session/Cookie
|
||||
- Validiere bei Callback
|
||||
|
||||
**Implementierung:**
|
||||
```go
|
||||
state := generateRandomToken(32)
|
||||
storeStateInSession(state)
|
||||
redirectURL := fmt.Sprintf("%s?state=%s&...", oauthURL, state)
|
||||
```
|
||||
|
||||
### 8.2 Token-Verschlüsselung
|
||||
|
||||
**Access/Refresh Tokens:**
|
||||
- Verschlüsselt in Datenbank speichern (AES-256)
|
||||
- Nie im Klartext loggen
|
||||
- Automatische Löschung bei Ablauf
|
||||
|
||||
### 8.3 Rate Limiting
|
||||
|
||||
**OAuth-Endpunkte:**
|
||||
- `/api/oauth/{provider}/auth`: 10 Requests/Minute pro IP
|
||||
- `/api/oauth/{provider}/callback`: 5 Requests/Minute pro IP
|
||||
|
||||
### 8.4 Secure Cookies
|
||||
|
||||
**Session Cookies:**
|
||||
- `HttpOnly`: true
|
||||
- `Secure`: true (HTTPS only)
|
||||
- `SameSite`: Lax oder Strict
|
||||
- `Path`: `/api`
|
||||
|
||||
---
|
||||
|
||||
## 9. Frontend-Integration
|
||||
|
||||
### 9.1 Login-Seite Erweiterung
|
||||
|
||||
**Aktuelle Login-Seite** (`frontend/src/pages/Login.jsx`) erweitern:
|
||||
|
||||
```jsx
|
||||
// OAuth Login Buttons hinzufügen
|
||||
<div className="oauth-providers">
|
||||
<button onClick={() => initiateOAuth('google')}>
|
||||
<img src="/icons/google.svg" /> Mit Google anmelden
|
||||
</button>
|
||||
<button onClick={() => initiateOAuth('microsoft')}>
|
||||
<img src="/icons/microsoft.svg" /> Mit Microsoft anmelden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
// Oder: Separater OAuth-Login-Bereich
|
||||
<div className="divider">oder</div>
|
||||
```
|
||||
|
||||
### 9.2 OAuth Callback Handler
|
||||
|
||||
**Neue Route**: `frontend/src/pages/OAuthCallback.jsx`
|
||||
|
||||
```jsx
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const code = params.get('code')
|
||||
const state = params.get('state')
|
||||
const provider = extractProviderFromURL() // z.B. '/oauth/google/callback'
|
||||
|
||||
if (code) {
|
||||
handleOAuthCallback(provider, code, state)
|
||||
}
|
||||
}, [])
|
||||
```
|
||||
|
||||
### 9.3 AuthContext Erweiterung
|
||||
|
||||
**Erweitere** `frontend/src/contexts/AuthContext.jsx`:
|
||||
|
||||
```jsx
|
||||
const loginWithOAuth = async (provider) => {
|
||||
// Redirect zu Backend OAuth Initiation
|
||||
window.location.href = `/api/oauth/${provider}/auth`
|
||||
}
|
||||
|
||||
const handleOAuthCallback = async (provider, code, state) => {
|
||||
const response = await fetch(`/api/oauth/${provider}/callback?code=${code}&state=${state}`)
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
// Store session token
|
||||
localStorage.setItem('oauth_session', data.sessionToken)
|
||||
setUser(data.user)
|
||||
setIsAuthenticated(true)
|
||||
navigate('/')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Migration & Backward Compatibility
|
||||
|
||||
### 10.1 Bestehende User
|
||||
|
||||
**Migration:**
|
||||
- Alle bestehenden User haben `auth_method = 'basic'`
|
||||
- `oauth_provider` und `oauth_provider_id` sind `NULL`
|
||||
- `password_hash` bleibt bestehen
|
||||
|
||||
### 10.2 API-Kompatibilität
|
||||
|
||||
**Bestehende Endpunkte:**
|
||||
- Funktionieren weiterhin mit Basic Auth
|
||||
- Neue Middleware prüft zuerst OAuth-Session, dann Basic Auth
|
||||
|
||||
**Middleware-Order:**
|
||||
```
|
||||
Request → oauthSessionMiddleware → basicAuthMiddleware → Handler
|
||||
↓ Invalid ↓ Invalid
|
||||
Try Basic Auth 401 Unauthorized
|
||||
```
|
||||
|
||||
### 10.3 User-Erstellung
|
||||
|
||||
**Admin-Erstellung:**
|
||||
- Admins können weiterhin lokale User erstellen
|
||||
- OAuth-User können manuell zu Admins gemacht werden
|
||||
|
||||
---
|
||||
|
||||
## 11. Konfiguration & Deployment
|
||||
|
||||
### 11.1 Environment Variables
|
||||
|
||||
```bash
|
||||
# OAuth Provider Credentials
|
||||
GOOGLE_CLIENT_ID=xxx
|
||||
GOOGLE_CLIENT_SECRET=xxx
|
||||
MICROSOFT_CLIENT_ID=xxx
|
||||
MICROSOFT_CLIENT_SECRET=xxx
|
||||
MICROSOFT_TENANT_ID=xxx
|
||||
GITHUB_CLIENT_ID=xxx
|
||||
GITHUB_CLIENT_SECRET=xxx
|
||||
|
||||
# OAuth Settings
|
||||
OAUTH_SESSION_SECRET=xxx # Für Session-Token Signing
|
||||
OAUTH_SESSION_LIFETIME=24h
|
||||
OAUTH_REDIRECT_BASE_URL=http://localhost:5173
|
||||
OAUTH_AUTO_CREATE_USERS=true
|
||||
OAUTH_AUTO_LINK_BY_EMAIL=false
|
||||
```
|
||||
|
||||
### 11.2 Provider-Registrierung
|
||||
|
||||
**Google:**
|
||||
1. Google Cloud Console → APIs & Services → Credentials
|
||||
2. OAuth 2.0 Client ID erstellen
|
||||
3. Authorized redirect URIs: `http://localhost:5173/api/oauth/google/callback`
|
||||
|
||||
**Microsoft:**
|
||||
1. Azure Portal → App Registrations
|
||||
2. Neue App registrieren
|
||||
3. Redirect URIs: `http://localhost:5173/api/oauth/microsoft/callback`
|
||||
|
||||
**GitHub:**
|
||||
1. GitHub Settings → Developer settings → OAuth Apps
|
||||
2. Neue OAuth App erstellen
|
||||
3. Authorization callback URL: `http://localhost:5173/api/oauth/github/callback`
|
||||
|
||||
---
|
||||
|
||||
## 12. Testing & Rollout
|
||||
|
||||
### 12.1 Test-Plan
|
||||
|
||||
**Phase 1: Backend-Integration**
|
||||
- [ ] OAuth Provider Manager implementieren
|
||||
- [ ] OAuth Handler Endpunkte
|
||||
- [ ] Session Management
|
||||
- [ ] Datenbank-Migrationen
|
||||
|
||||
**Phase 2: Frontend-Integration**
|
||||
- [ ] OAuth Login Buttons
|
||||
- [ ] Callback Handler
|
||||
- [ ] Session Storage
|
||||
- [ ] AuthContext Erweiterung
|
||||
|
||||
**Phase 3: Testing**
|
||||
- [ ] Unit Tests für OAuth Flow
|
||||
- [ ] Integration Tests
|
||||
- [ ] Security Tests (CSRF, Token Validation)
|
||||
- [ ] User Acceptance Testing
|
||||
|
||||
**Phase 4: Rollout**
|
||||
- [ ] Staging Deployment
|
||||
- [ ] Production Deployment
|
||||
- [ ] Monitoring & Logging
|
||||
|
||||
### 12.2 Rollback-Plan
|
||||
|
||||
**Falls Probleme auftreten:**
|
||||
1. OAuth-Endpunkte deaktivieren (Feature Flag)
|
||||
2. Bestehende Basic Auth bleibt funktionsfähig
|
||||
3. Datenbank-Migrationen sind rückwärtskompatibel
|
||||
|
||||
---
|
||||
|
||||
## 13. Monitoring & Logging
|
||||
|
||||
### 13.1 Logging
|
||||
|
||||
**OAuth-Events:**
|
||||
- OAuth Flow Initiation
|
||||
- OAuth Callback (Erfolg/Fehler)
|
||||
- User-Erstellung via OAuth
|
||||
- Session-Erstellung/Löschung
|
||||
- Token-Refresh
|
||||
|
||||
**Structured Logging:**
|
||||
```go
|
||||
logOAuthEvent("oauth_login_initiated", map[string]interface{}{
|
||||
"provider": "google",
|
||||
"user_id": userID,
|
||||
"ip_address": ip,
|
||||
"trace_id": traceID,
|
||||
})
|
||||
```
|
||||
|
||||
### 13.2 Metrics
|
||||
|
||||
**Zu tracken:**
|
||||
- Anzahl OAuth-Logins pro Provider
|
||||
- Erfolgsrate OAuth Flows
|
||||
- Session-Dauer
|
||||
- Fehlerrate (Invalid Code, Token Expiry, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 14. Zukünftige Erweiterungen
|
||||
|
||||
### 14.1 Multi-Factor Authentication
|
||||
- OAuth als Second Factor für Basic Auth User
|
||||
|
||||
### 14.2 SSO (Single Sign-On)
|
||||
- SAML 2.0 Support
|
||||
- OpenID Connect
|
||||
|
||||
### 14.3 Social Login
|
||||
- Weitere Provider: Facebook, Twitter, LinkedIn
|
||||
|
||||
### 14.4 Account Management
|
||||
- UI für Verknüpfung mehrerer OAuth-Accounts
|
||||
- Entkopplung von OAuth-Accounts
|
||||
|
||||
---
|
||||
|
||||
## 15. Abhängigkeiten
|
||||
|
||||
### 15.1 Backend (Go)
|
||||
|
||||
```go
|
||||
// OAuth Libraries
|
||||
github.com/golang/oauth2
|
||||
github.com/coreos/go-oidc/v3/oidc // Für OpenID Connect
|
||||
|
||||
// JWT
|
||||
github.com/golang-jwt/jwt/v5
|
||||
|
||||
// Encryption
|
||||
golang.org/x/crypto
|
||||
```
|
||||
|
||||
### 15.2 Frontend
|
||||
|
||||
Keine zusätzlichen Dependencies nötig (native Fetch API für OAuth Flow)
|
||||
|
||||
---
|
||||
|
||||
## 16. Risiken & Mitigation
|
||||
|
||||
### 16.1 Risiken
|
||||
|
||||
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|
||||
|--------|-------------------|--------|------------|
|
||||
| OAuth Provider Downtime | Mittel | Hoch | Fallback auf Basic Auth |
|
||||
| Token-Leak | Niedrig | Sehr Hoch | HttpOnly Cookies, Token Rotation |
|
||||
| CSRF-Angriffe | Mittel | Hoch | State Parameter Validation |
|
||||
| Account Takeover | Niedrig | Sehr Hoch | Email-Verification, Rate Limiting |
|
||||
|
||||
### 16.2 Security Best Practices
|
||||
|
||||
- ✅ HTTPS nur (in Production)
|
||||
- ✅ State Parameter für CSRF Protection
|
||||
- ✅ Token-Verschlüsselung in DB
|
||||
- ✅ Session Timeout
|
||||
- ✅ Rate Limiting
|
||||
- ✅ Audit Logging
|
||||
|
||||
---
|
||||
|
||||
## 17. Zusammenfassung
|
||||
|
||||
### 17.1 Vorteile
|
||||
|
||||
- **Benutzerfreundlichkeit**: Ein-Klick-Login mit bekannten Accounts
|
||||
- **Sicherheit**: Keine Passwort-Verwaltung für OAuth-User
|
||||
- **Skalierbarkeit**: Externe Provider übernehmen Authentifizierung
|
||||
- **Flexibilität**: Hybrid-System unterstützt beide Methoden
|
||||
|
||||
### 17.2 Herausforderungen
|
||||
|
||||
- **Komplexität**: Zusätzliche Infrastruktur und Code
|
||||
- **Abhängigkeit**: Abhängig von externen Providern
|
||||
- **Migration**: Bestehende User müssen unterstützt werden
|
||||
- **Testing**: Mehr Test-Szenarien durch Multi-Provider
|
||||
|
||||
---
|
||||
|
||||
**Erstellt am**: 2025-01-XX
|
||||
**Version**: 1.0
|
||||
**Status**: Konzept - Noch nicht implementiert
|
||||
|
||||
74
docs/PASSWORD_SECURITY_ANALYSIS.md
Normal file
74
docs/PASSWORD_SECURITY_ANALYSIS.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Passwort-Speicherung Sicherheitsanalyse
|
||||
|
||||
## Aktuelle Implementierung
|
||||
|
||||
### Wie werden Passwörter gespeichert?
|
||||
|
||||
1. **Algorithmus**: `bcrypt` (golang.org/x/crypto/bcrypt)
|
||||
2. **Cost Factor**: `bcrypt.DefaultCost` (Wert: **10**)
|
||||
3. **Speicherung**:
|
||||
- Feld: `password_hash TEXT NOT NULL` in SQLite
|
||||
- Format: bcrypt Hash-String (enthält automatisch Salt + Hash)
|
||||
- Beispiel: `$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy`
|
||||
|
||||
4. **Passwortrichtlinie**:
|
||||
- Mindestens 8 Zeichen
|
||||
- Großbuchstaben erforderlich
|
||||
- Kleinbuchstaben erforderlich
|
||||
- Zahlen erforderlich
|
||||
- Sonderzeichen erforderlich
|
||||
|
||||
5. **Validierung**:
|
||||
- Altes Passwort wird bei Änderung geprüft
|
||||
- `bcrypt.CompareHashAndPassword()` für Login-Validierung
|
||||
|
||||
## Entspricht es aktuellen Sicherheitsstandards?
|
||||
|
||||
### ✅ **Gut implementiert:**
|
||||
|
||||
1. **bcrypt ist ein sicherer, bewährter Algorithmus**
|
||||
- Speziell für Passwort-Hashing entwickelt
|
||||
- Verlangsamt Brute-Force-Angriffe durch anpassbare Rechenzeit
|
||||
- Wird von OWASP und anderen Sicherheitsorganisationen empfohlen
|
||||
|
||||
2. **Automatisches Salting**
|
||||
- bcrypt generiert für jedes Passwort einen eindeutigen Salt
|
||||
- Verhindert Rainbow-Table-Angriffe
|
||||
- Salt wird im Hash-String mitgespeichert
|
||||
|
||||
3. **Passwörter werden nie im Klartext gespeichert**
|
||||
- Nur gehashte Werte in der Datenbank
|
||||
- Einweg-Hashing (nicht reversibel)
|
||||
|
||||
4. **Passwortrichtlinie vorhanden**
|
||||
- Erzwingt starke Passwörter
|
||||
- Mindestanforderungen erfüllt
|
||||
|
||||
### ⚠️ **Verbesserungspotenzial:**
|
||||
|
||||
1. **Cost Factor könnte erhöht werden**
|
||||
- **Aktuell**: Cost 10 (DefaultCost)
|
||||
- **Empfohlen 2024/2025**: Cost 12-14
|
||||
- **Begründung**:
|
||||
- Cost 10 war vor ~10 Jahren Standard
|
||||
- Moderne Hardware ist schneller
|
||||
- Cost 12-14 bietet besseren Schutz gegen Brute-Force
|
||||
- Trade-off: Etwas langsamere Login-Zeit (~100-500ms), aber deutlich sicherer
|
||||
|
||||
2. **Fehlende Sicherheitsfeatures** (optional, aber empfohlen):
|
||||
- ❌ Rate Limiting für Login-Versuche (verhindert Brute-Force)
|
||||
- ❌ Passwort-Historie (verhindert Wiederverwendung)
|
||||
- ❌ Passwort-Ablaufzeit
|
||||
- ❌ Account-Lockout nach fehlgeschlagenen Versuchen
|
||||
- ❌ 2FA/MFA Support
|
||||
|
||||
## Empfehlung
|
||||
|
||||
Die aktuelle Implementierung ist **grundsätzlich sicher** und entspricht **modernen Standards**, aber:
|
||||
|
||||
1. **Sofort umsetzbar**: Cost Factor von 10 auf 12-14 erhöhen
|
||||
2. **Mittelfristig**: Rate Limiting für Login-Versuche implementieren
|
||||
3. **Langfristig**: Zusätzliche Sicherheitsfeatures (2FA, Passwort-Historie)
|
||||
|
||||
Soll ich den Cost Factor erhöhen?
|
||||
|
||||
Reference in New Issue
Block a user