5 mai 2009
Imprimer ce billet

Tomcat : Adresse IP de l’internaute, load balancer, reverse proxy et header Http X-Forwarded-For

Note du 24/01/2010 : La RemoteIpValve et le XForwardedFilter ont été intégrés au projet Tomcat. La RemoteIpValve est disponible depuis Tomcat 6.0.24, le XForwardedFilter a été renommé RemoteIpFilter et sera disponible dans Tomcat 7.

Une conférence est l'occasion de discuter avec les ingénieurs des difficultés que nous rencontrons à utiliser leurs produits. J'ai profité de ma participation à SpringOne 2009 pour échanger avec Mark Thomas sur le suivi de l'adresse IP des internautes dans les logs d'audit d'applications web. Mark Thomas est des principaux committer du projet Tomcat et de SpringSource tc Server.

Le problème se présente lorsqu'un serveur Tomcat est précédé d'un reverse proxy (e.g. mod_proxy, Squid Cache, etc) ou d'un load balancer (Nortel Alteon, F5 Big-IP, etc) : La méthode HttpServletRequest.getRemoteAddr() ne retourne plus l'adresse IP de l'internaute mais celle du composant réseau qui précède le serveur Tomcat.

Cette perte d'information a été compensée par les reverse-proxy en ajoutant un header http X-Forwarded-For à toutes les requêtes qu'ils font transiter. Bien que ce header ne soit pas standard (son nom commence d'ailleurs par "X-"), il est devenu un standard de facto utilisé par la très grande majorité des reverse proxy et load balancers (paramètre xforward=enable pour les Alteon d'après Command Reference ; propriété Insert XForwarded For de la GUI du profile http ou règle when HTTP_REQUEST { HTTP::header insert "X-Forwarded-For" [IP::client_addr] } pour les Big-IP d'après F5 Dev Central : Using "X-Forwarded-For" in Apache or PHP ).

Cependant, les implémentations de l'API Servlet ne sont pas tenues de prendre en compte ce paramètre dans la méthode request.getRemoteAddr() dont la javadoc stipule bien Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. . Tomcat est d'ailleurs autant concerné que ses concurrents Websphere ou Weblogic, pour ne citer qu'eux.

Toutes les couches sont impactées : l'Access Log Tomcat dont les patterns common et combined utilisent le remote host name (%h) et les frameworks de sécurité et d'audit comme Spring Security (cf WebAuthenticationDetails.getRemoteAddress()) ou CAS Inskektr ClientInfo).

Access Log Tomcat et Apache Httpd : modification du pattern de log

Depuis l'intégration de la RemoteIpValve dans Tomcat 6.0.24, il est plus élégant d'utiliser cette RemoteIpValve (voir infra) que d'utiliser le %{X-Forwarded-For}i dans le pattern de l'AccessLogValve.

Pour corriger l'access log Tomcat, il est possible de configurer l'utilisation du header X-Forwarded-For plutôt que de l'ip distante dans le pattern de log (pratique relativement bien documenté).

AccessLogValve utilisant le header X Forwarded For
...
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log."
       suffix=".txt" pattern="%{X-Forwarded-For}i %l %u %t %r %s %b" resolveHosts="false" />

...

Si les serveurs Apache Httpd sont précédés d'un load balancer, la modification est très similaire, mod_log_config permet de définir les informations affichées dans l'accès log. On modifie la pattern de log pour utiliser le header http X-Forwarded-For ( %{X-Forwarded-For}i ) plutôt que le remote host ( %h) :

Access Log Apache utilisant le header X Forwarded For
LogFormat "%{X-Forwarded-For}i %l %u %t "%r" %>s %b" common
CustomLog logs/access_log common

La prise en compte du header X-Forwarded-For dans Apache Httpd sera bientôt simplifiée avec la récente introduction dans le trunk du module mod_remoteip qui permettra de prendre en compte ce header avec une simple configuration RemoteIPHeader X-Forwarded-For ; il ne sera plus nécessaire de manipuler le pattern des logs.

Frameworks Java : extension des applications ou du moteur de servlet

S'il est simple de prendre en compte le paramètre X-Forwarded-For dans les access log Tomcat et Apache Httpd, il est sensiblement plus complexe de l'intégrer dans les frameworks de sécurité et d'audit. Ces briques sont souvent des librairies tierces et modifier leur code est fastidieux voire impossible si elles sont 'close source'. Il est donc nécessaire de se placer en amont de ces frameworks pour modifier le comportement de la méthode ServletRequest.getRemoteAddr().

Servlet API : XForwardedForFilter

La première solution est de développer un Servlet Filter que l'on déploie dans chaque web application.

  • Avantage : Simple à implémenter grâce aux interfaces Filter, HttpServletRequest et à l'HttpServletRequestWrapper qui est destiné à faciliter ce type de développement, ce Filter est utilisable aussi bien avec Tomcat qu'avec Glassfish, JBoss, Jetty, WebSphere, Weblogic ou tout autre moteur de servlet.
  • Inconvénient : lourd à déployer sur toutes les web applications.
  • Exemple d'implémentation: XForwardedFilter (XForwardedFilter.java) gère les headers X-Forwarded-For et X-Forwarded-Proto

Pour installer cette solution :

  1. Télécharger xebia-servlet-extras-1.0.2.jar, compiler XForwardedForFilter.java et déployer le jar sous WEB_APP_HOME/WEB-INF/lib ou ajouter la dépendance maven fr.xebia.web.extras:xebia-servlet-extras:1.0.2 à votre pom.xml (doc).
  2. Déclarer dans web.xml le filter XForwardedFilter :
Declaration du XForwardedFilter
...
   <filter>
      <filter-name>XForwardedForFilter</filter-name>
      <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
   </filter>

   <filter-mapping>
      <filter-name>XForwardedFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
   </filter-mapping>
   ...

  1. Tester le résultat avec x-forwarded-for_as_remote-addr.jsp, le résultat devrait ressembler à x-forwarded-for_as_remote-addr.jpeg.

Extension Tomcat : RemoteIpValve

La deuxième solution est d'ajouter une extension au serveur Tomcat pour en modifier le comportement ; il ne s'agit plus alors de Servlet Filter et d'HttpServletRequest mais de Valve et de Request :

  • Avantage : solution centralisée gérée par l'administrateur Tomcat sans intrusion dans les web applications. Cette solution permet de traiter dans le même temps l'AccessLogValve.
  • Inconvénient : intégré à Tomcat seulement depuis la version 6.0.24 ; pour les version antérieures, il faut déployer un jar additionnel.
  • Exemple d'implémentation : RemoteIpValve.java gère les headers X-Forwarded-For et X-Forwarded-Proto
  • RemoteIpValve.java a été intégré au projet Tomcat Bug 47330 - proposal : port of mod_remoteip in Tomcat as RemoteIpValve et est documentée sur GoogleCode : RemoteIpValve

Pour installer cette RemoteIpValve :

  1. Pour les versions de Tomcat antérieures à la 6.0.24, télécharger xebia-tomcat-extras-1.0.0.jar, compiler RemoteIpValve.java et déployer le jar sous TOMCAT_HOME/lib.
  2. Déclarer la RemoteIpValve dans server.xml :
Declaration de la RemoteIpValve
...
   <Valve className="org.apache.catalina.connector.RemoteIpValve" />
   <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log."
      suffix=".txt" pattern="common" resolveHosts="false" />

   ...

  1. Tester le résultat avec x-forwarded-for_as_remote-addr.jsp, le résultat devrait ressembler à x-forwarded-for_as_remote-addr.jpeg.

Mark Thomas reconnaît l'intérêt de faciliter le développement d'extensions pour Tomcat et la tâche devrait grandement se simplifier avec le projet Convert current Tomcat valves to Servlet Filters for The Apache Software Foundation du Google Summer of Code 2009. Cette évolution ouvre la perspective de développer des filtres d'infrastructure similaires aux modules écrits en C pour Httpd mais avec la souplesse du langage Java.

Pour aller plus loin

2009/05/17 : Ajout des paramètres de configuration des Alteon et BIG-IP pour activer l'insertion du header x-forwarded-for
2009/07/12 : Mise à jour du paragraphe sur la Valve tomcat pour utiliser la RemoteIpValve
2009/07/17 : Mise à jour du paragraphe sur le servlet filter XForwardedFilter
2009/10/13 : Montée de version de xebia-servlet-extras en version 1.0.1 pour corriger le bug soulevé par Alexandre Victoor
2009/11/08 : Ajout de la remarque préliminaire : la valve et le filtre ont été intégrés dans le projet Tomcat.
2010/01/24 : Mise à jour de l'article pour prendre en compte la disponibilité de la RemoteIpValve dans la distribution standard Tomcat depuis la version 6.0.24.
2010/01/31 : Montée de version de xebia-servlet-extras en version 1.0.2 pour corriger Issue 4 - XForwardedFilter : request.secure and request.scheme are not forced to "false" and "http" if X-Forwarded-Proto=http.