Running JBoss on Port 80 or 443 on Linux

转载自 http://community.jboss.org/wiki/RunningJBossonPort80or443

So, you've noticed that JBoss runs on port 8080 and to access it you need to go to http://<host>:8080 or https://<host>:8443.  Instead, you'd like to have JBoss run on port 80 (http) or 443 (https) so that you can access it via http://<host> or https://<host>.

The reason JBoss runs by default on these higher ports is that on some operating systems (linux, unix, etc.), only priviledged users can bind to low ports (below 1024).  Typically, people do not want their web or application servers running as priviledged users because of the security implications.

There are at least three ways to effectively let clients access JBoss over standard HTTP ports (80 / 443).

1.)  Use a Load Balancer
Most production environments use this approach as it allows for load balancing and failover to the back end server instances.  Also, this allows running multiple JBoss servers on the same machine, but all proxied through the standard ports (80 / 443).  In some cases though, this is overkill or not an option.
2.)  Use TCP Port Forwarding
In this approach, you can basically forward traffic from one port to another.  In this way, JBoss will be running on a higher port like 8080, but all traffic coming into port 80 will be automatically redirected to 8080.  In this way it will appear to clients that JBoss is runing on port 80.
There is more discussion of this approach here:  http://alek.xspaces.org/2004/12/07/tomcat-port-redirect
If you go this route, make sure that you setup the connector proxy port in $JBOSS_HOME/server/$CONFIG/deploy/jboss-web.deployer/server.xml so that any generated URLs will have the proxy port instead of the actual port.  Should look something like this below, as mentioned here:  http://tomcat.apache.org/tomcat-5.5-doc/proxy-howto.html

<Connector port="8080" ...
   proxyName="www.mycompany.com"
   proxyPort="80"/>

Here are the linux commands required to setup port forwarding:

iptables -F
iptables -X
iptables -t nat -A OUTPUT -d localhost -p tcp --dport 80 -j REDIRECT  --to-ports 8080
iptables -t nat -A OUTPUT -d <network IP address> -p tcp --dport 80 -j REDIRECT  --to-ports 8080
iptables -t nat -A PREROUTING -d <nework IP address> -p tcp --dport 80 -j  REDIRECT --to-ports 8080
/etc/init.d/iptables save
/etc/init.d/iptables restart

3.)  Run JBoss as a Priviledged User (root) Temporarily Such That It Can Bind to Lower Ports (80/443)

Running JBoss as the root user is very simple to do.  The problem is that if we just run the application server as root, it can be considered a security vulnerability.  So, what we're going to show below is one way in which the JBoss server can be started as root (in order to bind to desired ports) and then changed to be running as a different user after the ports have been bound.

It's actually more complex that one might think to accomplish this.  First, we'll walk through exactly what you need to do to make this work for you once everything has been built.  Second, we'll walk through an overview of how the pieces work together to make this happen.  Third, we'll walk through all the gory details of building the pieces necessary to make this work.  If you are running a 32 bit JVM on Linux, this third step is just FYI as the pieces have already been created for you and are attached to this wiki.

What do we need to do to get this working?

   1. Set the HTTP ports to 80 / 443 as desired in:  $JBOSS_HOME/server/$CONFIG/deploy/jboss-web.deployer/server.xml
   2. Copy the attached libsetuid.so to your LD_LIBRARY_PATH (probably /usr/lib)
   3. Copy the attached setuid.jar to your $JBOSS_HOME/server/$CONFIG/lib directory
   4. Copy the attached setuid.sar to your $JBOSS_HOME/server/$CONFIG/deploy directory
   5. Expand the sar and edit jboss-server.xml to have the correct username you want to run the server as
   6. Login as the user you want to run the server as
   7. su to root, keeping the other user's environment:
            su -m root
   8. Set the default group for this root shell to the group your other user is in:
            newgrp <group>"
          * This is so that directories and files created by root while the server is starting will be accessible by the user that the server ends up running as
   9. Set the umask for the current shell to have user/group priviledges:  "umask 0006"
  10. Delete any temp files if the server has already been started in the past:
            rm -rf $JBOSS_HOME/server/$CONFIG/tmp
            rm -rf $JBOSS_HOME/server/$CONFIG/workrm -rf $JBOSS_HOME/server/$CONFIG/log
  11. Start the server
          * You'll notice (via "top" or "ps") that the server is initially running as root, but switches to the user you configured in #5 before the server finishes starting.  You'll also see something like this in the server log:
          *
            19:52:28,569 WARN  [SetUidService] Changing UID of process to: apestel
            19:52:28,571 WARN  [SetUidService] Changed process UID to: apestel --> Successfully set uid/gi

That's it!  You've bound the server to priviledged ports, but it's now running as an unpriviledged user!

Ok, now let's get a little overview to see how this all works...

Changing the uid and gid of a running process (JVM in this case) requires executing a C API (setuid and setgid) from within the JVM via JNI.  So for this example, we have created:

   1. a Java class containing a native method that we compile into a standalone JAR file (setuid.jar)
   2. a shared object library (libsetuid.so) based off the native method in "A" that invokes the C setuid() api
   3. an MxBean that will start after the JBoss Web container has started and will use the Java Library in "A" to change the user of the running application server to a non-root user

Now, let's look at these three pieces in more detail.

Creating the Java Class with a Native Method

The Java class with the native method is attached (SetUid.java) and shown below.

package org.jboss.community;

public class SetUid {

   static {
      System.loadLibrary("setuid");
   }

   public native Status setUid(String username);

   public static class Status {

      private boolean success;
      private String message;

      public Status(boolean success, String message) {
         this.success = success;
         this.message = message;
      }

      public boolean getSuccess() {
         return success;
      }

      public String getMessage() {
         return message;
      }
   }
}

To create the C header for this setuid() native method, you simply need to execute this command:

javah -jni org.jboss.community.SetUid

This will create a file called org_jboss_community_SetUid.h that you can just rename from .h to .c, add the C method implementation, and build it into a shared object library which will be discussed in the next step.  The last thing we need to do for this step is compile our Java class and add it to a JAR as shown below:

javac -classpath $JBOSS_HOME/lib/log4j-boot.jar -d ./jarBuild SetUid.java
jar -cvf $JBOSS_HOME/server/default/lib/setuid.jar -C jarBuild org

Creating the Shared Object Library for the JNI Class

The shared object library (libsetuid.so) is attached.  This shared object library was built for 32 bit Linux (specifically built on Fedora 9), but you can build it for whatever your environment happens to be.  It needs to be put somewhere in the LD_LIBRARY_PATH so that the JBoss server will be able to find it and load it.  For my testing, I put it in /usr/lib.

Building this shared object library (if you need it for a different platform) is actually not that difficult.  Here is the code that needs built (attached as org_jboss_community_SetUid.c):

#include <jni.h>
#include <pwd.h>

#ifndef _Included_org_jboss_community_SetUid
#define _Included_org_jboss_community_SetUid
#ifdef __cplusplus
extern "C" {
#endif

jobject getStatus(JNIEnv *env, int successCode, const char * message) {

   jstring message_str = (*env)->NewStringUTF(env, message);
   jboolean success = successCode;
   jclass cls = (*env)->FindClass(env, "Lorg/jboss/community/SetUid$Status;");
   jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "(ZLjava/lang/String;)V");
   return (*env)->NewObject(env, cls, constructor, success, message_str);
}

JNIEXPORT jobject JNICALL Java_org_jboss_community_SetUid_setUid
  (JNIEnv *env, jobject obj, jstring username) {
 
      const char *user_str = (*env)->GetStringUTFChars(env, username, NULL);

      struct passwd *pwd = (struct passwd *)getpwnam(user_str);
      if(pwd == 0) {
         return (jobject)getStatus(env, 0, "Error getting uid/gid information for user.");
      }

      int uid = pwd->pw_uid;
      int gid = pwd->pw_gid;

      if(setgid(gid)) {
         return (jobject)getStatus(env, 0, "Error setting gid for user, current user may not have permission.");
      }

      if(setuid(uid)) {
         return (jobject)getStatus(env, 0, "Error setting uid for user, current user may not have permission.");
      }

      return (jobject)getStatus(env, 1, "Successfully set uid/gid.");
}

#ifdef __cplusplus
}
#endif
#endif

To build this into an executable, just execute the following command:

gcc -o libsetuid.so -shared -I/usr/java/jdk1.6.0_11/include -I/usr/java/jdk1.6.0_11/include/linux org_jboss_community_SetUid.c

That's it, now you've built the only platform specific piece of this solution.

Creating the MXBean SAR to Set the Server's UserID

There are three pieces to creating the MXBean SAR.

First, we need to create the ServiceMBean interface (attached as SetUidServiceMBean.java) as shown below:

package org.jboss.community;

public interface SetUidServiceMBean
{
   void setUsername(String username);
   String getUsername();
   void start();
   void stop();
}

Second, we need to create the implementation of this MxBean (attached as SetUidService) as shown below:

package org.jboss.community;
import org.apache.log4j.Logger;

public class SetUidService implements SetUidServiceMBean
{
   private Logger log = Logger.getLogger(this.getClass());
   private String username;

   public void setUsername(String username) {
      this.username = username;
   }

   public String getUsername() {
      return username;
   }

   public void start() {
      log.warn("Changing UID of process to: " + getUsername());
      SetUid.Status status = new SetUid().setUid(username);
      if(!status.getSuccess()) {
         log.error("Unable to change process UID to: " + getUsername()
           + " --> " + status.getMessage());
      } else {
         log.warn("Changed process UID to: " + getUsername()
           + " --> " + status.getMessage());
      }
   }
               
   public void stop() {
      log.info("Stopping SetUidService.");
   }          
}

Third, we need to create the jboss-service.xml file for this SAR (attached as jboss-service.xml) as shown below (note that this is where you'll need to set the real username that you want the process to run as):

<?xml version="1.0" encoding="UTF-8"?>
<server>
    <mbean code="org.jboss.community.SetUidService"
           name="SetUid:service=SetUid">
        <attribute name="Username">apestel</attribute>
        <depends>jboss.web:service=WebServer</depends>
    </mbean>
</server>

Lastly, we need to build these files into a SAR as shown below:

javac -classpath $JBOSS_HOME/lib/log4j-boot.jar:jarBuild -d ./sarBuild SetUidService.java SetUidServiceMBean.java

jar -cvf $JBOSS_HOME/server/default/deploy/setuid.sar META-INF -C sarBuild org -C sarBuild META-INF

That's it!  Now you should be able to do all this from scratch if desired.  The first time I did this, instead of creating an MxBean SAR to set the new userid for the process, I created a JSP to do it.  That actually works fine and I'll past the code in below, but probably more folks will want the userid to be set automatically at startup, which is why I created the MxBean example shown above.  Here's a JSP if you want to set the userid that way:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="org.jboss.community.SetUid"%>
<%
String username = request.getParameter("username");
String group = request.getParameter("group");
String message = null;


if(username != null) {
    message = new SetUid().setUid(username).getMessage();
}

%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
   <%
      if(message != null) {
         out.println("<h2>"+message+"</h2>\n");
      } 
   %>
   <form>
      <table bgcolor="#ccccff" cellspacing="1" border="1">
         <tr><td colspan="2">Specify a new user for this server to run as:</td></tr>
         <tr><td width="50%">Username:</td><td align="right" width="50%"><input type="text" name="username"></input></td></tr>
         <tr><td colspan="2" align="right"><input type="submit" value="Change Process Owner"></input></td></tr>
      </table>
   </form>
</body>
</html>

Closing Comments

1.)  One might ask why have a separate JAR for the file that loads the shared object library.  It turns out that you only want that Class file (with the System.loadLibrary() code) loaded by one class loader.  If you were to put it in the SAR archive (which I used to do), it works fine the first time.  But the next time you tried to republish the SAR without restarting the server, it would complain that that the shared object library is already loaded.  But since it's loaded from a different class loader, it needs to be reloaded again or you'll get UnsatisifedLinkErrors.  I don't fully understand all that (there are some related bugs on the Sun website), but figured it was safer to put it in a lib dir where it would only get loaded once by a single classloader.

你可能感兴趣的:(tomcat,linux,log4j,jboss,jni)