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.

UsageCe que step-ca lui apporteSection
Chaque appareil fait confiance à vos certificatsUne racine à installer, une foisLa confiance dans la racine
HTTPS interne pour tout ce qui parle ACMELe même protocole que Let’s Encrypt, pour des noms internesHTTPS interne avec ACME
Services sans client ACMEDes certificats qui se renouvellent seuls sur minuterieLes services qui ne savent pas faire d’ACME
Téléphones et autres certificats clientsDes clés qu’un appareil détient pour entrer, et un moyen de les révoquerSon propre article : les valeurs par défaut s’y opposent
Connexions SSHDes certificats au lieu de authorized_keysSon 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.

VariableRôle
DOCKER_STEPCA_INIT_NAMELe nom d’émetteur sur chaque certificat
DOCKER_STEPCA_INIT_DNS_NAMESLes noms sur lesquels l’AC répond
DOCKER_STEPCA_INIT_ACME=trueAjoute un provisioner ACME pour le HTTPS interne
DOCKER_STEPCA_INIT_SSH=trueCré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 ...
ProvisionerTypeSigne
adminJWKTout ce que vous demandez avec son mot de passe
acmeACMEDes certificats pour les clients qui prouvent qu’ils possèdent le nom
sshpopSSHPOPLes 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.

Une AC, cinq usagesConteneur step-caRacine, intermédiaireune racine à approuveracmeadminsshpopCRLprovisionersChaque appareilracine approuvée 1 foisCaddy, ACME24 h, se renouvelle seulSans ACMEstep ca renew --daemonTéléphones1 an, révoqué par CRLSSHson propre article
Une racine, approuvée une fois, et un provisioner pour chaque voie d’entrée. Les clients ACME et les services renouvelés vivent avec le défaut de 24 heures ; les téléphones ont besoin qu’on le change ; SSH a son propre article.

É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 passeDéverrouilleOù il finit
secrets/passwordLes clés de la racine et de l’intermédiaireDans le volume, utilisé à chaque démarrage de l’AC
Mot de passe du provisionerLe provisioner admin, qui signe chaque certificat que vous demandezLe 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érificationRésultat
Challengetls-alpn-01, auquel Caddy répond sur le port 443
ÉmetteurHome CA Intermediate CA
Durée de vie24 heures, de 17:31 à 17:32 le lendemain
RenouvellementLe 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érieValide jusqu’au
Émis241943140971…2026-10-10 17:33:03
Après step ca renew206409156462…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.


  1. 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 ». ↩︎

  2. Let’s Encrypt — Certificate Transparency logs — « Let’s Encrypt submits all certificates we issue to CT logs. » ↩︎

  3. 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 ». ↩︎ ↩︎

  4. smallstep/certificates — docker/entrypoint.sh — génère des fichiers password et provisioner_password sé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. ↩︎ ↩︎ ↩︎

  5. Smallstep — step certificate install — installe un certificat racine dans le magasin de confiance du système. ↩︎

  6. Smallstep — ACME basics — le provisioner ACME de step-ca et les types de challenge qu’il prend en charge. ↩︎

  7. 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 » ; --exec assure les « certificate reloads on your services ». ↩︎