Categorie
Guide Programmazione Sicurezza Tecnologia

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.

Rispondi

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.