Pendant des années, ma PKI interne a tenu sur trois fois rien : une autorité de certification OpenSSL pilotée à la main, une clé racine dans un dossier, et un réflexe — quand un service avait besoin de HTTPS, je sortais la ligne openssl x509 -req, je signais un certificat de deux ans, je le posais, et je passais à autre chose.
Ça a marché. Longtemps. Le problème d’un montage qui marche, c’est qu’on ne le remet jamais en question — jusqu’au jour où le nombre de services le rend intenable. Cette première partie raconte la sortie de l’artisanat : la bascule vers step-ca, une autorité de certification en ligne qui émet et renouvelle toute seule des certificats courts. Et surtout, comment faire cette bascule sans casser la confiance déjà déployée partout dans mon infra perso.
On ne parlera ici que du monde Linux et des appliances. Le versant Microsoft — Active Directory, autoenrollment, RDP — fera l’objet d’une Partie 2, parce que ce sera une autre logique et une autre autorité.
Le contexte de l’infra
Pour que la suite soit lisible, voici le décor, anonymisé mais fidèle à la structure réelle. Domaine interne example.internal, adressage en 192.0.2.0/24 (plage de documentation, RFC 5737).
ca-01: l’hôte qui porte l’autorité de certification. Sous Linux. C’est là que vivait la vieille CA OpenSSL, et c’est là que vivra step-ca.web-01,web-02: des services web derrière nginx et Apache (un gestionnaire de secrets, un inventaire de parc…).dns-01: un service de filtrage DNS avec une interface web à protéger.pve-01,pbs-01: des appliances de virtualisation et de sauvegarde. Elles savent parler ACME nativement — on y reviendra, c’est le cas idéal.
La racine s’appelle myCA. Son certificat était déjà déployé partout : dans le magasin de confiance des postes Windows par GPO, dans les trust stores Linux, dans les navigateurs. C’est un détail qui va devenir le pivot de toute la migration.
Un mot sur ce qu’est vraiment une PKI
Avant d’entrer dans la technique, posons le vocabulaire — parce qu’une PKI, ça paraît obscur tant qu’on n’a pas vu les trois pièces qui la composent.
Une infrastructure à clés publiques (PKI), c’est une chaîne de confiance. Trois niveaux :
- La racine (root CA) : l’autorité suprême. Son certificat est auto-signé, et c’est lui qu’on installe dans les magasins de confiance. Une machine qui fait confiance à la racine fait, par extension, confiance à tout ce qu’elle a signé. La racine est précieuse : si sa clé fuite, toute la chaîne s’effondre. On la garde donc hors-ligne autant que possible.
- L’intermédiaire (issuing CA) : une autorité signée par la racine, qui fait le travail quotidien d’émettre les certificats. On l’expose, on la sollicite — et si elle est compromise, on la révoque sans toucher à la racine.
- La feuille (leaf) : le certificat final, celui que présente
web-01quand votre navigateur s’y connecte. Il est signé par l’intermédiaire.
La confiance se vérifie de proche en proche : le navigateur reçoit la feuille, remonte à l’intermédiaire qui l’a signée, puis à la racine — qu’il connaît. Si un maillon manque, la chaîne casse et le cadenas devient rouge.
Ma vieille CA OpenSSL n’avait que deux de ces trois pièces : une racine, et des feuilles signées directement par elle. Pas d’intermédiaire. Pas d’automatisation. La racine servait à tout, tout le temps, à la main.
Avant : la racine signe tout, à la main, pour deux ans. Après : step-ca s’intercale comme intermédiaire sous la même racine, et émet des feuilles courtes automatiquement.
Pourquoi l’artisanat ne passe plus à l’échelle
Signer un certificat à la main, une fois, ce n’est rien. Le problème, c’est l’accumulation.
Chaque service voulait son certificat. À chaque fois : générer une clé, rédiger une demande, signer, poser le fichier au bon endroit avec les bons droits, recharger le service. Et comme signer était pénible, je faisais des certificats de deux ans — histoire de ne pas y revenir. C’est un réflexe compréhensible, et c’est exactement le mauvais.
Un certificat de deux ans, c’est :
- une clé privée qui reste identique pendant deux ans. Si elle fuite, la fenêtre d’exploitation est immense.
- aucune discipline de rotation. On ne teste jamais le renouvellement, donc le jour où il faut le faire en urgence, on a oublié comment.
- aucune visibilité. Combien de certificats en circulation ? Lesquels expirent quand ? Ma seule réponse, c’était un fichier texte. Le genre de fichier qui n’est jamais à jour.
Le monde va d’ailleurs dans le sens inverse de mes deux ans : la durée de vie des certificats publics fond d’année en année, précisément pour forcer l’automatisation. La leçon vaut aussi pour l’interne — un certificat court n’est un problème que si on le renouvelle à la main. Automatisé, c’est un non-événement.
D’où la cible : des certificats courts, renouvelés tout seuls. Ce qui suppose une autorité qui émet à la demande, sans intervention humaine. C’est exactement ce que fait step-ca.
La décision d’architecture : garder la racine, glisser step-ca dessous
Le premier réflexe, quand on découvre step-ca, serait de repartir de zéro : nouvelle racine, nouvelle CA, tout propre. Très mauvaise idée dans mon cas.
Parce que ma racine myCA était déjà déployée partout. Des années de GPO, de trust stores, de configurations qui référencent ce certificat racine. Repartir d’une racine neuve, c’est devoir redéployer la confiance sur toute l’infra avant que quoi que ce soit fonctionne. Un chantier énorme, pour un gain nul.
La bonne approche exploite une propriété clé de step-ca : il peut fonctionner comme intermédiaire d’une racine existante.
Le principe :
- On garde le certificat racine
myCAtel quel — il reste l’ancre de confiance, déjà connue de tout le monde. - On génère une paire intermédiaire pour step-ca, et on la fait signer par la racine
myCA. - step-ca émet désormais les feuilles avec cet intermédiaire.
Résultat : les feuilles émises par step-ca remontent, via l’intermédiaire, jusqu’à myCA — que tout le monde connaît déjà. Aucune machine à reconfigurer côté confiance. La bascule est transparente pour les clients.
L’idée à retenir : on ne remplace pas la racine, on l’étend. La racine devient une simple ancre hors-ligne ; step-ca fait le travail quotidien en dessous. C’est la différence entre déménager toute la maison et ajouter une pièce.
Concrètement, l’initialisation de step-ca se fait en lui fournissant la racine existante et sa clé, pour qu’il génère l’intermédiaire signé par elle. Une fois l’intermédiaire en place, la clé racine n’a plus besoin d’être en ligne : step-ca signe avec l’intermédiaire, pas avec la racine. On pourra donc la ressortir de la machine et la remiser hors-ligne — un point de sécurité sur lequel je reviendrai en fin de série.
step-ca en service : l’autorité qui répond toute seule
Une fois initialisé, step-ca tourne comme un service permanent. Il écoute sur un port (:9000 par défaut), et il expose deux façons de demander un certificat :
- une API interrogée par le client en ligne de commande
step; - un endpoint ACME — le même protocole que celui des autorités publiques bien connues — que savent parler de plus en plus d’outils et d’appliances.
Pour autoriser les demandes, step-ca utilise la notion de provisioner : une méthode d’authentification qui décide qui a le droit de faire signer un certificat. Un provisioner peut être une clé protégée par mot de passe, un compte ACME, un jeton… Dans mon cas, deux suffisent : un provisioner ACME pour les appliances qui parlent ACME, et un provisioner à mot de passe pour les émissions pilotées par script.
L’émission d’une feuille depuis un hôte Linux tient alors en une commande :
step ca certificate "web-01.example.internal" \
/etc/ssl/web-01/web-01.crt \
/etc/ssl/web-01/web-01.key \
--provisioner admin@example.internal
step-ca vérifie le provisioner, signe une feuille valable 90 jours, et dépose le certificat et sa clé aux chemins indiqués. On recharge le service, et le tour est joué.
Mais 90 jours, ça veut dire renouveler quatre fois par an. À la main, ce serait pire que les deux ans d’avant. C’est là que tout se joue : le renouvellement doit être automatique.
Le cœur du sujet : le renouvellement automatique
L’objectif est simple à énoncer : chaque certificat doit se renouveler avant d’expirer, tout seul, et prévenir quand il l’a fait. Sur Linux, systemd fournit tout ce qu’il faut avec deux briques : un service qui fait le travail, et un timer qui le déclenche périodiquement.
Le service de renouvellement appelle step ca renew. La subtilité — et c’est une leçon apprise à mes dépens — est dans la condition de renouvellement :
# /etc/systemd/system/cert-renewer@.service
[Unit]
Description=Renouvellement du certificat pour %i
[Service]
Type=oneshot
ExecStart=step ca renew --expires-in 720h \
--exec "/usr/local/bin/cert-renewed.sh" \
/etc/ssl/%i/%i.crt /etc/ssl/%i/%i.key
Le paramètre décisif est --expires-in 720h. Il dit : « ne renouvelle que s’il reste moins de 720 heures (30 jours) de validité ». Sans lui — avec un --force, par exemple — le certificat serait réémis à chaque passage du timer. Or…
Le piège que j’ai touché du doigt : mon premier jet renouvelait sans condition, à chaque exécution horaire. Résultat : un nouveau certificat toutes les heures, et surtout un mail de notification toutes les heures. Une boîte mail noyée en une nuit. La règle est simple : on ne renouvelle que si on approche de l’expiration.
--expires-inn’est pas une option, c’est la condition qui rend l’automatisation vivable.
Le timer, lui, se contente de réveiller le service à intervalle régulier :
# /etc/systemd/system/cert-renewer@.timer
[Unit]
Description=Vérifie le renouvellement du certificat pour %i
[Timer]
OnCalendar=*:0 # au début de chaque heure
Persistent=true
[Install]
WantedBy=timers.target
Toutes les heures, le service se réveille, vérifie s’il reste moins de 30 jours, et ne fait rien 99 % du temps. Le jour où le seuil est franchi, il renouvelle, exécute le hook, et se rendort.
Le hook cert-renewed.sh est la troisième pièce. C’est lui qui fait le travail après l’obtention du nouveau certificat : recharger le service concerné pour qu’il prenne le nouveau certificat, et envoyer la notification.
#!/bin/bash
# /usr/local/bin/cert-renewed.sh — exécuté après un renouvellement réussi
set -e
systemctl reload nginx
printf "Certificat renouvelé sur %s\nExpire le : %s\n" \
"$(hostname -f)" \
"$(step certificate inspect /etc/ssl/web-01/web-01.crt --format json \
| jq -r '.validity.end')" \
| mail -s "[PKI] Renouvellement $(hostname -s)" admin@example.internal
Le service de rechargement (nginx, apache2, ou le redémarrage d’un dashboard) dépend de la machine. C’est le seul morceau qui varie d’un hôte à l’autre — et c’est précisément ce qu’il faudra paramétrer quand on industrialisera.
Une fois le motif posé, le cycle tourne seul. Le timer vérifie chaque heure ; il ne renouvelle qu’à l’approche de l’expiration, recharge le service, et notifie. On n’y revient que si un mail attendu ne vient pas.
Le cas idéal : les appliances qui parlent ACME
Tout ce qui précède concerne les hôtes Linux qu’on pilote au script. Mais certaines appliances — hyperviseurs, serveurs de sauvegarde — embarquent nativement un client ACME. Pour celles-là, on n’a rien à scripter : elles savent demander et renouveler un certificat toutes seules, exactement comme elles le feraient auprès d’une autorité publique.
Il suffit de les pointer vers l’URL ACME de step-ca et de leur faire confiance à la racine. Sur une appliance de virtualisation, l’enregistrement d’un compte ACME et la commande de certificat se font en deux ou trois lignes dans son propre outillage, puis le renouvellement est intégralement pris en charge par l’appliance via sa tâche de mise à jour quotidienne. Zéro timer à écrire, zéro hook à maintenir.
C’est le cas le plus confortable, et un bon argument pour préférer, quand on a le choix, des équipements qui parlent ACME. Le seul point de vigilance : la validation du challenge ACME (le mécanisme par lequel l’appliance prouve à step-ca qu’elle est bien qui elle prétend être) doit pouvoir aboutir sur le réseau — un point à vérifier quand l’appliance et la CA ne sont pas dans le même segment.
Industrialiser : le même motif, partout
À ce stade, j’avais un motif qui fonctionnait, hôte par hôte : déployer la racine dans le trust store, installer le client step, émettre la feuille, poser le hook, armer le timer. Le refaire à la main sur chaque machine aurait juste déplacé la corvée. La vraie sortie de l’artisanat, c’est d’automatiser le déploiement du motif lui-même.
C’est le rôle d’un contrôleur d’automatisation (dans mon cas, un playbook idempotent) qui applique la même séquence à tout un groupe de machines :
- déployer le certificat racine dans le trust store de l’hôte ;
- installer le client
stepet l’outil d’envoi de mail ; - émettre la feuille via step-ca, en récupérant le mot de passe du provisioner depuis un coffre de secrets chiffré (jamais en clair dans le playbook) ;
- déposer le hook et armer le timer de renouvellement.
Ce qui change d’une machine à l’autre — le nom du certificat, la commande de rechargement du service — est décrit dans des variables par hôte. Le playbook, lui, reste unique. On ajoute un service à la flotte en écrivant trois lignes de variables, pas en réécrivant la procédure.
Le contrôleur applique le même motif partout : émission via step-ca, dépôt du certificat, armement du renouvellement. Les appliances ACME, elles, se débrouillent seules. Ce qui varie par machine tient dans quelques variables.
Le piège de l’application en masse
Industrialiser, c’est puissant. C’est aussi dangereux, et je dois être honnête sur ce point parce que je me suis fait piéger.
Un playbook qui applique un motif à un groupe de machines l’applique à toutes les machines du groupe — y compris celles qui n’auraient pas dû le recevoir. Le jour où le motif installe un composant qui entre en conflit avec un rôle particulier d’une machine (typiquement : un outil d’envoi de mail poussé sur un hôte qui est déjà le serveur de messagerie), on ne casse pas un service, on casse le service dont dépendent tous les autres.
La leçon : l’idempotence d’un playbook garantit qu’il produit le même état à chaque exécution. Elle ne garantit pas que cet état soit le bon pour toutes les machines du groupe. Les hôtes à rôle particulier — serveur de messagerie, contrôleur, tout ce qui est unique — doivent être explicitement exclus des tâches qui ne les concernent pas, avant le premier passage. Une exclusion oubliée ne se voit pas en relisant le playbook : elle se voit quand le service tombe.
Depuis, tout motif appliqué en masse commence par la question : « quelles machines de ce groupe ont un rôle qui rend une de ces tâches nuisible ? » — et ces machines sont écartées par condition, nommément, avant quoi que ce soit d’autre.
Ce que je retiens
- Réutiliser la racine plutôt que la remplacer. step-ca en intermédiaire d’une racine déjà déployée, c’est une bascule transparente pour les clients. Repartir d’une racine neuve, c’est se condamner à redéployer la confiance partout pour rien.
- Le certificat court n’est un problème que si on le renouvelle à la main. Automatisé, 90 jours est plus sûr et plus simple que deux ans. C’est l’automatisation qui rend la durée courte confortable, pas l’inverse.
--expires-inest la clé de voûte du renouvellement. Renouveler sans condition, c’est réémettre à chaque tic du timer et se noyer sous les notifications. On ne renouvelle qu’à l’approche de l’échéance.- Préférer les équipements qui parlent ACME. Quand une appliance gère ACME nativement, il n’y a rien à scripter ni à maintenir : elle se renouvelle seule. C’est un critère de choix à part entière.
- Industrialiser amplifie tout — les bonnes actions comme les erreurs. Le même motif appliqué à toute une flotte fait gagner des heures. Appliqué à une machine qu’il ne fallait pas toucher, il coûte une soirée. Les hôtes à rôle unique s’excluent explicitement, avant le premier passage.
Dans la Partie 2, on quittera le monde Linux pour le versant Microsoft : pourquoi j’y maintiens une seconde autorité distincte, intégrée à Active Directory, et comment elle sert l’authentification serveur — le RDP en tête — via l’autoenrollment natif. Deux autorités, deux mondes, une cohabitation assumée.
Sources et références
Documentation officielle step-ca (Smallstep) :
- step-ca — présentation et capacités — dont la faculté d’opérer comme intermédiaire en ligne sous une racine existante, la racine restant hors-ligne.
- Getting Started — initialisation de la CA,
step ca init, bootstrap de la confiance via l’empreinte racine. - Import an existing root or intermediate CA into step-ca — la procédure exacte pour faire signer l’intermédiaire step-ca par une racine déjà en place (le pivot de cet article).
- Provisioners — les méthodes d’autorisation des demandes (ACME, JWK…).
- Certificate renewal options — le renouvellement automatique par timers systemd (approche recommandée) et les templates
cert-renewer@.service/.timer. - Référence
step ca renew— dont le flag--expires-in: le renouvellement n’est pas effectué tant qu’il reste plus que la durée indiquée avant expiration (avec un jitter aléatoire dedurée/20). - Production considerations — durées de vie, révocation, bonnes pratiques d’exploitation.
- Run your own private ACME server using step-ca — le fonctionnement d’ACME côté serveur privé et la validation des challenges.
- smallstep/certificates (dépôt GitHub) — le code source du projet.
Normes et RFC :
- RFC 8555 — Automatic Certificate Management Environment (ACME) — le protocole d’automatisation des certificats.
- RFC 5280 — Internet X.509 Public Key Infrastructure Certificate and CRL Profile — le profil des certificats X.509 et des chaînes de confiance.
- RFC 5737 — IPv4 Address Blocks Reserved for Documentation — les plages
192.0.2.0/24,198.51.100.0/24et203.0.113.0/24utilisées ici pour l’anonymisation.
Sources vérifiées en ligne à la date de rédaction. Les liens pointent vers la documentation officielle des éditeurs et l’IETF.