Come filtrare gli accessi per IP su Tomcat

Continua la fortunata serie “Me lo appunto qui almeno non me lo dimentico“ ed anche in questa puntata parleremo di Tomcat e quindi più in generale di programmazione di applicazioni di rete in JAVA. Il titolo mi sembra abbastanza parlante e la domanda che nasce spontanea leggendolo è: perché filtrare gli accessi tramite l’application server (Tomcat) invece di agire alla “radice” andando ad impostare delle regole su quello che per comodità e brevità possiamo chiamare firewall? Le ragioni possono essere molteplici e tra queste ci può essere per esempio la volontà di limitare l’accesso ad una specifica applicazione piuttosto che addirittura a delle singole URI e non all’intera macchina su cui sono presenti un insieme di applicazioni, operazione che è molto meno macchinosa se effettuata lato applicativo che a livello di rete soprattutto in termini di efficienza dei processi aziendali in quanto è fattibile da chi sviluppa il software senza la necessità di disturbare amministratori di rete, di sistema e altre figure che in genere hanno dinamiche e politiche di intervento diverse e questo potrebbe se non altro rallentare la fase di sviluppo e/o quella di messa in produzione delle vostre applicazioni.

La soluzione più semplice è utilizzare il Valve Component di Apache Tomcat che permette di fare quanto richiesto semplicemente andando ad inserire nel file context.xml o della vostra installazione di Tomcat oppure in quello dell’applicazione web di cui volete andare a limitare gli accessi, la seguente riga:

<Context>

    [...]

    <Valve className="org.apache.catalina.valves.RemoteAddrValve"
           allow="12.34.56.78,192.168..*" deny="172.20.64.*"/>

</Context>

ovviamente andando ad inserire negli attributi allow e deny o con gli indirizzi IP a cui volete permettere o negare l’accesso (separati da virgola se maggiori di uno) o eventualmente dei pattern che facenti uso delle espressioni regolari che rappresentino correttamente i range di IP su cui volete andare ad agire.

Un problema molto comune a questo punto potrebbe verificarsi qualora utilizziate Apache come frontend per Tomcat, cosa molto comune se dovete gestire alias, proxy name e quant’altro o anche soltanto se volete ottimizzare il carico di lavoro della vostra macchina lasciando servire ad Apache i file statici (immagini, css, javascript) e demandando a Tomcat soltanto l’esecuzione della parte in Java delle vostre applicazioni. Se siete in questa situazione Tomcat vedrà arrivare tutte le richieste da un unico IP che è quello della macchina sulla quale è installato Apache (che potrebbe essere ) vanificando così il lavoro del componente Valve in quanto le richieste verranno viste da quest’ultimo non con l’IP del client che l’ha effettuata ma sempre e solo con il solito indirizzo (quello della macchina su cui è presente il web server Apache).

La soluzione a tutto questo è andare a cercare il vero indirizzo del client nell’header http di ciascuna richiesta nel campo X-Forwarded-For. In teoria a questo punto basterà andare ad attivare il componente RemoteIPValve per avere l’effetto desiderato; il tutto si effettua andando ad inserire nel context.xml di Tomcat o della vostra applicazione quanto segue:

<Valve
  className="org.apache.catalina.connector.RemoteIpValve"
  remoteIPHeader="x-forwarded-for"
  remoteIPProxiesHeader="x-forwarded-by"
  protocolHeader="x-forwarded-proto"
  />

Nel caso siate sfortunati a sufficienza ed il componente RemoteIPValve non funzioni come aspettato non disperatevi in quanto c’è ancora una piccolo barlume di speranza per far funzionare tutto.

L’ultima spiaggia è realizzare un Filter che vada a controllare il campo X-Forwarded-For nell’header di ciascuna richiesta HTTP ed eventualmente vada ad inibire l’accesso alla vostra applicazione o anche semplicemente ad una specifica servlet o pagina che volete proteggere.

Come Filter potete prendere come esempio quanto segue:

import java.io.IOException;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
* Servlet Filter implementation class AuthorizationFilter
*/
public class AuthorizationFilter implements Filter {
private FilterConfig filterConfig = null;
/**
* Default constructor.
*/
public AuthorizationFilter() {

}

/**
* @see Filter#destroy()
*/
public void destroy() {

}

/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
     InitialContext initialContext = null;;
     try {
          initialContext = new javax.naming.InitialContext();

     } catch (NamingException e) {

          e.printStackTrace();
          return;
     }
     HttpServletResponse httpResponse = null;
     if (response instanceof HttpServletResponse)
        httpResponse = (HttpServletResponse) response;
    final HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(httpRequest) {
         @Override
         public String getHeader(String name) {
                final String value = httpRequest.getParameter(name);
                if (value != null) {
                return value;
         }
         return super.getHeader(name);
      }
    };
    String ipAddress = wrapper.getHeader("X-Forwarded-For");
    if(ipAddress==null){
           ipAddress = request.getRemoteAddr();
    }
    String ipAddresses = filterConfig.getInitParameter("deny");
    if(ipAddresses!=null){
          String[] ipAddressesArray = ipAddresses.split(",");
          for(String pattern: ipAddressesArray){
                if(ipAddress.matches(pattern)){
                   httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
                   return;
                }
           }
      }
      // pass the request along the filter chain
      chain.doFilter(request, response);
}

/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
       this.filterConfig = fConfig;
}

}

Che effettua il controllo richiesto. A questo punto dovrete inserire il filtro nel web.xml le regole per impostare le URI a cui applicare il filtro con gli indirizzi IP incriminati con delle regole come le seguenti:

<filter>
  <filter-name>AuthorizationFilter</filter-name>
  <filter-class>spot.AuthorizationFilter</filter-class>
  <init-param>
      <param-name>deny</param-name>
      <param-value>192.168.*</param-value>
  </init-param>
  </filter>
  <filter-mapping>
	<filter-name>AuthorizationFilter</filter-name>
	  <url-pattern>/*</url-pattern>
  </filter-mapping>

Le domande a questo punto possono essere molteplici: siete riusciti nel vostro intento? Con quale dei metodi elencati sopra? Se avete utilizzato altre strategie quali sono e che vantaggi/svantaggi hanno rispetto a quelle illustrate? Come potete vedere sono molto curioso e spero che qualcuno riesca a placare almeno in parte questa mia fame di sapere.

Ovviamente sono ben accette osservazioni e correzioni di qualsivoglia tipologia.

Articolo interessante?

Rimani sempre aggiornato abbonandoti ai feed RSS di questo sito oppure riicevi comodamente gli aggiornamenti via e-mail.