Réplication externe (SSH/SFTP) 🛰️

Conservez une copie off-site de tous vos backups et une sauvegarde de la base de la plateforme elle-même, sur un serveur externe accessible en SSH. Objectif : survivre à la perte complète du site principal (incendie, ransomware sur l'infra ILYGO, panne datacenter).

Pourquoi

Par défaut, les backups (chiffrés) vivent sur le MinIO de la plateforme. Si ce site disparaît, vous perdez tout. La réplication SSH pousse une seconde copie vers un serveur que vous contrôlez ailleurs (autre datacenter, autre cloud, NAS distant…).

Deux flux sont répliqués :

  1. Les backups (ciphertext) : les chunks chiffrés + manifests de chaque backup complété. Le serveur distant ne voit que du ciphertext — la propriété zero-knowledge est préservée jusqu'au site secondaire.
  2. La base de la plateforme : un pg_dump de la base ILYGO (métadonnées, audit log, inventaire des bridges) pour pouvoir reconstruire le plan de contrôle en cas de sinistre.

Comment ça marche

   ILYGO (site principal)                       Serveur externe (SSH)
   ┌─────────────────────┐                     ┌──────────────────────────┐
   │ MinIO (ciphertext)  │── SFTP (push) ─────►│ /backups/<tenant>/<id>/  │
   │ Postgre (métadonnées)│── pg_dump → SFTP ──►│ /platform-db/dump.sql.gz │
   └─────────────────────┘                     └──────────────────────────┘
        worker de réplication                    accès SSH sortant only
  • Un worker périodique (toutes les 10 min par défaut) repère les backups complétés non encore répliqués et les pousse en SFTP.
  • Connexion sortante uniquement : ILYGO se connecte au serveur externe en SSH. Aucun port entrant requis côté serveur externe au-delà de SSH (22).
  • Authentification par clé SSH (recommandé) ou mot de passe. La clé privée est stockée chiffrée côté ILYGO (Fernet dérivé du MASTER_SECRET).

Configurer une cible de réplication

Dans la console : Paramètres → Réplication externe → Ajouter une cible, ou via l'API :

POST /api/v1/replication-targets
{
  "name": "DR Genève",
  "ssh_host": "dr.example.ch",
  "ssh_port": 22,
  "ssh_user": "ilygo-replica",
  "base_path": "/srv/ilygo-replica",
  "auth_method": "key",
  "ssh_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
  "replicate_platform_db": true
}
Champ Description
name Libellé de la cible
ssh_host / ssh_port Serveur externe (port 22 par défaut)
ssh_user Compte SSH dédié à la réplication
base_path Répertoire racine où écrire (doit être writable par ssh_user)
auth_method key (recommandé) ou password
ssh_private_key Clé privée OpenSSH (stockée chiffrée, jamais ré-affichée)
ssh_password Si auth_method=password (stocké chiffré)
replicate_platform_db true pour aussi répliquer le dump de la base plateforme
enabled Activer/désactiver sans supprimer

Préparer le serveur externe

# Sur le serveur externe : créer un compte dédié à accès restreint
sudo adduser --disabled-password --home /srv/ilygo-replica ilygo-replica
sudo -u ilygo-replica mkdir -p /srv/ilygo-replica/.ssh

# Ajouter la clé publique correspondant à la clé privée donnée à ILYGO
echo "ssh-ed25519 AAAA... ilygo-replication" \
  | sudo -u ilygo-replica tee /srv/ilygo-replica/.ssh/authorized_keys
sudo -u ilygo-replica chmod 600 /srv/ilygo-replica/.ssh/authorized_keys

Bonnes pratiques : compte dédié, chroot SFTP, quota disque, authorized_keys avec restriction (restrict,command=... si vous voulez du SFTP-only via internal-sftp).

Tester la connexion

Avant d'activer, testez :

POST /api/v1/replication-targets/{id}/test
→ { "ok": true, "latency_ms": 42, "writable": true }

La console affiche un bouton « Tester la connexion » qui valide hôte, auth et droits d'écriture.

Structure sur le serveur externe

<base_path>/
├── backups/
│   └── <tenant_id>/
│       └── <backup_id>/
│           ├── manifest.json        (chiffré)
│           ├── chunk-00000000        (chiffré)
│           ├── chunk-00000001
│           └── ...
└── platform-db/
    └── ilygo-<YYYY-MM-DD-HHMM>.sql.gz

Tout le contenu des backups est chiffré (AES-256-GCM) : même si le serveur externe est compromis, les données restent illisibles sans la passphrase du tenant.

Suivi

  • La console affiche, par backup, son état de réplication (pending, replicated, failed) et la date.
  • Le menu Sécurité liste les incidents de réplication (cible injoignable, espace plein…).
  • Endpoint : GET /api/v1/replication-targets/{id}/status → nombre de backups répliqués, dernier run, dernière erreur.

Restauration depuis le site externe (DR)

En cas de perte du site principal :

  1. Remontez une instance ILYGO neuve.
  2. Restaurez la base plateforme depuis platform-db/ilygo-*.sql.gz.
  3. Re-pointez le stockage objet, ou ré-importez les backups/ depuis le serveur externe vers le nouveau MinIO.
  4. Les bridges se reconnectent (mêmes clés API) ; les restaurations redeviennent possibles avec les passphrases des tenants (toujours côté client).

Sécurité

  • La clé privée SSH et l'éventuel mot de passe sont chiffrés au repos (Fernet dérivé de MASTER_SECRET), jamais ré-affichés via l'API.
  • Le contenu répliqué est du ciphertext : le serveur externe est un simple coffre, il ne déchiffre rien.
  • Connexion sortante uniquement depuis ILYGO ; vérification de la clé d'hôte (known_hosts) recommandée.
  • Chaque opération de réplication est tracée dans l'audit log immuable.

Limites & roadmap

  • v1 : réplication full (copie de tous les objets non encore présents). SFTP via asyncssh.
  • Roadmap : réplication incrémentale par diff, bande passante limitée (throttling), multi-cibles avec politiques différenciées, vérification d'intégrité distante (hash des chunks répliqués), rétention indépendante côté externe.