At 12:46 14/1/01 -0600, Glenn Nielsen wrote:
> Tomcat 4 Java SecurityManager Proposal
>
>
>Tomcat 4 will require the Java SecurityManager, use of the
>SecurityManager will be optional when Tomcat is embedded in
>another application.
Why not always have it optional possibly determined by a system property
value ? This way you won't have people complainig about slow down if they
don't care about security ;) It will also be easier to integrate into other
products.
>Setting policies for web application contexts
>---------------------------------------------
>
>A web application has its security based on either the default grant in
>tomcat.policy or an entry for the context which uses the Context path
>file URL as the codeBase. This policy will be in affect for any Class running
>within the Context thread no matter which ClassLoader loaded the class
>which triggered a security check. A default permission to read files in
>the Context path is granted.
>
>// Default permissions for a Context, all contexts have these permissions
>grant {
> permission java.util.PropertyPermission "file.separator", "read";
> permission java.util.PropertyPermission "path.separator", "read";
> permission java.util.PropertyPermission "line.separator", "read";
>};
>
>// Additional Permissions for tomcat examples context
>grant codeBase="file:${tomcat.home}/webapps/examples/- {
> permission java.util.PropertyPermission "*", "read";
>};
I have actually implemented something similar this in Avalon about 7 hours
ago checking it in. Avalon has notion of .sars (Server Application
Archives) which are very similar to .wars in idea. I was having some issues
with JVM wide policy which you advocate above so I started playing around
with a per-sar policy.
To do this I had to subclass SecureClassLoader (I did this via
URLClassLoader) and overide it's method
protected PermissionCollection getPermissions( final CodeSource codeSource );
I overode this to access a per-sar Policy object. If no policy was given it
would give full access to the system. As I was embedding policy information
in another file (an XML file) it looked something like
<policy>
<!--
<keystore name="foo-keystore"
location="file:${sar.home}${/}conf${/}keystore"
type="JKS" />
<grant signed-by="Fred" code-base="file:${sar.home}/blocks/*"
key-store="foo-keystore">
<permission class="java.io.FilePermission" target="/tmp/*"
action="read,write" />
</grant>
-->
<grant code-base="file:${sar.home}${/}blocks${/}*">
<permission class="java.security.AllPermission" />
</grant>
<grant code-base="file:${sar.home}${/}lib${/}*">
<permission class="java.security.AllPermission" />
</grant>
To do this I implemented AbstractPolicy (for generic facilities) and
SarPolicy (for configuring it from XML file). I have attached the relevent
classes just in case you are interested.
>Future enhancements
>-------------------
>
>See if it is possible and secure to let an individual
>web application set its security policies in its own
>policy file located in its /WEB-INF/ directory.
This is a great facility - when implementing the equivelent for Avalon I
found it vital for a usable system ;)
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.avalon.engine;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.PropertyPermission;
import org.apache.avalon.util.io.FileUtil;
import org.apache.avalon.Component;
import org.apache.avalon.Loggable;
import org.apache.log.Logger;
/**
* Abstract policy extended in avalon.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
*/
public abstract class AbstractPolicy
extends Policy
implements Component, Loggable
{
protected final static boolean DEBUG = true;
protected final ArrayList m_entries = new ArrayList();
protected Logger m_logger;
/**
* Internal Policy Entry holder class.
*/
protected final static class PolicyEntry
{
CodeSource m_codeSource;
Permissions m_permissions;
}
public void setLogger( final Logger logger )
{
m_logger = logger;
}
/**
* Overide so we can have a per-application security policy with
* no side-effects to other applications.
*
* @param codeSource the codeSource to get permissions for
* @return the PermissionCollection
*/
public PermissionCollection getPermissions( CodeSource codeSource )
{
codeSource = normalize( codeSource );
getLogger().debug( "getPermissions(" + codeSource.getLocation() + ");" );
final Permissions permissions = new Permissions();
final int size = m_entries.size();
for( int i = 0; i < size; i++ )
{
final PolicyEntry entry = (PolicyEntry)m_entries.get( i );
if( entry.m_codeSource.implies( codeSource ) )
{
if( DEBUG )
{
getLogger().debug( entry.m_codeSource.getLocation() + " implies " +
codeSource.getLocation() );
}
copyPermissions( permissions, entry.m_permissions );
}
}
if( DEBUG )
{
getLogger().debug( codeSource.getLocation() + " permissions = " +
permissions );
}
return permissions;
}
/**
* Refresh policy. Ignored in this implementation.
*/
public void refresh()
{
}
/**
* Normalizing CodeSource involves removing relative addressing
* (like .. and .) for file urls.
*
* @param codeSource the codeSource to be normalized
* @return the normalized codeSource
*/
protected CodeSource normalize( final CodeSource codeSource )
{
final URL initialLocation = codeSource.getLocation();
// This is a bit of a hack. I don't know why CodeSource should behave like
this
// Fear not, this only seems to be a problem for home grown classloaders.
// - Paul Hammant, Nov 2000
if( null == initialLocation ) return codeSource;
String location = null;
if( !initialLocation.getProtocol().equalsIgnoreCase( "file" ) )
{
location = initialLocation.getFile();
location = FileUtil.normalize( location );
}
else
{
final File file = new File( initialLocation.getFile() );
location = file.getAbsoluteFile().toString().replace( File.separatorChar,
'/' );
location = FileUtil.normalize( location );
}
URL finalLocation = null;
try
{
finalLocation = new URL( initialLocation.getProtocol(),
initialLocation.getHost(),
initialLocation.getPort(),
location );
}
catch( final MalformedURLException mue )
{
getLogger().warn( "Error building codeBase", mue );
}
return new CodeSource( finalLocation, codeSource.getCertificates() );
}
protected void copyPermissions( final Permissions destination, final Permissions
src )
{
final Enumeration enum = src.elements();
while( enum.hasMoreElements() )
{
destination.add( (Permission)enum.nextElement() );
}
}
/**
* Create a permission set for a codeBase.
* These are read-write permissions and can be written till until the
* time in which they are applied to code.
*
* @param location the location of codes to apply permission set to.
* @param signers a comma seperated string of thos who signed codebase
* @return the new permission set
* @exception MalformedURLException if location string is malformed
*/
protected Permissions createPermissionSetFor( final String location,
final Certificate[] signers )
throws MalformedURLException
{
final PolicyEntry entry = new PolicyEntry();
entry.m_codeSource = new CodeSource( new URL( location ), signers );
entry.m_codeSource = normalize( entry.m_codeSource );
getLogger().debug( "createPermissionSetFor(" +
entry.m_codeSource.getLocation() + ");" );
entry.m_permissions = new Permissions();
m_entries.add( entry );
return entry.m_permissions;
}
protected final Logger getLogger()
{
return m_logger;
}
}
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.avalon.engine;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
/**
* Classloader that applies correct policy information.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
*/
public class SarClassLoader
extends URLClassLoader
{
protected Policy m_policy;
public SarClassLoader( final URL[] urls,
final ClassLoader classLoader,
final Policy policy )
{
super( urls, classLoader );
m_policy = policy;
}
/**
* Overide so we can have a per-application security policy with
* no side-effects to other applications.
*
* @param codeSource the codeSource to get permissions for
* @return the PermissionCollection
*/
protected PermissionCollection getPermissions( final CodeSource codeSource )
{
if( null == m_policy )
{
final Permissions permissions = new Permissions();
permissions.add( new java.security.AllPermission() );
return permissions;
}
else
{
return m_policy.getPermissions( codeSource );
}
}
}
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.avalon.engine;
import java.io.InputStream;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.UnresolvedPermission;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.PropertyPermission;
import java.util.StringTokenizer;
import org.apache.avalon.Component;
import org.apache.avalon.Configurable;
import org.apache.avalon.Configuration;
import org.apache.avalon.ConfigurationException;
import org.apache.avalon.Context;
import org.apache.avalon.DefaultContext;
import org.apache.avalon.Contextualizable;
import org.apache.avalon.Initializable;
import org.apache.avalon.util.PropertyException;
import org.apache.avalon.util.PropertyUtil;
import org.apache.avalon.util.io.FileUtil;
import org.apache.log.Logger;
/**
* Policy that extracts information from policy files.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
*/
public class SarPolicy
extends AbstractPolicy
implements Component, Contextualizable, Configurable, Initializable
{
protected DefaultContext m_context;
public void contextualize( final Context context )
{
m_context = new DefaultContext( System.getProperties(), context );
m_context.put( "/", File.separator );
}
public void configure( final Configuration configuration )
throws ConfigurationException
{
final Iterator keyStoreConfigurations = configuration.getChildren( "keystore"
);
final HashMap keyStores = configureKeyStores( keyStoreConfigurations );
final Iterator grants = configuration.getChildren( "grant" );
configureGrants( grants, keyStores );
}
public void init()
throws Exception
{
//these properties straight out ot ${java.home}/lib/security/java.policy
final Permissions permissions = createPermissionSetFor( "file:/-", null );
permissions.add( new PropertyPermission( "os.name", "read" ) );
permissions.add( new PropertyPermission( "os.arch", "read" ) );
permissions.add( new PropertyPermission( "os.version", "read" ) );
permissions.add( new PropertyPermission( "file.separator", "read" ) );
permissions.add( new PropertyPermission( "path.separator", "read" ) );
permissions.add( new PropertyPermission( "line.separator", "read" ) );
permissions.add( new PropertyPermission( "java.version", "read" ) );
permissions.add( new PropertyPermission( "java.vendor", "read" ) );
permissions.add( new PropertyPermission( "java.vendor.url", "read" ) );
permissions.add( new PropertyPermission( "java.class.version", "read" ) );
permissions.add( new PropertyPermission( "java.vm.version", "read" ) );
permissions.add( new PropertyPermission( "java.vm.vendor", "read" ) );
permissions.add( new PropertyPermission( "java.vm.name", "read" ) );
permissions.add( new PropertyPermission( "java.specification.version", "read"
) );
permissions.add( new PropertyPermission( "java.specification.vendor", "read" )
);
permissions.add( new PropertyPermission( "java.specification.name", "read" ) );
permissions.add( new PropertyPermission( "java.vm.specification.version",
"read" ) );
permissions.add( new PropertyPermission( "java.vm.specification.vendor",
"read" ) );
permissions.add( new PropertyPermission( "java.vm.specification.name", "read"
) );
}
protected HashMap configureKeyStores( final Iterator configurations )
throws ConfigurationException
{
final HashMap keyStores = new HashMap();
while( configurations.hasNext() )
{
final Configuration configuration = (Configuration)configurations.next();
final String type = configuration.getAttribute( "type" );
final String location = configuration.getAttribute( "location" );
final String name = configuration.getAttribute( "name" );
try
{
final KeyStore keyStore = KeyStore.getInstance( type );
final URL url = new URL( location );
final InputStream ins = url.openStream();
keyStore.load( ins, null );
keyStores.put( name, keyStore );
}
catch( final Exception e )
{
throw new ConfigurationException( "Error configuring keystore " +
name, e );
}
}
return keyStores;
}
protected void configureGrants( final Iterator configurations, final HashMap
keyStores )
throws ConfigurationException
{
while( configurations.hasNext() )
{
final Configuration configuration = (Configuration)configurations.next();
configureGrant( configuration, keyStores );
}
}
protected void configureGrant( final Configuration configuration, final HashMap
keyStores )
throws ConfigurationException
{
//<grant signed-by="Fred" code-base="file:${sar.home}/blocks/*"
key-store="foo-keystore">
//<permission class="java.io.FilePermission" target="/tmp/*"
action="read,write" />
//</grant>
final String signedBy = configuration.getAttribute( "signed-by", null );
final String keyStoreName = configuration.getAttribute( "key-store", null );
String codeBase = configuration.getAttribute( "code-base", null );
if( null != codeBase )
{
codeBase = expand( codeBase );
}
final Certificate[] signers = getSigners( signedBy, keyStoreName, keyStores );
Permissions permissions = null;
try { permissions = createPermissionSetFor( codeBase, signers ); }
catch( final MalformedURLException mue )
{
throw new ConfigurationException( "Malformed code-base " + codeBase, mue );
}
configurePermissions( configuration.getChildren( "permission" ),
permissions,
keyStores );
}
protected void configurePermissions( final Iterator configurations,
final Permissions permissions,
final HashMap keyStores )
throws ConfigurationException
{
while( configurations.hasNext() )
{
final Configuration configuration = (Configuration)configurations.next();
configurePermission( configuration, permissions, keyStores );
}
}
protected void configurePermission( final Configuration configuration,
final Permissions permissions,
final HashMap keyStores )
throws ConfigurationException
{
final String type = configuration.getAttribute( "class" );
final String actions = configuration.getAttribute( "actions", null );
final String signedBy = configuration.getAttribute( "signed-by", null );
final String keyStoreName = configuration.getAttribute( "key-store", null );
String target = configuration.getAttribute( "target", null );
if( null != target )
{
target = expand( target );
}
final Certificate[] signers = getSigners( signedBy, keyStoreName, keyStores );
final Permission permission = createPermission( type, target, actions, signers
);
permissions.add( permission );
}
protected String expand( final String value )
throws ConfigurationException
{
try
{
final Object resolvedValue = PropertyUtil.resolveProperty( value,
m_context, false );
return resolvedValue.toString();
}
catch( final PropertyException pe )
{
throw new ConfigurationException( "Error resolving property " + value, pe
);
}
}
protected Permission createPermission( final String type,
final String target,
final String actions,
final Certificate[] signers )
throws ConfigurationException
{
if( null != signers )
{
return createUnresolvedPermission( type, target, actions, signers );
}
try
{
final Class c = Class.forName( type );
Class paramClasses[] = null;
Object params[] = null;
if( null == actions && null == target )
{
paramClasses = new Class[ 0 ];
params = new Object[ 0 ];
}
else if( null == actions )
{
paramClasses = new Class[1];
paramClasses[0] = String.class;
params = new Object[1];
params[0] = target;
}
else
{
paramClasses = new Class[2];
paramClasses[0] = String.class;
paramClasses[1] = String.class;
params = new Object[2];
params[0] = target;
params[1] = actions;
}
final Constructor constructor = c.getConstructor( paramClasses );
final Object o = constructor.newInstance( params );
return (Permission)o;
}
catch( final ClassNotFoundException cnfe )
{
return createUnresolvedPermission( type, target, actions, signers );
}
catch( final Exception e )
{
throw new ConfigurationException( "Failed to create permission " + type +
" due to " + e, e );
}
}
protected Permission createUnresolvedPermission( final String type,
final String target,
final String actions,
final Certificate[] signers )
{
return new UnresolvedPermission( type, target, actions, signers );
}
protected Certificate[] getSigners( final String signedBy,
String keyStoreName,
final HashMap keyStores )
throws ConfigurationException
{
if( null != signedBy && null == keyStoreName )
{
keyStoreName = "default";
}
Certificate[] signers = null;
if( null != signedBy )
{
signers = getCertificates( signedBy, keyStoreName, keyStores );
}
return signers;
}
protected Certificate[] getCertificates( final String signedBy,
final String keyStoreName,
final HashMap keyStores )
throws ConfigurationException
{
final KeyStore keyStore = (KeyStore)keyStores.get( keyStoreName );
if( null == keyStore )
{
throw new ConfigurationException( "Unable to aquire keyStore " +
keyStoreName );
}
final ArrayList certificateSet = new ArrayList();
final StringTokenizer tokenizer = new StringTokenizer( signedBy, "," );
while( tokenizer.hasMoreTokens() )
{
final String alias = ((String)tokenizer.nextToken()).trim();
Certificate certificate = null;
try { certificate = keyStore.getCertificate( alias ); }
catch( final KeyStoreException kse )
{
throw new ConfigurationException( "Error aquiring certificate " +
alias,
kse );
}
if( null == certificate )
{
throw new ConfigurationException( "Unable to locate alias " + alias +
" in keystore named " + keyStoreName
);
}
if( !certificateSet.contains( certificate ) )
{
if( DEBUG ) getLogger().debug( "Certificate " + certificate );
certificateSet.add( certificate );
}
}
return (Certificate[])certificateSet.toArray( new Certificate[ 0 ] );
}
}
Cheers,
Pete
*-----------------------------------------------------*
| "Faced with the choice between changing one's mind, |
| and proving that there is no need to do so - almost |
| everyone gets busy on the proof." |
| - John Kenneth Galbraith |
*-----------------------------------------------------*
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, email: [EMAIL PROTECTED]