Initial checkin

This commit is contained in:
Dominic Reynolds 2006-08-16 16:32:49 +00:00
parent f9df421131
commit 704e1e4d36
7 changed files with 568 additions and 0 deletions

View file

@ -0,0 +1 @@
Main-Class: ChangeHatValve

View file

@ -0,0 +1,195 @@
# ------------------------------------------------------------------
#
# Copyright (C) 2002-2006 Novell/SUSE
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
----------------------
1. Overview
2. Requirements
3. Compiling the code
4. Installation
5. Generating a basic Tomcat profile
6. Implementation Notes
7. Profile Generation Tools and change_hat
8. Feedback/Resources
-----------------------
1. Overview
--------
This package provides an implementation of a Tomcat 5.0 Valve that calls out
to the change_hat(2) function provided by libapparmor to allow a process to
change its security context. This code is intended as a proof of concept plugin
for AppArmor and Tomcat. Any feedback is greatly appreciated.
2. Requirements
------------
AppArmor version 1.2 or later
Tomcat version 5.0.X
JDK 1.4.2 or later
3. Compiling the Code
-----------------
From the top level directory execute:
ant jar jni_so
This will create a jar file and a shared library (for the JNI interface to the
libapparmor library) and place them under dist/.
4. Installation
------------
- Copy the jar file to $TOMCAT_HOME/server/lib:
[SLES10 example]
cp dist/changeHatValve.jar /usr/share/tomcat5/server/lib
- Copy the shared library to somehere in your library search path:
cp dist/libJNIChangeHat.so /usr/local/lib
[Note: you must ensure that the target directory is in your search path or set
the env variable LD_LIBRARY_PATH to include this directory so that tomcat can
find this library at startup]
- Configure the Tomcat server to use ChangeHatValve:
Place the configuration directive below in your server.xml file. The valve
definition should be the initial configuration option declared in the
top-level container in the container hierarchy.
<Valve className="com.novell.apparmor.catalina.valves.ChangeHatValve"
mediationType="ServletPath"/>
[Note: The mediationType attribute may be set to ServletPath or URI depending
on the granularity of containers that you wish to create. URI will
prompt the user to create containers for every URI it processes. This
is not recommended for most deployment scenarios and so the default
"ServletPath" should be used. This maps to containers identified by
the ServletPath header defined in the HttpRequest.]
- Defining a default and required hat for the tomcat profile
Edit the file /etc/apparmor/logprof.conf and add the following line to the
section [required_hats]:
^.+/catalina.sh$ = DEFAULT
Edit the file /etc/apparmor/logprof.conf and add the following line to the
section [default_hat]:
^.+/catalina.sh$ = DEFAULT
5. Generating a basic Tomcat profile
-------------------------------
Once the installation steps above have been started you are ready to begin
creating a profile for your application. The profile creation tool genprof will
guide you through generating a profile and its support for change_hat will
prompt you create discrete hats as requested byt the changeHatValve during
tomcat execution.
1. Create a basic profile for the tomcat server.
- Run the command "genprof PATH_TO_CATALINA.SH"
- In a seperate window start tomcat and then stop tomcat
- In the genprof window press "S" to scan for events
- Answer the questions about the initial profile for tomcat
2. Extending the profile to include containers for your web-app
- Stop the tomcat server
- Deploy your WAR file or equivalent files under the container.
- execute "genprof PATH_TO_CATALINA.SH"
- In a seperate window start tomcat and then exercise your web application
- In the genprof window press "S" to scan for events
During the prompting you will be asked questions similar to:
-----------------------------------------------
Profile: /usr/share/tomcat5/bin/catalina.sh
Default Hat: DEFAULT
Requested Hat: /servlet/CookieExample
(A)dd Requested Hat / (U)se Default Hat / (D)eny / Abo(r)t / (F)inish
------------------------------------------------
This example shows the tomcat valve for changehat attempting to change to
the hat "/servlet/CookieExample". You can choose to create this hat, and
subsequently fill it with resources, use the Default hat, named "DEFAULT"
and is the default hat for request processing.
6. Implementation Notes
--------------------
- Selecting the hat during request processing
This implementation follows the following pattern to decide what hat to execute in for an incoming request:
Try #1: Request data (URI or ServletInfo)
if #1 fails (because the hat does not exist) then try #2
Try #2: DEFAULT - this is the default hat for request processing
if #2 fails (because of any error) then..
Error: report that change_hat calls failed and remain in current security context.
- Java 1.4.2 Notes
This library uses java.security.SecureRandom to generate random numbers used as cookies in
the change_hat(2) interface. This class on java version 1.4.2 uses /dev/random which is a
blocking call and can adversely effect performance. Java can be configured to use
/dev/urandom instead. For details:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4705093
7. Profile Tools and change_hat
----------------------------
When using the profile generation tool, genprof, you will be prompted to add a
new hat when you exercise your program and requests are processed by the
changeHatValve. You can choose to Add the hat or use the Default hat.
If you choose to add the requested hat: genprof will create the hat and then
all subsequent resource requests will be mediated in this hew hat (or security
context).
If you choose to use the default hat: genprof will mediate all resource
requests in the default hat for the duration of processing this request.
When the request processng is complete the valve will change_hat back to the
parent context.
8. Feedback/Resources
-----------------
To provide feedback or ask questions please contact the
apparmor-dev@forge.novell.com mail list. This is the development list for the
AppArmor team.

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="changeHatValve" default="jar" basedir=".">
<property name="src" location="src"/>
<property name="jni_src" location="src/jni_src"/>
<property name="build" location="build"/>
<property name="lib" location="lib"/>
<property name="dist" location="dist"/>
<property name="jarfile" location="${dist}/${ant.project.name}.jar"/>
<property name="compile.debug" value="true"/>
<fileset id="lib.jars" dir="${lib}">
<include name="**/*.jar"/>
</fileset>
<fileset id="tomcat.jars" dir="/usr/share/tomcat5/server/lib">
<include name="**/*.jar"/>
</fileset>
<fileset id="servlet.jars" dir="/usr/share/tomcat5/common/lib">
<include name="**/*.jar"/>
</fileset>
<path id="lib.path">
<fileset refid="lib.jars"/>
<fileset refid="servlet.jars"/>
<fileset refid="tomcat.jars"/>
</path>
<path id="servlet.path">
<fileset refid="servlet.jars"/>
</path>
<path id="tomcat.path">
<fileset refid="tomcat.jars"/>
</path>
<target name="compile" description="Compile code">
<mkdir dir="${build}"/>
<mkdir dir="${lib}"/>
<javac srcdir="${src}" destdir="${build}" includeAntRuntime="no"
classpathref="lib.path" debug="${compile.debug}">
</javac>
</target>
<!--
<target name="jni_header" depends="compile" description="Generate JNI headers">
<javah destdir="${jni_src}" class="com.novell.apparmor.JNIChangeHat"/>
</target>
-->
<target name="jni_so" depends="compile" description="Build JNI library">
<mkdir dir="${dist}"/>
<exec dir="${jni_src}" executable="/usr/bin/make"/>
</target>
<target name="jar" depends="compile" description="Build jar">
<mkdir dir="${dist}"/>
<jar jarfile="${jarfile}" basedir="${build}" manifest="Manifest">
<!-- Merge library jars into final jar file -->
<zipgroupfileset refid="lib.jars"/>
</jar>
</target>
<target name="run" depends="jar" description="Run jar file">
<java jar="${jarfile}" fork="yes" failonerror="true"/>
</target>
<target name="clean" description="Remove build and dist directories">
<delete dir="${build}"/>
<delete dir="${dist}"/>
<exec dir="${jni_src}" executable="/usr/bin/make">
<arg value="clean"/>
</exec>
</target>
</project>

View file

@ -0,0 +1,38 @@
/* ------------------------------------------------------------------
*
* Copyright (C) 2002-2005 Novell/SUSE
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* ------------------------------------------------------------------ */
package com.novell.apparmor;
import java.nio.*;
/**
*
* JNI interface to AppArmor change_hat(2) call
*
**/
public class JNIChangeHat
{
public static int ENOMEM = 12;
public static int EACCES = 13;
public static int EFAULT = 14;
// Native 'c' function delcaration
public native int changehat_in(String subdomain, int magic_token);
// Native 'c' function delcaration
public native int changehat_out(int magic_token);
static
{
// The runtime system executes a class's static initializer
// when it loads the class.
System.loadLibrary("JNIChangeHat");
}
}

View file

@ -0,0 +1,183 @@
/* ------------------------------------------------------------------
*
* Copyright (C) 2002-2005 Novell/SUSE
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* ------------------------------------------------------------------ */
package com.novell.apparmor.catalina.valves;
import com.novell.apparmor.JNIChangeHat;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.HttpRequest;
import org.apache.catalina.Container;
import org.apache.catalina.HttpResponse;
import org.apache.catalina.valves.ValveBase;
import java.security.SecureRandom;
public final class ChangeHatValve extends ValveBase {
// JNI interface class for AppArmor change_hat
private static JNIChangeHat changehat_wrapper = new JNIChangeHat();
private static SecureRandom randomNumberGenerator = null;
private static String DEFAULT_HAT = "DEFAULT";
private static int SERVLET_PATH_MEDIATION = 0;
private static int URI_MEDIATION = 1;
private int mediationType = ChangeHatValve.SERVLET_PATH_MEDIATION;
/*
*
* Property setter called during the parsing of the server.xml.
* If the <code>mediationType</code> is an attribute of the
* Valve definition for
* <code>com.novell.apparmor.catalina.valves.ChangeHatValve</code>
* then this setter will be called to set the value for this property.
*
* @param type <b>URI|ServletPath<b>
*
* Controls what granularity of security confinement when used with
* AppArmor change_hat(2). Either based upon <code>getServletPath()</code>
* or <code>getRequestURI()</code> called against on the request.
*
*/
public void setMediationType( String type ) {
if ( type.equalsIgnoreCase("URI") ) {
this.mediationType = ChangeHatValve.URI_MEDIATION;
} else if ( type.equalsIgnoreCase("servletPath") ) {
this.mediationType = ChangeHatValve.SERVLET_PATH_MEDIATION;
}
}
/*
*
* Return an int value representing the currently configured
* <code>mediationType</code> for this instance.
*
*/
int getMediationType() {
return this.mediationType;
}
/*
*
* Return an instance of <code>SecureRandom</code> creating one if necessary
*
*/
SecureRandom getRndGen() {
if ( ChangeHatValve.randomNumberGenerator == null) {
ChangeHatValve.randomNumberGenerator = new java.security.SecureRandom();
}
return ChangeHatValve.randomNumberGenerator;
}
/*
*
* Call to return a random cookie from the <code>SecureRandom</code> PRNG
*
*/
int getCookie() {
SecureRandom rnd = getRndGen();
if ( rnd == null ) {
this.getContainer().getLogger().log( "[APPARMOR] can't initialize SecureRandom for cookie generation for change_hat() call.", container.getLogger().ERROR);
return 0;
}
return rnd.nextInt();
}
/*
*
* Call out to AppArmor change_hat(2) to change the security
* context for the processing of the request by subsequent valves.
* Returns to the current security context when processing is complete.
* The security context that is chosen is govern by the
* <code>mediationType</code> property - which can be set in the
* <code>server.xml</code> file.
*
* @param request Request being processed
* @param response Response being processed
* @param context The valve context used to invoke the next valve
* in the current processing pipeline
*
* @exception IOException if an input/output error has occurred
* @exception ServletException if a servlet error has occurred
*
*/
public void invoke( org.apache.catalina.Request request,
org.apache.catalina.Response response,
org.apache.catalina.ValveContext context )
throws IOException, ServletException {
Container container = this.getContainer();
int cookie, result;
boolean inSubHat = false;
container.getLogger().log(this.getClass().toString() +
"[APPARMOR] Request received [" + request.getInfo()
+ "]", container.getLogger().DEBUG);
if ( !( request instanceof HttpRequest)
|| !(response instanceof HttpResponse) ) {
container.getLogger().log(this.getClass().toString()
+ "[APPARMOR] Non HttpRequest received. Not changing context. ["
+ request.getInfo() + "]", container.getLogger().ERROR);
context.invokeNext(request, response);
return;
}
HttpRequest httpRequest = (HttpRequest) request;
HttpServletRequest servletRequest = (HttpServletRequest) httpRequest.getRequest();
String hatname = ChangeHatValve.DEFAULT_HAT;;
if ( getMediationType() == ChangeHatValve.SERVLET_PATH_MEDIATION ) {
hatname = servletRequest.getServletPath();
} else if ( getMediationType() == ChangeHatValve.URI_MEDIATION ) {
hatname = servletRequest.getRequestURI();
}
/*
* Select the AppArmor container for this request:
*
* 1. try hat name from either URI or ServletPath (based on configuration)
*
* 2. try hat name of the defined DEFAULT_HAT
*
* 3. run in the current AppArmor context
*/
cookie = getCookie();
container.getLogger().log("[APPARMOR] ChangeHat to [" + hatname
+ "] cookie [" + cookie + "]", container.getLogger().DEBUG);
result = changehat_wrapper.changehat_in(hatname, cookie);
if ( result == JNIChangeHat.EACCES ) {
changehat_wrapper.changehat_out(cookie);
result = changehat_wrapper.changehat_in(ChangeHatValve.DEFAULT_HAT, cookie);
if ( result != 0 ) {
changehat_wrapper.changehat_out(cookie);
container.getLogger().log("[APPARMOR] ChangeHat to [" + hatname
+ "] failed. Running in parent context.",
container.getLogger().ERROR);
} else {
inSubHat = true;
}
} else if ( result != 0 ) {
changehat_wrapper.changehat_out(cookie);
container.getLogger().log("[APPARMOR] ChangeHat to [" + hatname
+ "] failed. Running in parent context.",
container.getLogger().ERROR);
} else {
inSubHat = true;
}
context.invokeNext(request, response);
if ( inSubHat ) changehat_wrapper.changehat_out(cookie);
}
}

View file

@ -0,0 +1,59 @@
/*
------------------------------------------------------------------
Copyright (C) 2002-2005 Novell/SUSE
This program is free software; you can redistribute it and/or
modify it under the terms of version 2 of the GNU General Public
License published by the Free Software Foundation.
------------------------------------------------------------------
*/
#include "jni.h"
#include <errno.h>
#include "sys/apparmor.h"
#include "com_novell_apparmor_JNIChangeHat.h"
/* c intermediate lib call for Java -> JNI -> c library execution of the change_hat call */
JNIEXPORT jint Java_com_novell_apparmor_JNIChangeHat_changehat_1in
(JNIEnv *env, jobject obj, jstring hatnameUTF, jint token)
{
int len = (*env)->GetStringLength(env, hatnameUTF);
jint result = 0;
if ( len > 0 ) {
if ( len > 128 ) {
len = 128;
}
char hatname[128];
(*env)->GetStringUTFRegion(env, hatnameUTF, 0, len, hatname);
result = (jint) change_hat(hatname, (unsigned int) token);
if ( result ) {
return errno;
} else {
return result;
}
}
return (jint) result;
}
JNIEXPORT jint JNICALL Java_com_novell_apparmor_JNIChangeHat_changehat_1out
(JNIEnv *env, jobject obj, jint token)
{
jint result = 0;
result = (jint) change_hat(NULL, (unsigned int) token);
if ( result ) {
return errno;
} else {
return result;
}
}

View file

@ -0,0 +1,21 @@
INCLUDE=/usr/lib/java/include
TOP=../..
CLASSPATH=${TOP}/build
CFLAGS=-g -O2 -Wall -Wstrict-prototypes -pipe -fpic -D_REENTRANT
INCLUDES=-I$(INCLUDE) -I$(INCLUDE)/linux
CLASSFILE=${CLASSPATH}/com/novell/apparmor/JNIChangeHat.class
all:
make jnichangehat
jnichangehat: libJNIChangehat.so
clean:
rm -f ${TOP}/dist/*.so JNIChangeHat.java com_novell_apparmor_JNIChangeHat.h
JNIChangeHat.java com_novell_apparmor_JNIChangeHat.h: ${CLASSFILE}
javah -jni -classpath ${CLASSPATH} com.novell.apparmor.JNIChangeHat
libJNIChangehat.so: JNIChangeHat.c JNIChangeHat.java com_novell_apparmor_JNIChangeHat.h
gcc ${INCLUDES} ${CFLAGS} -shared -o ${TOP}/dist/libJNIChangeHat.so JNIChangeHat.c -lapparmor