Pourquoi faire tourner votre propre AC
Regardez ce qu’il y a aujourd’hui sur un réseau domestique. Un NAS, Proxmox, Home Assistant, une imprimante, la page d’administration d’un routeur. Chacun sert du HTTPS avec un certificat auto-signé. Chaque navigateur qui s’y rend lève un avertissement. Les gens cliquent pour passer. Puis ils cliquent sur le suivant, et sur celui d’après, jusqu’à ce que passer outre un avertissement de certificat soit simplement ce qu’on fait. C’est l’habitude dont un attaquant a besoin.
Une AC publique ne règle pas ça. Let’s Encrypt n’émet pas pour localhost « because nobody uniquely owns it »1, et c’est pareil pour tout nom qui n’existe qu’à l’intérieur de la maison. Même pour un vrai domaine, chaque certificat émis part dans les journaux publics de Certificate Transparency2. Prenez-en un pour nas.yourname.uk et le nom de votre NAS est désormais public.
Votre propre AC règle les deux. Elle signe les noms que vous utilisez à l’intérieur. Rien n’est publié nulle part. Une fois que chaque appareil fait confiance à sa racine, les avertissements s’arrêtent, et un avertissement redevient quelque chose qui veut dire quelque chose.
step-ca est l’AC open source de Smallstep, et elle tourne dans un seul conteneur. Tout ce qui suit a été lancé sur step-ca 0.30.2 sous Podman, avec Caddy 2.11.7 et nginx 1.30.5 comme clients.
| Usage | Ce que step-ca lui apporte | Section |
|---|---|---|
| Chaque appareil fait confiance à vos certificats | Une racine à installer, une fois | La confiance dans la racine |
| HTTPS interne pour tout ce qui parle ACME | Le même protocole que Let’s Encrypt, pour des noms internes | HTTPS interne avec ACME |
| Services sans client ACME | Des certificats qui se renouvellent seuls sur minuterie | Les services qui ne savent pas faire d’ACME |
| Téléphones et autres certificats clients | Des clés qu’un appareil détient pour entrer, et un moyen de les révoquer | Son propre article : les valeurs par défaut s’y opposent |
| Connexions SSH | Des certificats au lieu de authorized_keys | Son propre article : une autre installation |
Deux d’entre eux ont leur propre article, parce que chacun est une installation à part. Des certificats clients depuis step-ca couvre les téléphones et le mTLS, où cinq valeurs par défaut doivent changer. Des connexions SSH signées par step-ca couvre SSH, qui utilise d’autres clés et d’autres fichiers sur chaque machine.
Le démarrer
L’image s’initialise d’elle-même au premier lancement à partir de quelques variables d’environnement3. Deux d’entre elles décident de choses qu’on ne peut plus changer ensuite sans tout recommencer, alors posez-les dès le premier jour.
| Variable | Rôle |
|---|---|
DOCKER_STEPCA_INIT_NAME | Le nom d’émetteur sur chaque certificat |
DOCKER_STEPCA_INIT_DNS_NAMES | Les noms sur lesquels l’AC répond |
DOCKER_STEPCA_INIT_ACME=true | Ajoute un provisioner ACME pour le HTTPS interne |
DOCKER_STEPCA_INIT_SSH=true | Crée aussi les clés d’AC SSH d’hôte et d’utilisateur |
Les interrupteurs ACME et SSH sont lus par le point d’entrée de l’image4. Activez-les maintenant même si vous n’en voulez qu’un aujourd’hui. Ajouter SSH plus tard, c’est construire une nouvelle AC.
podman run -d --name step-ca \
-v step:/home/step \
-p 9000:9000 \
-e DOCKER_STEPCA_INIT_NAME="Home CA" \
-e DOCKER_STEPCA_INIT_DNS_NAMES="localhost,ca.home.example" \
-e DOCKER_STEPCA_INIT_ACME=true \
-e DOCKER_STEPCA_INIT_SSH=true \
docker.io/smallstep/step-ca:0.30.2
Elle construit une racine, un intermédiaire signé par cette racine, les clés SSH et trois provisioners, puis sert sur le port 9000 :
X.509 Root Fingerprint: cd2f8d4c60af32a0a534781cd63b4e2a9ce362e2c1ae5983b56a370bfa2c2489
SSH Host CA Key: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy...
SSH User CA Key: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy...
Serving HTTPS on :9000 ...
| Provisioner | Type | Signe |
|---|---|---|
admin | JWK | Tout ce que vous demandez avec son mot de passe |
acme | ACME | Des certificats pour les clients qui prouvent qu’ils possèdent le nom |
sshpop | SSHPOP | Les renouvellements de certificats d’hôte SSH |
Le provisioner, c’est l’idée que l’AC se fait de « qui demande ». Chacun a ses propres règles, et l’essentiel de ce qui suit consiste à poser ces règles.
Épinglez la version. latest valait 0.30.2 le jour où ceci a été écrit, et une AC est bien la dernière chose que vous voulez voir changer sous vos pieds à un pull.
Deux mots de passe, pas un
Le guide Docker de Smallstep dit qu’un seul mot de passe généré couvre à la fois « the encrypted CA keys and the default CA provisioner », conservé dans secrets/password3. Sur cette image, ce n’est pas ce qui se passe. Essayez d’émettre un certificat avec ce mot de passe et le provisioner le refuse :
failed to decrypt JWE: invalid password
Le point d’entrée de l’image l’explique lui-même. Il génère deux mots de passe aléatoires, un pour les clés de l’AC et un pour le provisioner. Celui du provisioner est affiché une fois dans le journal, puis détruit4 :
echo "👉 Your CA administrative password is: $(< $STEPPATH/provisioner_password )"
...
shred -u $STEPPATH/provisioner_password
| Mot de passe | Déverrouille | Où il finit |
|---|---|---|
secrets/password | Les clés de la racine et de l’intermédiaire | Dans le volume, utilisé à chaque démarrage de l’AC |
| Mot de passe du provisioner | Le provisioner admin, qui signe chaque certificat que vous demandez | Le journal du premier lancement, et nulle part ailleurs |
Lisez donc le journal au premier démarrage et rangez ce second mot de passe en lieu sûr. Perdez-le et vous ne pouvez plus émettre de certificat sans retirer le provisioner à la main. Posez DOCKER_STEPCA_INIT_PASSWORD avant le premier démarrage si vous préférez le choisir vous-même ; le point d’entrée l’utilise alors pour les deux4.
La confiance dans la racine
Rien ne fait confiance à votre AC tant que vous ne le lui dites pas. Une machine Debian qui demande à Caddy une page signée par step-ca :
$ curl https://nas.home.example/
curl: (60) SSL certificate problem: unable to get local issuer certificate
Copiez la racine, et la même requête passe :
cp root_ca.crt /usr/local/share/ca-certificates/home-ca.crt
update-ca-certificates
$ curl https://nas.home.example/
internal service, certificate from step-ca
C’est tout le travail. Une fois par appareil. step certificate install fait la même chose sur le système où il tourne5. Seule la racine va dans un magasin de confiance. L’intermédiaire voyage avec chaque certificat, et un téléphone qui présentera un certificat client en a besoin aussi, ce que couvre l’article sur les certificats clients.
Traitez la racine comme la clé de la porte d’entrée qu’elle est devenue. Tout ce qui détient la clé privée de la racine peut fabriquer un certificat pour n’importe quel nom, et chaque appareil qui lui fait confiance le croira.
HTTPS interne avec ACME
ACME est le protocole qu’utilise Let’s Encrypt, et step-ca le parle pour les noms internes6. Tout ce qui a un client ACME obtient des certificats internes de la même façon qu’il obtient des certificats publics, renouvellements compris. Pour Caddy, ce sont deux lignes globales :
{
acme_ca https://ca.home.example:9000/acme/acme/directory
acme_ca_root /etc/caddy/root_ca.crt
}
nas.home.example {
reverse_proxy 192.0.2.30:5000
}
Ce que Caddy a journalisé au premier démarrage :
"msg":"trying to solve challenge","identifier":"nas.home.example","challenge_type":"tls-alpn-01"
"msg":"certificate obtained successfully","identifier":"nas.home.example"
| Vérification | Résultat |
|---|---|
| Challenge | tls-alpn-01, auquel Caddy répond sur le port 443 |
| Émetteur | Home CA Intermediate CA |
| Durée de vie | 24 heures, de 17:31 à 17:32 le lendemain |
| Renouvellement | Le travail de Caddy lui-même, rien à configurer |
Un certificat de 24 heures a l’air inquiétant. Il ne l’est pas. Un client ACME renouvelle bien avant l’expiration, chaque jour, sans que personne n’intervienne. Une clé volée vaut une journée. C’est le défaut autour duquel step-ca a été construit, et ici il tombe exactement juste.
Le challenge exige tout de même que l’AC atteigne le nom pour lequel elle signe. step-ca doit résoudre nas.home.example et s’y connecter sur le port 443 pour tls-alpn-01, ou le port 80 pour http-01. Un DNS interne utilisé par les deux côtés, c’est ce qu’il faut régler en premier.
Les services qui ne savent pas faire d’ACME
Beaucoup de matériel n’a aucun client ACME : l’interface web d’un hyperviseur, une imprimante, un vieil appareil. Pour ceux-là, émettez le certificat avec le provisioner admin et renouvelez-le avec step ca renew :
step ca certificate proxmox.home.example pve.crt pve.key
step ca renew --force pve.crt pve.key
| Numéro de série | Valide jusqu’au | |
|---|---|---|
| Émis | 241943140971… | 2026-10-10 17:33:03 |
Après step ca renew | 206409156462… | 2026-10-10 17:33:06 |
Nouveau numéro de série, 24 heures neuves, et aucun mot de passe demandé. Le certificat en cours prouve qui demande, et c’est tout l’intérêt : le renouvellement n’exige aucun secret stocké sur la machine au-delà de la clé qu’elle a déjà. Il faut en revanche que le certificat soit encore valide, donc un renouvellement qui rate sa fenêtre oblige à réémettre à la main.
Laissé tourner, il se renouvelle tout seul. --daemon renouvelle par défaut aux deux tiers de la durée de vie, et --exec lance une commande après chaque renouvellement, et c’est là que va le rechargement7 :
step ca renew --daemon --exec "systemctl reload nginx" pve.crt pve.key
Lancez ça depuis une unité systemd et la machine s’occupe d’elle-même. Oubliez-le, et dans 24 heures le service lève le même avertissement dont vous vouliez vous débarrasser.
L’AC est la partie facile
Faire tourner step-ca a pris une commande. Faire confiance à la racine sur une machine Debian en a pris deux. Caddy a eu besoin de deux lignes et n’a plus jamais rien demandé. C’est la partie que les gens repoussent pendant des années, en vivant avec un avertissement du navigateur sur chaque page interne, et c’est le travail le moins cher de la liste.
Ce qui coûte, c’est tout ce qu’un certificat touche une fois émis : la racine sur chaque appareil, les renouvellements que personne ne surveille, le service qui ne sait pas faire d’ACME et dont il faut se souvenir. Rien de tout ça n’est difficile. Tout ça est le genre de travail qui n’est bien fait que lorsque quelqu’un en est responsable.
Un réseau domestique est un petit parc. Il mérite ce qu’en reçoit un vrai : une racine, gardée avec soin, approuvée délibérément, et un avertissement qui veut dire quelque chose quand il apparaît. En tant que tel, l’AC n’est pas la partie astucieuse. La partie astucieuse, c’est de ne plus jamais cliquer pour passer.
Let’s Encrypt — Certificates for localhost — ne peut pas émettre pour « localhost » « because nobody uniquely owns it, and it’s not rooted in a top level domain ». ↩︎
Let’s Encrypt — Certificate Transparency logs — « Let’s Encrypt submits all certificates we issue to CT logs. » ↩︎
Smallstep — Run a private CA in Docker — les variables
DOCKER_STEPCA_INIT_*; dit que le mot de passe généré sert pour « the encrypted CA keys and the default CA provisioner ». ↩︎ ↩︎smallstep/certificates — docker/entrypoint.sh — génère des fichiers
passwordetprovisioner_passwordséparés, affiche une fois le mot de passe du provisioner puis le détruit ; un mot de passe fourni est écrit dans les deux. ↩︎ ↩︎ ↩︎Smallstep — step certificate install — installe un certificat racine dans le magasin de confiance du système. ↩︎
Smallstep — ACME basics — le provisioner ACME de step-ca et les types de challenge qu’il prend en charge. ↩︎
Smallstep — step ca renew — avec
--daemon, « By default, it will renew the certificate before 2/3 of the validity period of the certificate has elapsed » ;--execassure les « certificate reloads on your services ». ↩︎