|IBM home | Products & services | Support & downloads | My account|
|Securing Linux for Java services|
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
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.
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#?
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.
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
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
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:
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.
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:
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
This will work only until you restart your system. If you want to make this redirection permanent, you'll need to execute the command:
in order to save the current iptables configuration.
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:
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
The following is a sample tcstart.sh you can modify to fit your installation.Listing 4. Sample tcstart.sh
chroot: the maximum security
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
Once all this is set up, you can try executing:
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
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.
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.