Files
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

142 lines
12 KiB
Markdown

# API Proxmox VE 9.x utilisée par pve-confsync
Vérifiée le 2026-10-13 (comptes : 2026-10-23) dans les sources Proxmox (miroirs GitHub officiels), faute d'accès
à l'API viewer (https://pve.proxmox.com/pve-docs/api-viewer/) depuis l'environnement de
développement. Versions lues : pve-manager 9.2.21, libpve-network-perl 1.6.7, pve-cluster 9.1.6.
**À recouper avec l'API viewer et les deux clusters de labo avant la phase 1.**
Toutes les routes sont relatives à `/api2/json`. Authentification par ticket du compte
`root@pam` : `POST /access/ticket` (`username`, `password`) renvoie `ticket` et
`CSRFPreventionToken` ([AccessControl.pm L198](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/API2/AccessControl.pm#L198)) ; le ticket
est envoyé dans le cookie `PVEAuthCookie`, le jeton CSRF dans l'en-tête
`CSRFPreventionToken` sur les écritures. Durée de vie du ticket : 2 h
([AccessControl.pm L58](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/AccessControl.pm#L58)) ; pve-confsync se reconnecte après 90 min
ou sur un 401. Un compte root avec TFA renvoie `NeedTFA`
([L167](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/API2/AccessControl.pm#L167)) : refusé. Écritures en
`application/x-www-form-urlencoded` (tableaux = paramètre répété), suppression de champs
via le paramètre `delete=a,b`.
## Version et cluster
| Méthode | Chemin | Usage | Droit |
|---|---|---|---|
| GET | `/cluster/status` | nœuds en ligne | Sys.Audit sur / |
| GET | `/nodes/{node}/version` | `version` (9.2.21), `release` (9.2) : refus si majeur ≠ 9 | — |
| GET | `/version` | repli pour un nœud isolé ([API2.pm](https://github.com/proxmox/pve-manager/blob/9c3f5e5258f73f31e273493232a79d615fd9e072/PVE/API2.pm#L86)) | — |
| GET | `/nodes/{node}/tasks/{upid}/status` | suivi de la tâche de rechargement SDN | — |
## Options datacenter (`datacenter.cfg`)
| Méthode | Chemin | Droit |
|---|---|---|
| GET | `/cluster/options` ([Cluster.pm](https://github.com/proxmox/pve-manager/blob/9c3f5e5258f73f31e273493232a79d615fd9e072/PVE/API2/Cluster.pm#L775)) | Sys.Audit sur / (sinon seuls console et tag-style) |
| PUT | `/cluster/options` ([Cluster.pm](https://github.com/proxmox/pve-manager/blob/9c3f5e5258f73f31e273493232a79d615fd9e072/PVE/API2/Cluster.pm#L830)) | Sys.Modify sur / |
Points d'attention ([DataCenterConfig.pm](https://github.com/proxmox/pve-cluster/blob/7091d92e594952dba65c1e57568b3d7cc244e960/src/PVE/DataCenterConfig.pm#L560)) :
- le GET renvoie des valeurs **déjà analysées** : `tag-style`, `next-id`, `user-tag-access`,
`migration`, `ha`, `crs`, `notify`, `webauthn`, `u2f` sous forme d'objets,
`registered-tags` et `user-tag-access.user-allow-list` sous forme de tableaux ;
le PUT attend des *property strings* (`shape=full,color-map=a:ff0000`, listes séparées par `;`).
L'adaptateur stocke la forme canonique « property string, clés triées ».
- le GET ajoute toujours `description` (vide possible), `mac_prefix` (`BC:24:11` par défaut,
[L11](https://github.com/proxmox/pve-cluster/blob/7091d92e594952dba65c1e57568b3d7cc244e960/src/PVE/DataCenterConfig.pm#L11)) et `allowed-tags` (lecture seule) : retirés à la normalisation.
- formats : `tag-style` [L286](https://github.com/proxmox/pve-cluster/blob/7091d92e594952dba65c1e57568b3d7cc244e960/src/PVE/DataCenterConfig.pm#L286),
`user-tag-access` [L321](https://github.com/proxmox/pve-cluster/blob/7091d92e594952dba65c1e57568b3d7cc244e960/src/PVE/DataCenterConfig.pm#L321),
`next-id` [L203](https://github.com/proxmox/pve-cluster/blob/7091d92e594952dba65c1e57568b3d7cc244e960/src/PVE/DataCenterConfig.pm#L203).
## SDN ([SDN.pm](https://github.com/proxmox/pve-network/blob/ce388c5eec2376576d62a6be708439a64cf7fb62/src/PVE/API2/Network/SDN.pm))
| Méthode | Chemin | Usage | Droit |
|---|---|---|---|
| POST | `/cluster/sdn/lock` | verrou global, renvoie le `lock-token` ; refusé s'il existe des changements en attente (sauf `allow-pending=1`) | SDN.Allocate sur /sdn |
| DELETE | `/cluster/sdn/lock` | libère le verrou (`lock-token` ou `force=1`) | SDN.Allocate |
| POST | `/cluster/sdn/rollback` | annule les changements en attente (`lock-token`, `release-lock`) | SDN.Allocate |
| PUT | `/cluster/sdn` | commit + rechargement réseau de tous les nœuds, renvoie un UPID (`lock-token`, `release-lock`) | SDN.Allocate |
| GET | `/cluster/sdn/dry-run?node=` | diff frr / interfaces.d/sdn (libpve-network-perl ≥ 1.3.0, mars 2026) : non utilisé en phase 0 | Sys.Audit sur /nodes/{node} |
| GET | `/nodes/{node}/sdn/zones` | statut des zones par nœud (`available`, `pending`, `error`) | SDN.Audit |
Le verrou et le paramètre `lock-token` sur toutes les écritures existent depuis
libpve-network-perl 1.1.3 (juillet 2025, avant PVE 9.0) : disponibles sur tout PVE 9.x.
### Zones, VNets, subnets
| Objet | Liste / création | Élément | Notes |
|---|---|---|---|
| zone | `/cluster/sdn/zones` | `/cluster/sdn/zones/{zone}` | GET d'un élément = SDN.**Allocate** : on lit donc la liste (SDN.Audit suffit) |
| vnet | `/cluster/sdn/vnets` | `/cluster/sdn/vnets/{vnet}` | |
| subnet | `/cluster/sdn/vnets/{vnet}/subnets` | `.../subnets/{zone}-{ip}-{masque}` | création : `subnet=<cidr>`, `type=subnet` ; id calculé ([SubnetPlugin.pm](https://github.com/proxmox/pve-network/blob/ce388c5eec2376576d62a6be708439a64cf7fb62/src/PVE/Network/SDN/SubnetPlugin.pm#L42)) |
Les GET acceptent `running=1` (configuration appliquée) ou `pending=1` ; sans paramètre,
la configuration courante, en attente comprise. La référence est lue en `running=1`,
les clients sans paramètre.
Champs (schémas : [VlanPlugin.pm](https://github.com/proxmox/pve-network/blob/ce388c5eec2376576d62a6be708439a64cf7fb62/src/PVE/Network/SDN/Zones/VlanPlugin.pm),
[VxlanPlugin.pm](https://github.com/proxmox/pve-network/blob/ce388c5eec2376576d62a6be708439a64cf7fb62/src/PVE/Network/SDN/Zones/VxlanPlugin.pm),
[VnetPlugin.pm](https://github.com/proxmox/pve-network/blob/ce388c5eec2376576d62a6be708439a64cf7fb62/src/PVE/Network/SDN/VnetPlugin.pm),
[SubnetPlugin.pm](https://github.com/proxmox/pve-network/blob/ce388c5eec2376576d62a6be708439a64cf7fb62/src/PVE/Network/SDN/SubnetPlugin.pm)) :
- zone vlan : `bridge` (obligatoire), `bridge-disable-mac-learning`, `nodes`, `mtu`, `dns`, `reversedns`, `dnszone`, `ipam`
- zone vxlan : `peers`, `vxlan-port` (4789), `fabric`, `nodes`, `mtu`, `dns`, `reversedns`, `dnszone`, `ipam`
- vnet : `zone` (obligatoire), `tag`, `alias`, `vlanaware`, `isolate-ports`
- subnet : `vnet`, `gateway`, `snat`, `dnszoneprefix`, `dhcp-range` (tableau), `dhcp-dns-server`
- calculés par PVE, ignorés : `digest`, `state`, `pending`, et pour les subnets `cidr`, `network`, `mask`, `zone`
## Comptes du royaume pve (vérifié le 2026-10-23, pve-access-control dff26b45, pve-cluster 86d4bb9e)
| Objet | Liste / création | Élément | Notes |
|---|---|---|---|
| groupe | `/access/groups` ([Group.pm L26](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/API2/Group.pm#L26)) | `/access/groups/{groupid}` | champ `comment` ; `users` calculé, ignoré |
| rôle | `/access/roles` ([Role.pm L31](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/API2/Role.pm#L31)) | `/access/roles/{roleid}` (PUT) | rôles `special` (intégrés) ignorés, non modifiables ([L146](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/API2/Role.pm#L146)) ; `privs` trié |
| utilisateur | `/access/users?full=1` ([User.pm L156](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/API2/User.pm#L156)) | `/access/users/{userid}` | seuls les `@pve` ; `tokens`, `realm-type`, champs de verrou TFA ignorés ; `groups` liste triée ; `password` jamais envoyé par l'API ; `root@pam` jamais supprimé |
**Mots de passe.** L'API ne renvoie pas les hachés : ils sont dans
`/etc/pve/priv/shadow.cfg` (lignes `user:hash:`, utilisateur sans royaume,
[Auth/PVE.pm L13-L50](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/Auth/PVE.pm#L13)). Lecture et écriture par SSH en root :
- lecture : `cat /etc/pve/priv/shadow.cfg` ;
- écriture : script perl sous `PVE::Auth::PVE::lock_shadow_config`
([L51](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/Auth/PVE.pm#L51)) avec `cfs_read_file` / `cfs_write_file`, comme
`store_password` ([L95-L98](https://github.com/proxmox/pve-access-control/blob/dff26b458651b4e1141ff0a41a296d2904b16228/src/PVE/Auth/PVE.pm#L95)) : le haché est recopié tel quel, sans
le recalculer. `cfs_read_file` sans version relit toujours le fichier
([Cluster.pm](https://github.com/proxmox/pve-cluster/blob/86d4bb9efc0956311c01dbf59a38fa0583b60a19/src/PVE/Cluster.pm)).
**À valider sur un vrai cluster** : script d'écriture du shadow, forme réelle de
`/access/users?full=1`, mise à jour d'un utilisateur avec champs vidés.
## Réplication de nœuds (vérifié le 2026-10-23, pve-manager f643ce4b, pve-common master)
| Méthode | Chemin | Usage |
|---|---|---|
| GET | `/nodes/{node}/network` ([Network.pm](https://github.com/proxmox/pve-manager/blob/f643ce4bceb99e8b23094cd04075405281eb3f27/PVE/API2/Network.pm)) | interfaces, lues dans `interfaces.new` s'il existe ; `lo` retiré |
| POST | `/nodes/{node}/network` | création (`iface`, `type` + champs) dans `interfaces.new` |
| PUT | `/nodes/{node}/network/{iface}` | mise à jour (`type` obligatoire, `delete=`) |
| DELETE | `/nodes/{node}/network/{iface}` | suppression (prune) |
| PUT | `/nodes/{node}/network` | renomme `interfaces.new` puis `ifreload -a` dans une tâche (UPID) |
| DELETE | `/nodes/{node}/network` | annule les changements en attente (supprime `interfaces.new`) |
| GET / PUT | `/nodes/{node}/dns` ([Nodes.pm](https://github.com/proxmox/pve-manager/blob/f643ce4bceb99e8b23094cd04075405281eb3f27/PVE/API2/Nodes.pm)) | `search` (obligatoire au PUT), `dns1` à `dns3` ; le PUT réécrit tous les serveurs |
Points d'attention :
- champs écrits (`$confdesc`) : `type`, `comments`, `comments6`, `autostart`, `bridge_vlan_aware`,
`bridge_vids`, `bridge_ports`, `ovs_*`, `slaves`, `bond_mode`, `bond-primary`,
`bond_xmit_hash_policy`, `vlan-raw-device`, `vlan-id`, `gateway`, `gateway6`, `cidr`, `cidr6`, `mtu`.
Le GET renvoie aussi `address`/`netmask` (forme canonique : `cidr`), `bridge_stp`,
`bridge_fd`, `bond_miimon` (écrits par défaut par PVE), `options` (lignes inconnues de
l'API, jamais écrites), `active`, `exists`, `families`, `method`, `priority` : non répliqués.
- le PUT d'une interface recalcule `method` et `families` à partir des paramètres reçus :
sans `address`/`cidr`, une interface statique devient `manual`. pve-confsync envoie donc
toute la configuration de l'interface à chaque mise à jour.
- `interfaces.d/*` n'est pas lu par l'API ([Interfaces.pm](https://github.com/proxmox/pve-common/blob/master/src/PVE/Network/Interfaces.pm)) : répliqué en fichier par SSH.
Scripts SSH (première ligne `# confsync:<étape>`) : lecture (`stat`, `base64 -w0`),
préparation (contrôle SHA-256, `cp -p` vers la sauvegarde, `restore.sh`, écriture par
`mktemp` + `mv`), restauration, `systemd-run --on-active=120s` / `systemctl stop <unité>.timer`,
rechargements. Le script de préparation est testé tel quel avec le shell local
(`internal/nodesync/script_test.go`) ; **le reste est à valider sur les clusters de labo.**
## Droits
Accès en `root@pam` (API + SSH, même mot de passe), sans TFA. La référence (primaire)
n'est jamais écrite. Le certificat TLS et la clé d'hôte SSH sont épinglés au premier test.