Tomcat e datasource: come criptare la password del database

Siamo al secondo appuntamento con i datasource e la cifratura della password del database. Nel post precedente abbiamo visto come risolvere il problema in JBoss AS 7, adesso vediamo come si fa con Tomcat.

Con questo tipo di server è possibile dichiarare un datasource a livello globale, ovvero visibile ad altre applicazioni, o locale, cioè strettamente legato all’applicazione corrente. Vediamo in questo esempio come criptare la password di un datasource locale (il metodo non differisce da quello globale).

In principio fu il contesto

In Tomcat, per creare un datasource locale abbiamo bisogno di 2 elementi:

  • la dichiarazione della nuova risorsa nel contesto del server, ovvero un file context.xml nella cartella /META-INF del WAR. Per intendersi, in un progetto Maven corrisponde alla cartella src/main/webapp/META-INF del progetto web (occhio quindi che non è la META-INF delle risorse dei sorgenti). La vostra dichiarazione, riferita a MySQL, sarà del tipo:
    <Context>
      <Resource 
        name="jdbc/testDS" 
        auth="Container" 
        type="javax.sql.DataSource"
        maxActive="20" 
        maxIdle="10" 
        maxWait="10000"
        username="test" 
        password="test" 
        driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/test"/>
    
    </Context>
    
  • Registrare la risorsa nel web.xml del war in modo che sia referenziabile dall’applicativo tramite lookup JNDI.
    <resource-ref>
        <description>MySQL Connection</description>
        <res-ref-name>jdbc/testDS</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
    

    Nelle ultime versioni di Tomcat questo non è più necessario ma è consigliato scriverlo a scopo documentativo per dichiarare le dipendenze del war dalla configurazione del server.
    Se per esempio usiamo Spring, il lookup può essere fatto semplicemente così:
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/testDS" expected-type="javax.sql.DataSource" />
    

Nascondiamo la password!

Su JDev si trova un post molto interessante su come criptare la password del datasource. L’idea è quella di estendere la classe DataSourceFactory per introdurre la decriptazione della password prima della creazione del datasource. Nel post, l’operazione di cifratura viene eseguita da una classe opportunamente creata. Se vogliamo risparmiare tempo e linee di codice, possiamo usare Jasypt: Java Simplified Encryption! Questo framework ci dà tutti gli strumenti necessari alla cifratura delle nostre password nonché dei file di properties (che trovo molto utile).

Prima di cominciare, aggiorniamo le nostre dipendenze del progetto:

<dependency>
   <groupId>org.apache.tomcat</groupId>
   <artifactId>tomcat-jdbc</artifactId>
   <version>7.0.41</version>
   <scope>provided</scope>
   </dependency>
<dependency>
   <groupId>org.jasypt</groupId>
   <artifactId>jasypt-spring31</artifactId>
   <version>1.9.2</version>
</dependency>

A questo punto possiamo scrivere il nostro adapter per l’encoding/decoding della password basato su Jasypt:

public class EncryptorAdapter {

   private StandardPBEStringEncryptor encryptor;
   
   public EncryptorAdapter() {
      encryptor = new StandardPBEStringEncryptor();
      encryptor.setAlgorithm("PBEWithSHA1AndDESede");
      encryptor.setPassword("My53cr37P455w0rd");
   }

   public String encrypt(String message) {
      return encryptor.encrypt(message);
   }

   public String decrypt(String encryptedMessage) {
      return encryptor.decrypt(encryptedMessage);
   }
   
   public static void main(String[] args) {
      EncryptorAdapter adapter = new EncryptorAdapter();
      System.out.println(adapter.encrypt(args[0]));
   }
}

e per completezza, la nostra factory:

public class EncryptedDataSourceFactory extends DataSourceFactory {

   private EncryptorAdapter encryptor; 
   
   @Override
   public DataSource createDataSource(Properties properties, Context context,
         boolean XA) throws Exception {

      PoolConfiguration poolProperties = EncryptedDataSourceFactory.parsePoolProperties(properties);
      poolProperties.setPassword(encryptor.decrypt(poolProperties.getPassword()));

        // The rest of the code is copied from Tomcat's DataSourceFactory.
      if (poolProperties.getDataSourceJNDI() != null && poolProperties.getDataSource() == null) {
          performJNDILookup(context, poolProperties);
      }
      org.apache.tomcat.jdbc.pool.DataSource dataSource = XA ? new XADataSource(poolProperties)
                : new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
      dataSource.createPool();

      return dataSource;
   }
}

Quest’ultimo codice è praticamente lo stesso del post di JDev, ad eccezione della classe che esegue l’encryption: si tratta di un semplice adapter basato sulla classe StandardPBEStringEncryptor di Jasypt, configurata per usare l’algoritmo di cifratura PBEWithSHA1AndDESede (PBE sta per Password-Based-Encryption) e chiave di cifratura “My53cr37P455w0rd”. L’adapter contiene anche un semplice main per criptare la password e inserirla nel context.xml, insieme al riferimento alla factory:

<Context>
  <Resource 
    name="jdbc/jsf22DS" 
    auth="Container" 
    type="javax.sql.DataSource"
    maxActive="20" 
    maxIdle="10" 
    maxWait="10000"
    username="test" 
    password="qQKhJ3oTedCJUkRYK8JtLQ==" 
    driverClassName="com.mysql.jdbc.Driver"
    url="jdbc:mysql://localhost:3306/jsf2" 
    factory="it.cosenonjaviste.tomcat.dbcp.EncryptedDataSourceFactory"/>

</Context>

Questo il risultato della dichiarazione del vostro datasource! Le due classi interessate possono essere inserite direttamente nel war o nella cartella lib di Tomcat.

Conclusioni

Come accennato nel post dedicato a JBoss, il problema della segretezza della password si sposta da quella del database a quella di cifratura. Conoscendo infatti la chiave di cifratura e l’algoritmo usato, è come avere la password in chiaro. “Embeddando” entrambi i valori direttamente nel
sorgente dell’adapter si aggiunge un po’ più di protezione perché andrebbe decompilata la classe per risalire ai loro valori (non che sia difficile ma almeno un utente mal intenzionato deve sudarsela un po’ di più 🙂 ). Per i maniaci della sicurezza, la soluzione sarebbe quella di dichiarare una variabile d’ambiente contenente la password a cui l’adapter dovrebbe accedere prima di creare il datasource. Una volta avviato, è possibile cancellarla.
Nel prossimo post vedremo come Jasypt e Spring insieme possano dare questa garanzia con poco sforzo (e niente codice!).

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+