- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
315 lines
7.9 KiB
PHP
315 lines
7.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Logging\Security;
|
|
|
|
/**
|
|
* Sensitive Data Redactor für PII Protection in Logs
|
|
*
|
|
* Automatische Erkennung und Maskierung von sensitiven Daten:
|
|
* - Passwörter
|
|
* - API Keys/Tokens
|
|
* - Kreditkarten-Nummern
|
|
* - Email-Adressen (optional)
|
|
* - IP-Adressen (optional)
|
|
* - SSN/Personalausweis-Nummern
|
|
*
|
|
* Verwendung:
|
|
* $redactor = new SensitiveDataRedactor();
|
|
* $sanitized = $redactor->redact($data);
|
|
*/
|
|
final readonly class SensitiveDataRedactor
|
|
{
|
|
/**
|
|
* Liste der Keys, die immer redacted werden
|
|
*/
|
|
private const SENSITIVE_KEYS = [
|
|
'password',
|
|
'passwd',
|
|
'pwd',
|
|
'secret',
|
|
'api_key',
|
|
'apikey',
|
|
'api_secret',
|
|
'apisecret',
|
|
'token',
|
|
'access_token',
|
|
'refresh_token',
|
|
'bearer',
|
|
'auth',
|
|
'authorization',
|
|
'private_key',
|
|
'privatekey',
|
|
'encryption_key',
|
|
'encryptionkey',
|
|
'session_id',
|
|
'sessionid',
|
|
'cookie',
|
|
'csrf',
|
|
'csrf_token',
|
|
'credit_card',
|
|
'creditcard',
|
|
'card_number',
|
|
'cardnumber',
|
|
'cvv',
|
|
'cvc',
|
|
'ssn',
|
|
'social_security',
|
|
'tax_id',
|
|
'passport',
|
|
];
|
|
|
|
/**
|
|
* Regex-Patterns für Content-basierte Erkennung
|
|
*/
|
|
private const PATTERNS = [
|
|
// Kreditkarten (Visa, MasterCard, Amex, Discover)
|
|
'credit_card' => '/\b(?:\d{4}[-\s]?){3}\d{4}\b/',
|
|
|
|
// Email-Adressen
|
|
'email' => '/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/',
|
|
|
|
// Bearer Tokens (JWT-ähnlich)
|
|
'bearer_token' => '/Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/',
|
|
|
|
// API Keys (alphanumerische Strings mit mindestens 20 Zeichen)
|
|
'api_key' => '/\b[A-Za-z0-9]{20,}\b/',
|
|
|
|
// IP-Adressen (IPv4)
|
|
'ipv4' => '/\b(?:\d{1,3}\.){3}\d{1,3}\b/',
|
|
|
|
// Sozialversicherungsnummern (US Format: XXX-XX-XXXX)
|
|
'ssn' => '/\b\d{3}-\d{2}-\d{4}\b/',
|
|
];
|
|
|
|
public function __construct(
|
|
private RedactionMode $mode = RedactionMode::PARTIAL,
|
|
private bool $redactEmails = false,
|
|
private bool $redactIps = false,
|
|
private string $mask = '[REDACTED]'
|
|
) {}
|
|
|
|
/**
|
|
* Redacted sensitive Daten in einem Array rekursiv
|
|
*
|
|
* @param array<string, mixed> $data
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function redact(array $data): array
|
|
{
|
|
$result = [];
|
|
|
|
foreach ($data as $key => $value) {
|
|
$normalizedKey = $this->normalizeKey($key);
|
|
|
|
// Key-basierte Redaction
|
|
if ($this->isSensitiveKey($normalizedKey)) {
|
|
$result[$key] = $this->maskValue($value);
|
|
continue;
|
|
}
|
|
|
|
// Rekursive Redaction für Arrays
|
|
if (is_array($value)) {
|
|
$result[$key] = $this->redact($value);
|
|
continue;
|
|
}
|
|
|
|
// Content-basierte Redaction für Strings
|
|
if (is_string($value)) {
|
|
$result[$key] = $this->redactContent($value);
|
|
continue;
|
|
}
|
|
|
|
// Andere Werte unverändert
|
|
$result[$key] = $value;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Redacted sensitive Daten in einem String
|
|
*/
|
|
public function redactString(string $content): string
|
|
{
|
|
return $this->redactContent($content);
|
|
}
|
|
|
|
/**
|
|
* Prüft ob ein Key sensitiv ist
|
|
*/
|
|
private function isSensitiveKey(string $key): bool
|
|
{
|
|
return in_array($key, self::SENSITIVE_KEYS, true);
|
|
}
|
|
|
|
/**
|
|
* Normalisiert einen Key für Vergleich (lowercase, underscores)
|
|
*/
|
|
private function normalizeKey(string $key): string
|
|
{
|
|
return strtolower(str_replace(['-', ' '], '_', $key));
|
|
}
|
|
|
|
/**
|
|
* Maskiert einen Wert basierend auf RedactionMode
|
|
*/
|
|
private function maskValue(mixed $value): string|array
|
|
{
|
|
// Arrays rekursiv redacten
|
|
if (is_array($value)) {
|
|
return array_map(fn($v) => $this->mask, $value);
|
|
}
|
|
|
|
// Strings basierend auf Mode maskieren
|
|
if (is_string($value)) {
|
|
return match ($this->mode) {
|
|
RedactionMode::FULL => $this->mask,
|
|
RedactionMode::PARTIAL => $this->partialMask($value),
|
|
RedactionMode::HASH => $this->hashValue($value),
|
|
};
|
|
}
|
|
|
|
// Andere Typen vollständig redacten
|
|
return $this->mask;
|
|
}
|
|
|
|
/**
|
|
* Partial Masking: Zeige erste und letzte Zeichen
|
|
*/
|
|
private function partialMask(string $value): string
|
|
{
|
|
$length = mb_strlen($value);
|
|
|
|
if ($length <= 4) {
|
|
return str_repeat('*', $length);
|
|
}
|
|
|
|
$visible = 2;
|
|
$start = mb_substr($value, 0, $visible);
|
|
$end = mb_substr($value, -$visible);
|
|
$masked = str_repeat('*', max(0, $length - ($visible * 2)));
|
|
|
|
return $start . $masked . $end;
|
|
}
|
|
|
|
/**
|
|
* Hash-basierte Redaction für deterministische Maskierung
|
|
*/
|
|
private function hashValue(string $value): string
|
|
{
|
|
$hash = hash('sha256', $value);
|
|
return '[HASH:' . substr($hash, 0, 12) . ']';
|
|
}
|
|
|
|
/**
|
|
* Content-basierte Redaction mit Regex-Patterns
|
|
*/
|
|
private function redactContent(string $content): string
|
|
{
|
|
// Kreditkarten
|
|
$content = preg_replace(
|
|
self::PATTERNS['credit_card'],
|
|
'[CREDIT_CARD]',
|
|
$content
|
|
);
|
|
|
|
// Bearer Tokens
|
|
$content = preg_replace(
|
|
self::PATTERNS['bearer_token'],
|
|
'Bearer [REDACTED]',
|
|
$content
|
|
);
|
|
|
|
// API Keys (nur wenn nicht bereits durch Key-Matching redacted)
|
|
$content = preg_replace(
|
|
self::PATTERNS['api_key'],
|
|
'[API_KEY]',
|
|
$content
|
|
);
|
|
|
|
// SSN
|
|
$content = preg_replace(
|
|
self::PATTERNS['ssn'],
|
|
'[SSN]',
|
|
$content
|
|
);
|
|
|
|
// Optional: Email-Adressen
|
|
if ($this->redactEmails) {
|
|
$content = preg_replace_callback(
|
|
self::PATTERNS['email'],
|
|
fn($matches) => $this->maskEmail($matches[0]),
|
|
$content
|
|
);
|
|
}
|
|
|
|
// Optional: IP-Adressen
|
|
if ($this->redactIps) {
|
|
$content = preg_replace(
|
|
self::PATTERNS['ipv4'],
|
|
'[IP_ADDRESS]',
|
|
$content
|
|
);
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Maskiert Email-Adresse: john.doe@example.com → j***e@example.com
|
|
*/
|
|
private function maskEmail(string $email): string
|
|
{
|
|
[$local, $domain] = explode('@', $email, 2);
|
|
|
|
$length = mb_strlen($local);
|
|
if ($length <= 2) {
|
|
$maskedLocal = str_repeat('*', $length);
|
|
} else {
|
|
$maskedLocal = mb_substr($local, 0, 1)
|
|
. str_repeat('*', max(0, $length - 2))
|
|
. mb_substr($local, -1);
|
|
}
|
|
|
|
return $maskedLocal . '@' . $domain;
|
|
}
|
|
|
|
/**
|
|
* Factory: Erstelle Redactor für Production (Full Redaction)
|
|
*/
|
|
public static function production(): self
|
|
{
|
|
return new self(
|
|
mode: RedactionMode::FULL,
|
|
redactEmails: true,
|
|
redactIps: true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory: Erstelle Redactor für Development (Partial Redaction)
|
|
*/
|
|
public static function development(): self
|
|
{
|
|
return new self(
|
|
mode: RedactionMode::PARTIAL,
|
|
redactEmails: false,
|
|
redactIps: false
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory: Erstelle Redactor für Testing (Hash-based Redaction)
|
|
*/
|
|
public static function testing(): self
|
|
{
|
|
return new self(
|
|
mode: RedactionMode::HASH,
|
|
redactEmails: false,
|
|
redactIps: false
|
|
);
|
|
}
|
|
}
|