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é).
<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) :
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-ForetX-Forwarded-Proto
Pour installer cette solution :
- 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). - Déclarer dans
web.xmllefilter 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>
...
- 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-ForetX-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 :
- 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.
- Déclarer la
RemoteIpValvedansserver.xml:
<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" />
...
- 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
- Apache Httpd mod_proxy : Reverse Proxy Request Headers
- Squid Cache : forwarded_for configuration parameter
- Xebia-france Google Code : RemoteIpValve
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.









Bonjour,
Avez-vous une solution lorsque le flux est chiffré (HTTPS / SSL) ?
En effet comment injecter dans le header l'IP du client lorsque l'on ne dispose pas sur tous les boîtiers répartiteurs / load balancing en amont ?
En effet, si le Reverse Proxy peut disposer du certificat et de la clé privé du site, les répartiteurs ne le dispose pas toujours.
Existe-il un autre moyen d'avoir l'IP du client depuis le serveur web en bout de chaîne lorsque le flux est chiffré et j'injection dans le header impossible ?
Ce problème est vraiment d'actualité avec les besoins de traçabilité exigés de plus en plus en matière d'IP client.
Bonjour,
L'utilisation du header X-Forwarded-For est certes intéressante, mais ne règle pas le problème dans le cas où l'on a un boîtier Load-Balancer ET un boîtier Reverse-Proxy devant Apache/Tomcat.
Car dans ce cas, la valeur du header X-Forwarded-For est l'IP du boîtier Load-Balancer... dommage
=> A quand le X-X-Forwarded-For ?
@Fabien
Le header "X-Forwarded-For" est prévu pour être multi valué (séparateur ',') avec pour première valeur l'ip de l'internaute et peut valoir
"X-Forwarded-For: client-ip, load-balancer-ip, reverse-proxy-1-ip"(cf Wikipedia : X-Forwarded-For).Si vous utilisez Apache mod_proxy, la concaténation est valeurs pour composer le header "X-Forwarded-For" est géré dans mod_proxy_http.c par la ligne
apr_table_mergen(r->headers_in, "X-Forwarded-For", c->remote_ip);; la fonctionapr_table_mergendu Apache Portable Runtime a pour rôle de "Add data to a table by merging the value with data that has already been stored".Nous noterons à l'occasion que les headers "Via", "X-forwarded-For', "X-Forwarded-Host" et "X-Forwarded-Server" sont tous multi valué de cette même façon pour conserver les informations de chaque maillon de la chaine d'invocation.
@Mega,
Je n'ai pas encore eu le temps de répondre à votre question. Cela relève de la différence entre les "layer 4 load balancer" et les "layer 7 load balancer". Au risque de faire de la publicité, j'aurai plus de temps après la soirée Data Grid que nous présentons mardi 12 Mai au Paris JUG ; c'est demain et nous espérons vous y voir
.
Cyrille (Xebia)
bonjour Cyrille,
Concernant le Big IP de chez F5 Networks, celui-ci masque nativement l'adresse IP client. Toutefois, il est très aisé de retrouver celle-ci avec le header X-forwarded-For' via une simple option à cocher dans le profil http utilisé dans la configuration applicative du BIG IP.
La modification ne prend guère plus de 25 s, montre en main
De mémoire, il en est de même pour les autres header de la même catégorie
Cdt,
Miguel
@Miguel
Merci pour ces éclaircissements, j'ai modifié le billet pour prendre en compte votre complément en citant les documentations des Alteon et des BIG-IP.
@Mega,
Mes connaissances réseau sont hélas limitées et je ne sais pas comment retrouver l'adresse IP du client si un composant réseau intermédiaire intervient à la couche de niveau 4 plutôt qu'au niveau 7 pour manipuler les headers HTTP.
Cependant, je profite de l'occasion pour promouvoir l'utilisation des load balancers hardware pour réaliser les traitements très consommateurs de CPU que sont l'encryption/décryption SSL et la compression de flux (aka deflate/gzip) :
- Du hardware dédié est beaucoup plus performant que nos valeureux serveurs HTTP
- La gestion des certificats SSL est plus souvent réalisée par les équipes réseau qui ont la charge des load balancer hardware que par les équipes d'informatique de gestion qui gèrent les serveurs Web
- La décryption SSL à l'entrée du data center permet d'avoir un réseau interne sécurisé utilisable pour les communications serveur-à-serveur sans pour autant avoir à payer le prix de SSL ni du complexe keep-alive que SSL requiert.
- Confier aux équipes réseau ces piliers de la performance et de la sécurité peut valoriser ces équipes infrastructure auprès de leurs alter ego de la direction des études qui perçoivent trop souvent les équipes d'infrastructure comme des freins à la réalisation des projets.
Si les load balancers hardware peuvent servir à la cohésion des DSI
SSL et compression pour les BIG-IP : SSL Acceleration et Intelligent Compression
Cyrille (Xebia)
Merci pour cette réponse.
Je ne connaissais pas cette technique de multi-valuation de ce header.
Néanmoins, si je ne m'abuse, cette solution n'est valable que si tous les maillons de la chaine sont des Apache, produits dérivés d'Apache, ou en tout cas, produits qui gèrent la valuation de ce header.
Dans mon cas, le Load-Balancer frontal est un boîtier Alteon, il ne me semble pas que ce soit un Apache.
Dans mon architecture, je sais qu'il ne value pas ce champ. Mais peut-être, est-ce configurable ?
Si vous avez des infos sur ce point, cela m'intéresse.
Fabien
@Fabien,
J'ai sur mon projet actuel une architecture qui me semble similaire à la votre et j'obtiens comme attendu l'adresse ip de l'internaute :
1 x BIG-IP -> N x (Apache Httpd + mod_proxy) -> M x Tomcat-6
- Le load balancer hardware BIG-IP ajoute le http header 'x-forwarded-for' avec l'adresse ip de l'internaute que je vois notamment dans l'access_log de mes serveurs Apache Httpd
- Les modules mod_proxy de mes serveurs Apache Httpd concatènent sur le header 'x-forwarded-for' l'adresse IP de l'internaute avec l'adresse IP du load balancer Hardware BIG-IP. Cette deuxième adresse IP est inutile à mes audits de sécurité puisque c'est celle du load-balancer. Cette adresse de load balancer est typiquement une adresse de réseau privé en 192.168.x.x voire en 10.x.x.x .
En revanche, je n'ai pas fait de test pour savoir si les BIG-IP et autres Alteon travaillent en concaténation ou en écrasement du header 'x-forwarded-for'. En théorie, dans l'esprit de ce qui est décrit dans Wikipedia : X-Forwarded-For, les load balancers hardware devraient travailler en concaténation et nous devrions avoir :
x-forwarded-for: client-ip, load-balancer-1-ip, ..., load-balancer-n-ip, reverse-proxy-1-ip, ..., reverse-proxy-(m-1)-ipCyrille (Xebia)
Bonjour,
Pour le F5, à noter qu'il s'agit à la base d'un boitier faisant tourner du linux (Red Hat 3 based) avec un serveur tomcat pour tout gérer
Concernant la gestion des headers, tout est possible : l'utilisation de paramètres par défaut comme l'utilisation d'irule permettant d'avoir un très large champ d'action et de manipulation des données transitant via le F5, à condition d'aimer le langage de programmation utilisé (le TCL).
Miguel, il faut nous en dire plus !
Je croyais que les load balancers hardware étaient incroyablement plus rapides et fiables que nos valeureux serveurs Apache grâce à des cartes accélératrices et vous brisez le mythe en expliquant que les Big-ip exécute sur un vieux Linux des règles écrites en script TCL !
Alors, un big-ip est-il vraiment plus fiable et rapide qu'un Apache Httpd avec mod_ssl, mod_deflate et mod_rewrite ?
Merci pour votre éclairage "infrastructure réseau" sur ce blog plus qui a une sensibilité plus développement.
Cyrille (Xebia)
PS: En même temps, TCL me rappelle mes premiers scripts d'administration Websphere, avant l'avènement de jython. Pas mon meilleur souvenir le TCL
Bonjour
2 petites remarques à propos du filtre XForwardedFilter :
- dans les exemples et la doc il y a une erreur;, il faut remplacer "internalProxies" dans la conf par "allowedInternalProxies"
- avec les logs debug activés ça plante
Bonjour Alexandre,
Merci pour vos remarques, le bug est corrigé en version 1.0.1 le jar xebia-servlet-extras et les docs mis à jour.
J'ai été victime du syndrome "le mieux est l'ennemi du bien" en ajoutant des informations supplémentaires de debug sans les tester.
J'espère que cette librairie vous rend service,
Cyrille (Xebia)
Merci Cyrille
Je travaille actuellement sur une application déployée derrière une série de proxys en tout genre, votre filtre me rend bien service !
Cordialement
Alexandre
La RemoteIpValve et le XForwardedFilter ont été intégrés au projet Tomcat. La RemoteIpValve sera disponible dès Tomcat 6.0.21, le XForwardedFilter a été renommé RemoteIpFilter et sera disponible dans Tomcat 7.
Cyrille (Xebia)
"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."
Sur un plan théorique vous avez l'adresse de l'internaute.
En pratique la chaîne d'adresses X-Forwarded-For n'est pas entièrement fiable. Elle n’est fiable juste devant et à partir du premier proxy de confiance. Ce qui précède n'est qu'indicatif.
Bonjour Stéphane,
Merci pour cette précision. L'algorithme pour déterminer si l'on peut faire confiance à un proxy est décrit dans dans Google Code : RemoteIpValve et XForwardedFilter ; il est repris de mod_remoteip.
Cyrille (Xebia)
Bonjour,
Voulant déployer la RemoteIpValve sur un tomcat 5.5.28, j'ai essayé avec le .jar déjà compilé xebia-tomcat-extras-1.0.0.jar mais j'obtiens le message d'erreur suivant:
14 janv. 2010 17:06:10 org.apache.tomcat.util.digester.Digester startElement
GRAVE: Begin event threw error
java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory
...
at org.apache.tomcat.util.digester.Digester.startElement(Digester.java:1276)
...
at org.apache.tomcat.util.digester.Digester.parse(Digester.java:1562)
at org.apache.catalina.startup.Catalina.load(Catalina.java:490)
...
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:432)
Dois je utiliser xebia-tomcat-extras-1.0.0-sources.jar et compiler le jar moi même avec un tomcat 5.5.28 pour que celà fonctionne.
Merci de votre indulgence, les applis JSP/java, c'est pas du tout ma partie, par contre l'ajout du support X-Forwarded-Proto aux appli derrière mes reverse proxy m'est indispensable.
Bonjour Emmanuel,
Pouvez-vous vérifier que vous n'êtes pas victime d'un problème de repackaging astucieux de Tomcat par une distribution Linux comme décrit dans Since java.logging is the default commons-logging implementation in Tomcat, why is it not working in my Linux distribution? ?
Avez-vous un jar
tomcat-juli.jardans le répertoire$CATALINA_HOME/bin? Si vous avez installé Tomcat 5 avec un RPM pour RHEL, la commande pour lister les fichiers installés devraient-être "yum list installed tomcat5".Si vous utilisez un Tomcat packagé par une distribution Linux, une solution plus radicale mais surtout dans l'esprit du produit est de la remplacer par une installation Tomcat standard téléchargée sur http://tomcat.apache.org/ .
Cyrille
Malheureusement, ce n'est pas sur un Linux, mais sur un Windows.
tomcat-juli.jar est bien présent dans $CATALINA_HOME/bin
Le tomcat a été installé par l'éditeur du logiciel déployé sur celui ci. Il semble être installé a partir de la version standard de la fondation Apache.
Je vais vérifier les scripts de démarrage comme spécifié dans la FAQ.
Merci !
Emmanuel
Bonjour Emmanuel,
Je reproduis sur Tomcat 5.5.28. Je prépare une version pour Tomcat 5.5 juste pour vous
.
Cyrille
Re bonjour Emmanuel,
Pouvez-vous essayer cette version xebia-tomcat-extras-tc55-1.0.0.jar destinée à Tomcat 5.x ?
J'ai testé avec Tomcat 5.5.28.
Cyrille (Xebia)
Un grand grand merci !! je teste dès que possible et vous fait le retour ici même.
Emmanuel.
Merci Emmanuel, j'attends votre retour pour mette à jour le ce billet et la page de la wiki.
Cyrille (Xebia).
Premier test a distance concluant: plus de plantage au démarrage.
Jeudi je pourrai faire un retour sur des tests fonctionnels.
Emmanuel.
Tout a l'air de fonctionner comme prévu.
Merci !
Par contre, il semble que cela n'ait aucun effet sur les redirects générés lors de l'accès à un répertoire sans le / final. Mais ça peut se gérer coté reverse proxy.
Emmanuel.
Merci pour la bonne nouvelle.
En revanche, je ne saisis pas votre point "sur les redirects générés lors de l'accès à un répertoire sans le / final".
Qu'est ce qui ne marche pas ? L'identification du protocole http/https ou la résolution de l'adresse ip de l'appelant ?
Cyrille
Pardon, j'ai pas été assez précis: les redirects sont générés avec http comme protocole et non https. Je sais pas si la valve est capable normalement d'influer sur la génération de ces redirects.
Par contre, on récupère bien https comme proto dans l'application.
Etrange, étrange
* Qui génère le redirect ? Tomcat ? Un serveur Apache ou équivalent en amont ?
* Si c'est Tomcat qui génère la 302 ? Le moteur Tomcat ? Comment le ferait-il ? La webapp ? Une Security Constraint dans web.xml ? Spring Security ? Url Rewrite Filter ?
* La requête 302 apparait-elle dans l'access log Tomcat ?
Cyrille
PS : par 302, j'entends l'ensemble des codes 3xx de redirection.
Tomcat
Normalement c'est le moteur Tomcat, qui suit la pratique courante qui consiste a émettre un redirect vers l'url avec le slash de fin lorsque aucune ressource sans le slash existe mais que le directory existe.
Sur un serveur apache http, la désactivation ou pas de cette "feature" est contrôlée par les directives du module mod_dir. Il y a beaucoup d'article sur cette problématique de canonisation/normalisation des URL a cause de "mauvais" comportement de certains moteur de recherche et de ignorance de la chose de la part du commun des mortels: avec ou sans slash les serveurs et navigateurs retombent sur leurs pattes moyennant un redirect discrètement généré par le serveur.
Dans le cas qui nous occupe, un appel ou un lien vers une "mauvaise" URL qui génère un redirect peut être très gênant car visiblement la valve n'a pas d'effet sur la manière dont les redirects sont générés à l'intérieur du moteur http de tomcat.
Si c'est un pemier mauvais appel fait par l'utilisateur, on peut y remédier coté reverse proxy. Si c'est des liens dans des pages générés par l'appli, cela deviens plus problématique.
Comme dans apache http, la génération ou non de ces redirects se contrôle peut être avec un paramètre de configuration (je ne connais pas assez tomcat pour y répondre), mais leur génération correcte dynamiquement en fonction du X-Forwarded-Proto n'est pas si simple.
La version 1.0.2 de xebia-servlet-extras a été publiée pour corriger Issue 4: XForwardedFilter : request.secure and request.scheme are not forced to "false" and "http" if X-Forwarded-Proto=http.