Traefik (reverse-proxy) et Portainer
Traefik est un load-balancer HTTP & TCP et reverse-proxy pour conteneurs. Portainer est l'interface graphique de gestion de conteneurs open source par excellence pour Kubernetes, Docker, Swarm et ACI.
Traefik avec A+ sur SSL Labs et les headers
https://github.com/Khroners/Traefik-with-A-plus-on-SSL-Labs-Headers
Traefik est un reverse-proxy pour des conteneurs (ici, Docker). La connexion s'établie directement avec ce dernier. C'est pour cela qu'il est important d'assurer la sécurité de l'accès, en utilisant le protocole HTTPS avec TLS et des certificats. On peut renforcer la connexion en utilisant HTTP Strict Transport Security (HSTS).
L'en-tête de réponse HTTP Strict-Transport-Security (souvent abrégé en HSTS) permet à un site web d'indiquer aux navigateurs qu'il ne doit être accessible qu'en utilisant HTTPS, au lieu d'utiliser HTTP.
On y ajoute des sécurités au niveau des Headers, des Ciphersuites et la version du protocole TLS.
Les en-têtes HTTP permettent au client et au serveur de transmettre des informations supplémentaires avec la requête ou la réponse.
Une suite de chiffrement est un ensemble d'algorithmes qui permettent de sécuriser une connexion réseau. Les suites utilisent généralement le protocole TLS (Transport Layer Security) ou son prédécesseur SSL (Secure Socket Layer), désormais obsolète. L'ensemble d'algorithmes que contiennent généralement les suites de chiffrement comprend : un algorithme d'échange de clés, un algorithme de chiffrement global et un algorithme de code d'authentification de message (MAC).
Tout d'abord, le docker-compose. Il permet le déploiement de Traefik et de Portainer (permet la création de stacks, avec support de Kubernetes).
Ensuite, les fichiers de configurations. Il en existe deux types : statiques et dynamiques. Le statique (traefik.yml) définit les points d'entrées et les "providers" : docker et les fichiers de configuration dynamiques.
Les fichiers dynamiques (tls.yml et config.yml) définissent les middlewares, la redirection HTTPS, les headers et les options TLS. Dans mon cas, j'utilise un certificat wildcard déjà existant, mais Traefik supporte Let's Encrypt pour créer un certificat par service.
Le docker-compose (version des images à mettre à jour) :
# By Khroners
version: '2'
services:
traefik:
image: traefik:2.4.6 #don't use latest tag
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- 80:80
- 443:443
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /apps/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- /apps/traefik/config/:/etc/traefik/config/:ro
# Uncomment this line if not using own certificate
# - /apps/traefik/acme.json:/acme.json
- /etc/letsencrypt/archive/khroners.fr-0001/:/certs:ro # Edit the path of your certificates
labels:
- traefik.enable=true
- traefik.http.routers.traefik.entrypoints=http
- traefik.http.routers.traefik.rule=Host("traefik.khroners.fr")
- traefik.http.middlewares.traefik-auth.basicauth.users=admin:{SHA}0DPiKuNIrrVmD8IUCuw1hQxNqZc=
- traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https
- traefik.http.routers.traefik.middlewares=traefik-https-redirect
- traefik.http.routers.traefik-secure.entrypoints=https
- traefik.http.routers.traefik-secure.rule=Host("traefik.khroners.fr")
- traefik.http.routers.traefik-secure.middlewares=traefik-auth
- traefik.http.routers.traefik-secure.tls=true
# Uncomment this line if not using own certificate
# - traefik.http.routers.traefik-secure.tls.certresolver=http
- traefik.http.routers.traefik-secure.service=api@internal
portainer:
image: portainer/portainer-ce:2.1.1 #don't use latest. check Docker-hub
container_name: portainer
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /apps/portainer/data:/data #edit /apps/portainer/data to the path you want
labels:
- traefik.enable=true
- traefik.http.routers.portainer.entrypoints=http
- traefik.http.routers.portainer.rule=Host("portainer.khroners.fr")
- traefik.http.middlewares.portainer-https-redirect.redirectscheme.scheme=https
- traefik.http.routers.portainer.middlewares=portainer-https-redirect
- traefik.http.routers.portainer-secure.entrypoints=https
- traefik.http.routers.portainer-secure.rule=Host("portainer.khroners.fr")
- traefik.http.routers.portainer-secure.tls=true
# Uncomment this line if not using own certificate
# - traefik.http.routers.portainer-secure.tls.certresolver=http
- traefik.http.routers.portainer-secure.service=portainer
- traefik.http.services.portainer.loadbalancer.server.port=9000
- traefik.docker.network=proxy
networks:
proxy:
external: true
Traefik.yml :
api:
dashboard: true
entryPoints:
http:
address: ":80"
https:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
directory: /etc/traefik/config/
watch: true
#uncomment if not using own certificate
#certificatesResolvers:
# http:
# acme:
# email: email@exemple.net
# storage: acme.json
# httpChallenge:
# entryPoint: http
Config.yml (Dossier config) :
# By Khroners
http:
middlewares:
https-redirect:
redirectScheme:
scheme: https
hsts-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
forceStsHeader: true
referrerPolicy: same-origin
customResponseHeaders:
permissions-Policy: vibrate=(self), geolocation=(self), midi=(self), notifications=(self), push=(self), microphone=(), $
X-Permitted-Cross-Domain-Policies: none
expect-ct: max-age=604800, report-uri="https://oak.ct.letsencrypt.org/2021"
Tls.yml (Dossier config) :
# Dynamic configuration
# by Khroners
tls:
options:
default:
minVersion: VersionTLS12
sniStrict: true
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # TLS 1.2
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 # TLS 1.2
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # TLS 1.2
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 # TLS 1.2
- TLS_AES_256_GCM_SHA384 # TLS 1.3
- TLS_CHACHA20_POLY1305_SHA256 # TLS 1.3
- TLS_FALLBACK_SCSV # TLS FALLBACK
curvePreferences:
- secp521r1
- secp384r1
modern:
minVersion: VersionTLS13
# Comment below if not using own certificate
certificates:
- certFile: "/certs/fullchain2.pem" #certificate path in the container
keyfile: "/certs/privkey2.pem" #private key path in the container
stores:
- default
stores:
default:
defaultCertificate:
certFile: "/certs/fullchain2.pem" #certificate path in the container
keyFile: "/certs/privkey2.pem" #private key path in the container
On pourrait renforcer l'échange de clés mais cela rendrait l'accès impossible à certains navigateurs (anciennes versions).
Un exemple pour ce site :
Feature-Policy n'est pas présent, car remplacé récemment par un autre entête, présent lui ici (permissions-Policy).
Pour appliquer cela aux conteneurs, il faut que celui-ci soit dans le réseau du Traefik (dans mon cas, "proxy") et d'ajouter les labels au docker-compose.
Voici un exemple avec Bookstack (ce site) :
version: "3.2"
services:
# BookStack : https://www.bookstackapp.com/
bookstack:
image: linuxserver/bookstack:version-v21.04
container_name: $SERVICE
environment:
- PUID=1000
- PGID=1000
- DB_HOST=bookstack_db
- DB_USER=$DB_USER
- DB_PASS=$DB_PASSWORD
- DB_DATABASE=bookstackapp
- APP_URL=https://$SERVICE.$NDD
volumes:
- $DATA_LOCATION/config:/config
# ports:
# - 6875:80
restart: unless-stopped
depends_on:
- bookstack_db
# Facultatif
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.$SERVICE.entrypoints=http"
- "traefik.http.routers.$SERVICE.rule=Host(`$SERVICE.$NDD`)"
- "traefik.http.middlewares.$SERVICE-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.$SERVICE.middlewares=$SERVICE-https-redirect"
- "traefik.http.routers.$SERVICE.middlewares=hsts-headers@file"
- "traefik.http.routers.$SERVICE-secure.entrypoints=https"
- "traefik.http.routers.$SERVICE-secure.rule=Host(`$SERVICE.$NDD`)"
- "traefik.http.routers.$SERVICE-secure.middlewares=hsts-headers@file"
- "traefik.http.routers.$SERVICE-secure.tls=true"
- "traefik.docker.network=proxy"
# Base de données
bookstack_db:
image: linuxserver/mariadb
container_name: bookstack_db
environment:
- PUID=1000
- PGID=1000
- MYSQL_ROOT_PASSWORD=$DB_ROOT
- TZ=Europe/Paris
- MYSQL_DATABASE=bookstackapp
- MYSQL_USER=$DB_USER
- MYSQL_PASSWORD=$DB_PASSWORD
volumes:
- $DATA_LOCATION/db:/config
restart: unless-stopped
# Facultatif
networks:
- proxy
networks:
proxy:
external:
name: proxy
On observe que chaque conteneur est dans le réseau "proxy", et il est définie en bas du docker-compose. Les labels sont rajoutés pour le conteneur exposé (ici, bookstack).
Portainer
Le déploiement se réalise dans le livre précédent.
Des templates sont disponibles. On peut en rajouter.
La liste des stacks :
C'est ici que l'on déploie nos stacks, nos applications, via un docker-compose ou un template.
On peut définir des variables ici.
On peut également voir le statut des conteneurs, les stopper, allumer, redémarrer ainsi que les logs.
On a la liste des conteneurs :
On a également la liste des images, et on peut en télécharger.
Ici, les réseaux :
On peut gérer les utilisateurs et les groupes, avec leurs permissions.
On peut ajouter d'autres hôtes.
On peut ajouter des registres Docker (Serveur d'images Docker, Dockerhub en est un)
Continuité du service
Pour que le service reste disponible :
- Sauvegarder régulièrement les conteneurs (mysqldump pour les bases de données de type SQL)
- Avoir un OS à jour
- Docker et ses composants à jour
- Dans les Docker-compose, insérer une option de redémarrage (unless-started par exemple)
- Pourvoir redéployer le service rapidement (Ici, c'est le cas, car les conteneurs sont légers)
- Traefik doit toujours être en service, on peut envisager de la haute disponibilité
- Connectivité entre Traefik et les autres conteneurs
- Mettre à jour les images régulièrement (requiert un redémarrage du/des conteneur(s) concerné(s), mais cela est rapide)
- Connectivité internet toujours disponible