mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 22:04:10 +00:00
Add new collections and blocks for BlogWoman affiliate and video content: Collections: - Favorites: Affiliate products with categories, badges, and price ranges - Series: YouTube series with custom branding (logo, colors) Blocks: - FavoritesBlock: Grid/list/carousel display for affiliate products - SeriesBlock: Series overview with filtering - SeriesDetailBlock: Single series page with hero - VideoEmbedBlock: YouTube/Vimeo embed with privacy mode - FeaturedContentBlock: Curated mixed-content collections Also includes documentation updates for deployment and API guides. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
353 lines
9.8 KiB
Markdown
353 lines
9.8 KiB
Markdown
# Security-Richtlinien - Payload CMS Multi-Tenant
|
|
|
|
> Letzte Aktualisierung: 29.12.2025
|
|
|
|
## Übersicht
|
|
|
|
Dieses Dokument beschreibt die implementierten Sicherheitsmaßnahmen für das Payload CMS Multi-Tenant-Projekt.
|
|
|
|
**Umgebungen:**
|
|
|
|
| Umgebung | URL | TRUST_PROXY |
|
|
|----------|-----|-------------|
|
|
| Production | https://cms.c2sgmbh.de | `true` (Nginx) |
|
|
| Staging | https://pl.porwoll.tech | `true` (Caddy) |
|
|
|
|
---
|
|
|
|
## Security-Module
|
|
|
|
Alle Security-Funktionen befinden sich in `src/lib/security/`:
|
|
|
|
| Modul | Datei | Zweck |
|
|
|-------|-------|-------|
|
|
| Rate Limiter | `rate-limiter.ts` | Schutz vor API-Missbrauch |
|
|
| IP Allowlist | `ip-allowlist.ts` | IP-basierte Zugriffskontrolle |
|
|
| CSRF Protection | `csrf.ts` | Cross-Site Request Forgery Schutz |
|
|
| Data Masking | `data-masking.ts` | Sensitive Daten in Logs maskieren |
|
|
|
|
### Rate Limiter
|
|
|
|
**Vordefinierte Limiter:**
|
|
|
|
| Name | Limit | Fenster | Verwendung |
|
|
|------|-------|---------|------------|
|
|
| `publicApiLimiter` | 60 Requests | 1 Minute | Öffentliche API-Endpunkte |
|
|
| `authLimiter` | 5 Requests | 15 Minuten | Login-Versuche |
|
|
| `emailLimiter` | 10 Requests | 1 Minute | E-Mail-Versand |
|
|
| `searchLimiter` | 30 Requests | 1 Minute | Suche & Posts-API |
|
|
| `formLimiter` | 5 Requests | 10 Minuten | Formular-Submissions |
|
|
|
|
**Verwendung:**
|
|
```typescript
|
|
import { searchLimiter, rateLimitHeaders } from '@/lib/security'
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const ip = getClientIp(req.headers)
|
|
const rateLimit = await searchLimiter.check(ip)
|
|
|
|
if (!rateLimit.allowed) {
|
|
return NextResponse.json(
|
|
{ error: 'Too many requests' },
|
|
{
|
|
status: 429,
|
|
headers: rateLimitHeaders(rateLimit, 30)
|
|
}
|
|
)
|
|
}
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Redis-Support:**
|
|
- Automatischer Fallback auf In-Memory-Store wenn Redis nicht verfügbar
|
|
- Für verteilte Systeme: Redis via `REDIS_URL` konfigurieren
|
|
|
|
### IP Allowlist/Blocklist
|
|
|
|
> **WICHTIG: TRUST_PROXY Konfiguration**
|
|
>
|
|
> Wenn die Anwendung hinter einem Reverse-Proxy (Caddy, Nginx, etc.) läuft,
|
|
> **muss** `TRUST_PROXY=true` gesetzt werden. Ohne diese Einstellung werden
|
|
> `X-Forwarded-For` und `X-Real-IP` Header ignoriert, um IP-Spoofing zu verhindern.
|
|
>
|
|
> ```bash
|
|
> # In .env setzen wenn hinter Reverse-Proxy:
|
|
> TRUST_PROXY=true
|
|
> ```
|
|
>
|
|
> Ohne `TRUST_PROXY=true` wird für alle Requests `direct-connection` als IP verwendet,
|
|
> was Rate-Limiting und IP-Allowlists/-Blocklists ineffektiv macht.
|
|
|
|
**Environment-Variablen:**
|
|
|
|
| Variable | Zweck | Format |
|
|
|----------|-------|--------|
|
|
| `TRUST_PROXY` | Proxy-Header vertrauen | `true` oder leer |
|
|
| `BLOCKED_IPS` | Globale Blocklist | IP, CIDR, Wildcard |
|
|
| `SEND_EMAIL_ALLOWED_IPS` | E-Mail-Endpoint Allowlist | IP, CIDR, Wildcard |
|
|
| `ADMIN_ALLOWED_IPS` | Admin-Panel Allowlist | IP, CIDR, Wildcard |
|
|
| `WEBHOOK_ALLOWED_IPS` | Webhook-Endpoint Allowlist | IP, CIDR, Wildcard |
|
|
|
|
**Unterstützte Formate:**
|
|
```bash
|
|
# Einzelne IP
|
|
BLOCKED_IPS=192.168.1.100
|
|
|
|
# Mehrere IPs
|
|
BLOCKED_IPS=192.168.1.100,10.0.0.50
|
|
|
|
# CIDR-Notation
|
|
BLOCKED_IPS=10.0.0.0/24
|
|
|
|
# Wildcard
|
|
BLOCKED_IPS=192.168.*
|
|
|
|
# Kombiniert
|
|
BLOCKED_IPS=10.0.0.0/8,192.168.1.50,172.16.*
|
|
```
|
|
|
|
**Verwendung:**
|
|
```typescript
|
|
import { validateIpAccess } from '@/lib/security'
|
|
|
|
const ipCheck = validateIpAccess(req, 'sendEmail')
|
|
if (!ipCheck.allowed) {
|
|
return NextResponse.json(
|
|
{ error: 'Access denied', reason: ipCheck.reason },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
```
|
|
|
|
### CSRF Protection
|
|
|
|
**Pattern:** Double Submit Cookie
|
|
|
|
**Token-Endpoint:** `GET /api/csrf-token`
|
|
|
|
> **WICHTIG: CSRF_SECRET in Production**
|
|
>
|
|
> In Production **muss** entweder `CSRF_SECRET` oder `PAYLOAD_SECRET` konfiguriert sein.
|
|
> Ohne Secret startet der Server nicht. Ein vorhersagbares Secret würde Angreifern
|
|
> erlauben, gültige CSRF-Tokens offline zu generieren.
|
|
>
|
|
> ```bash
|
|
> # In .env setzen:
|
|
> CSRF_SECRET=mindestens-32-zeichen-langes-geheimnis
|
|
> # ODER PAYLOAD_SECRET wird als Fallback verwendet
|
|
> ```
|
|
|
|
**Frontend-Integration:**
|
|
```typescript
|
|
// 1. Token abrufen
|
|
const res = await fetch('/api/csrf-token', { credentials: 'include' })
|
|
const { csrfToken } = await res.json()
|
|
|
|
// 2. Bei Mutations mitschicken
|
|
await fetch('/api/send-email', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-csrf-token': csrfToken,
|
|
},
|
|
body: JSON.stringify({ /* ... */ })
|
|
})
|
|
```
|
|
|
|
**Server-seitige Validierung:**
|
|
```typescript
|
|
import { validateCsrf } from '@/lib/security'
|
|
|
|
const csrf = validateCsrf(req)
|
|
if (!csrf.valid) {
|
|
return NextResponse.json(
|
|
{ error: 'CSRF validation failed' },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
```
|
|
|
|
**Bypass für Server-to-Server:**
|
|
- Requests ohne `Origin`/`Referer` Header werden als Server-to-Server behandelt
|
|
- API-Key-basierte Authentifizierung umgeht CSRF-Check
|
|
|
|
### Data Masking
|
|
|
|
**Automatisch maskierte Felder:**
|
|
- password, passwd, pwd
|
|
- token, accessToken, refreshToken
|
|
- apiKey, api_key
|
|
- secret, clientSecret
|
|
- credentials
|
|
- privateKey, private_key
|
|
- smtpPassword, smtp_pass
|
|
|
|
**Verwendung:**
|
|
```typescript
|
|
import { maskObject, createSafeLogger } from '@/lib/security'
|
|
|
|
// Objekte maskieren
|
|
const safeData = maskObject(sensitiveObject)
|
|
|
|
// Safe Logger verwenden
|
|
const logger = createSafeLogger('MyModule')
|
|
logger.info('User action', { password: 'secret' })
|
|
// Output: { password: '[REDACTED]' }
|
|
```
|
|
|
|
**Pattern-Erkennung:**
|
|
- JWT Tokens: Header bleibt, Payload/Signature maskiert
|
|
- Connection Strings: Passwort maskiert
|
|
- Private Keys: Komplett ersetzt
|
|
|
|
---
|
|
|
|
## Pre-Commit Hook
|
|
|
|
Secret Detection bei jedem Commit via `scripts/detect-secrets.sh`:
|
|
|
|
**Installation:**
|
|
```bash
|
|
ln -sf ../../scripts/detect-secrets.sh .git/hooks/pre-commit
|
|
```
|
|
|
|
**Erkannte Patterns:**
|
|
- API Keys und Tokens
|
|
- AWS Credentials
|
|
- Private Keys
|
|
- Passwörter in Code
|
|
- SMTP Credentials
|
|
- Database Connection Strings
|
|
- JWT Tokens
|
|
- Webhook URLs (Slack, Discord)
|
|
- GitHub/SendGrid/Stripe Tokens
|
|
|
|
**Ignorierte Dateien:**
|
|
- `*.min.js`, `*.min.css`
|
|
- `package-lock.json`, `pnpm-lock.yaml`
|
|
- `*.md`, `*.txt`
|
|
- `*.example`, `*.sample`
|
|
- `*.spec.ts`, `*.test.ts` (Test-Dateien)
|
|
|
|
---
|
|
|
|
## CI/CD Security
|
|
|
|
**.github/workflows/security.yml:**
|
|
|
|
| Job | Prüfung |
|
|
|-----|---------|
|
|
| `secrets` | Gitleaks Secret Scanning |
|
|
| `dependencies` | npm audit, Dependency Check |
|
|
| `codeql` | Static Code Analysis |
|
|
| `security-tests` | 177 Security Unit & Integration Tests |
|
|
|
|
---
|
|
|
|
## Test Suite
|
|
|
|
**Ausführung:**
|
|
```bash
|
|
# Alle Security-Tests
|
|
pnpm test:security
|
|
|
|
# Nur Unit-Tests
|
|
pnpm test:unit
|
|
```
|
|
|
|
**Abdeckung:**
|
|
|
|
| Test-Datei | Tests | Bereich |
|
|
|------------|-------|---------|
|
|
| `rate-limiter.unit.spec.ts` | 24 | Limiter, Tracking, Reset, TRUST_PROXY |
|
|
| `csrf.unit.spec.ts` | 34 | Token, Validierung, Origin |
|
|
| `ip-allowlist.unit.spec.ts` | 35 | CIDR, Wildcards, Endpoints, TRUST_PROXY |
|
|
| `data-masking.unit.spec.ts` | 41 | Felder, Patterns, Rekursion |
|
|
| `security-api.int.spec.ts` | 33 | API-Integration |
|
|
|
|
---
|
|
|
|
## Empfehlungen
|
|
|
|
### Production Checklist
|
|
|
|
- [ ] **`TRUST_PROXY=true`** setzen (Pflicht hinter Reverse-Proxy wie Caddy)
|
|
- [ ] **`CSRF_SECRET`** oder **`PAYLOAD_SECRET`** setzen (Server startet nicht ohne)
|
|
- [ ] Alle `BLOCKED_IPS` für bekannte Angreifer setzen
|
|
- [ ] `SEND_EMAIL_ALLOWED_IPS` auf vertrauenswürdige IPs beschränken
|
|
- [ ] `ADMIN_ALLOWED_IPS` auf Office/VPN-IPs setzen
|
|
- [ ] Redis für verteiltes Rate Limiting konfigurieren
|
|
- [ ] Pre-Commit Hook aktivieren
|
|
|
|
### Monitoring
|
|
|
|
- Rate Limit Violations loggen (429 Responses)
|
|
- CSRF Validation Failures überwachen
|
|
- Blocked IP Attempts tracken
|
|
- Login-Fehlversuche (authLimiter) alertieren
|
|
|
|
---
|
|
|
|
## Custom Login Route
|
|
|
|
Das Admin Panel verwendet eine Custom Login Route (`src/app/(payload)/api/users/login/route.ts`) mit folgenden Features:
|
|
|
|
- **Audit-Logging:** Jeder Login-Versuch wird in AuditLogs protokolliert
|
|
- **Rate-Limiting:** 5 Versuche pro 15 Minuten (authLimiter)
|
|
- **Browser-Redirect:** Sichere Weiterleitung nach erfolgreichem Login
|
|
- **Content-Type Support:**
|
|
- JSON (`application/json`)
|
|
- FormData mit `_payload` JSON-Feld (Payload Admin Panel Format)
|
|
- Standard FormData (`multipart/form-data`)
|
|
- URL-encoded (`application/x-www-form-urlencoded`)
|
|
|
|
**Browser Form Redirect:**
|
|
```
|
|
POST /api/users/login?redirect=/admin/collections/posts
|
|
Content-Type: application/x-www-form-urlencoded
|
|
|
|
email=admin@example.com&password=secret
|
|
```
|
|
|
|
**Redirect-Validierung:**
|
|
- Nur relative Pfade erlaubt (`/admin/...`)
|
|
- Externe URLs werden blockiert
|
|
- Protocol-Handler (`javascript:`, `data:`) abgelehnt
|
|
- Default: `/admin` bei fehlendem/ungültigem Redirect
|
|
|
|
**Sicherheitsaspekte:**
|
|
- Passwort wird nie in Logs/Responses exponiert
|
|
- Fehlgeschlagene Login-Versuche werden mit IP und User-Agent geloggt
|
|
- Rate-Limiting verhindert Brute-Force-Angriffe
|
|
- Open Redirect Prevention durch URL-Validierung
|
|
|
|
---
|
|
|
|
## Änderungshistorie
|
|
|
|
| Datum | Änderung |
|
|
|-------|----------|
|
|
| 29.12.2025 | **Dokumentation aktualisiert:** Custom Login Page Abschnitt entfernt (wurde am 27.12.2025 entfernt) |
|
|
| 17.12.2025 | **Security-Audit Fixes:** TRUST_PROXY für IP-Header-Spoofing, CSRF_SECRET Pflicht in Production, IP-Allowlist Startup-Warnungen, Tests auf 177 erweitert |
|
|
| 09.12.2025 | Custom Login Route Dokumentation, multipart/form-data _payload Support |
|
|
| 08.12.2025 | Security Test Suite (143 Tests) |
|
|
| 07.12.2025 | Rate Limiter, CSRF, IP Allowlist, Data Masking |
|
|
| 07.12.2025 | Pre-Commit Hook, GitHub Actions Workflow |
|
|
|
|
---
|
|
|
|
## Dateien
|
|
|
|
| Pfad | Beschreibung |
|
|
|------|--------------|
|
|
| `src/lib/security/rate-limiter.ts` | Rate Limiting mit Redis/Memory |
|
|
| `src/lib/security/ip-allowlist.ts` | IP-basierte Zugriffskontrolle |
|
|
| `src/lib/security/csrf.ts` | CSRF Token Generation & Validation |
|
|
| `src/lib/security/data-masking.ts` | Sensitive Data Masking |
|
|
| `src/app/(payload)/api/users/login/route.ts` | Custom Login API mit Audit |
|
|
| `scripts/detect-secrets.sh` | Pre-Commit Secret Detection |
|
|
| `.github/workflows/security.yml` | CI Security Scanning |
|
|
| `tests/unit/security/` | Security Unit Tests |
|
|
| `tests/int/security-api.int.spec.ts` | Security Integration Tests |
|