Tomcat, SSL, communications sécurisées et X-Forwarded-Proto

Suite à vos retours nombreux, aux différents articles touchant à la sécurisation par SSL de Tomcat en production (Tomcat : Adresse IP de l’internaute, load balancer, reverse proxy et header HTTP X-Forwarded-For, Sécuriser Tomcat 5 derrière un proxy Apache 2 HTTPS), nous commençons une série sur Tomcat en production.
Dans ce premier article, nous abordons l’utilisation de SSL pour sécuriser les communications. Pourquoi utiliser SSL ? Comment SSL est-il intégré avec le moteur de Servlet ? Et surtout quelle configuration correspond à votre application, qu’elle soit hébergée sur un serveur Tomcat seul, avec un serveur Apache Httpd en frontal, avec un accélérateur SSL ou avec un load-balancer hardware.

Pourquoi utilisons nous SSL ? Qu’est ce qu’une communication sécurisée ?

Il est souvent nécessaire de protéger les accès à différents services d’une application web. C’est le cas par exemple des formulaires d’authentification. Il faut donc sécuriser tout ou partie des services de l’application. Mais qu’est-ce qu’une application sécurisée ? La sécurité informatique repose sur quatre grande règles: confidentialité, intégrité, disponibilité et non répudiation. Si votre application respecte ces 4 règles, alors elle est sécurisée.

La plupart du temps, SSL est utilisé pour adresser la confidentialité et l’intégrité des communications web échangées entre le client et le serveur. Schématiquement, SSL encapsule le protocole HTTP dans un canal sécurisé. Le canal utilise un chiffrage fort qui garantie la confidentialité de la communication. Pour garantir que les données ne sont ni modifiées ni rejouées par un tiers, SSL signe et numérote les messages échangés. De plus, SSL supporte l’utilisation de certificats qui vont permettre de garantir l’identité des interlocuteurs. S’il est possible d’utiliser un certificat client pour authentifier l’utilisateur, dans la grande majorité des cas, seul le serveur possède son certificat. La mise en place de certificats clients à grande échelle, via une PKI par exemple, est un sujet à part entière que nous n’aborderons pas ici.
Dans le schéma ci-dessous, les communications provenant d’Internet sont sécurisées grâce à l’utilisation de SSL. Les communications provenant des autres serveurs de la « zone de confiance » du data center n’utilisent pas SSL. Certains estiment que cette dispense vient du fait que tous les messages doivent passer en clair dans la zone de confiance pour que les outils de sécurité puissent surveiller tout ce qui transite ; d’autres avanceront que SSL est inutile car d’autres techniques de sécurisation sont utilisées (audit de la couche réseau, gestion des permissions sur les serveurs, etc). Quelle que soit la motivation, il est fréquent de ne pas utiliser SSL pour les communications internes ; cela simplifie le tuning et le dimensionnement de nos applications.

tomcat-secured-communications

Comment savoir en Java si une requête HTTP est sécurisée ou utilise SSL ?

L’API Servlet offre une méthode générique ServletRequest.isSecure() qui indique si une requête a emprunté un canal sécurisé. Nous noterons que cette méthode est plus générique que l’utilisation spécifique de SSL et permet notamment d’identifier comme secure, bien que non SSL, une requête provenant, par exemple, du même data center. Une autre méthode plus spécifique mais souvent inutile si l’on repose sur ServletRequest.isSecure() est ServletRequest.getScheme() qui indique le protocole utilisé : http, https, etc

Comment forcer l’utilisation de communications sécurisées (SSL) en Java

Les frameworks web communément utilisés en Java permettent de forcer de façon déclarative l’utilisation d’un canal sécurisé (ie. https) pour les requêtes entrantes. Dans les faits, ils interdisent l’accès à la ressource via un canal non sécurisé en retournant l’erreur HTTP 403. Ils peuvent aussi forcer la redirection sur HTTPS.

Spring Security

Spring Security utilise l’attribut requires-channel="https" qui est hélas légèrement trompeur puisqu’il repose sur la méthode ServletRequest.isSecure() plutôt que sur ServletRequest.getScheme() et ne vérifie donc pas l’utilisation du protocole https mais le fait que la requête est secure pour le moteur de servlet.

<beans
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="http://www.springframework.org/schema/security"
   xsi:schemaLocation="
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd
   ">

   <sec:http auto-config="true">
      <sec:intercept-url pattern="/services/**" requires-channel="https" access="IS_AUTHENTICATED_FULLY" />
   </sec:http>

</beans>

Configuration Spring Security forçant SSL

API Servlet

L’API Servlet offre une approche déclarative similaire dans le fichier web.xml avec l’instruction <transport-guarantee>CONFIDENTIAL</transport-guarantee> qui repose elle aussi sur ServletRequest.isSecure().

<web-app
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   version="2.5">
   ...
   <security-constraint>
      <web-resource-collection>
         <web-resource-name>restricted web services</web-resource-name>
         <url-pattern>/services/*</url-pattern>
      </web-resource-collection>
      <user-data-constraint>
         <transport-guarantee>CONFIDENTIAL</transport-guarantee>
      </user-data-constraint>
   </security-constraint>
   ...
</web-app>

Configuration web.xml forçant SSL

Comment transmettre au moteur de servlet l’information qu’une requête est sécurisée (SSL) ?

Puisqu’il existe deux ports standards respectivement réservés au HTTP et au HTTPS, le standard des serveurs J2EE est d’utiliser une approche multi-canaux dans laquelle un port est réservé au canal sécurisé via HTTPS et l’autre non sécurisé via HTTP.
Avec la généralisation des proxy, des load-balancers et des cartes accélératrices SSL, il est maintenant fréquent que les requêtes HTTPS soient décryptées bien avant les serveurs JavaEE sur lesquels elles arrivent en clair.

Il existe alors deux approches pour différencier les requêtes HTTP et HTTPS :

  • S’inspirer de la différenciation de ports utilisés par HTTPS et HTTP en faisant emprunter des canaux différents aux requêtes sécurisées et non sécurisées, même après le décryptage des premières.
  • S’inspirer de l’ajout par les proxys du header X-Forwarded-For pour propager l’adresse IP de l’appelant (cf Tomcat : Adresse IP de l’internaute, load balancer, reverse proxy et header Http X-Forwarded-For) et introduire un header X-Forwarded-Proto pour indiquer le protocole utilisé.

Configuration multi-canaux : utiliser des connecteurs/ports différents

L’approche historique de Tomcat pour différencier les requêtes qui sont entrées en HTTPS de celles qui sont entrées en HTTP est de définir plusieurs connecteurs. Cette approche permet aussi bien de gérer l’encryption SSL dans Tomcat que, depuis la version 6, de la faire en amont dans le serveur web ou un load balancer hardware par exemple. Dans ce deuxième cas, un connecteur est configuré pour recevoir des communications en HTTP tout en valorisant request.isSecure() à true et request.getScheme() à https.

Hélas, cette configuration rend la gestion de requêtes sécurisées non SSL (<Connector ... secure="true" scheme="http" />) très délicate, car la spécification Servlet stipule que le conteneur doit émettre un cookie de session ‘secure’ si la requête HTTP à l’occasion de laquelle la session a été créée est ‘request.secure = true’.
Le problème vient du fait que des clients HTTP comme Jakarta Commons Http Client traitent automatiquement les connections non SSL comme non sécurisées et ne renvoient donc pas le cookie JSESSIONID pour les appels non SSL même si celui ci a été défini comme secure par le conteneur. La définition de requêtes comme sécurisée mais non SSL est alors incompatible avec l’utilisation de sessions http avec une erreur très difficile à diagnostiquer. Du fait de ce problème, nous ne traiterons pas dans ce billet de la configuration Tomcat multi-canaux pour gérer les requêtes sécurisées qui n’utilisent pas SSL.

Exemple de configuration Tomcat multi-connecteurs pour dissocier les requêtes HTTP de requêtes HTTPS :

<server>
   ...
   <Service name="Catalina">

      <!-- Connecteur pour les requêtes non SSL -->
      <Connector ... scheme="http" />

      <!-- Connecteur pour les requêtes SSL -->
      <Connector ... secure="true" scheme="https" />
      ...
</server>

Extrait de server.xml utilisant les attributs secure et scheme pour différencier les requêtes SSL

Configuration mono-canal avec header HTTP : utiliser le header X-Forwarded-Proto

Une autre approche, disponible dès le prochain Tomcat 6.0.21, est d’utiliser un header http pour indiquer si le protocole était http ou https. Ce header est communément nommé X-Forwarded-Proto: https|http (Microsoft Internet Security and Acceleration Server utilise Front-End-HTTPS:on|off). Ce header est souvent associé au header X-Forwarded-For déjà utilisé par les load balancers et proxys pour transmettre l’adresse IP de l’appelant.

Pour prévenir tout ‘spoofing‘ des requêtes, il faut s’assurer que les points d’entrée HTTP/HTTPS de la plate-forme écrasent la valeur de ce header http X-Forwarded-Proto avec le protocole utilisé (http ou https) ; il s’agit de supprimer les headers nommés X-Forwarded-Proto s’ils existent et d’en insérer un nouveau ; omettre l’étape de suppression des headers X-Forwarded-Proto éventuellement existants serait une faille de sécurité.

Gestion du header X-Forwarded-Proto avec une valve Tomcat

La solution la plus simple pour gérer le header X-Forwarded-Proto sur un serveur Tomcat est de déclarer la valve RemoteIpValve dans le serveur Tomcat :

  1. En attendant Tomcat 6.0.21 qui embarquera la RemoteIpValve, télécharger xebia-tomcat-extras-1.0.0.jar ou compiler RemoteIpValve.java et déployer le jar sous TOMCAT_HOME/lib.
  2. Déclarer la RemoteIpValve (doc) dans server.xml. Cette valve doit être déclarée avant les autres valves qui s’appuient sur le protocole ou l’adresse IP de l’internaute. Il est possible de déclarer aussi la SecuredRemoteAddressValve (doc, également incluse dans xebia-tomcat-extras-1.0.0.jar) pour bénéficier du mécanisme de requête sécurisée non SSL :
<Server ...>
   ...
   <Service name="Catalina">
      <Connector ... />
      <Engine ...>
         <!-- Process x-Forwarded-For to get remote address and X-Forwarded-Proto to identify SSL requests -->
         <Valve className="org.apache.catalina.connector.RemoteIpValve" protocolHeader="X-Forwarded-Proto" />

         <!-- Flag as secure all requests coming from private network IP address blocks. Must be declared after RemoteIpValve -->
         <Valve className="org.apache.catalina.connector.SecuredRemoteAddressValve" />

         <!-- AccessLogValve must be declared after RemoteIpValve to get the remote address and the scheme https/http -->
         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="common" prefix="access_log."
            resolveHosts="false" suffix=".txt" />

         ...
         </Host>
      </Engine>
   </Service>
</Server>

Gestion de SSL et des communications sécurisées avec des valves Tomcat

Gestion du header X-Forwarded-Proto avec un filtre Servlet API

Si vous ne pouvez pas changer la configuration Tomcat ou si vous utilisez un autre moteur de servlet (Glassfish, Websphere, Weblogic, etc), il est possible de gérer le header X-Forwarded-For grâce à un Servlet Filter de l’application web :

  1. Télécharger xebia-servlet-extras-1.0.2.jar, compiler XForwardedFilter.java ou ajouter la dépendance maven fr.xebia.web.extras:xebia-servlet-extras:1.0.2 à votre pom.xml (doc).
  2. Déclarer XForwardedFilter (doc) dans web.xml Cet filtre doit être déclaré avant les autres filtres qui s’appuient sur le protocole ou l’adresse IP de l’internaute. Il est possible de déclarer aussi le SecuredRemoteAddressFilter (doc, intégré dans xebia-servlet-extras-1.0.2.jar) pour gérer les requêtes sécurisées non SSL :
<web-app ...>
   ...
   <filter>
      <filter-name>XForwardedFilter</filter-name>
      <description>Process x-Forwarded-For to get remote address and X-Forwarded-Proto to identify SSL requests</description>
      <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
      <init-param>
         <param-name>protocolHeader</param-name>
         <param-value>x-forwarded-proto</param-value>
      </init-param>
   </filter>
   <filter>
      <filter-name>SecuredRemoteAddressFilter</filter-name>
      <description>Flag as secure all requests coming from private network IP address blocks.
Must be declared after XForwardedFilter</description>
      <filter-class>fr.xebia.servlet.filter.SecuredRemoteAddressFilter</filter-class>
   </filter>

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

Gestion de SSL et des communications sécurisées avec des Servlet Filters

Mise en oeuvre multi-canaux et header X-Forwarded-Proto suivant les topologies Tomcat

Topologie Load Balancer Hardware Apache Httpd Tomcat

Il est très fréquent, lorsqu’une application web doit être hautement disponible, de faire précéder les serveurs web d’un Load Balancer Hardware qui devient alors un Single Point Of Failure extrêmement fiable. Nous prendrons pour ce paragraphe l’hypothèse que le load balancer hardware prend en charge l’encryption HTTPS.

Remarque SSL et load balancer

La pertinence de gérer SSL dans le load balancer est très discutée ; certains y sont farouchement opposés (cf loadbalancing.org), d’autres sont plus mesurés (c.f. HAProxy) et enfin les vendeurs de load balancers hardware y sont très favorables :-) . Ce débat, qui porte aussi sur la pertinence de déléguer la compression (aka gzip ou deflate) au load balancer, dépasse le cadre de ce billet et le domaine de compétence des architectes applicatifs. Pour aller plus loin, nous vous recommandons Making applications scalable with Load Balancing de Willy Tarreau (HA Proxy).

Remarque Apache et mod_proxy_http

Nous utiliserons dans ce billet Apache Httpd en version 2.2.11 avec le connecteur mod_proxy_http / mod_proxy_balancer. Il serait également possible d’utiliser le protocole AJP (avec mod_jk, mod_proxy_ajp ou mod_cluster) ; nous expliquerons notre préférence pour mod_proxy_http dans un prochain billet.

tomcat-load-balancing

Gestion du paramètre X-Forwarded-Proto dans les load balancers

L’utilisation du header X-Forwarded-Proto nécessite une configuration très simple : le load balancer valorise le header X-Forwarded-Proto, les serveurs web et Tomcat écoutent sur un seul port et la RemoteIpValve Tomcat (ou le XForwardedFilter) traite le header X-Forwarded-Proto pour valoriser request.isSecure() et request.getScheme() .

Note : cette approche permet très facilement de gérer les communications sécurisées non SSL en utilisant une SecuredRemoteAddressValve Tomcat (ou un SecuredRemoteAddressFilter) qui marquera request.isSecure() des requêtes non SSL émises depuis des plages d’adresses IP pré-déterminées.

tomcat-ssl-mono-channel-with-x-forwarded-proto-header

Sur un F5 Big-IP, le header X-Forwarded-Proto sera défini grâce à une iRule de type :

when HTTP_REQUEST {
   HTTP::header remove X-Forwarded-Proto
   HTTP::header insert X-Forwarded-Proto http
}

when HTTPS_REQUEST {
   HTTP::header remove X-Forwarded-Proto
   HTTP::header insert X-Forwarded-Proto https
}

Source : F5 DevCentral wiki – HTTP::header

Astuce Big-IP iRules: ‘remove’ versus ‘replace’

Par rigueur, nous avons préféré utiliser HTTP::header remove puis HTTP::header insert plutôt que HTTP::header replace qui ne traite que la première occurence du header. Si une personne malveillante injecte plusieurs headers X-Forwarded-Proto, HTTP::header replace n’écrasera que la première occurence alors que le duo HTTP::header remove & HTTP::header insert les supprimera toutes avant d’en insérer une valide.

La configuration du serveur Http est alors triviale, il suffit d’écouter sur le seul port 80 et de transmettre au serveur Tomcat en passe plat.

..
# 'myapplication' cluster
<Proxy balancer://myapplication>
   BalancerMember      http://node-1:8080 route=node-1
   ...
   BalancerMember      http://node-n:8080 route=node-n
</Proxy>

ProxyPreserveHost On
ProxyPass /mypath balancer://myapplication/mypath stickysession=JSESSIONID

Configuration Apache httpd.conf

Gestion multi-canaux depuis le load balancer

La configuration multi-canaux du load balancer nécessite une configuration plus lourde des serveurs Apache et Tomcat puisque le nombre de canaux (port d’écoute, etc) est doublé.

tomcat-ssl-dual-channels
# 'myapplication' non ssl and ssl clusters
<Proxy balancer://myapplication>
   BalancerMember      http://node-1:8080 route=node-1
   ...
   BalancerMember      http://node-n:8080 route=node-n
</Proxy>
<Proxy balancer://myapplicationssl>
   BalancerMember      http://node-1:8083 route=node-1
   ...
   BalancerMember      http://node-n:8083 route=node-n
</Proxy>

<VirtualHost default:80>
   ...
   ProxyPreserveHost On
   ProxyPass /mypath balancer://myapplication/mypath stickysession=JSESSIONID
   ...
</VirtualHost>

<VirtualHost default:83>
   ...
   ProxyPreserveHost On
   ProxyPass /mypath balancer://myapplicationssl/mypath stickysession=JSESSIONID
   ...
</VirtualHost>

Configuration Apache httpd.conf multi canaux

Topologie Apache Httpd – Tomcat

Gestion du paramètre X-Forwarded-Proto dans Apache

Bien qu’il soit possible de décrypter SSL sur les serveur web Apache Httpd, nous préférons largement la déléguer au load balancer pour les raisons suivantes :

  • Sécurité : la passphrase d’un certificat SSL est un secret très sensible qu’il est plus facile de gérer sur un nombre très limité de load balancers à l’accès très restreint plutôt que sur les serveurs web qui sont plus nombreux et sur lesquels de nombreuses équipes interviennent. Il est certes possible de protéger la passphrase d’un serveur Apache en limitant l’acccès au user root mais on voit souvent sur le terrain le user root de serveur web utilisé par beaucoup d’intervenants pour faciliter le travail au quotidien. Il est beaucoup plus simple de limiter les accès sur des serveurs ultra spécialisés comme les load balancers.
  • Performance : HTTPS est très consommateur en CPU et son tuning est délicat (réutilisation des sessions SSL, etc). Pour plus de détails, Apache Con EU 2008 – Apache Performance Tuning par Sander Temme.
  • Simplicité : grâce à l’utilisation d’un header X-Forwarded-Proto pour indiquer le protocole utilisé, la configuration du serveur se limite au seul port d’écoute 80.

Si vous avez malgré tout besoin de décrypter HTTPS dans les serveurs web, voici un exemple de configuration Apache Httpd.

# 'myapplication' cluster
<Proxy balancer://myapplication>
   BalancerMember      http://node-1:8080 route=node-1
   ...
   BalancerMember      http://node-n:8080 route=node-n
</Proxy>

<VirtualHost default:80>
# Declare X-Forwarded-Proto as "http" for incoming request
RequestHeader set X-Forwarded-Proto "http"
...
</VirtualHost>

<VirtualHost default:443>
# mod_ssl configuration
SSLEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile "/private/etc/apache2/server.crt"
SSLCertificateKeyFile "/private/etc/apache2/server.key"

# Overwrite X-Forwarded-Proto declaration for port 443, request are "https"
RequestHeader set X-Forwarded-Proto "https"
...
</VirtualHost>
..
ProxyPreserveHost On
ProxyPass /mypath balancer://myapplicationssl/mypath stickysession=JSESSIONID

Configuration Apache httpd.conf portant le header X-Forwarded-Proto

Remarque Apache Httpd : Il n’est pas possible de déclarer deux points de configuraiton pour un même <VirtualHost />, par conséquent, si votre configuration référence un fragment extra/httpd-ssl.conf pour gérer SSL, vous devez modifier ce fragment pour ajouter la directive RequestHeader set ..., plutôt que d’utiliser un fragment ‘maison’ extra/httpd-myconfig.conf.

Gestion multi-réseaux

La gestion multi-réseaux avec Apache n’est pas particulièrement compliquée mais introduit une duplication de code verbeux qu’il est agréable d’éviter car il faut déclarer deux balancers qui ne diffèrent que par leur port d’écoute.

# 'myapplication' non ssl and ssl clusters
<Proxy balancer://myapplication>
   BalancerMember      http://node-1:8080 route=node-1
   ...
   BalancerMember      http://node-n:8080 route=node-n
</Proxy>
<Proxy balancer://myapplicationssl>
   BalancerMember      http://node-1:8083 route=node-1
   ...
   BalancerMember      http://node-n:8083 route=node-n
</Proxy>

<VirtualHost default:80>
   ...
   ProxyPreserveHost On
   ProxyPass /mypath balancer://myapplication/mypath stickysession=JSESSIONID
   ...
</VirtualHost>

<VirtualHost default:443>
   SSLEngine on
   SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
   SSLCertificateFile "/private/etc/apache2/server.crt"
   SSLCertificateKeyFile "/private/etc/apache2/server.key"
   ...
   ProxyPreserveHost On
   ProxyPass /mypath balancer://myapplicationssl/mypath stickysession=JSESSIONID
   ...
</VirtualHost>

Configuration Apache httpd.conf multi canaux

Tomcat seul

L’utilisation de Tomcat seul, sans serveur web ni load balancer en amont, simplifie le choix. Tomcat écoute à la fois en http et https (généralement respectivement les ports 80 et 443). Seule l’approche multi-connecteurs s’applique. La littérature regorge de mises en oeuvre de SSL avec Tomcat. Le principal point d’attention est la délégation de l’encryption SSL à OpenSSL grâce au connecteur APR (Http11AprConnector) plutôt que de reposer sur l’implémentation SSL des JVM (JSSE) qui est beaucoup moins performante.

tomcat-openssl
<server>
   ...
   <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" SSLRandomSeed="builtin" />
   ...
   <Service name="Catalina">
      <Connector protocol="org.apache.coyote.http11.Http11AprProtocol"
           port="8443" minSpareThreads="5" maxSpareThreads="75"
           enableLookups="true" disableUploadTimeout="true"
           acceptCount="100"  maxThreads="200"
           scheme="https" secure="true" SSLEnabled="true"
           SSLCertificateFile="/usr/local/ssl/server.crt"
           SSLCertificateKeyFile="/usr/local/ssl/server.pem"
           clientAuth="false" sslProtocol="TLS"/>
      ...
</server>

Extrait de server.xml

Astuce : comment éviter de démarrer le serveur Tomcat avec les privilèges root ?

Sur les serveur Unix/Linux, écouter sur les ports <1024 requiert les privilèges root ; faire écouter un serveur Tomcat sur les ports 80 (http) et 443 (https) devient donc très délicat pour la sécurité de la plate-forme. Une astuce est d'utiliser iptables pour faire du port forwarding de 80 et 443 vers 8080 et 8443. Plus de détails dans Tomcat Wiki - How to run Tomcat without root priviledges? ou Rife - Installing Tomcat on port 80 with iptables.

Que choisir ? Multi-canaux ou header X-Forwarded-Proto ?

Si l'approche multi-canaux avec un premier connecteur pour les communications non http et un second pour les communications https/ssl a le mérite d'être l'approche "historique", elle présente les défauts de :

  • ne pas gérer les communications sécurisées non http,
  • complexifier sensiblement les configurations Apache Httpd et, dans une moindre mesure, Tomcat.

Notre préférence va à l'utilisation d'un header http X-Forwarded-Proto qui permet de simplifier les configurations Apache Httpd et Tomcat et aussi de permettre des communications sécurisées non SSL.

L'argument d'une complexification de la configuration Tomcat par l'ajout de la RemoteIpValve (ou du XForwardedFilter) est discutable puisque cette valve est la plupart du temps nécessaire pour gérer le header X-Forwarded-For qui permet d'obtenir l'adresse IP de l'internaute, élément important pour l'audit d'actions qui requièrent SSL.

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.

13 commentaires

  • Astuce : comment éviter de démarrer le serveur Tomcat avec les privilèges root ?

    Il est aussi possible avec httpd.conf de faire un alias/forward de 8080 vers 80.

  • Bonjour Eric,

    Nous sommes d’accord. lorsqu’un serveur Apache Httpd est en amont du serveur Tomcat, on peut se permettre de faire écouter le serveur Tomcat sur des ports supérieurs à 1024 (e.g. 8080) ; il n’y a alors pas de problème d’écoute sur les ports 80 et 443 qui requièrent de s’exécuter en root.

    Notre point porte sur un serveur Tomcat seul, sans Apache Httpd en frontal. Faire écouter un serveur sur les ports <1024 comme 80 et 443 requiert de démarrer le processus en root. Cependant, à la différence d’Apache Httpd qui sait changer de user après son démarrage en root pour restreindre ses privilèges, une JVM (Tomcat mais également Glassfish, JBoss, WebSphere, Weblogic, etc) ne sait pas changer de user et devrait donc s’exécuter en root.
    Comme il est le plus souvent incompatible avec les règles de sécurité de s’exécuter en root, nous avons proposé l’astuce iptables.

    Cyrille (Xebia)

  • Si on utilise Squid 3 comme frontal à la place de Apache Mod-Proxy, il est très difficile d’appliquer la solution du X-Fowarded-Proto, car squid ne peut pas facilement ajouter des headers.

    Par contre, la solution est d’ajouter au niveau du cache_peer la directive front-end-https :

    cache_peer localhost parent 8080 0 no-query originserver login=PASS name=tomcat_peer front-end-https=auto

    Cela aura pour effet d’ajouter un header
    FRONT_END_HTTPS = On
    si la requete est en https coté squid, et pas de header dans le cas http.

    Il ne reste plus qu’a adapter ensuite le XForwardedFilter ou bien la configuration du serveur Java pour prendre en compte ce header au lieu de X-Forwarded-Proto.

  • Merci pour ce complément Nicolas,

    Je trouve amusant que Squid ait été l’inventeur du header X-Forwarded-For et qu’en revanche, ils aient oublié le header X-Forwarded-Proto au point ne ne pas le voir et d’intégrer son équivalent FRONT_END_HTTPS utilisé par Microsoft Outlook Web Access (OWA) :-).

    Cyrille (Xebia)

  • J’en profite pour signaler une erreur dans ce qui a été commité chez Tomcat :
    http://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/filters/RemoteIpFilter.java

    La JavaDoc dit :
    protocolHeaderHttpsValue: Value of the protocolHeader to indicate that it is an Https request

    Le code source dit autre chose :
    protected static final String PROTOCOL_HEADER_SSL_VALUE_PARAMETER = « protocolHeaderSslValue »;

    Quoi qu’il en soit, merci pour le code de ce filter, il m’est bien utile.

  • Merci Nicolas,

    Ce sera Bug 48387 – Make RemoteIpFilter parameters consistent with RemoteIpValve.

    Cyrille (Xebia)

  • @Nicolas,

    Merci encore, la correction est appliquée : Commit 890483 : Make configuration attributes consistent between Filter and Valves, le paramètre de configuration s’appelle désormais protocolHeaderHttpsValue.

    Cyrille (Xebia)

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

  • Cyrille –

    Apologies for the English!

    Back in November 2010 we had a discussion about an enhancement to the Tomcat RemoteIpValve (http://www.coderanch.com/t/445496/Tomcat/request-getRemoteAddr). It went like this:
    =========================================================================

    Andrew Swanson wrote:
    I am using the RemoteIpValve to correctly set request.isSecure and request.scheme but I am using the « ProxyPreserveHost » Apache httpd directive so that request.serverHost and request.serverPort are correctly set in Tomcat. Is there anyway to prevent RemoteIpValve from populating the request.serverPort when it detects the presence of the $protocolHeader http header?

    Cyrille Le Clerc wrote:Hello Andrew,

    You are right, RemoteIpValve (and RemoteIpFilter) currently override the serverPort when you specify a protocol header (e.g. x-forwarded-proto). Would you like to add a boolean configuration option override-http-server-port-with-protocol-information to disable this behavior for your use case ?

    Thanks for your interest in RemoteIpValve,

    Cyrille

    Andrew Swanson wrote:
    Yes, something like that would be most helpful.
    =========================================================================

    Has this change made it into Tomcat? If so, what version? If not, are there any plans to add it?

    Thanks for your time.

    Andrew Swanson
    Caterpillar Inc.

  • Hello Andrew,

    I am sorry but I didn’t have the time to work on this as I would have liked and I will not have the time during the next months.

    Can you continue this discussion on the tomcat-users mailing list as the RemoteIpValve is now a mature part of Tomcat.

    I hope you will find people interested in developing such extension.

    Cyrille

  • Cyrille –

    No problem. I will take this up with the Tomcat folks. Thanks for your time.

    Andrew

Laisser un commentaire