622 lines
15 KiB
Markdown
622 lines
15 KiB
Markdown
# 🔒 Sicherheit
|
||
|
||
Sicherheitsrichtlinien und Best Practices für das Ninja Cross Parkour System.
|
||
|
||
## 📋 Inhaltsverzeichnis
|
||
|
||
- [🛡️ Sicherheitsübersicht](#️-sicherheitsübersicht)
|
||
- [🔐 Authentifizierung](#-authentifizierung)
|
||
- [🔑 Autorisierung](#-autorisierung)
|
||
- [🛡️ Datenverschlüsselung](#️-datenverschlüsselung)
|
||
- [🌐 Netzwerksicherheit](#-netzwerksicherheit)
|
||
- [🗄️ Datenbanksicherheit](#️-datenbanksicherheit)
|
||
- [📱 API-Sicherheit](#-api-sicherheit)
|
||
- [🔍 Monitoring](#-monitoring)
|
||
- [🚨 Incident Response](#-incident-response)
|
||
|
||
## 🛡️ Sicherheitsübersicht
|
||
|
||
### Sicherheitsprinzipien
|
||
- **Defense in Depth** - Mehrschichtige Sicherheit
|
||
- **Least Privilege** - Minimale Berechtigungen
|
||
- **Zero Trust** - Kein Vertrauen ohne Verifikation
|
||
- **Security by Design** - Sicherheit von Anfang an
|
||
|
||
### Bedrohungsmodell
|
||
- **Externe Angriffe** - Unbefugter Zugriff von außen
|
||
- **Interne Bedrohungen** - Missbrauch durch autorisierte Benutzer
|
||
- **Datenlecks** - Unbefugte Offenlegung von Daten
|
||
- **Service-Ausfälle** - Verfügbarkeitsprobleme
|
||
|
||
## 🔐 Authentifizierung
|
||
|
||
### Passwort-Sicherheit
|
||
```javascript
|
||
// Passwort-Hashing mit bcrypt
|
||
const bcrypt = require('bcrypt');
|
||
|
||
// Passwort hashen
|
||
const saltRounds = 12;
|
||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||
|
||
// Passwort verifizieren
|
||
const isValid = await bcrypt.compare(password, hashedPassword);
|
||
```
|
||
|
||
### Passwort-Richtlinien
|
||
- **Mindestlänge:** 12 Zeichen
|
||
- **Komplexität:** Groß-/Kleinbuchstaben, Zahlen, Sonderzeichen
|
||
- **Keine Wörterbuchwörter**
|
||
- **Regelmäßige Rotation** (alle 90 Tage)
|
||
|
||
### Multi-Faktor-Authentifizierung (MFA)
|
||
```javascript
|
||
// TOTP-Implementierung
|
||
const speakeasy = require('speakeasy');
|
||
|
||
// Secret generieren
|
||
const secret = speakeasy.generateSecret({
|
||
name: 'Ninja Parkour',
|
||
account: 'admin@example.com'
|
||
});
|
||
|
||
// Token verifizieren
|
||
const verified = speakeasy.totp.verify({
|
||
secret: secret.base32,
|
||
encoding: 'base32',
|
||
token: userToken,
|
||
window: 2
|
||
});
|
||
```
|
||
|
||
### Session-Management
|
||
```javascript
|
||
// Sichere Session-Konfiguration
|
||
app.use(session({
|
||
secret: process.env.SESSION_SECRET,
|
||
resave: false,
|
||
saveUninitialized: false,
|
||
cookie: {
|
||
secure: process.env.NODE_ENV === 'production',
|
||
httpOnly: true,
|
||
maxAge: 24 * 60 * 60 * 1000, // 24 Stunden
|
||
sameSite: 'strict'
|
||
}
|
||
}));
|
||
```
|
||
|
||
## 🔑 Autorisierung
|
||
|
||
### Rollenbasierte Zugriffskontrolle (RBAC)
|
||
```javascript
|
||
// Rollen definieren
|
||
const ROLES = {
|
||
ADMIN: 'admin',
|
||
MODERATOR: 'moderator',
|
||
USER: 'user',
|
||
GUEST: 'guest'
|
||
};
|
||
|
||
// Berechtigungen definieren
|
||
const PERMISSIONS = {
|
||
READ_PLAYERS: 'read:players',
|
||
WRITE_PLAYERS: 'write:players',
|
||
DELETE_PLAYERS: 'delete:players',
|
||
READ_TIMES: 'read:times',
|
||
WRITE_TIMES: 'write:times',
|
||
ADMIN_ACCESS: 'admin:access'
|
||
};
|
||
|
||
// Rollen-Berechtigungen
|
||
const rolePermissions = {
|
||
[ROLES.ADMIN]: Object.values(PERMISSIONS),
|
||
[ROLES.MODERATOR]: [
|
||
PERMISSIONS.READ_PLAYERS,
|
||
PERMISSIONS.WRITE_PLAYERS,
|
||
PERMISSIONS.READ_TIMES,
|
||
PERMISSIONS.WRITE_TIMES
|
||
],
|
||
[ROLES.USER]: [
|
||
PERMISSIONS.READ_PLAYERS,
|
||
PERMISSIONS.READ_TIMES
|
||
]
|
||
};
|
||
```
|
||
|
||
### API-Key-Management
|
||
```javascript
|
||
// API-Key generieren
|
||
const generateAPIKey = () => {
|
||
return crypto.randomBytes(32).toString('hex');
|
||
};
|
||
|
||
// API-Key validieren
|
||
const validateAPIKey = async (apiKey) => {
|
||
const token = await db.query(
|
||
'SELECT * FROM api_tokens WHERE token = $1 AND is_active = true',
|
||
[apiKey]
|
||
);
|
||
return token.rows.length > 0;
|
||
};
|
||
|
||
// Berechtigungen prüfen
|
||
const checkPermission = (userRole, requiredPermission) => {
|
||
const userPermissions = rolePermissions[userRole] || [];
|
||
return userPermissions.includes(requiredPermission);
|
||
};
|
||
```
|
||
|
||
### Middleware für Autorisierung
|
||
```javascript
|
||
// Authentifizierung prüfen
|
||
const requireAuth = (req, res, next) => {
|
||
if (!req.session || !req.session.userId) {
|
||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||
}
|
||
next();
|
||
};
|
||
|
||
// Rolle prüfen
|
||
const requireRole = (role) => {
|
||
return (req, res, next) => {
|
||
if (req.session.role !== role) {
|
||
return res.status(403).json({ error: 'Keine Berechtigung' });
|
||
}
|
||
next();
|
||
};
|
||
};
|
||
|
||
// Berechtigung prüfen
|
||
const requirePermission = (permission) => {
|
||
return (req, res, next) => {
|
||
const userRole = req.session.role;
|
||
if (!checkPermission(userRole, permission)) {
|
||
return res.status(403).json({ error: 'Keine Berechtigung' });
|
||
}
|
||
next();
|
||
};
|
||
};
|
||
```
|
||
|
||
## 🛡️ Datenverschlüsselung
|
||
|
||
### Verschlüsselung im Ruhezustand
|
||
```javascript
|
||
// Datenbank-Verschlüsselung
|
||
const crypto = require('crypto');
|
||
|
||
// Verschlüsselung
|
||
const encrypt = (text, key) => {
|
||
const iv = crypto.randomBytes(16);
|
||
const cipher = crypto.createCipher('aes-256-cbc', key);
|
||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||
encrypted += cipher.final('hex');
|
||
return iv.toString('hex') + ':' + encrypted;
|
||
};
|
||
|
||
// Entschlüsselung
|
||
const decrypt = (text, key) => {
|
||
const textParts = text.split(':');
|
||
const iv = Buffer.from(textParts.shift(), 'hex');
|
||
const encryptedText = textParts.join(':');
|
||
const decipher = crypto.createDecipher('aes-256-cbc', key);
|
||
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
||
decrypted += decipher.final('utf8');
|
||
return decrypted;
|
||
};
|
||
```
|
||
|
||
### Verschlüsselung in Bewegung
|
||
```javascript
|
||
// HTTPS-Konfiguration
|
||
const https = require('https');
|
||
const fs = require('fs');
|
||
|
||
const options = {
|
||
key: fs.readFileSync('private-key.pem'),
|
||
cert: fs.readFileSync('certificate.pem'),
|
||
ciphers: [
|
||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||
'ECDHE-RSA-AES256-SHA384',
|
||
'ECDHE-RSA-AES128-SHA256'
|
||
].join(':'),
|
||
honorCipherOrder: true
|
||
};
|
||
|
||
https.createServer(options, app).listen(443);
|
||
```
|
||
|
||
### Passwort-Verschlüsselung
|
||
```javascript
|
||
// Starke Passwort-Generierung
|
||
const generatePassword = (length = 16) => {
|
||
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
|
||
let password = '';
|
||
for (let i = 0; i < length; i++) {
|
||
password += charset.charAt(Math.floor(Math.random() * charset.length));
|
||
}
|
||
return password;
|
||
};
|
||
|
||
// Passwort-Stärke prüfen
|
||
const validatePassword = (password) => {
|
||
const minLength = 12;
|
||
const hasUpperCase = /[A-Z]/.test(password);
|
||
const hasLowerCase = /[a-z]/.test(password);
|
||
const hasNumbers = /\d/.test(password);
|
||
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
||
|
||
return password.length >= minLength &&
|
||
hasUpperCase &&
|
||
hasLowerCase &&
|
||
hasNumbers &&
|
||
hasSpecialChar;
|
||
};
|
||
```
|
||
|
||
## 🌐 Netzwerksicherheit
|
||
|
||
### Firewall-Konfiguration
|
||
```bash
|
||
# UFW-Firewall (Ubuntu)
|
||
sudo ufw enable
|
||
sudo ufw default deny incoming
|
||
sudo ufw default allow outgoing
|
||
|
||
# Erlaubte Ports
|
||
sudo ufw allow 22/tcp # SSH
|
||
sudo ufw allow 80/tcp # HTTP
|
||
sudo ufw allow 443/tcp # HTTPS
|
||
sudo ufw allow 3000/tcp # App (nur intern)
|
||
|
||
# Rate Limiting
|
||
sudo ufw limit ssh
|
||
```
|
||
|
||
### DDoS-Schutz
|
||
```javascript
|
||
// Rate Limiting
|
||
const rateLimit = require('express-rate-limit');
|
||
|
||
const limiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000, // 15 Minuten
|
||
max: 100, // Max 100 Requests pro IP
|
||
message: 'Zu viele Anfragen von dieser IP',
|
||
standardHeaders: true,
|
||
legacyHeaders: false
|
||
});
|
||
|
||
app.use('/api/', limiter);
|
||
|
||
// Strikte Rate Limits für sensible Endpoints
|
||
const strictLimiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000,
|
||
max: 5,
|
||
message: 'Zu viele Login-Versuche'
|
||
});
|
||
|
||
app.use('/api/login', strictLimiter);
|
||
```
|
||
|
||
### CORS-Konfiguration
|
||
```javascript
|
||
// Sichere CORS-Einstellungen
|
||
const cors = require('cors');
|
||
|
||
const corsOptions = {
|
||
origin: (origin, callback) => {
|
||
const allowedOrigins = [
|
||
'https://ninja.reptilfpv.de',
|
||
'https://www.ninja.reptilfpv.de'
|
||
];
|
||
|
||
if (!origin || allowedOrigins.includes(origin)) {
|
||
callback(null, true);
|
||
} else {
|
||
callback(new Error('Nicht erlaubt durch CORS'));
|
||
}
|
||
},
|
||
credentials: true,
|
||
optionsSuccessStatus: 200
|
||
};
|
||
|
||
app.use(cors(corsOptions));
|
||
```
|
||
|
||
### SSL/TLS-Konfiguration
|
||
```javascript
|
||
// SSL-Redirect
|
||
app.use((req, res, next) => {
|
||
if (req.header('x-forwarded-proto') !== 'https') {
|
||
res.redirect(`https://${req.header('host')}${req.url}`);
|
||
} else {
|
||
next();
|
||
}
|
||
});
|
||
|
||
// Security Headers
|
||
const helmet = require('helmet');
|
||
|
||
app.use(helmet({
|
||
contentSecurityPolicy: {
|
||
directives: {
|
||
defaultSrc: ["'self'"],
|
||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||
scriptSrc: ["'self'"],
|
||
imgSrc: ["'self'", "data:", "https:"],
|
||
connectSrc: ["'self'"],
|
||
fontSrc: ["'self'"],
|
||
objectSrc: ["'none'"],
|
||
mediaSrc: ["'self'"],
|
||
frameSrc: ["'none'"]
|
||
}
|
||
},
|
||
hsts: {
|
||
maxAge: 31536000,
|
||
includeSubDomains: true,
|
||
preload: true
|
||
}
|
||
}));
|
||
```
|
||
|
||
## 🗄️ Datenbanksicherheit
|
||
|
||
### Verbindungssicherheit
|
||
```javascript
|
||
// Sichere Datenbankverbindung
|
||
const dbConfig = {
|
||
host: process.env.DB_HOST,
|
||
port: process.env.DB_PORT,
|
||
database: process.env.DB_NAME,
|
||
user: process.env.DB_USER,
|
||
password: process.env.DB_PASSWORD,
|
||
ssl: {
|
||
rejectUnauthorized: true,
|
||
ca: fs.readFileSync('ca-certificate.pem')
|
||
},
|
||
connectionTimeoutMillis: 5000,
|
||
idleTimeoutMillis: 30000,
|
||
max: 20
|
||
};
|
||
```
|
||
|
||
### SQL-Injection-Schutz
|
||
```javascript
|
||
// Parametrisierte Queries
|
||
const getUser = async (userId) => {
|
||
const query = 'SELECT * FROM players WHERE id = $1';
|
||
const values = [userId];
|
||
const result = await db.query(query, values);
|
||
return result.rows[0];
|
||
};
|
||
|
||
// Input-Validierung
|
||
const validateInput = (input) => {
|
||
if (typeof input !== 'string') {
|
||
throw new Error('Invalid input type');
|
||
}
|
||
|
||
// SQL-Injection-Patterns erkennen
|
||
const sqlPatterns = [
|
||
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi,
|
||
/(\b(OR|AND)\s+\d+\s*=\s*\d+)/gi,
|
||
/(\b(OR|AND)\s+['"]\s*=\s*['"])/gi
|
||
];
|
||
|
||
for (const pattern of sqlPatterns) {
|
||
if (pattern.test(input)) {
|
||
throw new Error('Potential SQL injection detected');
|
||
}
|
||
}
|
||
|
||
return input;
|
||
};
|
||
```
|
||
|
||
### Datenbank-Berechtigungen
|
||
```sql
|
||
-- Benutzer mit minimalen Rechten erstellen
|
||
CREATE USER ninja_app WITH PASSWORD 'secure_password';
|
||
|
||
-- Nur notwendige Berechtigungen gewähren
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON players TO ninja_app;
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON times TO ninja_app;
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON locations TO ninja_app;
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON achievements TO ninja_app;
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON player_achievements TO ninja_app;
|
||
|
||
-- Keine Admin-Rechte
|
||
REVOKE ALL ON SCHEMA public FROM ninja_app;
|
||
```
|
||
|
||
## 📱 API-Sicherheit
|
||
|
||
### Input-Validierung
|
||
```javascript
|
||
// Joi-Schema für Validierung
|
||
const Joi = require('joi');
|
||
|
||
const playerSchema = Joi.object({
|
||
firstname: Joi.string().min(2).max(50).required(),
|
||
lastname: Joi.string().min(2).max(50).required(),
|
||
birthdate: Joi.date().max('now').required(),
|
||
rfiduid: Joi.string().pattern(/^[A-F0-9:]{11}$/).optional()
|
||
});
|
||
|
||
const validatePlayer = (req, res, next) => {
|
||
const { error } = playerSchema.validate(req.body);
|
||
if (error) {
|
||
return res.status(400).json({ error: error.details[0].message });
|
||
}
|
||
next();
|
||
};
|
||
```
|
||
|
||
### API-Versionierung
|
||
```javascript
|
||
// API-Versionierung
|
||
app.use('/api/v1', v1Routes);
|
||
app.use('/api/v2', v2Routes);
|
||
|
||
// Deprecation-Warnungen
|
||
app.use('/api/v1', (req, res, next) => {
|
||
res.set('X-API-Version', '1.0.0');
|
||
res.set('X-API-Deprecated', 'false');
|
||
next();
|
||
});
|
||
```
|
||
|
||
### Request-Logging
|
||
```javascript
|
||
// Sicherheitsrelevante Logs
|
||
const securityLogger = winston.createLogger({
|
||
level: 'info',
|
||
format: winston.format.combine(
|
||
winston.format.timestamp(),
|
||
winston.format.json()
|
||
),
|
||
transports: [
|
||
new winston.transports.File({ filename: 'logs/security.log' })
|
||
]
|
||
});
|
||
|
||
// Login-Versuche loggen
|
||
app.post('/api/login', (req, res) => {
|
||
const { username, ip } = req.body;
|
||
securityLogger.info('Login attempt', { username, ip, timestamp: new Date() });
|
||
// ... Login-Logik
|
||
});
|
||
```
|
||
|
||
## 🔍 Monitoring
|
||
|
||
### Sicherheits-Monitoring
|
||
```javascript
|
||
// Anomalie-Erkennung
|
||
const detectAnomalies = (req, res, next) => {
|
||
const ip = req.ip;
|
||
const userAgent = req.get('User-Agent');
|
||
|
||
// Verdächtige Patterns erkennen
|
||
const suspiciousPatterns = [
|
||
/sqlmap/i,
|
||
/nikto/i,
|
||
/nmap/i,
|
||
/masscan/i
|
||
];
|
||
|
||
for (const pattern of suspiciousPatterns) {
|
||
if (pattern.test(userAgent)) {
|
||
securityLogger.warn('Suspicious user agent detected', { ip, userAgent });
|
||
return res.status(403).json({ error: 'Request blocked' });
|
||
}
|
||
}
|
||
|
||
next();
|
||
};
|
||
```
|
||
|
||
### Log-Analyse
|
||
```bash
|
||
# Sicherheitsrelevante Logs analysieren
|
||
grep "ERROR\|WARN\|SECURITY" logs/server.log | tail -100
|
||
|
||
# Failed Login-Versuche
|
||
grep "Login attempt" logs/security.log | grep "failed"
|
||
|
||
# Verdächtige Aktivitäten
|
||
grep "suspicious\|anomaly\|attack" logs/security.log
|
||
```
|
||
|
||
### Alerting
|
||
```javascript
|
||
// E-Mail-Benachrichtigungen
|
||
const nodemailer = require('nodemailer');
|
||
|
||
const transporter = nodemailer.createTransporter({
|
||
host: 'smtp.gmail.com',
|
||
port: 587,
|
||
secure: false,
|
||
auth: {
|
||
user: process.env.EMAIL_USER,
|
||
pass: process.env.EMAIL_PASS
|
||
}
|
||
});
|
||
|
||
const sendSecurityAlert = async (message) => {
|
||
await transporter.sendMail({
|
||
from: process.env.EMAIL_USER,
|
||
to: process.env.ADMIN_EMAIL,
|
||
subject: 'Security Alert - Ninja Parkour',
|
||
text: message
|
||
});
|
||
};
|
||
```
|
||
|
||
## 🚨 Incident Response
|
||
|
||
### Incident-Response-Plan
|
||
1. **Erkennung** - Monitoring und Alerts
|
||
2. **Bewertung** - Schweregrad bestimmen
|
||
3. **Eindämmung** - Schaden begrenzen
|
||
4. **Eliminierung** - Bedrohung entfernen
|
||
5. **Wiederherstellung** - System reparieren
|
||
6. **Lektionen** - Aus Fehlern lernen
|
||
|
||
### Notfall-Kontakte
|
||
- **Systemadministrator:** +49 123 456 789
|
||
- **Sicherheitsbeauftragter:** security@ninjaparkour.de
|
||
- **Management:** management@ninjaparkour.de
|
||
|
||
### Backup-Strategie
|
||
```bash
|
||
# Tägliche Backups
|
||
pg_dump -h localhost -U username -d ninjaserver | gzip > backup_$(date +%Y%m%d).sql.gz
|
||
|
||
# Wöchentliche Vollständige Backups
|
||
tar -czf full_backup_$(date +%Y%m%d).tar.gz /var/lib/postgresql/data/
|
||
|
||
# Backup-Verifizierung
|
||
psql -d ninjaserver < backup_$(date +%Y%m%d).sql
|
||
```
|
||
|
||
### Recovery-Prozeduren
|
||
```bash
|
||
# System wiederherstellen
|
||
sudo systemctl stop ninjaserver
|
||
psql -d ninjaserver < latest_backup.sql
|
||
sudo systemctl start ninjaserver
|
||
|
||
# Datenbank reparieren
|
||
psql -d ninjaserver -c "VACUUM FULL;"
|
||
psql -d ninjaserver -c "REINDEX DATABASE ninjaserver;"
|
||
```
|
||
|
||
## 🔧 Sicherheits-Checkliste
|
||
|
||
### Regelmäßige Überprüfungen
|
||
- [ ] Passwörter geändert (alle 90 Tage)
|
||
- [ ] API-Keys rotiert (alle 180 Tage)
|
||
- [ ] SSL-Zertifikate gültig
|
||
- [ ] Sicherheits-Updates installiert
|
||
- [ ] Logs überprüft
|
||
- [ ] Backups getestet
|
||
- [ ] Penetrationstests durchgeführt
|
||
|
||
### Wöchentliche Aufgaben
|
||
- [ ] Sicherheits-Logs analysiert
|
||
- [ ] System-Updates geprüft
|
||
- [ ] Backup-Status überprüft
|
||
- [ ] Performance-Metriken analysiert
|
||
|
||
### Monatliche Aufgaben
|
||
- [ ] Sicherheits-Audit durchgeführt
|
||
- [ ] Berechtigungen überprüft
|
||
- [ ] Incident-Response-Plan getestet
|
||
- [ ] Sicherheitsschulungen durchgeführt
|
||
|
||
---
|
||
|
||
**Wichtig:** Diese Sicherheitsrichtlinien müssen regelmäßig überprüft und aktualisiert werden. Bei Sicherheitsvorfällen wenden Sie sich sofort an den Sicherheitsbeauftragten.
|