2026-10-23 17:43:20 +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.

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
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%