Comment j'ai créé une infrastructure locale complète et RGPD-compliant pour une entreprise du secteur médical. Docker, reverse proxy, monitoring et sécurité renforcée.
Client : Cabinet médical (Marseille)
Problème : Données patients sensibles, obligation de conformité RGPD
Contrainte : Infrastructure 100% locale, aucun cloud externe
L'infrastructure SecureLocal se compose de plusieurs composants :
Cet article contient des configurations Docker complètes, des scripts de sécurité et des techniques de monitoring avancées.
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: securelocal-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
- ./logs/nginx:/var/log/nginx
depends_on:
- app
restart: unless-stopped
app:
build: ./app
container_name: securelocal-app
environment:
- DATABASE_URL=postgresql://user:pass@postgres:5432/securelocal
- REDIS_URL=redis://redis:6379
volumes:
- ./app:/app
- ./data/uploads:/app/uploads
depends_on:
- postgres
- redis
restart: unless-stopped
postgres:
image: postgres:15-alpine
container_name: securelocal-postgres
environment:
- POSTGRES_DB=securelocal
- POSTGRES_USER=user
- POSTGRES_PASSWORD=secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: securelocal-redis
command: redis-server --requirepass secure_redis_password
volumes:
- redis_data:/data
restart: unless-stopped
prometheus:
image: prom/prometheus:latest
container_name: securelocal-prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: securelocal-grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=secure_grafana_password
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
restart: unless-stopped
volumes:
postgres_data:
redis_data:
prometheus_data:
grafana_data:
# nginx/nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'";
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# SSL Configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Upstream
upstream app {
server app:8000;
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name securelocal.local;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name securelocal.local;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# Security
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://app;
}
# Static files
location /static/ {
alias /app/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
#!/bin/bash
# scripts/security-setup.sh
# Configuration du pare-feu
ufw --force enable
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 3000/tcp # Grafana
ufw allow 9090/tcp # Prometheus
# Configuration des logs
echo "local0.* /var/log/securelocal.log" >> /etc/rsyslog.conf
systemctl restart rsyslog
# Configuration de fail2ban
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 10
EOF
systemctl enable fail2ban
systemctl start fail2ban
# Configuration de l'audit
auditctl -w /etc/passwd -p wa -k identity
auditctl -w /etc/group -p wa -k identity
auditctl -w /etc/shadow -p wa -k identity
auditctl -w /var/log/securelocal.log -p wa -k securelocal
echo "Configuration de sécurité terminée"
# scripts/encrypt-data.sh
#!/bin/bash
# Génération des clés de chiffrement
openssl rand -base64 32 > /etc/securelocal/encryption.key
chmod 600 /etc/securelocal/encryption.key
# Chiffrement des volumes Docker
docker volume create securelocal_postgres_encrypted
docker run --rm -v securelocal_postgres_encrypted:/data -v /etc/securelocal/encryption.key:/key \
alpine sh -c "apk add --no-cache cryptsetup && \
echo 'secure_password' | cryptsetup luksFormat /dev/loop0 && \
echo 'secure_password' | cryptsetup luksOpen /dev/loop0 encrypted_volume"
# Script de chiffrement des sauvegardes
encrypt_backup() {
local file=$1
local encrypted_file="${file}.enc"
openssl enc -aes-256-cbc -salt -in "$file" -out "$encrypted_file" \
-pass file:/etc/securelocal/encryption.key
rm "$file"
echo "Backup chiffré: $encrypted_file"
}
# Chiffrement automatique des exports de base de données
export_db() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="/backups/securelocal_${timestamp}.sql"
docker exec securelocal-postgres pg_dump -U user securelocal > "$backup_file"
encrypt_backup "$backup_file"
}
export_db
# monitoring/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'nginx'
static_configs:
- targets: ['nginx:9113']
- job_name: 'postgres'
static_configs:
- targets: ['postgres:9187']
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'securelocal-app'
static_configs:
- targets: ['app:8000']
metrics_path: '/metrics'
scrape_interval: 30s
# monitoring/grafana/dashboards/securelocal.json
{
"dashboard": {
"title": "SecureLocal Infrastructure",
"panels": [
{
"title": "CPU Usage",
"type": "graph",
"targets": [
{
"expr": "100 - (avg(irate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
"legendFormat": "CPU Usage %"
}
]
},
{
"title": "Memory Usage",
"type": "graph",
"targets": [
{
"expr": "100 - ((node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100)",
"legendFormat": "Memory Usage %"
}
]
},
{
"title": "Database Connections",
"type": "graph",
"targets": [
{
"expr": "pg_stat_database_numbackends",
"legendFormat": "Active Connections"
}
]
},
{
"title": "HTTP Requests",
"type": "graph",
"targets": [
{
"expr": "rate(nginx_http_requests_total[5m])",
"legendFormat": "Requests/sec"
}
]
}
]
}
}
#!/bin/bash
# scripts/backup.sh
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
# Création du répertoire de sauvegarde
mkdir -p "$BACKUP_DIR"
# Sauvegarde de la base de données
echo "Sauvegarde de la base de données..."
docker exec securelocal-postgres pg_dump -U user securelocal | gzip > "$BACKUP_DIR/postgres_${DATE}.sql.gz"
# Sauvegarde des fichiers de l'application
echo "Sauvegarde des fichiers de l'application..."
tar -czf "$BACKUP_DIR/app_files_${DATE}.tar.gz" /app/uploads /app/static
# Sauvegarde des configurations
echo "Sauvegarde des configurations..."
tar -czf "$BACKUP_DIR/config_${DATE}.tar.gz" /etc/nginx /etc/securelocal
# Chiffrement des sauvegardes
for file in "$BACKUP_DIR"/*_${DATE}.*; do
if [[ -f "$file" ]]; then
openssl enc -aes-256-cbc -salt -in "$file" -out "${file}.enc" \
-pass file:/etc/securelocal/encryption.key
rm "$file"
fi
done
# Nettoyage des anciennes sauvegardes
find "$BACKUP_DIR" -name "*.enc" -mtime +$RETENTION_DAYS -delete
# Envoi d'alerte en cas d'échec
if [ $? -eq 0 ]; then
echo "Sauvegarde réussie: $DATE"
else
echo "ERREUR: Échec de la sauvegarde" | mail -s "Alerte SecureLocal" admin@securelocal.local
fi
# scripts/rgpd-compliance.sh
#!/bin/bash
# Audit des accès aux données personnelles
audit_data_access() {
echo "=== Audit des accès aux données personnelles ==="
# Logs d'accès à la base de données
docker logs securelocal-postgres 2>&1 | grep -E "(SELECT|INSERT|UPDATE|DELETE)" | \
grep -E "(patient|client|user)" > /var/log/securelocal/data_access.log
# Logs d'accès aux fichiers
find /app/uploads -type f -exec stat -c "%n %x %U %G" {} \; | \
grep -E "(patient|client)" >> /var/log/securelocal/data_access.log
echo "Audit terminé. Voir /var/log/securelocal/data_access.log"
}
# Suppression des données personnelles (droit à l'oubli)
delete_personal_data() {
local user_id=$1
if [ -z "$user_id" ]; then
echo "Usage: delete_personal_data "
return 1
fi
echo "Suppression des données personnelles pour l'utilisateur: $user_id"
# Suppression de la base de données
docker exec securelocal-postgres psql -U user -d securelocal -c \
"DELETE FROM patients WHERE id = $user_id;"
# Suppression des fichiers
find /app/uploads -name "*${user_id}*" -delete
# Log de la suppression
echo "$(date): Suppression des données pour l'utilisateur $user_id" >> /var/log/securelocal/deletions.log
echo "Données supprimées avec succès"
}
# Export des données personnelles (droit à la portabilité)
export_personal_data() {
local user_id=$1
local export_file="/exports/user_${user_id}_$(date +%Y%m%d).json"
if [ -z "$user_id" ]; then
echo "Usage: export_personal_data "
return 1
fi
echo "Export des données personnelles pour l'utilisateur: $user_id"
# Export JSON des données
docker exec securelocal-postgres psql -U user -d securelocal -t -c \
"SELECT json_agg(row_to_json(t)) FROM (SELECT * FROM patients WHERE id = $user_id) t;" > "$export_file"
# Chiffrement de l'export
openssl enc -aes-256-cbc -salt -in "$export_file" -out "${export_file}.enc" \
-pass file:/etc/securelocal/encryption.key
rm "$export_file"
echo "Export chiffré créé: ${export_file}.enc"
}
# Vérification de la conformité
check_compliance() {
echo "=== Vérification de la conformité RGPD ==="
# Vérification du chiffrement
if [ -f "/etc/securelocal/encryption.key" ]; then
echo "✓ Chiffrement des données configuré"
else
echo "✗ Chiffrement des données manquant"
fi
# Vérification des logs
if [ -d "/var/log/securelocal" ]; then
echo "✓ Système de logs configuré"
else
echo "✗ Système de logs manquant"
fi
# Vérification des sauvegardes
if [ -d "/backups" ] && [ "$(ls -A /backups)" ]; then
echo "✓ Système de sauvegarde opérationnel"
else
echo "✗ Système de sauvegarde manquant"
fi
# Vérification du pare-feu
if ufw status | grep -q "Status: active"; then
echo "✓ Pare-feu actif"
else
echo "✗ Pare-feu inactif"
fi
}
# Exécution des vérifications
check_compliance
Chaque entreprise a ses besoins spécifiques en matière de sécurité et de conformité. Contactez-moi pour une solution sur-mesure.
Discuter de votre projet