Edu-Kit — Onboarding d’un Proxmox client

1. Vue d’ensemble

Ce document décrit les appels API et commandes SSH nécessaires pour intégrer un Proxmox VE client dans la plateforme Edu-Kit. L’onboarding configure les accès et permissions requis par VmService, et établit le contrat entre l’infrastructure (OPS) et le backend.

L’ensemble des étapes est automatisé par le script onboard-proxmox.sh. Ce document sert de référence pour comprendre ce que le script fait et pour le reproduire manuellement si besoin.

Architecture

┌──────────────────┐         ┌──────────────────────────────────────────────┐
│     Edu-Kit      │         │          Proxmox Client (PVE 8.x)            │
│                  │         │                                              │
│  - VmService     │  HTTPS  │  edukit-api@pve                             │
│    (clones, VMs) │◄───────►│    └── Token vm-service                     │
│                  │         │    └── Rôle EduKitRole (VM + SDN)           │
│  - OPS (admin)   │  SSH    │                                              │
│                  │◄───────►│  edukit-deploy (snippets cloud-init)        │
└──────────────────┘         │    └── /var/lib/vz/snippets/                │
                             │                                              │
                             │  Templates cloud-init (VMID 9xxx)           │
                             │    └── Debian 12, Ubuntu 24.04…             │
                             └──────────────────────────────────────────────┘

Authentification

Contexte Mécanisme Header
Onboarding (script OPS) Ticket root@pam, valide 2h Cookie PVEAuthCookie={ticket} + CSRFPreventionToken: {csrf}
VmService (runtime) Token API, sans expiration Authorization: PVEAPIToken=edukit-api@pve!vm-service={secret}

L’onboarding utilise root@pam pour créer les ressources. VmService utilise ensuite exclusivement le token — root@pam ne lui est jamais transmis.


2. Onboarding initial (une seule fois par Proxmox)

Déclencheur : un nouvel établissement apporte son propre Proxmox VE. L’admin OPS exécute onboard-proxmox.sh depuis n’importe quelle machine.

bash onboard-proxmox.sh --host <IP> --password <root_password> --node <noeud>

2.1 Authentification root@pam

POST /access/ticket
{ "username": "root@pam", "password": "<password>" }

Retourne { data: { ticket, CSRFPreventionToken } }. Le ticket est injecté en cookie sur tous les appels suivants. Le CSRFPreventionToken est requis en header sur toutes les requêtes mutantes (POST / PUT / DELETE).

2.2 Compte de service

Créer un user dédié dans le realm pve (authentification interne Proxmox, pas PAM). Ce user n’a pas de mot de passe — il s’authentifie uniquement via son token API.

POST /access/users
{ "userid": "edukit-api@pve", "comment": "EduKit service account (VmService)" }

Si le user existe déjà (relance du script), Proxmox retourne HTTP 500 already exists. Le script l’ignore et continue — comportement idempotent.

2.3 Rôle et permissions

Créer le rôle avec les privilèges minimaux nécessaires à VmService (principe de moindre privilège) :

POST /access/roles
{
  "roleid": "EduKitRole",
  "privs": "VM.Allocate,VM.Audit,VM.Clone,VM.Config.CDROM,VM.Config.CPU,VM.Config.Cloudinit,VM.Config.Disk,VM.Config.HWType,VM.Config.Memory,VM.Config.Network,VM.Config.Options,VM.Console,VM.Migrate,VM.PowerMgmt,Datastore.Allocate,Datastore.AllocateSpace,Datastore.Audit,SDN.Use"
}

SDN.Use est obligatoire dès que vmbr0 appartient à une zone SDN Proxmox (config par défaut sur les installations récentes). Sans ce privilège, le clone retourne 403 Permission check failed (/sdn/zones/localnetwork/vmbr0, SDN.Use).

VM.Console couvre également l’accès au QEMU guest agent (network-get-interfaces). VM.Monitor et VM.GuestAgent.* n’existent pas sur Proxmox VE 8.x — les inclure fait échouer la création du rôle.

Assigner le rôle sur le datacenter entier, avec propagation aux ressources enfants :

PUT /access/acl
{ "path": "/", "users": "edukit-api@pve", "roles": "EduKitRole", "propagate": 1 }

2.4 Token API

POST /access/users/edukit-api@pve/token/vm-service
{ "privsep": 0, "comment": "EduKit VmService token" }

privsep=0 : le token hérite des permissions du user sans restriction supplémentaire.

Le secret UUID n’est retourné qu’une seule fois. Si le script est relancé, l’ancien token est supprimé et recréé (le secret change). Stocker immédiatement dans Vaultwarden.

Le retour contient { data: { value: "<secret_uuid>" } }. VmService l’utilise avec :

Authorization: PVEAPIToken=edukit-api@pve!vm-service=<secret>

2.5 Utilisateur SSH pour les snippets cloud-init

VmService dépose les fichiers cloud-init (user-data, network-config) sur le nœud via SFTP avant chaque démarrage de VM. Un user système dédié est créé via SSH root :

# Créer le user système
useradd -m -s /bin/bash edukit-deploy

# Déployer la clé publique ed25519 générée localement
mkdir -p /home/edukit-deploy/.ssh
echo "<clé_publique>" >> /home/edukit-deploy/.ssh/authorized_keys
chmod 700 /home/edukit-deploy/.ssh
chmod 600 /home/edukit-deploy/.ssh/authorized_keys
chown -R edukit-deploy:edukit-deploy /home/edukit-deploy/.ssh

# Donner l'accès en écriture au répertoire des snippets
mkdir -p /var/lib/vz/snippets
chown edukit-deploy:edukit-deploy /var/lib/vz/snippets
chmod 755 /var/lib/vz/snippets

La paire de clés ed25519 est générée localement par le script (ssh-keygen). La clé privée est affichée en sortie et doit être stockée dans HypervisorCluster.SshPrivateKey (chiffrée AES-256-GCM dans la base Edu-Kit).

2.6 Stockage local — content-type images

Sans ce paramètre, Proxmox ne peut pas écrire l’ISO cloud-init (ide2: local:cloudinit) au démarrage des VMs, avec l’erreur : storage 'local' does not support content-type 'images'.

Lire d’abord le contenu actuel pour ne pas écraser une configuration existante :

GET /storage/local
→ { data: { content: "iso,vztmpl,backup" } }

Ajouter images uniquement s’il est absent :

PUT /storage/local
{ "content": "iso,vztmpl,backup,images" }

2.7 Firewall datacenter et nœud

Voir doc-ops-proxmox-network-et-bastion.md — sections 2.2 et 2.3.

2.8 Installation dnsmasq

Voir doc-ops-proxmox-network-et-bastion.md — section 2.1.

Sortie — paramètres HypervisorCluster

En fin d’onboarding, le script affiche le bloc à renseigner dans l’entité HypervisorCluster de VmService :

Champ Valeur
BaseUrl https://{proxmox_host}:8006
ApiTokenUser edukit-api@pve
ApiTokenId vm-service
ApiTokenSecret <secret_uuid> — chiffré AES-256-GCM en base
DefaultNode nom du nœud (auto-détecté)
VerifySsl false en lab, true avec CA valide en prod
SshHost {proxmox_host}
SshPort 22
SshUsername edukit-deploy
SshSnippetsPath /var/lib/vz/snippets
VmBridge vmbr0
VmCloudInitStorage local-lvm
VmSnippetsStorage local

Résumé du flux

Admin lance onboard-proxmox.sh
    │
    ├──► Proxmox API : authentification root@pam → ticket
    │
    ├──► Proxmox API : créer user edukit-api@pve
    │
    ├──► Proxmox API : créer rôle EduKitRole + assigner sur /
    │
    ├──► Proxmox API : créer token vm-service → secret (une seule fois)
    │
    ├──► SSH nœud : créer user edukit-deploy + déployer clé SSH
    │
    ├──► Proxmox API : activer content-type images sur stockage local
    │
    ├──► Proxmox API : firewall datacenter (règles ACCEPT + policy DROP)
    │
    ├──► Proxmox API : firewall nœud (nftables + FORWARD DROP inter-zone)
    │
    └──► SSH nœud : installer dnsmasq (désactivé, géré par SDN)

    Sortie → bloc HypervisorCluster à saisir dans Edu-Kit

3. Gestion des templates cloud-init

Les templates sont des VMs Proxmox converties en modèle immuable (qm template). VmService les clone pour créer les VMs étudiantes. Le VMID est référencé dans OsImage.ProxmoxTemplateId.

Le script manage-templates.sh automatise ces opérations.

3.1 Lister les templates existants

GET /nodes/{node}/qemu

Filtrer les VMs avec template == 1. Le VMID retourné est la valeur à renseigner dans OsImage.ProxmoxTemplateId (chaîne, ex: "9999").

3.2 Ajouter un template

Tout se passe sur le nœud via SSH root. La cloud image est téléchargée directement depuis les dépôts officiels.

# 1. Télécharger la cloud image sur le nœud
wget -q --continue \
  -O /var/lib/vz/template/iso/debian-12-genericcloud-amd64.qcow2 \
  https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2

# 2. Créer la VM porteuse
qm create <VMID> \
  --name "debian-12-bookworm-cloud-init" \
  --memory 2048 --cores 2 \
  --net0 virtio,bridge=vmbr0 \
  --scsihw virtio-scsi-pci \
  --agent enabled=1,fstrim_cloned_disks=1 \
  --serial0 socket --vga serial0

# 3. Importer le disque dans le stockage
qm importdisk <VMID> /var/lib/vz/template/iso/debian-12-genericcloud-amd64.qcow2 local-lvm

# 4. Câbler le disque comme scsi0 + attacher le lecteur cloud-init
qm set <VMID> --scsi0 local-lvm:vm-<VMID>-disk-0
qm set <VMID> --ide2 local:cloudinit
qm set <VMID> --boot order=scsi0 --ipconfig0 ip=dhcp

# 5. Convertir en template immuable
qm template <VMID>

# 6. Nettoyer l'image source
rm -f /var/lib/vz/template/iso/debian-12-genericcloud-amd64.qcow2

--agent enabled=1,fstrim_cloned_disks=1 : active le QEMU guest agent dans le template. Sans ça, VmService ne peut pas récupérer l’IP de la VM via GET /nodes/{node}/qemu/{vmid}/agent/network-get-interfaces et la VM reste inaccessible.

--ide2 local:cloudinit : le lecteur cloud-init doit être sur un stockage avec le content-type images activé (voir section 2.6). Proxmox génère une ISO à la volée à chaque démarrage en y injectant clé SSH, user, réseau.

Après qm template, la VM ne peut plus démarrer directement — elle est une source de clonage uniquement.

3.3 Supprimer un template

DELETE /nodes/{node}/qemu/{vmid}?destroy-unreferenced-disks=1

Retourne un UPID (tâche asynchrone). Attendre status == "stopped" et exitstatus == "OK" via GET /nodes/{node}/tasks/{upid}/status avant de retirer le VMID de OsImage.ProxmoxTemplateId dans VmService — sinon le prochain provisionnement échoue en silence.

Catalogue des images supportées

OS URL officielle VMID conseillé
Debian 12 Bookworm https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2 9999
Debian 11 Bullseye https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-genericcloud-amd64.qcow2 9998
Ubuntu 24.04 Noble https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img 9997
Ubuntu 22.04 Jammy https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img 9996

4. Points d’attention

Token API : secret visible une seule fois

Proxmox retourne le secret UUID uniquement à la création. Si le script est relancé, l’ancien token est supprimé et recréé — le secret change. Toujours stocker dans Vaultwarden immédiatement après l’onboarding.

Tâches asynchrones Proxmox

Clone, suppression et conversion en template sont des opérations asynchrones. Elles retournent un UPID, pas un résultat direct. Toujours poller GET /nodes/{node}/tasks/{upid}/status jusqu’à status == "stopped" et exitstatus == "OK" avant de continuer.

SDN.Use obligatoire sur les installs récentes

Proxmox 8.x rattache vmbr0 à une zone SDN par défaut sur les nouvelles installations. Sans SDN.Use dans EduKitRole, le clone retourne 403 même si tous les autres privilèges VM sont présents.

Ordre des règles firewall du nœud

Les règles FORWARD ACCEPT intra-zone doivent toujours être avant la règle FORWARD DROP globale. Utiliser le paramètre pos pour insérer à la bonne position. Voir doc-ops-proxmox-network-et-bastion.md.

Changements qui cassent VmService

Se coordonner avec l’équipe backend avant d’effectuer ces actions :

  • Supprimer ou renuméroter un template référencé dans OsImage.ProxmoxTemplateId
  • Révoquer ou faire tourner le token vm-service sans mettre à jour le secret dans Edu-Kit
  • Retirer un privilège de EduKitRole, en particulier VM.Clone, VM.Console ou SDN.Use
  • Changer le port SSH, le nom d’utilisateur ou les authorized_keys de edukit-deploy
  • Déplacer /var/lib/vz/snippets/ ou retirer le droit d’écriture à edukit-deploy

Retour en haut