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.