IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Linux | Java technology | Open source projects
developerWorks
Securing Linux for Java services
e-mail it!
Contents:
Why use the Java platform?
Apache Tomcat
The port dilemma
Auto-starting Tomcat
chroot: the maximum security prison
Conclusion
Resources
About the author
Rate this article
Related content:
Practical Linux security
Filtering tricks for your Tomcat
Taming your Tomcat: Filtering tricks for Tomcat 5
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Build a safe cage for Tomcat

Level: Introductory

Dennis M. Sosnoski (mailto:dms@sosnoski.com?cc=&subject=Securing Linux for Java services)
President, Sosnoski Software Solutions, Inc.
1 April 2003

Enterprise Java expert Dennis Sosnoski starts with his view of how Java server technologies fit with Linux, then gives pointers on setting up the Tomcat Java servlet engine on Linux -- securely.

Linux and the Java platform have had a long -- but often troubled -- relationship. Early moves to develop open source clean room implementations of the Java platform largely floundered on the complexities of building high-performance virtual machines while simultaneously trying to keep up with the ever-growing set of core Java APIs. Licensed implementations of Java technologies eventually became available for Linux, but these implementations were not open source. Most Linux distributions don't include the licensed implementations for that reason.

Despite these difficulties, the Java platform provides major benefits that have led to increasing use of the licensed implementations on Linux, especially for server applications. In this article, I review the advantages of the Java platform for server applications, then look at the issues involved in simply and safely deploying Java services on Linux. As a practical example, I'll cover the details of setting up the Apache Software Foundation's widely used Tomcat Java servlet engine for standalone operation.

Why use the Java platform?
There are a number of reasons why the Java platform has become a widely accepted choice for server-based business applications. I'll focus on three that I see as crucial for this environment: cross-platform compatibility, managed runtime environment, and ease of development.

Java applications offer binary compatibility across a wide range of operating systems and hardware platforms. This is especially true of non-GUI server applications, where generally very little testing needs to be performed on the actual target system. Developers can code and debug on whichever platform they prefer while still deploying to an environment that may be outside of their direct control.

The runtime features of the Java Virtual Machine (JVM) environment enforce program safety in several ways. One of the most significant aspects is that the combination of strict type checks, array bounds checking, and automatic garbage collection thoroughly blocks the most destructive forms of server code vulnerabilities: buffer overruns, double-release errors, and rogue pointers. Developing out of its early use for applets, the Java language also has a sophisticated system of fine-grained access controls for facilities that might conceivably involve security risks. These are optional for use by standalone applications, but are built into many of the frameworks for Java services.

These runtime program safety features also contribute to the Java language's ease of development. It's difficult to get any accurate measurements for this type of issue, but most developers who came to Java programming from a background in languages such as C and C++ agree that their productivity increased with the transition. Part of this is the strict enforcement of typing, both at compile-time and runtime, along with the simplicity of automatic memory management. Another factor is the extensive collection of standard APIs developed for the Java platform. These can represent a significant challenge to new developers, but once learned, the APIs provide excellent cross-platform support for a wide variety of enterprise requirements.

There are certainly applications where the Java platform may be a poor choice. Despite continuing improvements in JVM architectures, Java applications will generally run somewhat slower than C or C++ applications using the same algorithms. Based on my experience and tests, I'd estimate this speed difference at somewhere in the 20 to 50% range for most server applications running on the licensed JVMs, though this will depend heavily on the quality of the code. Java applications running on these JVMs also suffer from a relatively slow startup compared to stand-lone programs, though this is generally not a major issue for long-running server applications. In most cases the reduced performance and slower startup are small prices to pay for the enhanced security and faster development advantages of the Java platform.

Open source alternatives
Besides the standard licensed JVMs (free to use, but restricted source; available for Linux from Sun, IBM, BEA, and the Blackdown organization), there are several other alternatives for Linux. These include clean room open source JVM implementations, with Kaffe probably the most widely used (and it's included in many Linux distributions). Kaffe is a very significant project that has accomplished some amazing work, but it can only offer limited compatibility with current licensed JVMs. As a result, it's generally not usable for the enterprise-type server applications that are the focus of this article.

The same is true of the open source work going on with native code compilers for Java programs. Here the most significant project is the GNU Compiler Collection's GCJ. Using a native code compiler such as GCJ converts the platform-independent Java byte code into platform-specific code prior to execution (as opposed to executing in a JVM, which will generally convert the byte code into platform-specific code at runtime).

Native code compilation shows great potential as a way of avoiding the relatively slow startup of a Java application running in a JVM. However, compilers using this approach generally can't match the steady state performance of current-generation licensed JVMs. This is especially true if the Java applications make use of dynamic features of the Java platform (such as using reflection to access fields or load classes selected at runtime). Depending on the implementation and compile options used, native code compilation may also weaken many of the runtime safety features of the Java platform. Finally, many of the Java APIs cannot be used with compiled native code due to licensing issues. Because of these limitations, native code compilation currently isn't a good choice for Java platform server applications.

What about C#?
An alternate approach that has a lot in common with the Java runtime environment is Microsoft's C# language and the associated Common Language Runtime (CLR). C# is a close derivative of the Java language, and the CLR potentially allows it to be used on a wide variety of platforms. The CLR also provides many of the runtime safety features of a JVM (though with escape hatches that substantially dilute the safety guarantees). The Microsoft .Net implementation also supports an option of precompiling to native code for a faster startup, duplicating what GCJ has done for Java byte code. Of course, none of this is of much direct use to Linux users, since .Net is only for Windows systems.

The Mono Project is working on building a clean room open source C# and CLR-equivalent for many flavors of Linux. At this point they've got the C# compiler working, along with most of the CLR that Microsoft has released for standardization. This still has a long way to go before it becomes a reasonable alternative to the Java platform, though, either in terms of performance or functionality. The CLR covers only basics equivalent to the Java core class libraries. It would need to be supplemented with many additional APIs before it could be considered a reasonable choice for enterprise software development.

The Mono Project is working on ports of other parts of .Net beyond the CLR, and if these ports are successful -- and if Microsoft does not enforce its patents on these parts of .Net -- they will go a long way toward meeting the needs for C# to become a solid platform for server software development on Linux. But that's a long string of ifs away, and in the meantime, both the native code compilers for Java programs and the open source JVMs offer relatively stable alternatives for users who really want to avoid using licensed JVMs and can live with limited functionality.

Apache Tomcat
One of the most ubiquitous Java platform server applications is Apache Tomcat. Tomcat is an open source project based on source code originally donated by Sun. It's an HTTP server that is the official reference implementation for the very widely used servlet and JavaServer Page (JSP) technologies, developed by Sun through the Java Community Process. I'll use Tomcat in this article as a sample Java application to be deployed as a service on Linux. If you want to try running Tomcat for yourself, you'll need to have a Java Development Kit (JDK) installed on your system, rather than the smaller Java Runtime Environment (JRE).

The servlet and JSP technologies are used for constructing HTTP server applications. The servlet technology itself is roughly equivalent to a CGI interface that has been customized for fast and direct Java language calls, though with many added features (including access security, session management, and threading control). The JSP technology provides an easy way of working with dynamically generated HTML pages that are compiled directly into servlets for fast runtime operation.

Tomcat provides a number of other features beyond just these two technologies. It's actually a fully functional Web server in its own right, but is often used on Linux systems in combination with an Apache Web server front end. Apache offers much superior performance to Tomcat for serving static content. For Web applications with a relatively high proportion of static content and high usage, the Apache front end can be very worthwhile. But for many simple Web applications, it's overkill, and running Tomcat alone offers adequate performance while being much easier to configure and manage (at least for developers who haven't previously worked with Apache).

The port dilemma
One big problem with running Tomcat standalone is that it cannot access the standard HTTP port 80 unless run as root. The idea of running a server application as root is not normally something discussed in polite company, so I'll just skip over that idea completely! Using a port other than 80 is a better choice (the Tomcat default port is 8080, for instance). This is often fine for testing, but it leads to messy URLs when a service is being accessed by users, since the port number needs to be spelled out in the request. Using a non-standard port also means that any firewalls need to be reconfigured if external access is needed.

The xinetd solution
Fortunately, Linux supports some easy ways of handling port 80 requests with Tomcat (or any other user mode application). One common way is through xinetd. xinetd is an Internet service daemon with extensive access control and logging support, as well as a handy redirection feature. Redirection lets you configure your system to accept incoming requests on a port, then pass the requests on to another port or even another IP address for handling.

If you want to set up Tomcat to handle port 80 requests on your system, you'll need to add a xinetd configuration file for this purpose. Assuming xinetd is installed with the usual paths, you can do this by adding a file (as user root) to the /etc/xinetd.d directory. Listing 1 gives a sample configuration file for Tomcat.

Listing 1. xinetd redirect configuration
       
# Redirects any requests on port 80 
# to port 8080 (where Tomcat is listening)
service tomcat
{
        socket_type     = stream
        protocol        = tcp
        user            = root
        wait            = no
        port            = 80
        redirect        = localhost 8080
        disable         = no
}

After you've added the configuration file, you'll need to restart xinetd to actually activate the redirection. On most Linux installations you'd do this by executing:

/sbin/service xinetd restart

as root. As long as you leave the configuration file in the /etc/xinetd.d directory, the redirection will start up automatically when you restart your system. If Tomcat isn't set up for automatic start, incoming requests will just be rejected until Tomcat is started.

The iptables solution
xinetd is a great way to handle request redirection, but it does add some overhead by running a process to actually forward data between the ports. Recent Linux kernel versions support an even better way of setting up redirection, by using iptables. iptables differs from xinetd in that it's an actual kernel component. Because of this, it can avoid the overhead added by the xinetd approach. The only downsides to working with iptables are that it can be more confusing to configure than xinetd, and it's only available on fairly recent kernel versions.

You'll need to be running a 2.4.x or later kernel that supports iptables in order to use the technique I describe here. Configuring and setting up iptables is a topic that could easily warrant several articles on its own, so I'm not going to try to cover that here. If you need help getting started with iptables, check the manuals for your Linux distribution. For a quick check to see if iptables is running on your system, try executing:

/sbin/service iptables status

as root. If it's running, you'll see a listing of tables and chains on the console.

iptables uses several separate tables and chains of packet handling rules. In order to redirect incoming HTTP requests from port 80 to another port within the system, you'll use the nat table (which stands for Network Address Translation) and the PREROUTING chain. Listing 2 gives the actual command to be executed (as root) in order to add a rule to handle this. The effect of this rule is to modify incoming packets aimed at port 80 to instead target port 8080, so it will only work properly if you don't have port 8080 blocked from outside use. Once the command has been executed, you should immediately be able to process incoming requests.

Listing 2. iptables redirection rule
       
/sbin/iptables -t nat \
  -A PREROUTING -j REDIRECT -p tcp \
  --destination-port 80:80 --to-ports 8080

This will work only until you restart your system. If you want to make this redirection permanent, you'll need to execute the command:

/sbin/service iptables save

in order to save the current iptables configuration.

Auto-starting Tomcat
Another issue when running a Java service such as Tomcat is how to automatically start the application when the system comes up and stop it when the system goes down (run it as a daemon, in other words). Experienced Linux users will already know how to do this, but if you are relatively new to Linux, here are the basics.

If you're just running this on your personal system and want to use the same access to Tomcat's files and directories as you'd have running Tomcat directly, you can set this up with your own user name. It's generally a good idea to set up a separate user for anything that's going to be run as a daemon, though. To do this for Tomcat, execute:

/usr/sbin/useradd tomcat

as root. This will create a user account named tomcat along with a home directory at /home/tomcat that can be used for the Tomcat installation. The created home directory will have the tomcat user as owner and normally will only allow access to that user (along with root, of course). If you want access to the Tomcat installation from other accounts, you can change permissions to include group access and add the tomcat group to these other accounts.

Either way, to run Tomcat as a daemon you'll need to add a service configuration file in the /etc/init.d directory, which you'll probably want to name "tomcat." Listing 3 gives a sample of what this might look like. This assumes that Tomcat is installed under /home/tomcat, with a pair of shell script files at that location to handle starting and stopping the server (tcstart.sh and tcstop.sh). These are needed to set the environmental variables needed by Tomcat (including JAVA_HOME and JDK_HOME) before executing the actual Tomcat startup or shutdown scripts.

Listing 3. Tomcat service definition
       
#!/bin/bash
#
# tomcat       Starts Tomcat Java server.
#
#
# chkconfig: 345 88 12
# description: Tomcat is the server for Java servlet applications.
### BEGIN INIT INFO
# Provides: $tomcat
### END INIT INFO

# Source function library.
. /etc/init.d/functions

[ -f /home/tomcat/tcstart.sh ] || exit 0
[ -f /home/tomcat/tcstop.sh ] || exit 0

RETVAL=0

umask 077

start() {
        echo -n $"Starting Tomcat Java server: "
        daemon su -c /home/tomcat/tcstart.sh tomcat
        echo
        return $RETVAL
}
stop() {
        echo -n $"Shutting down Tomcat Java server: "
        daemon su -c /home/tomcat/tcstop.sh tomcat
        echo
        return $RETVAL
}
restart() {
        stop
        start
}
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart|reload)
        restart
        ;;
  *)
        echo $"Usage: $0 {start|stop|restart}"
        exit 1
esac

exit $?

The following is a sample tcstart.sh you can modify to fit your installation.

Listing 4. Sample tcstart.sh

#!/bin/bash
export JDK_HOME=/usr/java/jdk
export JAVA_HOME=/usr/java/jdk
#run the startup script from Tomcat installation
/home/tomcat/server/bin/startup.sh

chroot: the maximum security prison
For the truly paranoid, it's also possible to go even further with securing a Java language service. This is especially useful when the service provides some form of access to the local file system. The JVM runtime safety features don't prevent an application that already has access to the file system from accessing files other than the ones intended by the user. In Tomcat's case, file access is inherent in its use as an HTTP server. It normally limits the files served for each Web application to those within that application's directory, but servlet applications are able to get around these limits. This is true even when Tomcat is run in combination with a front end Web server such as Apache.

Using chroot, you can block Tomcat (and all Web applications run under Tomcat) from accessing anything outside the space set aside for the server. chroot is not in any way specific to Java applications, but it's handy as a way of adding a final wrapper to the security provided by the JVM. I'll cover the high points of setting this up here for those new to the chroot concept.

What chroot does is similar to the JVM sandbox used for executing Java code, but applied to the file system itself. chroot executes a command with an effective root directory set wherever you specify. The command that gets executed (which can be a shell script that executes other commands, including applications) only has access to the portion of the file system under the specified effective root directory. The rest of the file system literally does not exist for this command.

To use chroot for an application such as Tomcat, you need to copy some of the basic system applications and libraries (including the actual Java JDK installation) into the new virtual root. This can take up a lot of space -- from perhaps one hundred megabytes to a gigabyte or more -- depending on how much effort you want to go through to trim things to a minimum. The easiest way to set this up is also the most wasteful of space: just copy the entire /bin, /lib, /usr/bin, and /usr/lib directory trees to the new root, along with the Java installation, keeping root as the owner and only authorized writer to all the files. If you want to keep disk usage to a minimum, you can instead selectively copy across only the commands you'll need within the chroot (including basics such as ls, rm, echo, cat, etc., as well as the actual Java installation) and the libraries used by those commands (which you can find by using ldd).

Next you'll need to create some additional directories as pared-down versions of the normal system. This includes /dev, with devices /dev/null and /dev/zero; /etc, with edited versions of the /etc/passwd and /etc/group files (keeping only root and tomcat entries), and perhaps hosts. If you're running on a multiple-processor system, you'll also need to mount the /proc system under the new root directory, since the JVM uses this to coordinate between processors.

Finally, in order to still run as user tomcat after the chroot, you may need to build a version of the su command that can run in the virtual root (the normal one for many distributions doesn't allow this). To do this, you can get sh-utils source from the GNU project and build su directly from this source, then copy it into the /bin directory of your new root.

Once all this is set up, you can try executing:

/usr/sbin/chroot /home/tomcat /bin/su tomcat

as root (assuming your new root directory is at /home/tomcat). If you're set up properly, this should leave you running as tomcat within the new root, and you can try your scripts to start and stop Tomcat. After you've verified that everything works, the last step is to change the Tomcat service definition from Listing 3 to use chroot instead of su, moving the su command to the tcstart.sh and tcstop.sh scripts. You also need to make sure that these scripts are only modifiable by root.

As this outline hopefully makes clear, this is not a procedure for the faint of heart! You'll definitely want to refer to one of the chroot references on the net for details if you choose to go this route and haven't previously used chroot. This does give you the best possible isolation for your Java server code, though, and in some cases the resulting peace of mind is worth the effort.

Conclusion
Linux and Java technologies are both gaining market share in business systems. Despite the philosophical differences between open source Linux and licensed Java technology, these two really fit well together. Linux is a wonderful deployment environment for Java applications, especially server-type applications, while Java technology is established and recognized as a leading approach for enterprise software development.

With proper precautions, Java server applications running on Linux can provide a very high degree of security -- even higher than native applications -- because Java technology eliminates many of the common sources of vulnerabilities in server applications. The cross-platform nature of Java technology translates into a huge pool of enterprise developers and applications that are immediately Linux-ready. Java server applications are starting to play a major role in growing Linux's share of the server market, and this is a trend that can only help both these technologies in the future.

My thanks to Miles Sabin for pointing me at the /proc filesystem to resolve problems with chroot'ing Java on multiprocessor systems.

Resources

About the author
author photoDennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc.. Dennis's professional software development experience spans over 30 years and a range of programming languages from assembler to C and C++, with the last several years focused on Java language development for enterprise applications. He's been exclusively using Linux for his own development work for three years and has set up secure Java services on Linux for several client companies. You can contact Dennis at dms@sosnoski.com.


e-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Linux | Java technology | Open source projects
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact