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.