Traefik 2 - Configuration du TLS (Rang A+ sur SSLLabs)

Traefik 2 - Configuration du TLS (Rang A+ sur SSLLabs)

Mise à jour le 22/12/2021

Ce contenu a été mis à jour le 22/12/2021 pour pointer vers la version 2.5.5 de Traefik Proxy et Kubernetes 1.20, le middleware modifiant les headers, ainsi que les ciphers autorisés ont aussi été mis à jour

Disclaimer : De part les descriptions que je donne ci dessous, ce post sera plus long que mes post habituels.

La sécurité est l'affaire de tous. En tant qu'architecte sécurité, je suis quelqu'un d'assez sensible à ces sujets.

Comme j'en ai parlé dernièrement, j'ai migré le reverse proxy de ce blog de Traefik 1 à Traefik 2 dernièrement. J'ai donc décidé de configurer ensuite la partie TLS, mon but étant d'avoir un site sécurisé (et donc sécurisant pour mes utilisateurs) en laissant Traefik gérer toute cette partie.

Je ne vais pas faire un post en vous indiquant simplement quoi faire, ce qui n'aurait aucun autre intérêt que de vous faire copier ma configuration, mais plutôt vous expliquer en quoi consiste les couches de sécurité que je met en place, puis comment je les ai mises en place. Comme le dit l'adage : "Si tu donnes un poisson à un homme, il mangera un jour. Si tu lui apprends à pêcher il mangera toujours.". Par ce post, fini l'estomac vide!

Les différentes couches de sécurité utilisées

Le TLS

Le TLS est un protocole permettant l'échange d'informations sécurisées entre un client et un serveur. Il est l'évolution logique du SSL. J'avais expliqué le principe de base du TLS dans un de mes anciens posts, que je vous invite à lire si vous souhaitez plus d'informations.

Il faut ensuite comprendre qu'il y a plusieurs version de TLS allant de TLS 1.0 à TLS 1.3 et prendre en compte le fait que tous les navigateurs et OS ne prennent pas forcément en charge toutes les versions TLS. De manière générale, on autorisera la plupart du temps le TLS 1.2 et 1.3 (avec quelques nuances dont je parlerais ensuite) qui sont les versions les plus récentes. le TLS 1.0 et 1.1 sont obsolètes et à peu près équivalents à ne pas mettre de protection du tout.

Ensuite le TLS est composé de sous parties, les ciphers et curves. Ce sont ces derniers qui vont permettre de configurer finement ce protocole.

Un cipher (suite cryptographique en français) est l'algorithme de chiffrement utilisé par le serveur pour négocier avec son client. En effet, lors d'une connexion, le client indique au serveur les algorithmes qu'il supporte et lui répondra donc avec l'algorithme le plus fort disponible si cela est possible. Tout comme la version TLS, les ciphers ne sont pas compatibles avec tous les navigateurs et/ou OS, il s'agit donc de choisir avec soin les algorithmes que l'on autorise en étant conscient que certains clients ne pourront pas se connecter sur notre site, ou en tout cas, sans profiter du chiffrement en transit.

Ensuite viennent les elliptic curves (Courbe elliptique en français) qui sont associées avec certins ciphers (les ciphers dit elliptiques) et vont permettre de définir la façon dont le cipher va faire ses calculs.

Les ciphers sont très nombreux, et il peut être difficile de savoir lesquels choisir. Dans ce cas, je vous propose de regarder en annexe de ce document quelques liens utiles.

Vous me direz dans ce cas, je peux aussi tout autoriser vu que le navigateur choisit le protocole le plus fiable ... et bien non! Il existe aussi une méthode nommé le downgrade TLS qui consiste à réduire la version TLS utilisée, même sur un navigateur récent, ou à utiliser un cipher non sécurisé. Je vous parlerais plus bas des impacts que cela peut avoir.

Avec ces explications, vous devez vous dire que le TLS ralentit la négociation HTTP dans ce cas, puisque je fait faire plus de calcul à mon serveur. Dans l'absolu, c'est vrai, néanmoins, avec les performances actuelle des serveurs, même pour un raspberry pi (qui je le rappelle fonctionne avec un quad core 1.2GHz et 1 Go de RAM) ces coûts en performances sont négligeables. D'autant plus avec le TLS 1.3 qui réduit le nombre d'aller retour.

Les headers renvoyés par le serveur

Une autre couche de sécurité disponible passe par les headers que l'on renvoie depuis le serveur vers le navigateur. Ces headers vont permettre d'indiquer au navigateur le comportement à suivre et vont permettre d'indiquer notre politique de sécurité.

Attention toutefois, il faut garder en tête que les headers sont indicatifs, et peuvent tout à fait être ignorés par le navigateur.

Les headers que j'ai définis sont divisé en deux parties, dans un premier temps, les headers "utilisateur", nous allons en trouver 4 ici :

  • frameDeny : indique au navigateur que la page ne peut pas être chargée dans une iframe
  • sslRedirect : Permet d'indiquer de rediriger une connexion http vers du https lorsque c'est possible
  • browserXssFilter : Permet de limiter les attaques de type "Cross Site Scripting" depuis le navigateur (plus d'infos sur le XSS ici)
  • contentTypeNosniff : Indique au navigateur de ne pas faire de détection de mime type, le but ici est de limiter les attaques de type "Drive by download" (plus d'infos sur le drive by download ici)

Nous avons ensuite les headers HSTS (HTTP Strict Transport Security), ces headers vont indiquer au navigateur de ne communiquer que de manière sécurisée avec le serveur. La différence majeure par rapport à un simple header 301/Redirect vers du https est que le navigateur n'enverra aucune information vers le serveur, comme par exemple les cookies utilisateur. En cas d'attaque de type man in the middle, un site enregistré en HSTS avec du preloading sera donc protégé. Nous avons ici 3 headers :

  • stsSeconds : Indique au navigateur pendant combien de temps ce domaine doit être accéder exclusivement en https. Pour pouvoir utiliser le HSTS Preloading, il est nécessaire que ce paramètre soit d'au moins 31536000 secondes (un an).
  • stsPreload : Permet d'indiquer que ce site supporte le preloading HSTS, cela permet de sécuriser la connexion dès la première tentative au lieu de la seconde. Le preloading nécessite d'enregistrer son site au préalable sur https://hstspreload.org/ qui permettra qu'il soit directement présent dans le navigateur.
  • stsIncludeSubdomains : Permet d'indiquer que nous souhaitons que tous les sous domaines rattachés au domaine demandé soient aussi en HSTS

Note importante sur le HSTS : Afin de pouvoir utiliser sereinement le HSTS avec les fonctions de preloading, il est nécessaire de s'assurer que le https est prévu pour être utilisé à long terme sur le site. Il est simple de s'enregistrer en HSTS, il est difficile de demander la suppression de son site. De plus une fois un site enregistré, le non support du https peut empêcher les utilisateurs de se connecter.

Ajout d'une entrée DNS : le champ CAA

Nous allons maintenant évoquer la dernière action que nous allons effectuer, à savoir créer une nouvelle entrée DNS, de type CAA (Certification Authority Authorization).

Cette entrée permet d'indiquer quelles sont les autorités que nous avons autorisé à délivrer un certificat pour ce domaine. Ce champ n'est pas prévu pour les navigateurs, mais pour les autorités de certifications. Cela permet en cas de détournement d'un record DNS de ne délivrer le certificat qu'avec certaines autorités.

Pour ma part le champ CAA est le suivant, il permet donc d'indiquer que mon autorité de certification "reconnue" est Let's Encrypt :

-sh-4.2$ dig CAA tferdinand.net

; <<>> DiG 9.9.4-RedHat-9.9.4-74.el7_6.1 <<>> CAA tferdinand.net
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48603
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;tferdinand.net.                        IN      CAA

;; ANSWER SECTION:
tferdinand.net.         86400   IN      CAA     0 issue "letsencrypt.org"

;; Query time: 10 msec
;; SERVER: 213.186.33.99#53(213.186.33.99)
;; WHEN: Tue Jan 14 06:27:49 CET 2020
;; MSG SIZE  rcvd: 77

Je ne documenterai pas dans ce post comment créer ce champ, car la procédure est propre à votre serveur DNS.

Pourquoi il est important de passer en full HTTPS, quelque soit le site ?

Depuis le début de ce post, je vous dis que le https est important. Vous me direz donc que je suis bien gentil, mais que ce site (par exemple) ne paraît pas vraiment très sensible. Mais le full https apporte de nombreux avantages :

  • C'est sécurisant pour l'utilisateur : Personne ne veut voir en allant sur un site la petite indication près de la barre d'adresse "Ce site n'est pas sécurisé". Ce n'est pas rassurant pour un utilisateur. De plus de nombreux sites utilisent des interfaces de back-office avec authentification, cela permet donc d'échanger de manière chiffrée les informations et de se prémunir d'attaques de type "man in the middle".
  • C'est sécurisant pour vous : Le https, et notamment le preloading HSTS, permettent de prévenir que l'on usurpe pas aussi facilement votre domaine pour publier des malwares par exemple.
  • C'est important pour votre SEO : De plus en plus de moteurs de recherche prennent en compte la configuration du https dans le référencement. Avoir un site sécurisé permet donc d'être mieux référencé sur le web.
  • C'est simple d'avoir un certificat : Avoir un certificat HTTPS est maintenant une question de secondes, il est aussi possible d'en obtenir gratuitement, il n'y a plus d'excuses pour ne pas en utiliser un !

Configurer Traefik 2 pour fonctionner en full HTTPS

Pour configurer les points que j'ai décrit plus haut (sauf le CAA), nous allons donc utiliser les fonctionnalité de middlewares de Traefik 2.

Ces middlewares se placent en interception entre le frontend et le backend, et permettent donc de modifier le comportement avant d'atteindre le backend.

Image de la documentation officielle de Traefik : https://docs.traefik.io/middlewares/overview/

Dans un premier temps, nous allons donc créer un middleware. Grâce au CRD de Traefik, le middleware est un objet Kubernetes standard. Nous créons donc le fichier de configuration suivant :

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: security
  namespace: default
spec:
  headers:
    addVaryHeader: true
    customFrameOptionsValue: sameorigin
    sslRedirect: true
    browserXssFilter: true
    contentTypeNosniff: true
    
    #HSTS Configuration
    stsIncludeSubdomains: true
    stsPreload: true
    stsSeconds: 31536000
    
    # Hide powered by headers
    customResponseHeaders:
      x-powered-by: ""

De ce fait, nous allons donc dire à Traefik de renvoyer les headers que je vous indique plus haut.

Nous appliquons ensuite cette modification avec kubectl.

Ensuite nous allons donc ajouter une ligne dans notre IngressRoute http et https pour indiquer à Traefik d'utiliser ce middleware.

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-web-ui
  namespace: default
spec:
  entryPoints:
    - web
  routes:
  - kind: Rule
    match: (Host(`www.tferdinand.net`) || Host(`tferdinand.net`)) && PathPrefix(`/`)
    services:
    - name: ghost-tfe-fr
      port: 2368
      healthcheck:
        path: /
        host: tferdinand.net
        intervalSeconds: 10
        timeoutSeconds: 5
    middlewares:
      - name: security

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-web-ui-tls
  namespace: default
spec:
  entryPoints:
    - websecure
  routes:
  - kind: Rule
    match: (Host(`www.tferdinand.net`) || Host(`tferdinand.net`)) && PathPrefix(`/`)
    services:
    - name: ghost-tfe-fr
      port: 2368
      healthcheck:
        path: /
        host: tferdinand.net
        intervalSeconds: 10
        timeoutSeconds: 5
    middlewares:
      - name: security
  tls:
    certResolver: le
    options:
      name: mytlsoption
      namespace: default

Vous pouvez voir au lignes 20 et 43 l'ajout du middleware. Pour utiliser pleinement le HSTS, il est important que ce middleware soit présent en HTTP et HTTPS.

Enfin, nous allons créer un autre objet Kubernetes, encore une fois créé par nos CRD, qui se nomme TLSOptions. Comme son nom l'indique, cet objet va permet d'indiquer à Traefik comment se comporter au niveau du TLS.

apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
  name: mytlsoption
  namespace: default
spec:
  minVersion: VersionTLS12
  cipherSuites:
    - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
    - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
  sniStrict: true

On retrouve dans ce fichier les options du TLS tel que je vous l'ai indiqué plus haut. Comme vous pouvez le voir, j'ai choisi de ne pas supporter en dessous du TLS 1.2 et seulement certains ciphers et curves. De plus j'ai indiqué d'utiliser exclusivement mes certificats en mode SNI (Server Name Indication, plus d'informations ici). Il est a noter que cette configuration est assez stricte et ne permet de fait pas aux navigateurs et/ou OS anciens et obsolètes de se connecter.

Concernant le TLS 1.3, j'ai indiqué des ciphers, néanmoins il faut prendre en compte qu'ils sont actuellement ignorés par Traefik (comme indiqué dans la documentation officielle qui renvoie vers la documentation GoLang).

On applique ensuite nos changements.

Il vous est maintenant possible de tester votre site sur SSLLabs, et vous devriez avoir sans souci une évaluation très positive. A noter que ce test n'est ni intrusif, ni risqué. Le test consiste à émuler des navigateurs/OS pour observer le comportement du site, et contrôler la possibilité de certains hacks par les informations renvoyées par le serveur.

Annexes

Vous trouverez ci dessous quelques liens utiles pour configurer votre serveur :

"Recommandations de sécurité relatives à TLS" publié par l'ANSSI : Un document très complet pour expliquer la configuration du TLS préconisé par l'ANSSI, attention toutefois, il s'adresse à un public averti.

Le générateur de configuration TLS de Mozilla (compatible avec Traefik 2) : Un outil très complet qui va vous permettre de générer toute la configuration que je vous ai décrit de manière simple.