Nous allons dans cet article installer notre blog wordpress en quelques minutes et facilement grâce à Docker et Traefik. Et sans négliger un aspect important : la sécurité.

Nous allons sécuriser notre blog de plusieurs façons :

  • Gérer ses en-têtes de sécurité afin d'obtenir un A/A+ sur securityheaders.com,
  • Un certificat SSL avec let's encrypt qui obtiendra lui un A+ sur ssllabs.com,
  • Protégez notre interface d'administration Wordpress avec un filtrage par IP.

J'ai déjà abordé plus en détails les tests de headers et de certificat dans 2 précédents articles, que vous pouvez retrouver sur cette page si cela vous intéresse :

Traefik 2 : Obtenir le rang A+ sur SSLLABS et SecurityHeaders
Vous avez réussi à exposer votre site sur le web mais celui-ci n’est pas encore en HTTPS ? Vous souhaitez sécuriser l’échange de données entre vos visiteurs et votre site ? Ou plus simplement, le référencement SEO de votre site nécessite le passage en HTTPS. Peu importe vos raisons, je vais vous exp…

Pré-requis

Pour réaliser cette installation, nous allons avoir besoin des outils suivants :

  • Une installation Docker,
  • docker-compose.

Quickstart

L'intégralité des configurations sont présentes sur github :

lfache/awesome-traefik
Welcome to Awesome Traefik, a collection of contributions around Traefik. - lfache/awesome-traefik

Afin de réaliser cette installation en quelques minutes, voici comment récupérer et lancer cette stack.

Récupération des fichiers :

git clone https://github.com/lfache/awesome-traefik/

Modifiez la configuration de Traefik afin de correspondre à vos informations, traefik.yaml ligne 31 dans le dossier Traefik :

cd traefik
vim traefik.yaml

Lancez l'instance de notre reverse-proxy :

docker-compose up -d

Maintenant, occupons nous de WordPress :

cd ../wordpress

Modifiez les secrets en générant vos propres mot de passe, ils sont dans le dossier secrets :

vim secrets/mysql-database.txt
vim secrets/mysql-password.txt
vim secrets/mysql-user.txt

Vous devez modifier les informations de labels pour Traefik dans le fichier docker-compose.yaml :

traefik.http.routers.wordpress.rule: 'Host(`wordpress.mydomain.com`)'
traefik.http.middlewares.wp-ipwhitelist.ipwhitelist.sourcerange: 'X.X.X.X/32'
traefik.http.routers.wordpress-admin.rule: 'Host(`wordpress.mydomain.com`) && (Path(`/wp-admin`)|| Path(`/wp-login.php`))'

Je n'ai mis ici que les lignes à modifier.

Enfin, lancez la stack WordPress :

docker-compose up -d

Vous pouvez ensuite vous connectez à votre installation via le nom de domaine que vous avez renseigné :

Quelques explications

La partie concernant notre reverse-proxy est un grand classique ! Mais voici tout de même un petit rappel à ce sujet.

Déclaration du service :

Je déclare mon service, et j'utilise la version 2.2 :

version: "3.7"
services:
  reverse_proxy:
    image: traefik:2.2

Cette version nous permet notamment :

  • D'effectuer la redirection HTTP -> HTTPS directement depuis notre entrypoint.
  • De mettre en place un middleware par défaut avec nos en-têtes de sécurité.

Traefik offre trois possibilités afin de déclarer sa configuration statique. Mon choix reste totalement personnel, voici globalement ma vision sur ce point :

  • Déclaration dans le fichier docker-compose.yml avec l'utilisation de command:

Exemple de configuration :

command: --providers.docker --entrypoints.web.address=:80 --providers.docker.exposedbydefault=false
Cette utilisation peut être idéale en cas d'installation rapide et ne nécessitant que très peu de configuration ( environnement de développement par exemple avec uniquement du HTTP ).  
  • Variables d'environnements.

Oui c'est possible... mais je vais être honnête, je n'ai jamais utilisé cette solution. Je n'en ai jamais eu l'intérêt et je préfère dans ce cas utiliser la configuration CLI.

Comme vous pouvez le constater, je fais donc le choix de la troisième option. L'utilisation d'un fichier de configuration qui peut être écrit en TOML ou YAML. Je vais plutôt utiliser ce dernier que je trouve plus lisible.

Pour "monter" ma configuration dans le container, je vais utiliser un bind mount :

    volumes:
      # traefik static configuration
      - ./traefik.yaml:/etc/traefik/traefik.yaml:ro

Enfin je vais créer un volume pour les certificats let's encrypt et mon dossier avec les configurations dynamiques :

    volumes:
      # custom folder with dynamic configuration
      - ./custom:/etc/traefik/custom:ro
      # ssl volumes to store acme.json
      - certs:/letsencrypt

Je déclare un réseau web qui sera mon interface WAN. Les containers ne nécessitant pas de reverse-proxy ne seront pas associés à ce réseau mais auront leurs propres LAN.

Configuration de Traefik :

Je vais configurer Traefik à l'aide de plusieurs fichiers YAML :

  • La configuration statique : traefik.yaml
  • La configuration dynamique dans le dossier custom

Dans la configuration statique, je déclare deux entrypoints :

entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https

  websecure:
    address: :443
    http:
      tls:
        certResolver: myresolver
      middlewares:
      - SecHeaders@file

Vous pouvez constater que toutes les requêtes arrivant sur le port 80 sont redirigées vers mon entrée sur le port 443. De plus, j'applique un middleware que je vais détailler juste après :

http:
  middlewares:
    SecHeaders:
      headers:
        frameDeny: true
        contentTypeNosniff: true
        browserXssFilter: true

        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsSeconds: 31536000

        contentSecurityPolicy: default-src 'self'; img-src 'self'; object-src 'none'
        referrerPolicy: "same-origin"
        featurePolicy: "vibrate 'self'; geolocation 'self'; midi 'self'; notifications 'self'; push 'self'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; vibrate 'self'; fullscreen 'self'"

Pour en apprendre plus à ce sujet, voici un lien très intéressant :

https://wiki.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Headers

Déclaration du service MySQL et WordPress :

Je ne vais pas m'attarder sur la déclaration des services MySQL et WordPress. Je reprends dans les grandes lignes, la documentation officielle présente sur le hub Docker.

🚩 Je tiens d'ailleurs à préciser que pour correspondre à cette documentation, j'ai utilisé une image MySQL en 5.7. Vous pouvez utiliser Mariadb:10 si vous le souhaitez. 🚩

Ma seule modification est l'ajout des secrets plutôt que d'utiliser les variables en clair dans le fichier docker-compose.yaml. Pour une mise en production, n'hésitez pas à modifier les emplacements des fichiers et surtout les ajouter à un éventuel .gitignore pour versionner vos déploiements.

J'ai ajouté quelques labels, pour la configuration de Traefik. Tout d'abord les éléments habituels :

traefik.enable: true
traefik.docker.network: traefik_web
traefik.http.routers.wordpress.entrypoints: websecure
traefik.http.routers.wordpress.rule: 'Host(`wp.dubarbu.fr`)'
traefik.http.services.wordpress.loadbalancer.server.port: 80

Mais également des éléments de sécurité :

# remove Server and X-Powered-By headers
traefik.http.middlewares.wp-headers.headers.customresponseheaders.Server: ''
traefik.http.middlewares.wp-headers.headers.customresponseheaders.X-Powered-By: ''
traefik.http.routers.wordpress.middlewares: 'wp-headers'

L'image wordpresse:latest utilise apache et laisse quelques headers trainer. Afin de laisser quelques informations inutiles sortir de mon serveur, je vais retirer ces headers à l'aide de Traefik.

Je vais également restreindre l'accès aux pages de login à mon IP publique par exemple :

# whitelist access to /wp-admin, replace X.X.X.X with your public IP  
# if you dont need restrict wp-admin, remove or comment next lines   
traefik.http.middlewares.wp-ipwhitelist.ipwhitelist.sourcerange: 'X.X.X.X/32'
traefik.http.routers.wordpress-admin.rule: 'Host(`wordpress.mydomain.com`) && (Path(`/wp-admin`)|| Path(`/wp-login.php`))'
traefik.http.routers.wordpress-admin.middlewares: 'wp-ipwhitelist'

Voici une notion que je n'ai jamais abordée sur le blog. L'ordre de priorité des règles !

Le poids est calculé en fonction de la longueur de celle-ci, exemple :

  • Host(foobar.traefik.com) : poids de 26
  • HostRegexp(.*\.traefik\.com) : poids de 30

Les rules sont alors lues du poids le plus important, vers le plus petit. Il est bien évidement possible de manipuler la priorité afin de s'assurer du placement d'une règle dans la file :

http:
  routers:
    Router-1:
      rule: "HostRegexp(`.*\.traefik\.com`)"
      entryPoints:
      - "web"
      service: service-1
      priority: 1

De mon côté, je n'ai pas besoin de le faire car ma règle est bien plus longue que ma requête générique. Elle sera donc traitée en premier !


Voilà, nous avons donc pu voir ensemble, comment mettre en place une stack composée de MySQL, WordPress et Traefik afin de publier notre blog.

Grâce aux options fournies par notre reverse-proxy, nous avons pu limiter les accès aux pages d'administration de notre site et même obtenir notre rang A+ sur SSLLABS et securityheaders.com :

SSLABS RANG A
HEADERS RANG A

Nous avons également pu sécuriser notre déploiement avec l'utilisation des secrets de Docker.

Bien évidement il ne s'agit pas de la seule solution pour installer WordPress dans un environnement Docker. Nous aurions pu utiliser une image WordPress fpm avec Nginx ou Apache avec ou sans reverse proxy... Bref il s'agit d'une façon de faire, la mienne ! J'espère juste quelle vous permettra de créer ... la vôtre !

En tout cas  n'hésitez pas à m'apporter des remarques ou des commentaires sur Twitter ! C'est toujours un plaisir d'avoir des retours ! 😇