Monday, May 23, 2011

Installing Apache Tomcat under Linux

After a security outage I experienced recently with JBOSS I decided to move my webapp to Apache Tomcat. The incident was an exploit of zecmd.war that needs to be removed from $JBOSS_HOME/server/default/deploy/management/ directory. Once again this reminds me of the thinning that needs to take place before JBOSS is taken to production. A good guide for thinning JBOSS can be found here.

Nonetheless, the decision was taken: we were moving to Apache Tomcat version 7. The installation we performed under Linux is not a difficult one; however, certain steps need to take place so as to address reliability/security. Let’s go through the installation step by step. First, we need to download and install java, as well as tomcat. Tomcat needs java version 1.6 in order to function and so JDK 1.6 can be found here.
After java is in place, we may download Tomcat 7 and this can be found here.
After downloading apache-tomcat-7.0.14.tar.gz (current stable version as of this writing), as root, I did the following:
Create tomcat user and group:

$>groupadd tomcat
$>useradd tomcat –m –g tomcat

Move apache-tomcat-7.0.14.tar.gz  under /opt. Then unpack the *.tar.gz and remove it so as to gain space. Finally change ownership of the newly created directory to tomcat user:

$>mv apache-tomcat-7.0.14.tar.gz /opt
$>tar –xvf apache-tomcat-7.0.14.tar.gz
$>rm apache-tomcat-7.0.14.tar.gz
$>chown –R tomcat:tomcat apache-tomcat-7.0.14

What we have now is a brand new installation of Apache Tomcat owned by tomcat OS user. Directory /opt/ apache-tomcat-7.0.14 is our CATALINA_HOME. Next we need to fix up a bit tomcat user with the right environment variables, like CATALINA_HOME mentioned above. Go to the user’s home directory and edit .profile file. Add the following parameters:

JAVA_HOME=/usr/java/jdk1.6.0_25  
export JAVA_HOME  
PATH=$JAVA_HOME/bin:$PATH  
export PATH  
CATALINA_BASE=/opt/ apache-tomcat-7.0.14
export CATALINA_BASE
CATALINA_HOME=/opt/ apache-tomcat-7.0.14
export CATALINA_HOME
CATALINA_OPTS=”$CATALINA_OPTS -Xms128m -Xmx512m -server”

JAVA_HOME is needed to point out to the directory that java is installed. So,\ as to be sure, $JAVA_HOME/bin/java file should exist. Otherwise, you will get an error in Tomcat’s log directory stating that $JAVA_HOME/bin/java file is invalid. Also, if java can be found under /usr/bin do not use /usr as CATALINA_HOME, but better the actual directory where java was installed (in my case =/usr/java/jdk1.6.0_25 ).  After all, /usr/bin/java is a symbolic link pointing to the actual java binary.

CATALINA_BASE and CATALINA_HOME is the directory where Tomcat is installed. CATALINA_BASE may be left out, but CATALINA_HOME is absolutely mandatory.

CATALINA_OPTS dictates, among others, the heap parameters for starting Tomcat, as well as indicate that we need to start it as a server.
 
Another good idea is to run Tomcat as a service in Linux, by placing the appropriate tomcat file under /etc/init.d. Mine looks like this:

JAVA_HOME=/usr/java/jdk1.6.0_25  
export JAVA_HOME  
PATH=$JAVA_HOME/bin:$PATH  
export PATH  
CATALINA_BASE=/opt/ apache-tomcat-7.0.14
export CATALINA_BASE
CATALINA_HOME=/opt/ apache-tomcat-7.0.14
export CATALINA_HOME
CATALINA_OPTS=”$CATALINA_OPTS -Xms128m -Xmx512m -server”
case $1 in  
start)  
bash $CATALINA_HOME/bin/startup.sh  
;;   
stop)     
bash $CATALINA_HOME/bin/shutdown.sh  
;;   
restart)  
bash $CATALINA_HOME/bin/shutdown.sh  
pid=$(ps -ef | grep tomcat | grep java | grep -v grep | grep -v stop | awk '{print $2}')
while [[ "$pid" != "" ]]
do
sleep 2
echo "shutting down app server.."
pid=$(ps -ef | grep tomcat | grep java | grep -v grep | grep -v stop | awk '{print $2}')
done
bash $CATALINA_HOME/bin/startup.sh  
;;   
esac      
exit 0 

We may then add appropriate permissions to this file. Do the following so as to fix this:

$>chmod 755 /etc/init.d/tomcat

We are now ready to start Tomcat as a service under tomcat user, like this:

$>su – tomcat
$>service tomcat start

But beware! Let us first secure our installation. Take the following steps:

1. Remove all files and folders under $CATALINA_HOME/webapps, $CATALINA_HOME/server/webapps, as well as some other files not necessary to common production:

$>rm –rf $CATALINA_HOME/webapps
$>rm $CATALINA_HOME/server/webapps

   
2. Make sure $CATALINA_HOME/conf/web.xml file has an entry called listings set to
false. By default it is, but you may edit the file just to make sure:

<init-param>
     <param-name>listings</param-name>
     <param-value>false</param-value> 
</init-param>


In the same file, make sure that the following entry exists as follows:
       
<error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/manos.jsp</location>
</error-page>


As you may have guessed, manos.jsp does not exist, so in case of a Java Exception the user will receive a blank page instead of the stacktrace, which can reveal vulnerability in your app. Please note however that in my app I have configured my own 500 and 404 error pages.

3. So as to reveal as little as possible to a potential attacker in HTTP headers, you may edit $CATALINA_HOME/conf/server.xml file and add the server=”Something” entry in all Connectors. It will look like this:

<Connector port="8080" server="Something" />

4. Change the shutdown command in $CATALINA_HOME/conf/server.xml to something other than the default SHUTDOWN. Also, make sure that a proper set up of iptables hides port 8005 from the outside world.
        
<Server port="8005" shutdown="LukeSkywalker">
 
5.  Make certain directory/file restrictions harder, as follows:

$>chmod 700 $CATALINA_HOME/conf
$>chmod 700 $CATALINA_HOME/conf/*
$>chmod 700 $CATALINA_HOME/temp
 
And after all this set up, you are pretty much done!

Some additional steps I did was to configure all Connector entries in $CATALINA_HOME/conf/server.xml to use UTF8 by placing entry URIEncoding="UTF-8", place connector/j library for mysql under $CATALINA_HOME/lib directory and editing $CATALINA_HOME/conf/context.xml to add the mysql datastore (the latter might not be a best practice, since this resource is globally available to all apps running under Tomcat - an alternative approach would be to place it under $CATALINA_HOME/conf/Catalina/localhost/ROOT.xml, assuming your app is called ROOT).

Friday, May 6, 2011

Everything is a query these days - Part 2

In my previous post I advocated a solution for detecting and subsequently deleting duplicate records from a Database. What about the reverse problem, i.e. when two Databases have a common table, but they are not synchronized? Let us suppose that a table in a production Database is correctly updated via an external app, but the same backup Database table is not updated due to a synchronization issue. Assuming that no fancy tools are out there to assist you in this issue, how would you manually update the backup Database table so as to be in sync with production?

The table in question is called emp and has four columns, emp_id, first_name, last_name, dept_id. It is obvious from this structure that the primary key is emp_id and records missing from the backup Database table are lacking this attribute.

The first solution may be something like the following query: 

INSERT INTO emp_backup
SELECT * FROM emp
WHERE emp_id NOT IN (SELECT DISTINCT emp_id FROM emp_backup);

This query has two evident problems. The first is performance, given that for every row fetched from emp table a check has to be made so as to define whether or not emp_id column value is present in the corresponding backup table.
But there is a bigger issue than this. Most RDBMS systems will not allow for an indefinite list of values in the NOT IN … clause. In other words, if query

SELECT DISTINCT emp_id FROM emp_backup

fetches more than x-number of rows, it will subsequently fail, giving you a warning for your actions. This So, what is the best query to solve this? The first solution that comes to mind is to upsert emp_backup table, i.e. make insert if record does not exist or make a dummy update if the record exists. Let us transform this into an Oracle query:

MERGE INTO emp_backup b
USING emp e
 ON (b.emp_id = e.emp_id)
WHEN NOT MATCHED THEN
  INSERT (b.emp_id, b.first_name, b.last_name, b.dept_id)
  VALUES (e.emp_id, e.first_name, e.last_name, e.dept_id);

The query above is Oracle's way of upserting data from one table to another. MySQL has a similar command called REPLACE INTO. More on REPLACE INTO can be found here.