Migration : 1 Certificat par Machine¶
Statut : ✅ Implémenté Date : 2026-01-03 Commits :
4cb0375,051c388
Objectif¶
Migrer l'architecture certificats de "1 certificat par client" vers "1 certificat par machine". Chaque machine possède son propre certificat mTLS unique, permettant une révocation granulaire.
Migration forcée : Tous les anciens certificats sont supprimés, les agents doivent être réinstallés.
Architecture¶
Avant¶
Client → 1 Certificat (CN=client_id) → N Machines
Révocation = Toutes les machines du client bloquées
Après¶
Client → N Certificats → N Machines (1:1)
CN = client_id:machine_id
Révocation = 1 seule machine bloquée
Fichiers modifiés¶
Backend¶
| Fichier | Modification |
|---|---|
backend/internal/services/installer/cert.go |
GenerateMachineCertificate(clientID, machineID) avec CN=client_id:machine_id |
backend/internal/services/installer/cert_db.go |
Colonne machine_id, StoreMachineCertificateMetadata(), RestoreCertificate() |
backend/internal/handlers/bootstrap.go |
Request avec hostname/hardware_id, créer machine au bootstrap |
backend/internal/middleware/middleware.go |
Parser CN=client_id:machine_id, extraire les deux IDs |
backend/internal/middleware/revocation.go |
Vérifier révocation par machine_id |
backend/internal/handlers/certificate_renewal.go |
Renouveler par machine |
backend/internal/handlers/certificates.go |
Endpoints admin : stats, list, restore, purge |
backend/cmd/api/main.go |
Routes certificates admin |
Installer (Windows)¶
| Fichier | Modification |
|---|---|
installer/stub/main.go |
Support bootstrap API avec token |
backend/internal/handlers/downloads.go |
Créer token pour Windows au lieu de certificat |
backend/internal/services/installer/windows.go |
GenerateWindowsSelfExtractorWithToken() |
backend/internal/services/installer/tokens.go |
GetLatestActiveToken() |
Database¶
-- Migration: 1 cert per machine
-- 1. Supprimer tous les anciens certificats
DELETE FROM client_certificates;
-- 2. Ajouter colonne machine_id
ALTER TABLE client_certificates ADD COLUMN machine_id UUID REFERENCES machines(id);
-- 3. Index pour performance
CREATE INDEX idx_client_certificates_machine_id ON client_certificates(machine_id);
-- 4. Contrainte: 1 certificat actif par machine
CREATE UNIQUE INDEX idx_client_certificates_active_machine
ON client_certificates(machine_id)
WHERE revoked_at IS NULL AND expires_at > NOW();
Flow d'installation¶
Linux (curl)¶
- Script shell télécharge le token
- Appelle
POST /api/public/bootstrapavec token + hostname + hardware_id - Backend valide le token, crée/trouve la machine
- Backend génère certificat CN=client_id:machine_id
- Retourne certificat + clé privée + CA cert + URL binaire
- Script installe l'agent avec le certificat
Windows (EXE)¶
- Admin télécharge l'installeur depuis le dashboard
- Backend crée un token d'installation (24h, usage unique)
- Backend génère EXE = stub + token (pas de certificat)
- User exécute l'EXE sur la machine cible
- Stub lit le token, appelle
POST /api/public/bootstrap - Backend génère certificat spécifique à cette machine
- Stub installe l'agent avec le certificat reçu
Format du CN (Common Name)¶
CN = {client_id}:{machine_id}
Exemple: CN = 550e8400-e29b-41d4-a716-446655440000:6ba7b810-9dad-11d1-80b4-00c04fd430c8
Le middleware parse ce CN pour extraire :
- clientID → Identifie le client/organisation
- machineID → Identifie la machine spécifique
API Bootstrap¶
Endpoint¶
Request¶
{
"token": "base64_encoded_install_token",
"hostname": "DESKTOP-ABC123",
"hardware_id": "4C4C4544-0044-4810-8031-B3C04F4A5331",
"arch": "amd64"
}
Response¶
{
"client_id": "550e8400-e29b-41d4-a716-446655440000",
"machine_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"certificate": "-----BEGIN CERTIFICATE-----...",
"private_key": "-----BEGIN PRIVATE KEY-----...",
"ca_cert": "-----BEGIN CERTIFICATE-----...",
"api_url": "https://optralis-agent-api.2lacs-it.com",
"binary_url": "https://optralis.2lacs-it.com/downloads/stable/optralis-agent-windows-amd64.exe",
"expires_at": "2027-01-03T00:00:00Z"
}
Avantages¶
| Avant | Après |
|---|---|
| Révocation = toutes les machines du client | Révocation = 1 machine uniquement |
| Pas de traçabilité par machine | Certificat lié à une machine spécifique |
| Réinstallation = même certificat | Réinstallation = nouveau certificat |
| Impossible de savoir quelle machine utilise le cert | Dashboard affiche machine ↔ certificat |
Sécurité¶
| Mesure | Description |
|---|---|
| Token unique | Chaque téléchargement Windows génère un token usage unique |
| Token éphémère | Validité 24h maximum |
| Certificat par machine | Révocation granulaire possible |
| Hardware ID | Identification unique de la machine physique |
| Bootstrap sécurisé | Le token est validé avant génération du certificat |
Commandes utiles¶
# Vérifier le CN d'un certificat
openssl x509 -in client.crt -text -noout | grep "Subject:"
# Parser le CN
CN="550e8400-e29b-41d4-a716-446655440000:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
CLIENT_ID=$(echo $CN | cut -d: -f1)
MACHINE_ID=$(echo $CN | cut -d: -f2)
# Vérifier les certificats d'un client (SQL)
SELECT c.serial, c.machine_id, m.hostname, c.expires_at, c.revoked_at
FROM client_certificates c
JOIN machines m ON c.machine_id = m.id
WHERE c.client_id = 'YOUR_CLIENT_ID';