Tomcat et Spring Boot avec Let's Encrypt

Avant, il fallait convertir le PEM en keystore et recommencer à chaque renouvellement. Les versions actuelles de Tomcat et Spring Boot lisent directement les fichiers Let's Encrypt, et Spring Boot les recharge même tout seul.

Quelle approche ?

Obtenir le certificat

Let's Encrypt valide via HTTP-01 sur le port 80 ou via DNS-01. Si rien ne tourne sur le port 80, le mode standalone de certbot suffit ; sinon webroot ou DNS. L’assistant ACME fournit les commandes. Les fichiers se trouvent ensuite dans /etc/letsencrypt/live/example.com/ :

Le piège des droits

Les répertoires live et archive ne sont lisibles que par root. Tomcat et Spring Boot tournent sous leur propre utilisateur et obtiennent « Permission denied ». Plutôt que d’assouplir les droits, un deploy hook copie les fichiers après chaque renouvellement vers un emplacement lisible par l’application :

#!/bin/sh
# /etc/letsencrypt/renewal-hooks/deploy/tomcat.sh  (à rendre exécutable)
for f in cert.pem chain.pem fullchain.pem privkey.pem; do
  install -o tomcat -g tomcat -m 0600 "$RENEWED_LINEAGE/$f" /opt/tomcat/conf/tls/$f
done
systemctl restart tomcat

Tomcat avec des fichiers PEM

Tomcat 9, 10 et 11 lisent directement le PEM, sans keystore. Dans conf/server.xml :

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           SSLEnabled="true" maxParameterCount="1000">
  <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
  <SSLHostConfig protocols="TLSv1.2+TLSv1.3">
    <Certificate certificateFile="conf/tls/cert.pem"
                 certificateKeyFile="conf/tls/privkey.pem"
                 certificateChainFile="conf/tls/chain.pem" />
  </SSLHostConfig>
</Connector>

Grâce à certificateChainFile, Tomcat livre aussi l’intermédiaire. Pour recharger le certificat sans redémarrage, utilise le manager : /manager/text/sslReload. L’utilisateur a besoin du rôle manager-script.

Spring Boot avec un SSL bundle

Depuis Spring Boot 3.1, on décrit les certificats sous forme de SSL bundle. Depuis 3.2, Spring Boot les recharge lui-même quand ils changent ; le redémarrage dans le hook devient inutile :

server.port=8443
server.ssl.bundle=web
spring.ssl.bundle.pem.web.keystore.certificate=file:/opt/app/tls/fullchain.pem
spring.ssl.bundle.pem.web.keystore.private-key=file:/opt/app/tls/privkey.pem
spring.ssl.bundle.pem.web.reload-on-update=true

Spring Boot 2.7 et 3.0 n’ont pas encore de bundles, mais le PEM fonctionne quand même :

server.ssl.certificate=file:/opt/app/tls/fullchain.pem
server.ssl.certificate-private-key=file:/opt/app/tls/privkey.pem

Versions plus anciennes : créer un keystore

Les anciennes versions de Tomcat ou Spring Boot ont besoin d’un keystore. Le hook le recrée alors à chaque renouvellement :

openssl pkcs12 -export -in "$RENEWED_LINEAGE/fullchain.pem" -inkey "$RENEWED_LINEAGE/privkey.pem" \
  -name tomcat -out /opt/app/tls/keystore.p12 -passout pass:secret
server.ssl.key-store=file:/opt/app/tls/keystore.p12
server.ssl.key-store-type=PKCS12
server.ssl.key-store-password=secret
server.ssl.key-alias=tomcat

L’assistant keystore Java fait la même chose dans le navigateur : colle certificat et clé, enregistre en PKCS#12.

Le port 443 sans root

Java ne devrait pas tourner en root et ne peut donc pas ouvrir de ports sous 1024. On démarre en général l’application sur 8443 et on redirige 443 dans le pare-feu :

sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443

Autre option : AmbientCapabilities=CAP_NET_BIND_SERVICE dans l’unité systemd. Vérifie ensuite chaîne et protocoles avec la vérification SSL et ajoute le serveur à l’aperçu des expirations.

FAQ

Faut-il redémarrer Tomcat après chaque renouvellement ?

Avec Tomcat oui, ou utilise sslReload dans le manager. Spring Boot 3.2+ avec reload-on-update recharge lui-même les fichiers modifiés.

Let's Encrypt fonctionne-t-il pour des serveurs internes ?

Oui, avec le défi DNS-01. Le serveur n’a pas besoin d’être joignable depuis Internet ; il suffit d’un enregistrement TXT dans le DNS public.

Tomcat affiche « Cannot recover key » ou « keystore password was incorrect ».

Les mots de passe du keystore et de la clé diffèrent, ou l’alias est faux. En PKCS#12, les deux mots de passe doivent être identiques.

Une clé ECDSA fonctionne-t-elle avec Java ?

Oui, toutes les versions actuelles de Java gèrent ECDSA. Pour de très vieux clients, certbot peut obtenir un certificat RSA avec --key-type rsa.