Interface web : écarts, dry-run et synchronisation (phases 1 et 2) #1

Merged
claude Bot merged 8 commits from phase-1-ui into main 2026-10-13 16:47:35 +02:00
3 changed files with 108 additions and 16 deletions
Showing only changes of commit 5561bcfcf9 - Show all commits

No files matched your search

+6 -2
View File
@@ -7,6 +7,7 @@ COPY go.mod go.sum ./
RUN go mod download RUN go mod download
COPY cmd ./cmd COPY cmd ./cmd
COPY internal ./internal COPY internal ./internal
COPY web ./web
ARG VERSION=dev ARG VERSION=dev
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" \ RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" \
-o /out/pve-confsync ./cmd/pve-confsync -o /out/pve-confsync ./cmd/pve-confsync
@@ -20,6 +21,9 @@ COPY --from=build /out/pve-confsync /usr/local/bin/pve-confsync
USER confsync USER confsync
WORKDIR /data WORKDIR /data
VOLUME /data VOLUME /data
ENV CONFSYNC_CONFIG=/config/confsync.yaml ENV CONFSYNC_CONFIG=/config/confsync.yaml DATA_DIR=/data LISTEN=:8080
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:8080/healthz || exit 1
ENTRYPOINT ["/usr/local/bin/pve-confsync"] ENTRYPOINT ["/usr/local/bin/pve-confsync"]
CMD ["check"] # interface web ; la CLI reste disponible : docker compose run --rm confsync diff
CMD ["serve"]
+64 -10
View File
@@ -4,9 +4,10 @@ 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. « 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. Sens unique référence → clients. Pas de VM, pas de disques.
**État : phase 0 (CLI).** Export, diff et apply en YAML pour les options datacenter **État : phases 0 à 2.** CLI (export, diff et apply en YAML) et interface web
et le SDN (zones VLAN/VXLAN, VNets, subnets). Testé contre un faux serveur PVE ; (écarts par client, dry-run et synchronisation par objet, par client ou global, journal,
**pas encore testé sur les clusters de labo.** 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 ## Périmètre
@@ -71,12 +72,59 @@ docker compose run --rm confsync check
docker compose run --rm confsync apply -confirm 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é ## Sécurité
- Secrets des tokens : jamais dans le YAML (`token_secret` en clair est refusé) ; lus depuis - 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`, une variable d'environnement (`token_secret_env`) ou un fichier (`token_secret_file`,
secret docker). Ils ne sont ni affichés ni journalisés. Le chiffrement au repos arrive avec secret docker). Ils ne sont ni affichés ni journalisés. Dans l'interface, ils sont chiffrés en base
le stockage SQLite (phase 1). (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), - TLS : `fingerprint` (pinning SHA-256 du certificat, adapté aux certificats auto-signés PVE),
`ca_file`, ou `insecure: true` réservé au labo. `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) : - Tokens (privilege separation activée, l'ACL doit être posée sur l'utilisateur et le token) :
@@ -99,7 +147,12 @@ un droit.
## Arborescence ## Arborescence
``` ```
cmd/pve-confsync/ CLI (check, export, diff, apply) 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/ client API : token, TLS, form-encoding, version, tâches
internal/pve/pvetest/ faux serveur PVE 9.x pour les tests internal/pve/pvetest/ faux serveur PVE 9.x pour les tests
internal/model/ objets génériques, snapshot YAML, valeurs canoniques internal/model/ objets génériques, snapshot YAML, valeurs canoniques
@@ -111,18 +164,19 @@ 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 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 manquant, rollback sur erreur, refus de PVE 8, prune et ordre de suppression, forme
canonique des options datacenter, CLI dry-run/apply/export). 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 ## Phasage
0. **CLI** export / diff / apply YAML (options datacenter, tags, SDN). ← ici 0. **CLI** export / diff / apply YAML (options datacenter, tags, SDN).
1. UI lecture seule : clusters, diff visuel par client. Ergonomie reprise de Logstream 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) ; (`web/index.html`, `style.css`, `app.js`, `login.html`, auth locale + OIDC, audit) ;
SQLite (clusters, profils, mappings, historique, audit), secrets chiffrés. 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. 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. 3. Planification, détection de dérive en lecture seule, notifications.
## Reste à faire avant la phase 1 ## Reste à faire
- Valider sur les deux clusters de labo : forme réelle des réponses (`nodes`, `dhcp-range`, - 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`. booléens), comportement de `PUT /cluster/options` avec `delete`.
+38 -4
View File
@@ -1,14 +1,44 @@
# Phase 0 : outil en ligne de commande, lancé à la demande : # Interface web (phases 1-2) :
# docker compose up -d confsync-web # http://<hôte>:8080 ; mot de passe admin dans les logs au 1er démarrage
# CLI de la phase 0, lancée à la demande avec confsync.yaml :
# docker compose run --rm confsync check # docker compose run --rm confsync check
# docker compose run --rm confsync diff # docker compose run --rm confsync diff
# docker compose run --rm confsync export -o /data/ref.yaml # docker compose run --rm confsync export -o /data/ref.yaml
# docker compose run --rm confsync apply # dry-run # docker compose run --rm confsync apply # dry-run
# docker compose run --rm confsync apply -confirm # application (confirmation SDN interactive) # docker compose run --rm confsync apply -confirm # application (confirmation SDN interactive)
services: services:
confsync: confsync-web:
build: . build: .
image: pve-confsync:dev
container_name: pve-confsync-web
command: ["serve"]
restart: unless-stopped
ports:
- "8080:8080" # ou derrière Traefik (HTTPS : cookies Secure)
environment:
TZ: ${TZ:-Europe/Paris}
CONFSYNC_SECRET_KEY_FILE: /run/secrets/confsync_key # clé des secrets en base, hors volume /data
# CONFSYNC_ADMIN_PASSWORD_FILE: /run/secrets/confsync_admin
# CONFSYNC_IMPORT: /config/confsync.yaml # import initial de la config CLI (base vide)
# OIDC_ISSUER: https://auth.example.org/application/o/confsync/
# OIDC_CLIENT_ID: confsync
# OIDC_CLIENT_SECRET_FILE: /run/secrets/oidc_client
# OIDC_REDIRECT_URL: https://confsync.example.org/auth/callback
# OIDC_ADMIN_GROUP: confsync-admins
# OIDC_OPERATOR_GROUP: confsync-operators
env_file:
- path: .env
required: false
volumes:
- confsync-data:/data # SQLite, session.key
secrets:
- confsync_key
confsync:
image: pve-confsync:dev image: pve-confsync:dev
container_name: pve-confsync container_name: pve-confsync
profiles: ["cli"] # pas démarré par "up" ; "run" l'active
command: ["check"]
environment: environment:
TZ: ${TZ:-Europe/Paris} TZ: ${TZ:-Europe/Paris}
PVE_SITE_B_SECRET: ${PVE_SITE_B_SECRET:-} # secrets par variable (voir token_secret_env) PVE_SITE_B_SECRET: ${PVE_SITE_B_SECRET:-} # secrets par variable (voir token_secret_env)
@@ -17,15 +47,18 @@ services:
required: false required: false
volumes: volumes:
- ./confsync.yaml:/config/confsync.yaml:ro - ./confsync.yaml:/config/confsync.yaml:ro
- confsync-data:/data # exports YAML, plus tard SQLite (phase 1) - confsync-cli:/data # exports YAML
secrets: secrets:
- pve_ref - pve_ref
- pve_site_a - pve_site_a
stdin_open: true stdin_open: true
tty: true tty: true
# Secrets des tokens API sous forme de fichiers (chmod 600), montés dans /run/secrets/<nom> # Fichiers chmod 600, propriétaire 10001, montés dans /run/secrets/<nom>.
# confsync_key : openssl rand -hex 32 > secrets/confsync_key
secrets: secrets:
confsync_key:
file: ./secrets/confsync_key
pve_ref: pve_ref:
file: ./secrets/pve_ref file: ./secrets/pve_ref
pve_site_a: pve_site_a:
@@ -33,3 +66,4 @@ secrets:
volumes: volumes:
confsync-data: confsync-data:
confsync-cli: