Aller au contenu principal

Certificats TLS wildcard avec Letsencrypt, Docker, Traefik via OVH Cloud et Gandi.net

· 8 minutes de lecture
Jonas Turbeaux

/img/blog/traefik_docker_letsencrypt.png

Vous avez dit sauvage ?

Un certificat "wildcard" C'est un certificat qui répond à tous les sous-domaines d'un domaine. Par exemple, si vous avez un certificat wildcard pour le domaine *.example.com, il sera valide pour foo.example.com, bar.example.com, prout.example.com, etc.

Vous voyez le * ? C'est un caractère joker, qui signifie "n'importe quoi". C'est pour cela qu'on appelle ça un certificat wildcard, un sauvage.

Pourquoi faire ? Ben c'est quand même 'achement pratique si on doit générer des sites en mode Saas, software as a service. TiBillet, par exemple, est construit en mode multi-tenant. Cela veut dire un noyau unique pour plusieurs instances fédérées. Chaque instance est un tenant, et chaque tenant possède son propre sous-domaine.

Dans le cas d'un déploiement en mode "logiciel à la demande", c'est à dire avec des instances créées automatiquement en quelques clics, avoir un seul certificat valide pour les adresses comme https//raffinerie.tibillet.re/ ou https://rezom.tibillet.re/ (notez le s de https), c'est quand même assez chouette, non ?

Let's Encrypt et Traefik, l'ultra combo.

Pour convaincre Letsencrypt de nous donner un certificat wildcard, il faut faire un petit peu plus de chose que pour un certificat simple. En plus de lui prouver que nous avons bien la main sur le serveur qui répond à l'ip résolu par le nom de domaine, il faut ausi lui prouver que nous sommes les proprietaires de la totalité du domaine. Autrement dit : que nous avons les droits d'écritures de toute la zone DNS.

Pour cela, Letsencrypt va nous demander d'ajouter un champ TXT dans la zone DNS du domaine. Ce champ contient une clé créé par Letsencrypt, s'il arrive à le lire, il va donc considérer à raison que nous avons bien les droits d'écriture sur la zone DNS. Donc que nous sommes le proprio.

Le truc, c'est qu'un certificat doit se renouveller régulièrement, et que donc, il faut que ce champ TXT soit présent et différent à chaque renouvellement. Et on va pas se le cacher, c'est un peu contraignant et fastidieux de le faire à la main.

C'est la qu'intervient Traefik. Traefik est un reverse proxy, c'est à dire un serveur qui va rediriger les requêtes http vers le bon conteneur docker. Il est capable de faire plein de choses, mais ce qui nous intéresse ici, c'est qu'il est capable de gérer les certificats SSL, et même de renouveller automatiquement les widlcards.

Traefik et Letsencrypt fonctionnent merveilleusement bien ensemble. Le premier va poser la question au second, puis va modifier la zone DNS automatiquement pour que le champs TXT soit à jour régulièrement. Cela nécessite de donner les trois d'écriture à notre Zone à notre conteneur Traefik. Pas de soucis particuliers, l'instance Traefik est hebergé sur notre serveur, nous avons donc la main dessus..

Voyons comment mettre tout ça en place.

Prérequis

  • Un nom de domaine : Ici, nous allons voir la methode avec Gandi et avec OVH
  • Docker ( et docker-compose ) : https://docs.docker.com/install/
  • Traefik : https://docs.traefik.io/
  • Un serveur ou heberger tout ça avec une ip statique et les ports 80 et 433 corectement redirigé. On utilise des VPS sous Ubuntu, mais ça marche aussi avec une autre distribution ou même un raspberry pi.

Installation

Je vous invite à aller du coté d'une précédente note de blog qui détaille comment préparer votre serveur pour la suite : https://codecommun.coop/blog/sysadmin-mon-chaton-part1/

Une légère différence ceci, nous allons utiliser le fichier docker-compose.yml du dossier "wildcard" de notre dépot github Traefik : https://github.com/TiBillet/Traefik-reverse-proxy/tree/main/wildcard

Si vous avez déja lancé le docker-compose.yml de la note de blog, vous pouvez le stopper et le supprimer et utiliser celui-ci à la place.

Si vous n'avez de compte ni sous OVH ni chez Gandi, pas d'inquiétude, Traefik supporte beaucoup de registrar : https://doc.traefik.io/traefik/https/acme/?ref=j.hommet.net#dnschallenge

Commençons par créer un nouveau champ A vers l'ip de votre serveur, avec comme sous domaine *.

* 10800 IN A <IP>

Ensuite, allons générer une clé API pour donner les droits d'écriture dans cette zone à Traefik :

Avec OVH Cloud

Comme nous le disions, nous allons avoir besoin de donner les droits d'écriture à Traefik. Il vous faut bien sur un compte ovh valide et avoir acheté un nom de domaine.

  • Allez sur https://eu.api.ovh.com/createApp/

  • Logguez vous, créez une nouvelle app et récuperez les cred'

  • Dans le même dossier que le docker-compose, Créez un fichier .env et remplissez les deux premières variables avec les cred' récupérés précédemment.

OVH_APPLICATION_KEY=""
OVH_APPLICATION_SECRET=""
OVH_CONSUMER_KEY=""
  • Lancez cette commande curl pour donner les droits de modification à l'application. Remplacez OVH_APPLICATION_KEY et VOTRE_DNS, avec vos propres valeurs.
curl -XPOST -H"X-Ovh-Application: <OVH_APPLICATION_KEY>" -H "Content-type: application/json" \
https://eu.api.ovh.com/1.0/auth/credential -d '{
"accessRules": [
{
"method": "POST",
"path": "/domain/zone/<VOTRE_DNS>/record"
},
{
"method": "POST",
"path": "/domain/zone/<VOTRE_DNS>/refresh"
},
{
"method": "DELETE",
"path": "/domain/zone/<VOTRE_DNS>/record/*"
}
],
"redirection":"https://www.<VOTRE_DNS>/"
}'

Si vous n'avez pas curl, installez le. Sinon, vous pouvez aussi utiliser l'application en ligne de OVH. Je vous laisse découvrir par vous-même.

  • Copiez le consumer_key et rentrez la variable correspondante dans le .env
  • Validez la requete avec le lien envoyé dans la réponse. Pensez à mettre unlimited dans la durée des droits d'execution car Traefik renouvelle le widlcard tous les trois mois.

Avec Gandi.net

Un ptit peu plus simple du coté de Gandi, il suffit de générer une clé API dans votre compte user / Authentication option / API key.

Dans votre .env, ajoutez la variable suivante :

GANDI_API_KEY="PLOUFPLOUFDEVINEMOI"

Décollage

Completez votre .env avec EMAIL et DOMAIN, paske c'est le votre et pas le mien :

DOMAIN="pingoin-sauvage.coop"
EMAIL="tux42@pingoin-sauvage.coop"

Et voici le compose magique :

version: "3"
services:
traefik:
image: "traefik:saintmarcelin"
container_name: "traefik_wild"
restart: always
command:
- --log.level=DEBUG
- --api.insecure=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false

- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https

- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --entrypoints.websecure.http.tls.certResolver=letsencrypt
- --entrypoints.websecure.http.tls.domains[0].main=${DOMAIN}
- --entrypoints.websecure.http.tls.domains[0].sans=*.${DOMAIN}

- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=gandiv5
- --certificatesresolvers.letsencrypt.acme.dnschallenge.delayBeforeCheck=60
- --certificatesResolvers.letsencrypt.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
- --certificatesresolvers.letsencrypt.acme.email=${EMAIL}
- --certificatesresolvers.letsencrypt.acme.storage=/dnschallenge/acme.json

- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory

ports:
- "80:80"
- "443:443"
environment: # choisissez votre provider. Gardez ceux de Gandi, ou ceux d'ovh, ou ceux indiqué par la doc Traefik.
- "GANDIV5_API_KEY=${GANDIV5_API_KEY}"
- OVH_APPLICATION_KEY=${OVH_APPLICATION_KEY}
- OVH_APPLICATION_SECRET=${OVH_APPLICATION_SECRET}
- OVH_CONSUMER_KEY=${OVH_CONSUMER_KEY}

volumes:
- "./dnschallenge:/dnschallenge"
- "/var/run/docker.sock:/var/run/docker.sock:ro"

networks:
- frontend

networks:
frontend:
external: true


Attention ! Deux choses à noter :

Pour gandi, ne touchez pas la ligne suivante :

      - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=gandiv5

Pour OVH, ou pour un autre registrar, il faudra bien le changer :

        - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=ovh

C'est parti !

# On lance le conteneur
docker compose up -d
# on check les logs
docker logs -f

Attendez quelques instants que les logs se calment, et allons tester tout ceci avec un conteneur de test.


version: "3"
services:
whoami:
image: "traefik/whoami"
container_name: "simple-service"
labels:
- traefik.enable=true
- traefik.docker.network=frontend
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
- traefik.http.routers.whoami.tls.domains[0].main=${DOMAIN}
- traefik.http.routers.whoami.tls.domains[0].sans=*.${DOMAIN}

- traefik.http.routers.whoami.rule=HostRegexp(`{sub:[a-zA-Z0-9-]+}.${DOMAIN}`) || Host(`${DOMAIN}`)
- traefik.http.routers.whoami.entrypoints=websecure

networks:
- frontend

networks:
frontend:
external: true

Notez la regex sur la règle DNS. Elle permet de matcher tous les sous-domaines de votre domaine. Sexy isn't it ?

Si tout ce passe bien, dans les logs de Traefik, vous devriez voir quelque chose comme ça :

 traefik_wild  | time="2023-11-08T16:40:36Z" level=debug msg="legolog: [INFO] [tibillet.coop] Server responded with a certificate."                                                            traefik_wild  | time="2023-11-08T16:40:36Z" level=debug msg="Certificates obtained for domains [tibillet.coop *.tibillet.coop]" providerName=letsencrypt.acme ACME CA="https://acme-v02.api.le
tsencrypt.org/directory"

Si non, remontez bien les logs, le problème est forcément indiqué dedans.

En production ?

Ahah ! Par ce que vous pensiez que c'était fini ?

Le compose de Traefik contien une petite astuce, nous avons utilisé le serveur de test de Letsencrypt. Pourquoi ? Eh bien le serveur de production qui génère de vrais certificats est soumis à une limite du nombre de demande.

Je l'ai découvert à mes dépens, en voulant tester un peu trop de fois mon système, j'ai dû attendre quelques heures pour pouvoir relancer un nouveau certificat... Le serveur de test nous permet par contre de se planter sans limite. J'adore.

Pour passer en production, lorsque les logs du conteneur traefik vous indiqueront que tout roule, il suffit de commenter la ligne suivante :

#      - --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory

Et hop, relancer en mode daemon :

docker compose up -d

Enfin, re-lancez le conteneur whoami et vérifier qu'il matche bien. Remplacez bien sur le DNS par le votre, lancez le conteneur, et allez vérifier sur votre navigateur que le certificat est valide pour tout les sous-domaines que vous souhaitez (https://patate.votre_dns , https://prout.votre_dns, etc...)

Si ça coince, rechargez, videz vos certifs auto générés, et enfin regardez les logs Traefik, ce dernier vous signalera forcement le problème dans un message d'erreur.

Sources et liens utiles :