Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015a8P6WMRPeqdpd5VCh6jAy
187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# pve-confsync
|
|
|
|
Synchronisation d'une partie de la configuration d'un cluster Proxmox VE 9.x
|
|
« référence » vers N clusters « clients », via l'API PVE et des tokens API.
|
|
Sens unique référence → clients. Pas de VM, pas de disques.
|
|
|
|
**État : phases 0 à 2.** CLI (export, diff et apply en YAML) et interface web
|
|
(écarts par client, dry-run et synchronisation par objet, par client ou global, journal,
|
|
audit) pour les options datacenter et le SDN (zones VLAN/VXLAN, VNets, subnets).
|
|
Testé contre un faux serveur PVE ; **pas encore testé sur les clusters de labo.**
|
|
|
|
## Périmètre
|
|
|
|
| Type (`kind`) | API | Politique par défaut |
|
|
|---|---|---|
|
|
| `datacenter-options` | `/cluster/options` | `tag-style`, `registered-tags`, `user-tag-access`, `next-id` en `sync`, le reste ignoré ; `webauthn`, `u2f`, `http_proxy` jamais synchronisés |
|
|
| `sdn-zone` (vlan, vxlan) | `/cluster/sdn/zones` | `nodes`, `bridge`, `peers`, `fabric`, `dns`, `reversedns`, `dnszone` en `override` (mapping client) ; le reste en `sync` |
|
|
| `sdn-vnet` | `/cluster/sdn/vnets` | `sync` |
|
|
| `sdn-subnet` | `/cluster/sdn/vnets/{vnet}/subnets` | `sync` |
|
|
|
|
Hors périmètre v1 : EVPN (et les zones simple, QinQ, faucet, avec leurs VNets, ignorées
|
|
avec un avertissement), firewall, storage, pools/ACL, jobs, secrets.
|
|
|
|
Détail des routes, schémas et droits, avec les sources : [docs/api-pve.md](docs/api-pve.md).
|
|
|
|
## Fonctionnement
|
|
|
|
```
|
|
lecture source -> normalisation -> diff -> plan -> apply -> vérification
|
|
```
|
|
|
|
- **Lecture** : la référence est lue en configuration SDN *appliquée* (`running=1`) ; les
|
|
clients 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 de la référence), `ignore`, `override` (valeur fournie
|
|
uniquement par le `mapping` du client). Le mapping d'un client est prioritaire sur la
|
|
référence. Résolution : politique du client → 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 le client a déjà des changements SDN
|
|
en attente : on ne mélange pas nos modifications avec celles de quelqu'un d'autre.
|
|
- **Vérification** : relecture du client, nouveau plan qui doit être vide, et aucune zone en
|
|
`error` sur les nœuds (`/nodes/{node}/sdn/zones`).
|
|
|
|
## Utilisation
|
|
|
|
```sh
|
|
go build -o pve-confsync ./cmd/pve-confsync
|
|
cp examples/confsync.example.yaml confsync.yaml # puis adapter
|
|
|
|
./pve-confsync check # accès, version 9.x, droits des tokens
|
|
./pve-confsync export -o ref.yaml # état normalisé de la référence (versionnable dans Git)
|
|
./pve-confsync diff # écarts par client ; code retour 2 s'il y en a
|
|
./pve-confsync diff -format json
|
|
./pve-confsync apply # dry-run (défaut) : affiche le plan, n'écrit rien
|
|
./pve-confsync apply -client site-a -confirm # applique ; SDN : saisir le nom du client
|
|
./pve-confsync apply -source ref.yaml -confirm -yes # depuis un export Git, sans question
|
|
```
|
|
|
|
Avec docker compose (images et utilisateur non privilégié alignés sur Logstream) :
|
|
|
|
```sh
|
|
mkdir -p secrets && printf '%s' 'xxxxxxxx-...' > secrets/pve_ref
|
|
chmod 600 secrets/* && sudo chown 10001 secrets/* # lisibles par l'utilisateur du conteneur uniquement
|
|
docker compose run --rm confsync check
|
|
docker compose run --rm confsync apply -confirm
|
|
```
|
|
|
|
## Interface web
|
|
|
|
```sh
|
|
go run ./cmd/pve-confsync serve -demo # démo : faux clusters PVE, http://localhost:8080
|
|
docker compose up -d confsync-web # production : données dans le volume /data
|
|
```
|
|
|
|
Le mode démo démarre une référence PVE 9.1, deux clients avec des écarts (dont un mapping
|
|
manquant, bloquant) et un cluster PVE 8 refusé. Comptes `admin`, `operateur` et `lecteur`,
|
|
mot de passe `confsync-demo`.
|
|
|
|
- **Écarts** : une carte par cluster (version, compteurs à créer / modifier / erreurs /
|
|
hors référence), puis les objets du client choisi ; le détail d'un objet montre pour
|
|
chaque champ la politique, la valeur de référence, le mapping, la valeur actuelle et
|
|
la valeur après synchro.
|
|
- **Dry-run / Synchroniser** par objet (avec ses dépendances : zone et vnet parents à
|
|
créer), par client ou global. La synchronisation 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 du client (celui de la référence en global).
|
|
Une seule synchronisation à la fois, suivie étape par étape.
|
|
- **Journal** des dry-runs et synchronisations (plans, étapes, résultats) et **Audit**
|
|
(connexions, actions, modifications des paramètres ; admin).
|
|
- **Paramètres** : clusters (test d'accès et des droits, TLS, mapping et politiques par
|
|
client), types synchronisés, sélection, politiques, suppression (désactivée par défaut),
|
|
utilisateurs locaux, import / export YAML (format de la CLI), compte, apparence.
|
|
|
|
Rôles : `admin` (tout), `operator` (dry-run et synchronisation), `viewer` (lecture).
|
|
Les comptes locaux restent disponibles même avec OIDC.
|
|
|
|
| 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 secrets en base |
|
|
| `CONFSYNC_ADMIN_USER`, `CONFSYNC_ADMIN_PASSWORD` / `_FILE` | `admin`, aléatoire (journalisé une fois) | premier administrateur, base vide |
|
|
| `CONFSYNC_IMPORT` | | YAML de la CLI importé si la base n'a aucun cluster |
|
|
| `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é
|
|
|
|
- Secrets des tokens : jamais dans le YAML (`token_secret` en clair est refusé) ; lus depuis
|
|
une variable d'environnement (`token_secret_env`) ou un fichier (`token_secret_file`,
|
|
secret docker). Ils ne sont ni affichés ni journalisés. Dans l'interface, ils sont chiffrés en base
|
|
(AES-256-GCM, clé hors base) et jamais renvoyés au navigateur ; l'export YAML les remplace
|
|
par `token_secret_env`.
|
|
- 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.
|
|
- TLS : `fingerprint` (pinning SHA-256 du certificat, adapté aux certificats auto-signés PVE),
|
|
`ca_file`, ou `insecure: true` réservé au labo.
|
|
- Tokens (privilege separation activée, l'ACL doit être posée sur l'utilisateur et le token) :
|
|
|
|
```sh
|
|
# référence : lecture seule
|
|
pveum role add ConfSyncRO --privs "Sys.Audit SDN.Audit"
|
|
pveum user add confsync@pve
|
|
pveum user token add confsync@pve ro --privsep 1
|
|
pveum acl modify / --users confsync@pve --roles ConfSyncRO
|
|
pveum acl modify / --tokens 'confsync@pve!ro' --roles ConfSyncRO
|
|
|
|
# clients : Sys.Modify uniquement si les options datacenter sont synchronisées
|
|
pveum role add ConfSyncRW --privs "Sys.Audit Sys.Modify SDN.Audit SDN.Allocate"
|
|
```
|
|
|
|
`check` signale un token de référence capable d'écrire et un token client à qui il manque
|
|
un droit.
|
|
|
|
## Arborescence
|
|
|
|
```
|
|
cmd/pve-confsync/ CLI (check, export, diff, apply, serve)
|
|
internal/service/ analyse pour l'UI : lecture parallèle, plans, lignes de diff par champ
|
|
internal/store/ SQLite : clusters (secrets chiffrés), paramètres, 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 : token, TLS, form-encoding, version, tâches
|
|
internal/pve/pvetest/ faux serveur PVE 9.x pour les tests
|
|
internal/model/ objets génériques, snapshot YAML, valeurs canoniques
|
|
internal/adapter/ un adaptateur par type : routes, champs calculés, locaux, immuables
|
|
internal/config/ configuration YAML : 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 : synchro complète, mapping
|
|
manquant, rollback sur erreur, refus de PVE 8, prune et ordre de suppression, forme
|
|
canonique des options datacenter, CLI dry-run/apply/export ; sélection d'objets et dépendances ; chiffrement, utilisateurs ;
|
|
serveur de bout en bout : rôles, CSRF, dry-run, empreinte périmée, confirmation SDN, apply).
|
|
|
|
## Phasage
|
|
|
|
0. **CLI** export / diff / apply YAML (options datacenter, tags, SDN).
|
|
1. UI lecture seule : clusters, diff visuel par client. 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.
|
|
2. UI d'action : Dry-run / Synchroniser (par objet, par client, global), journal des exécutions. ← ici
|
|
3. Planification, détection de dérive en lecture seule, notifications.
|
|
|
|
## Reste à faire
|
|
|
|
- Valider sur les deux clusters de labo : 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).
|