Update
This commit is contained in:
621
wiki/Sicherheit.md
Normal file
621
wiki/Sicherheit.md
Normal file
@@ -0,0 +1,621 @@
|
||||
# 🔒 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.
|
||||
Reference in New Issue
Block a user