Aller au contenu

Middleware headers Traefik — en-têtes de sécurité, CORS et réécriture de requête/réponse

Utiliser le middleware headers de Traefik pour HSTS, CSP, CORS et la réécriture — et les cas où chaque réglage fait réellement ce qu''on attend.

Le middleware headers de Traefik est le fourre-tout pour la manipulation d’en-têtes HTTP — en-têtes de sécurité (HSTS, X-Frame-Options, X-Content-Type-Options), CORS (origine, méthodes, identifiants), et réécriture arbitraire requête/réponse. Les valeurs par défaut sont raisonnables mais chaque champ a sa particularité : HSTS ne s’applique que quand la réponse est en HTTPS ; certains réglages CORS interagissent de façon non évidente ; et la chaîne vide customResponseHeaders est la façon documentée de retirer un en-tête. Cet article est une référence de travail pour les champs qui comptent.

Comment vérifier

curl -sI https://app.example.com/ | grep -iE 'strict-transport|x-frame|x-content|referrer-policy|content-security'
curl -sI -X OPTIONS https://api.example.com/ \
  -H "Origin: https://example.com" \
  -H "Access-Control-Request-Method: GET" \
  | grep -iE 'access-control'
# Une requête vers une route avec secure-headers ne DOIT PAS inclure Server: traefik
curl -sI https://app.example.com/ | grep -i ^server

Ce qui se passe

Le middleware headers opère dans les deux directions : en-têtes de requête (transférés au backend) et en-têtes de réponse (renvoyés au client). Les champs se divisent en trois groupes.

En-têtes de sécuritéstsSeconds, stsIncludeSubdomains, stsPreload, contentTypeNosniff, frameDeny, customFrameOptionsValue, referrerPolicy, permissionsPolicy. Ils ajoutent des en-têtes de réponse que les navigateurs honorent pour la politique de sécurité. HSTS est celui à plus fort impact : une fois réglé avec une soumission preload, vous vous engagez sur HTTPS uniquement pour ce nom d’hôte pour la durée de l’en-tête.

CORSaccessControlAllowOriginList, accessControlAllowMethods, accessControlAllowHeaders, accessControlExposeHeaders, accessControlAllowCredentials, accessControlMaxAge, addVaryHeader. Traefik gère le préflight OPTIONS automatiquement quand les réglages CORS sont présents. L’interaction qui mord : accessControlAllowCredentials: true est incompatible avec accessControlAllowOriginList: ["*"] selon la spec navigateur — il faut énumérer les origines.

En-têtes personnaliséscustomRequestHeaders (ajoutés à la requête avant qu’elle n’atteigne le backend), customResponseHeaders (ajoutés à la réponse avant qu’elle ne quitte Traefik). Une valeur de chaîne vide retire l’en-tête. C’est la façon de retirer Server: traefik et toute autre empreinte.

La référence

En-têtes de sécurité

http:
  middlewares:
    secure-headers:
      headers:
        # HSTS — un an, inclure sous-domaines, prêt pour preload
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        stsPreload: true

        # Reniflage MIME
        contentTypeNosniff: true

        # Clickjacking
        frameDeny: false                    # utilisez customFrameOptionsValue pour SAMEORIGIN
        customFrameOptionsValue: "SAMEORIGIN"

        # Referer
        referrerPolicy: "strict-origin-when-cross-origin"

        # Remplacements / ajouts modernes
        contentSecurityPolicy: "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'"
        permissionsPolicy: "geolocation=(), camera=(), microphone=()"

        # Retrait d''empreinte
        customResponseHeaders:
          Server: ""
          X-Powered-By: ""

CORS

http:
  middlewares:
    cors-api:
      headers:
        accessControlAllowOriginList:
          - "https://example.com"
          - "https://app.example.com"
        accessControlAllowMethods:
          - GET
          - POST
          - PUT
          - DELETE
          - OPTIONS
        accessControlAllowHeaders:
          - Authorization
          - Content-Type
          - X-Request-Id
        accessControlExposeHeaders:
          - X-Request-Id
          - X-Rate-Limit-Remaining
        accessControlAllowCredentials: true
        accessControlMaxAge: 600
        addVaryHeader: true

accessControlAllowOriginList accepte des origines exactes ; la correspondance par regex est disponible via accessControlAllowOriginListRegex pour des schémas comme https://[a-z0-9-]+\.example\.com.

Réécriture de requête

http:
  middlewares:
    injecter-tenant:
      headers:
        customRequestHeaders:
          X-Tenant: "acme"
          X-Internal: "true"
          # Retirer un en-tête avant transfert
          Authorization: ""

Réécriture de réponse

http:
  middlewares:
    ajouter-cache-control:
      headers:
        customResponseHeaders:
          Cache-Control: "public, max-age=3600"
          X-Served-By: "edge-traefik"

Préréglage de production combiné

http:
  middlewares:
    web-standard:
      headers:
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        contentTypeNosniff: true
        customFrameOptionsValue: "SAMEORIGIN"
        referrerPolicy: "strict-origin-when-cross-origin"
        contentSecurityPolicy: "default-src 'self'; img-src 'self' data: https:; script-src 'self'"
        customResponseHeaders:
          Server: ""
    api-standard:
      headers:
        stsSeconds: 31536000
        accessControlAllowOriginList:
          - "https://app.example.com"
        accessControlAllowMethods: [GET, POST, PUT, DELETE, OPTIONS]
        accessControlAllowHeaders: [Authorization, Content-Type]
        accessControlAllowCredentials: true
        accessControlMaxAge: 600
        addVaryHeader: true
        customResponseHeaders:
          Server: ""

Pièges fréquents

  • HSTS stsPreload: true sans avoir réellement soumis à la liste preload — l’en-tête ment ; soumettez sur hstspreload.org ou retirez le drapeau.
  • Régler à la fois frameDeny: true et customFrameOptionsValueframeDeny gagne et émet DENY ; choisissez-en un.
  • CORS avec accessControlAllowOriginList: ["*"] plus accessControlAllowCredentials: true — les navigateurs refusent la réponse selon la spec ; énumérez les origines.
  • Oublier addVaryHeader: true quand CORS varie selon l’origine — les caches fusionnent les réponses pour différentes origines.
  • Retirer Authorization dans customRequestHeaders pour enlever un jeton avant le backend — fonctionne pour la requête visible mais l’original est parti pour l’authentification aval ; ne le faites que volontairement.

Le middleware headers est l’un des morceaux de configuration au niveau plateforme les plus utilisés — une définition unique secure-headers référencée par chaque router public est la façon dont les clients tournent une posture de sécurité uniforme à travers de nombreuses applis. L’audit de « qu’est-ce que chaque route émet » fait partie de la cadence des opérations infogérées. Pour la surface plus large des middlewares, voir traefik-routers-services-middlewares.