Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically load values into Tomcat's Context XML file

Tags:

java

mysql

tomcat

Given that Tomcat's Context XML file tends to contain sensitive information (often including credentials needed to connect to a Database), how can I dynamically load these values from a source other than the plain-text context.xml?

like image 366
Cody S Avatar asked Nov 05 '12 19:11

Cody S


2 Answers

Say you have a tomcat/conf/context.xml file that looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <Resource 
            name="jdbc/MyDB" 
            auth="Container" 
            type="javax.sql.DataSource" 
            removeAbandoned="true" 
            removeAbandonedTimeout="15" 
            maxActive="5" 
            maxIdle="5" 
            maxWait="7000" 
            username="${db.mydb.uid}"
            password="${db.mydb.pwd}"
            driverClassName="${db.mydb.driver}"
            url="${db.mydb.url}${db.mydb.dbName}?autoReconnectForPools=true&amp;characterEncoding=UTF-8"
            factory="com.mycompany.util.configuration.CustomDataSourceFactory"
            validationQuery="SELECT '1';"
            testOnBorrow="true"/>
</Context>

What we want to replace in this case is anything in the ${.*} stuff in this resource definition. With slight modification to the code below, however, you can perform these substitutions on pretty much whatever criteria you'd like.

Notice the line factory="com.mycompany.util.configuration.CustomDataSourceFactory"

What this means is that Tomcat will attempt to use this factory to process this resource. It should be mentioned that this means that this factory will have to be on Tomcat's classpath on startup (Personally, I put mine in a JAR in the Tomcat lib directory).

Here is what my factory looks like:

package com.mycompany.util.configuration;

import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import org.apache.commons.dbcp.BasicDataSourceFactory;

public class CustomDataSourceFactory extends BasicDataSourceFactory implements ObjectFactory {

    private static final Pattern _propRefPattern = Pattern.compile("\\$\\{.*?\\}");

    //http://tomcat.apache.org/tomcat-6.0-doc/jndi-resources-howto.html#Adding_Custom_Resource_Factories
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
        if (obj instanceof Reference) {
            Reference ref = (Reference) obj;
            System.out.println("Resolving context reference values dynamically");

            for(int i = 0; i < ref.size(); i++) {
                RefAddr addr = ref.get(i);
                String tag = addr.getType();
                String value = (String) addr.getContent();

                Matcher matcher = _propRefPattern.matcher(value);
                if (matcher.find()) {
                    String resolvedValue = resolve(value);
                    System.out.println("Resolved " + value + " to " + resolvedValue);
                    ref.remove(i);
                    ref.add(i, new StringRefAddr(tag, resolvedValue));
                }
            }
        }
        // Return the customized instance
        return super.getObjectInstance(obj, name, nameCtx, environment);
    }

    private String resolve(String value) {
        //Given the placeholder, do stuff to figure out what it's true value should be, and return that String.
        //This could be decryption, or maybe using a properties file.
    }
}

Then, once this code is on the classpath, restart Tomcat and watch catalina.out for the log messages. NOTE: The System.out.println statements will likely end up printing sensitive information to your logs, so you may want to remove them once you are done debugging.

On a sidenote, I wrote this out because I found that many examples were too specific to one specific topic (such as utilizing cryptography), and I wanted to show how this can be done generically. Furthermore, some of the other answers to this question don't explain themselves very well, and I had to do some digging to figure out what needed to be done to make this work. I wanted to share my findings with you guys. Please feel free to comment on this, asking any questions, or making corrections if you find problems, and I'll be sure to roll the fixes into my answer.

like image 65
Cody S Avatar answered Oct 21 '22 08:10

Cody S


Why go through all the trouble?

Simply configure the sensitive parameters on the host machine as JVM system properties and Tomcat will automatically recognise them and replace the values for all placeholders ${...}. This way the sensitive data stays on the host machine only and is never leaked in the source code.

From: https://tomcat.apache.org/tomcat-7.0-doc/config/

Apache Ant-style variable substitution is supported; a system property with the name propname may be used in a configuration file using the syntax ${propname}. All system properties are available including those set using the -D syntax, those automatically made available by the JVM and those configured in the $CATALINA_BASE/conf/catalina.properties file.

@ Note: This answer assumes that your context.xml and other Tomcat config files are under SCM, which is what typically happens when using virtualized deployment (e.g. Openshift).

like image 39
Pavel Lechev Avatar answered Oct 21 '22 10:10

Pavel Lechev