Retour au blog

SecureLocal RGPD : Infrastructure locale sécurisée

15 Septembre 2024 8 min de lecture Infrastructure

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.

Le défi : Conformité RGPD locale

Client : Cabinet médical (Marseille)

Problème : Données patients sensibles, obligation de conformité RGPD

Contrainte : Infrastructure 100% locale, aucun cloud externe

Architecture de la solution

L'infrastructure SecureLocal se compose de plusieurs composants :

Article Premium

Cet article contient des configurations Docker complètes, des scripts de sécurité et des techniques de monitoring avancées.

Ce que vous découvrirez :

  • Configuration Docker Compose complète
  • Scripts de sécurité et chiffrement
  • Monitoring avec Prometheus/Grafana
  • Sauvegarde automatisée
  • Conformité RGPD technique
  • Reverse proxy sécurisé

1. Configuration Docker Compose

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:

2. Configuration Nginx sécurisé

# 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";
        }
    }
}

3. Scripts de sécurité

#!/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"

4. Chiffrement des données

# 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

5. Monitoring avec Prometheus

# 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

6. Dashboard Grafana

# 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"
          }
        ]
      }
    ]
  }
}

7. Scripts de sauvegarde automatisés

#!/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

8. Conformité RGPD technique

# 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

Résultats obtenus

Points techniques importants

  1. Chiffrement : Toutes les données sensibles sont chiffrées
  2. Audit : Traçabilité complète des accès aux données
  3. Isolation : Chaque service dans son propre container
  4. Monitoring : Alertes automatiques en cas de problème
  5. Conformité : Scripts pour respecter les droits RGPD

🚀 Besoin d'une infrastructure sécurisée ?

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