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
... 

...

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
...
   
      XForwardedForFilter
      fr.xebia.servlet.filter.XForwardedFilter
   

   
      XForwardedFilter
      /*
      REQUEST
   
   ...
  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
...
   
   
   ...
  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.

Billets sur le même thème :

41 commentaires

  • 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 fonction apr_table_mergen du 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)-ip

    Cyrille (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.jar dans 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.

  • Bonjour,

    Merci pour cet article. J’ai essayé celà sur mon serveur de test et ça marchait.

    Tout content je cours mettre en prod mon exploit, je fais un petit test question d’être sûr (on ne sait jamais) et là PAM ça ne marche pas. Pourtant j’ai la même config apache / tomcat entre la test et la prod

    Je me suis cassé la tête dessus mais sans succès.

    Si quelqu’un peut m’aider ou me donner des pistes, j’en serai très reconnaissant.

    Merci

  • Peut être quelques informations sur ce que j’ai fais :

    j’ai déclaré la valve dans mon server.xml
    <Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIPHeader="X-Forwarded-For"/>

    J’utilise tomcat 6.0.26 et apache 2.2.9-10+lenny6 en mod proxy

    Ayant fait un tcpdump je vois bien que apache renseigne la vraie adresse du client dans le header http : X-Forwarded-For cependant lorsque je fais request.getRemoteAddr() j’obtiens l’adresse ip de mon serveur au lieu du client.

    Qu’est ce qui cloche ?

  • @Reda,

    Pouvez vous indiquer l’adresse IP que voit le serveur Tomcat ? N’utiliseriez vous pas par hasard IP V6 ? C’est une mauvaise surprise que j’ai déjà eu :-).

    L’adresse IP que voit le serveur Tomcat apparait dans les logs générées par l’AccessLogValve ou en invoquant request.getRemoteAddr().

    La RemoteIpValve ne regarde le paramètre X-Forwarded-For que si elle fait confiance à l’adresse IP qui se connecte au Tomcat (ie l’@ ip du serveur web). Par défaut, seules les adresses en 10.*, 192.168.*, 169.254.* et 127.* sont reconnues comme de confiance.

    Si votre serveur web utilise une adresse IP qui ne fait pas partie de ces plages, vous devez la déclarer avec l’attribut internalProxies. Exemple si votre serveur web présente l’adresse 85.158.206.34 au serveur Tomcat :

    <Valve 
       className="org.apache.catalina.valves.RemoteIpValve"
       internalProxies="85\.158\.206\.34"
       remoteIpHeader="X-Forwarded-For" />
    

    Pour aider au diagnostique, vous pouvez activer les logs de la RemoteIpValve en ajoutant dans $TOMCAT_BASE/conf/logging.properties la directive :

    org.apache.catalina.valves.RemoteIpValve.level=FINEST
    

    Les messages de log apparaitront dans $TOMCAT_BASE/logs/catalina.${date(yyy-MM-dd)}.log.

    Si la RemoteIpValve ne fait pas confiance à l’adresse IP de votre serveur web, vous verez le message de log :

    Sep 18, 2010 12:14:43 PM org.apache.catalina.valves.RemoteIpValve invoke
    Skip RemoteIpValve for request /foo with originalRemoteAddr '85.158.206.34'
    

    En revanche, si la RemoteIpValve fait confiance à l’adresse IP présentée par le serveur web, le message de log est du type

    Sep 18, 2010 12:14:43 PM org.apache.catalina.valves.RemoteIpValve invoke
    Incoming request /foo with originalRemoteAddr '85.158.206.34', originalRemoteHost='85.158.206.34', originalSecure='false', originalScheme='http' will be seen as newRemoteAddr='82.66.240.18', newRemoteHost='82.66.240.18', newScheme='http', newSecure='false'
    

    Normalement, il y a assez d’informations pour débugger.

    Cyrille (Xebia)

  • Bonjour Cyrille,

    Merci énormément pour ces explications que je garderai soigneusement sous le coude, la réponse s’y trouve.


    La RemoteIpValve ne regarde le paramètre X-Forwarded-For que si elle fait confiance à l'adresse IP qui se connecte au Tomcat (ie l'@ ip du serveur web). Par défaut, seules les adresses en 10.*, 192.168.*, 169.254.* et 127.* sont reconnue comme de confiance.

    En effet, l’adresse ip que Apache présente à tomcat est l’adresse publique du serveur, le fait de rajouter le paramètre internalProxies a tout de suite résolu le problème.
    Pour info, je n’utilise pas de l’IP V6.

    Un grand merci aussi d’avoir enrichi Tomcat avec cette valve très pratique.

    PS : Bravo, votre dîner est offert au prochain JUG :)

  • Merci Reda,

    Je serai en vacances pour le JUG d’Octobre, je vous propose donc le JUG de Novembre :-) .

    Cyrille (Xebia)

  • Bonjour Cyrille,

    Bravo pour cet article très instructif.

    J’en profite pour te poser une petite question.

    J’ai une application (Apache+Perl+Oracle) qui utilise le paramètre X-Forwarded-For pour tracer les utilisateurs qui se connecte. Le problème est lorsque ceux-ci passent par des proxies nous avons des erreurs (le champs qui stock les IPs dans l’application est trop petit et nous ne pouvons pas l’agrandir car il s’agit d’un fournisseur externe qui malheureusement fait la sourde oreille).

    Sachant que le format du paramètre X-Forwarded-For est « client1, proxy1, proxy2″, aurais-tu une solution pour ne garder que « client1″ et supprimer le reste au niveau de l’Apache en frontal de l’application (Apache 2.0.52) ?

    Dans tous les cas, bravo pour cet excellent blog ;)

  • @Jérôme,

    Merci :-) .

    Concernant l’extraction de la première ip, cette perte d’information me semble assez risquée.

    Le premier problème que je vois est le « spoofing » de header « x-forwarded-for » :
    Il faut s’assurer que le premier composant réseau qui manipule « x-forwarded-for » dans votre infrastructure écrase le header plutôt que de l’enrichir pour prévenir le spoofing « à la X-Forwarded-For Spoofer Firefox Plugin.

    Il y a probablement d’autres risques si on continue le raisonnement.

    J’imagine un compromis :
    1. Vous dumpez le header « X-Forwarded-For » dans les logs d’un Apache avec LogFormat « %{X-Forwarded-For}i %l %u %t « %r » %>s %b » common ou équivalent si c’est autre chose qu’un Apache.
    2. Dans votre couche applicative, vous remplacez la valeur du header « x-forwarded-for » par un « xforwardedFor = StringUtils.subStringBefore(xforwardedFor , ",")« . En java, ce serait un servlet filter de quelques lignes de code, en module apache (e.g. mod_perl) ou dans votre couche perl, il y a surement une solution. J’ai regardé rapidement mod_headers, ca ne me semble pas possible, je n’ai pas trouvé d’expression language ou équivalent pour manipuler la chaine de caractères.

    Cyrille

  • Merci pour ces informations Cyrille.

    Effectivement le but n’est pas de perdre de l’information mais d’en garder le minimum.

    Je vais donc voir comment faire avec un module Apache (voir retoucher le mod_proxy) pour conserver uniquement l’IP client et supprimer les IPs des proxies après avoir tracé le tout dans l’access_log.

  • Bonjour à tous,

    Je lisais cet article et j’ai essayé d’appliquer cette procédure sur un tomcat 5.5.17 mais je bloque sur la notion de déploiement du .jar je pense.

    Quelqu’un pourrait me dire s’il vous plaît comment je dois faire exactement? Le placer sur tomcat_home/server/bin ou bien tomcat_home/common/bin et redémarrer le service, cela ne doit pas suffire car je n’ai même pas par ex. des logs RemoteIpValve dans catalina.log

    Merci par avance et je m’excuse de cette question si basique…

    Carlos

  • Re-bonjour
    j’ai voulu dire dans \common\lib
    pardon!
    Carlos

  • j’ai réussi à le faire fonctionner!
    j’ai placé le jar dans server\lib et le valve après le premier <Engine… Note: Après le deuxième <Engine, la redirection vers https ne fonctionne pas
    j'ai des logs maintenant!

    J'ai voulais faire ceci car on a un altéon et dès l'activation du https au niveau de tomcat… on n'accède plus au serveur. on y accède uniquement si on attaque le serveur par son adresse IP ou en localhost…
    J'ai toujours le problème, je ne sais pas si c'est lié au fait que le xforward pourrait ne pas être activé au niveau de l'alteon… je n'ai pas accès à cette partie… j'ai demandé aux admin de me le confirmé.. au niveau des access_* logs acces, je vois que l'adresse du LBL et si je suis en localhost je vois bien 127.0.0.1…

  • Re-bonjour
    Mon incident semblait être liè au fait qu’il faut placer le <valve après le premier et j’ai placé le jar dans server\lib.
    J’ai des logs maintenant et je vois bien l’adresse ip du LBL mais par contre je ne vois pas l’adresse IP du client distant… je ne sais pas si le xforward est activé au niveau de l’alteon (je n’ai pas les moyens de le vérifier)

    Note: Mon souci est lié au fait que quand j’active le https (CONFIDENTIAL) au niveau du tomcat (car l’admin alteon n’a pas l’habitude du gérer la redirection https…), je n’accède plus au serveur (j’ai une page blanche et ça n’aboutit jamais)

    Carlos

Laisser un commentaire