cedricandClaude Opus 5.5 1e667ce8b9 Réplication de nœuds : réseau et DNS par l'API, multipath, chrony, sysctl par SSH
Deuxième mode de réplication, séparé de la réplication cluster vers cluster
(sélecteur « Clusters (API) | Nœuds (fichiers) » en haut de page) : un profil
réplique la configuration système d'un nœud maître vers des nœuds cibles.

- réseau par /nodes/{node}/network (interfaces.new puis rechargement), adresses
  et passerelles propres à chaque nœud, mapping des noms d'interfaces par cible ;
  DNS par /nodes/{node}/dns ; multipath.conf, wwids (ajout seul), chrony,
  sysctl.d, interfaces.d (hors sdn) par SSH
- découverte des nœuds (/cluster/status), accès SSH par nœud (mot de passe du
  cluster ou propre), clé d'hôte épinglée, contrôle du nom du nœud
- dry-run avec diff des fichiers ; apply (admin) avec contrôle SHA-256,
  sauvegarde et restore.sh sur le nœud, écriture atomique, rechargements,
  restauration si un rechargement échoue, timer systemd de restauration si le
  nœud est injoignable après le rechargement du réseau, confirmation tapée
- vue Nœuds et profils, journal séparé par mode, démo avec nœuds simulés

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 20:29:55 +02:00

pve-confsync

Réplication d'une partie de la configuration d'un cluster Proxmox VE 9.x primaire vers une ou plusieurs cibles. Sens unique primaire → cibles. Pas de VM, pas de disques. Tout se fait depuis l'interface web : plus de CLI ni de fichier YAML.

État : phases 0 à 2, version « full web ». Ajout des clusters (un par un ou en lot), choix du primaire, éléments répliqués et cibles choisis dans des menus, écarts par cible, dry-run et réplication par objet, par cible ou global, journal, audit. Testé contre un faux serveur PVE (API + SSH) ; pas encore testé sur un vrai cluster.

Périmètre

Type (kind) Accès Politique par défaut
datacenter-options API /cluster/options tag-style, registered-tags, user-tag-access, next-id en sync, le reste ignoré ; webauthn, u2f, http_proxy jamais répliqués
pve-group API /access/groups sync
pve-role API /access/roles sync (rôles intégrés ignorés)
pve-user API /access/users sync, comptes du royaume pve uniquement
pve-password SSH /etc/pve/priv/shadow.cfg sync (haché recopié tel quel, jamais affiché)
sdn-zone (vlan, vxlan) API /cluster/sdn/zones nodes, bridge, peers, fabric, dns, reversedns, dnszone en override (mapping de la cible) ; le reste en sync
sdn-vnet API /cluster/sdn/vnets sync
sdn-subnet API /cluster/sdn/vnets/{vnet}/subnets sync

Les comptes du realm pve et leurs mots de passe forment un seul élément à répliquer ; ils exigent leurs groupes, que le menu « Éléments répliqués » coche avec eux. Un parent absent de la cible et non répliqué est une erreur bloquante.

Hors périmètre : EVPN (et les zones simple, QinQ, faucet, avec leurs VNets, ignorées avec un avertissement), firewall, storage, pools, ACL, jobs, comptes @pam et autres royaumes.

Détail des routes, schémas et accès, avec les sources : docs/api-pve.md.

Réplication de nœuds

Deuxième mode, séparé du premier dans l'interface (sélecteur « Clusters (API) | Nœuds (fichiers) » en haut de page) : un profil réplique la configuration système d'un nœud maître vers des nœuds cibles, du même cluster ou d'un autre.

Élément Accès Rechargement Particularités
network API /nodes/{node}/network (écrit interfaces.new) PUT /nodes/{node}/network (ifreload -a) cidr, cidr6, gateway, gateway6 en override : chaque nœud garde ses adresses ; mapping des noms d'interfaces par cible (eno1=enp65s0f0, VLAN suivis) ; interface physique absente = bloquant ; options que l'API n'écrit pas (bond-miimon, post-up…) signalées, non répliquées
dns API /nodes/{node}/dns aucun domaine de recherche obligatoire pour l'API
multipath SSH /etc/multipath.conf multipathd reconfigure
wwids SSH /etc/multipath/wwids multipathd reconfigure ajout seul des WWID du maître
chrony SSH /etc/chrony/chrony.conf, sources.d/*.sources systemctl restart chrony
sysctl SSH /etc/sysctl.d/*.conf sysctl --system
interfaces-d SSH /etc/network/interfaces.d/* ifreload -a interfaces.d/sdn (généré par le SDN) jamais touché

Jamais répliqués : /etc/pve, corosync, hostname, machine-id, clés SSH, IQN iSCSI, /etc/frr.

  • Accès : « Découvrir les nœuds » lit /cluster/status de chaque cluster (adresse de chaque nœud). SSH en root sur chaque nœud, avec le mot de passe de son cluster ou le sien ; clé d'hôte épinglée au premier test, qui vérifie aussi que le nœud répond bien sous son nom.
  • Règles : politiques par champ (réseau, DNS), lignes protégées par expression régulière pour les fichiers (gardées de la cible), valeurs propres à une cible (vmbr1 cidr=…).
  • Apply (administrateurs), un nœud après l'autre, arrêt au premier échec : un script SSH vérifie que chaque fichier est celui du dry-run (SHA-256), sauvegarde dans /var/lib/pve-confsync/backup/<date>/ avec un restore.sh, écrit de façon atomique (fichier temporaire puis mv, droits et propriétaire gardés) ; l'API écrit les interfaces et le DNS ; les rechargements suivent. Un rechargement en échec restaure les sauvegardes.
  • Filet réseau : avant de recharger le réseau, un timer systemd est armé sur le nœud (pve-confsync-restore-<date>) : sans nouvelle connexion SSH et API dans les 120 s, le nœud relance restore.sh et ifreload -a de lui-même. Confirmation tapée (nom du nœud, ou du profil en global) dès qu'un plan recharge le réseau. Refus si la cible a déjà des changements réseau en attente (interfaces.new).

Fonctionnement

lecture source -> normalisation -> diff -> plan -> apply -> vérification
  • Lecture : le primaire est lu en configuration SDN appliquée (running=1) ; les cibles en configuration courante. Chaque cluster est refusé si un nœud en ligne n'est pas en PVE 9.x (/nodes/{node}/version).
  • Normalisation : objets JSON génériques → champs canoniques (chaînes, listes triées, property strings à clés triées). Les champs calculés par PVE sont retirés.
  • Politiques : sync (copie du primaire), ignore, override (valeur fournie uniquement par le mapping de la cible). Le mapping d'une cible est prioritaire sur le primaire. Résolution : politique de la cible → politique globale → défaut de l'adaptateur.
  • Plan : création / mise à jour (champs modifiés, champs à supprimer) ; suppression d'objets uniquement avec prune (option explicite), dépendants d'abord. Erreurs bloquantes : champ obligatoire absent à la création (ex. bridge d'une zone VLAN sans mapping), champ immuable modifié (type de zone, vnet d'un subnet).
  • Apply SDN : POST /cluster/sdn/lock → écritures avec lock-token → PUT /cluster/sdn (commit + rechargement réseau, suivi de la tâche). Toute erreur avant le commit déclenche POST /cluster/sdn/rollback. Le verrou est refusé si la cible a déjà des changements SDN en attente : on ne mélange pas nos modifications avec celles de quelqu'un d'autre.
  • Apply comptes : API pour groupes, rôles et comptes ; mots de passe par SSH (script perl sous lock_shadow_config, comme pveum passwd).
  • Vérification : relecture de la cible, nouveau plan qui doit être vide, et aucune zone en error sur les nœuds (/nodes/{node}/sdn/zones).

Démarrage

go run ./cmd/pve-confsync -demo        # démo : faux clusters PVE, http://localhost:8080
docker compose -f docker-compose.demo.yml up -d --build   # même démo en conteneur
docker compose up -d                   # production : données dans le volume /data

La clé AES-256 des mots de passe en base est créée au premier démarrage dans /data/secret.key (à sauvegarder : sans elle, les mots de passe enregistrés sont perdus). En production, on peut la garder hors du volume en secret docker (CONFSYNC_SECRET_KEY_FILE, voir les commentaires de docker-compose.yml). Port publié : CONFSYNC_PORT (.env, 8080 par défaut).

Le mode démo démarre un primaire PVE 9.1, deux cibles avec des écarts (dont un mapping manquant, bloquant) et un cluster PVE 8 refusé. Comptes admin, operateur et lecteur, mot de passe confsync-demo.

Base vide : la page de connexion propose de créer le premier administrateur (12 caractères minimum). Tant que ce n'est pas fait, le premier qui atteint l'instance devient administrateur : ne pas l'exposer avant. Alternative : CONFSYNC_ADMIN_USER et CONFSYNC_ADMIN_PASSWORD / _FILE créent ce compte au démarrage.

Interface web

  • Clusters : ajout d'un cluster (URL https://…:8006, mot de passe root) ou d'une liste (« nom URL » par ligne, même mot de passe), test (API, version 9.x, SSH), rôle primaire / cible / désactivé. Un seul primaire, jamais écrit.
  • Éléments répliqués et Cibles : menus de la vue Écarts ; les cibles décochées sont exclues du global (choix mémorisé dans le navigateur).
  • Écarts : une carte par cluster (version, compteurs à créer / modifier / erreurs / hors primaire), puis les objets de la cible choisie ; le détail d'un objet montre pour chaque champ la politique, la valeur du primaire, le mapping, la valeur actuelle et la valeur après réplication. Les hachés sont masqués (<masqué xxxxxx>).
  • Dry-run / Répliquer par objet (avec ses parents à créer), par cible ou global. Dans le dry-run, chaque élément peut être exclu de l'exécution (« Ne pas répliquer ») ; le dry-run est alors relancé sans lui, sauf ce dont les autres ont besoin. La réplication passe toujours par un dry-run : le serveur recalcule le plan et refuse s'il a changé depuis (empreinte du plan). Avec des changements SDN, il faut taper le nom de la cible (celui du primaire en global). Une seule réplication à la fois.
  • Journal des dry-runs et réplications et Audit (connexions, actions, modifications ; admin).
  • Paramètres : sélection, politiques, mapping par cible, suppression (désactivée par défaut), utilisateurs locaux, compte, apparence.

Rôles : admin (tout), operator (dry-run et réplication), viewer (lecture). Les comptes locaux restent disponibles même avec OIDC. Toutes les variables sont reprises, commentées, dans .env.example (cp .env.example .env).

Variable Défaut Rôle
LISTEN :8080 adresse d'écoute
DATA_DIR /data base SQLite, secret.key, session.key
CONFSYNC_SECRET_KEY / _FILE DATA_DIR/secret.key (créée) clé AES-256 (hex ou base64) des mots de passe en base
CONFSYNC_ADMIN_USER, CONFSYNC_ADMIN_PASSWORD / _FILE admin, vide premier administrateur sans passer par la page web
AUTH_LOCAL true comptes locaux
OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET / _FILE, OIDC_REDIRECT_URL OIDC (code + PKCE) ; OIDC_REDIRECT_URL complète, par ex. https://confsync.example.org/auth/callback
OIDC_SCOPES, OIDC_GROUPS_CLAIM openid profile email, groups ajouter le scope qui fournit les groupes chez l'IdP
OIDC_ADMIN_GROUP, OIDC_OPERATOR_GROUP, OIDC_DEFAULT_ROLE viewer rôle d'après les groupes
SESSION_TTL 12h durée des sessions
CONFSYNC_DEMO false mode démo

Sécurité

  • Accès PVE en root@pam (API par ticket, SSH avec le même mot de passe) : nécessaire pour les mots de passe du royaume pve, absents de l'API. Le compte root ne doit pas avoir de TFA (refusé). Le primaire n'est jamais écrit.
  • Épinglage au premier test : empreinte SHA-256 du certificat TLS (mode par défaut, adapté aux certificats auto-signés PVE) et clé d'hôte SSH. Une clé SSH différente ensuite bloque l'accès ; l'accepter demande une action explicite (« faire confiance à la nouvelle clé »). Changer l'URL ou le nœud SSH oublie les empreintes. Autres modes TLS : CA (PEM), CA système, insecure (labo).
  • Mots de passe root chiffrés en base (AES-256-GCM, clé hors base), jamais renvoyés au navigateur ni journalisés. Les hachés du shadow sont masqués dans l'UI, le journal et les réponses JSON.
  • Interface : mots de passe PBKDF2-SHA256 (600 000 itérations), cookie de session signé (HttpOnly, SameSite=Lax, Secure derrière HTTPS) invalidé au changement de mot de passe, en-tête X-Confsync exigé sur les écritures (CSRF), CSP sans script ni style en ligne.

Arborescence

cmd/pve-confsync/      serveur web (seul point d'entrée)
internal/service/      analyse pour l'UI : lecture parallèle, plans, lignes de diff par champ
internal/store/        SQLite : clusters (mots de passe chiffrés, rôles, empreintes), utilisateurs, journal, audit
internal/server/       HTTP : auth locale + OIDC, rôles, API, jobs de synchronisation
internal/demo/         faux clusters du mode démo
web/                   UI embarquée (index.html, app.js, style.css, login.html)
internal/pve/          client API : ticket root@pam, TLS épinglé, form-encoding, version, tâches
internal/sshx/         SSH root, clé d'hôte épinglée
internal/pve/pvetest/  faux serveur PVE 9.x (API + SSH) pour les tests
internal/model/        objets génériques, valeurs canoniques
internal/adapter/      un adaptateur par type : routes, champs calculés, locaux, immuables
internal/config/       configuration en mémoire : clusters, politiques, mappings, sélection
internal/engine/       lecture, plan, apply (verrou SDN, rollback), vérification
internal/nodesync/     réplication de nœuds : réseau et DNS (API), fichiers (SSH), diff, sauvegarde, restauration
docs/api-pve.md        routes et schémas vérifiés dans les sources PVE

Tests : go test ./... (bout en bout contre le faux serveur : réplication SDN et comptes avec mots de passe par SSH, mapping manquant, parent absent, rollback sur erreur, refus de PVE 8, prune et ordre de suppression ; sélection et dépendances ; chiffrement, migration de schéma, premier administrateur ; serveur depuis une base vide : création de l'admin, ajout des clusters, épinglage, primaire, réplication globale, primaire jamais écrit).

Phasage

  1. Moteur export / diff / apply (options datacenter, tags, SDN) ; la CLI a été retirée.
  2. UI lecture seule : clusters, diff visuel par cible. Ergonomie reprise de Logstream (web/index.html, style.css, app.js, login.html, auth locale + OIDC, audit) ; SQLite (clusters, profils, mappings, historique, audit), secrets chiffrés.
  3. UI d'action : Dry-run / Répliquer (par objet, par cible, global), journal ; gestion des clusters en web, primaire / cibles, comptes et mots de passe du royaume pve. ← ici
  4. Planification, détection de dérive en lecture seule, notifications.

Reste à faire

  • Valider sur un vrai cluster : écriture du shadow par SSH, forme de /access/users?full=1, forme réelle des réponses (nodes, dhcp-range, booléens), comportement de PUT /cluster/options avec delete.
  • Exploiter GET /cluster/sdn/dry-run (diff frr / interfaces, PVE ≥ 9.2 environ) pour enrichir le dry-run SDN.
  • Rechargement en échec après commit : aujourd'hui signalé, pas d'annulation automatique (la configuration appliquée est déjà la nouvelle).
S
Description
Synchronisation de configuration Proxmox VE 9.x (options datacenter, SDN VLAN/VXLAN) d'un cluster référence vers des clusters clients, via l'API PVE
Readme
736 KiB
0 Stars 0 Watchers 0 Forks
Languages
Go 63.5%
JavaScript 23.1%
CSS 7.2%
HTML 6.1%
Dockerfile 0.1%