Like a Daemon !
Now that there’s so much buzz on the net about parallelizing programs, servers and algorithms, I’ve come to love The-Unix-Way more every day.
If you’ve heard some of the discussion, it goes along the lines threads vs processes (concurrent computation), evented vs synchronous (input/output), and there’s also functional vs imperative languages (functional languages help/force you in designing parallelizable servers/algorithms).
I will talk about Unix processes and security today.
Unix processes are easy, you can kill-them and (if your program is ‘stateless’ enough) make no harm. That lets you have some “high-availability”, while in threads, if you just killed one without taking care of the locks it holds, the shared-memory it hasn’t freed, etc… you would be degrading your whole system (the other threads).
Also there are very accurate security semantics for Unix processes. A unix process has a User-ID, a Group-ID, a current working directory, and some File Descriptors open (sockets, files, etc), and you can put them in a jail (chroot) so that they make no harm to your system in case your server got hacked.
So if you program Unix servers, you better know all this stuff.
When I do a netstat -naptu on my servers, I want to see all the processes that are listening on the public internet, see their process-name (the program), and see the User ID in which they are running. This way I easily check I have no services that I don’t strictly need and that I don’t strictly trust how/where they’re giving their service.
Usually in UNIX network servers you do :
(as ROOT)
chroot(chroot_dir)
lisen(public_IP);
setgid(unprivileged_group);
setuid(unprivileged_user);
while(running)
accept(public_socket);
process(request);
you you can be sure that if the hacker breaks your program while processing the received request, it can do no harm, since the program is running without privileges (for example, group ‘nogroup’ user ‘nobody’ who has no shell and no home).
In Linux Standard Base (LSB) there’s a definition of how a UNIX network server should be started, stopped and checked. If you follow this guidelines (and return the correct status codes) you can automatically integrate your server in a high-availability scenario, for example with HeartBeat monitoring. (whenever a program sees your server has died, it sends your admin an email so you can complain to your software developers, and the server gets restarted again).
Now enter Java. You do not have all this Unix goodness in Java since it must be multi-platform and not all the operating-systems have this process/security semantics (at least not in the same exact way).
Fortunately there’s this framework called apache commons-daemon and this program called jsvc. It brings UNIX process/security stuff into your Java program.
let’s do an exercise. I did a netstat -naptu and saw I have a program listening on 0.0.0.0:843 and running as rooot. this is bad. even tough it’s running on the Java Virtual Machine and we all trust it very mucho. You dont have to trust any server if you don’t need to. And in this case there’s absolutely no need. We could start listening on 843 and then give-up privileges changing the User-ID and Goup-ID.
that’s what commons-daemon and jsvc is for.
now your java class should implement org.apache.commons.daemon.Daemon. Basically you need:
- init: start listening on your privileged port (open listening socket)
- (now jsvc drops privileges, changes UID, GID)
- start: start your threads that will accept(socket) and process(request)
- (this will go on and on until you want to stop this daemon, which will trigger a call to stop() as follows)
- stop: stop accepting and serve the running requests
- destroy: release resources
This is a sample daemon
public class DaemonPrueba implements Daemon, Runnable {
private Logger log = Logger.getLogger(DaemonPrueba.class);
private Thread worker;
private boolean working=false;
private int workunits=0;
private ServerSocket socket;
public void init(DaemonContext arg0) throws Exception {
socketServer = new ServerSocket(privileged_port, 5);
worker = new Thread(this);
log.debug("Init");
}
public void start() throws Exception {
worker.start();
log.debug("Start");
}
public void stop() throws Exception {
working=false;
log.debug("Stop");
serverSocket.close();
}
public void destroy() {
log.debug("Destroy");
}
public void run() {
while(working){
try {
Socket socket = socketServer.accept();
PrintWriter socketOut = new PrintWriter(socket.getOutputStream(), true);
socketOut.println("Work unit "workunits+);
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
this is a sample server. Now you can use it with commons-daemon, and start it in The-Unix-Way: it can listen on privileged ports (lower than 1024) but still run as an un-privileged user (like www-data or nobody or some other ‘normal’ user). And if it crashed for some reason, you could easily have it restarted by your UNIX system.
in Debian, an init script might look like this:
!/bin/sh
DEBUG=”false”
[ "$DEBUG" = "true" ] && set -x #for debugging
NAME=myDaemon
DESC="a simple daemon Server"
MY_SERVER_HOME="/home/unprivileged/src/java/myDaemon"
MY_SERVER_STDOUT="$MYY_SERVER_HOME/log/$NAME-stdout.log"
PID_FILE="$MY_SERVER_HOME/log/$NAME.pid"
DAEMON_CLASS=DaemonPrueba
DAEMON=/usr/bin/jsvc
JAVA_OPTS="-Xms128m -Xmx256m -XX:PermSize=64m -XX:MaxPermSize=80m "
if [ "$DEBUG" = "true" ]
then
JAVA_OPTS="-debug $JAVA_OPTS"
fi
JVM_TMP="$MY_SERVER_HOME/tmp"
JSVC_CLASSPATH="$MY_SERVER_HOME/resources:\
$MY_SERVER_HOME/lib/log4j-1.2.14.jar:\
$MY_SERVER_HOME/lib/commons-daemon.jar:\
$MY_SERVER_HOME/target/modules/myserver.jar"
MY_SERVER_USER=unprivileged
JAVA_HOME="/usr/lib/jvm/java-6-sun"
export JAVA_HOME
case "$1" in
start)
echo "Starting $DESC" "$NAME"
mkdir $JVM_TMP
chown $MY_SERVER_USER "$JVM_TMP"
cd "$JVM_TMP"
;;
stop)
echo "Stopping $DESC" "$NAME"
$DAEMON -cp "$JSVC_CLASSPATH" -pidfile "$MY_SERVER_USER" \
-stop "$DAEMON_CLASS"
rm -rf "$JVM_TMP"
exit 0
;;
restart|force-reload)
$0 stop
$0 start
;;
status )
;;
try-restart)
if start-stop-daemon --test --start --pidfile "$PID_FILE" \
--user $MY_SERVER_USER --startas "$JAVA_HOME/bin/java" \
>/dev/null; then
$0 start
fi
;;
*)
echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}"
exit 1
;;
esac
exit 0
$DAEMON -user "$MY_SERVER_USER" -cp "$JSVC_CLASSPATH" \
-outfile SYSLOG -errfile SYSLOG \
-pidfile "$PID_FILE" $JAVA_OPTS "$DAEMON_CLASS"
remember, if something doesn’t work, set -x in bash (so you see what’s happening, and -debug in jsvc. Also you shold configure log4j adding -Dlog4j.configuration=file://$MY_SERVER_HOME/log4j.properties” if something goes wrong.
Now you have a UNIX-compliant java server !
enjoy !
seguidor de elias said
Jan 21, 2010 @ 01:42 PM
Olé Elias ! como mola !!