Saturday, March 27, 2010

Security Resource, Permission and Administration

First, let's clarify what is a resource in terms of security. According to XACML, a resource is data, service or system component for which access is requested. WebLogic Security defines resource as any software component, such as a server, a service, an application, or an application artifact that can be secured using security roles and policies. There are subtle differences between XACML's definition and WebLogic's definition, e.g. a method to secure on an EJB is considered a resource in WebLogic while for XACML that method is an action on an EJB resource.

While Spring Security supports several resources to protect such as a URL, a method invocation and an (domain) object, it lacks a uniform abstraction for representing the same in its architecture. It also lacks an abstraction to represent a permission on a resource. Let me give examples for what I mean.

Defining and enforcing permissions for URL resources requires configuring FilterSecurityInterceptor with permissions defined in the form of intercept-url elements including URL patterns and allowed roles.

1:    <bean id="filterInvocationInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">  
2:      ...  
3:      <!--property name="securityMetadataSource" ref="cspaceMetadataSource"/-->  
4:      <property name="securityMetadataSource">  
5:        <sec:filter-security-metadata-source>  
6:          <sec:intercept-url pattern="/**" access="ROLE_USERS"/>  
7:        </sec:filter-security-metadata-source>  
8:      </property>  
9:    </bean>  

Defining and enforcing permissions for method resources using MethodSecurityInterceptor requires listing permissions using a set of properties including qualified methods as names and allowed roles as value.

1:  <bean id="bankManagerSecurity"  
2:    class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">  
3:  ...  
4:   <property name="securityMetadataSource">  
5:    <value>  
6:     com.mycompany.BankManager.delete*=ROLE_SUPERVISOR  
7:     com.mycompany.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR  
8:    </value>  
9:   </property>  
10:  </bean>   

Spring ACL for Domain Object Security has an abstraction for Permission but actually the permission is required to be configured programatically using abstractions such as Permission, ObjectIdentity and Sid (security identity). It offers no management interfaces to configure or administer permissions declaratively.

1:  <security:global-method-security pre-post-annotations="enabled">  
2:    <security:expression-handler ref="expressionHandler"/>  
3:   </security:global-method-security>  
4:   <bean id="expressionHandler"  
5:     class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">  
6:      <property name="permissionEvaluator" ref="myPermissionEvaluator"/>  
7:   </bean>  

The first two examples above show that Spring Security neatly allows to define permissions exactly where those permissions would be enforced. This makes it really easy to configure permissions. However, it also makes it really difficult to administer permissions independently as there are no distinct and unified administrative interfaces to do so. Now, if you had abstractions for Resource and Permission, you would be able to administer these separately and enforce them from FilterInvocation, MethodInvocation and PermissionEvaluator too.

A typical simple non-hierarchical resource would have the following information:
  • Type of the resource (URL, method, object, class, db table, etc.)
  • Id to uniquely identify the resource
  • Pattern (e.g. com.mycompany.BankManager.delete* or /**)
A typical permission might have the following
  • Resource on which access is defined
  • A set of principals (roles and users) it is applicable to
  • An effect (permit or deny)
How this can help in administering and enforcing permissions? I will write about that in my next post...

Saturday, March 20, 2010

Spring ACL for RESTful web services

I had mentioned before that the easiest way to provision permissions for URIs using Spring Security is by adding intercept-urls and corresponding expressions in security metadata for a filter in a web application. It is also possible to define security metadata in file/database for these URIs so that permission could be changed dynamically after the webapp is deployed. Here is a sample using a property file named url.properties (credits: custom security metadata)for more details.

1:  <bean id="filterInvocationInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">  
2:      <property name="authenticationManager" ref="authenticationManager"/>  
3:      <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>  
4:      <property name="securityMetadataSource" ref="cspaceMetadataSource"/>  
5:    </bean>  
6:    <bean id="cspaceMetadataSource" class="org.collectionspace.services.authorization.spring.CSpaceSecurityMetadataSource">  
7:      <property name="urlProperties">  
8:        <util:properties location="classpath:urls.properties" />  
9:      </property>  
10:    </bean>  

The url.properties includes a list of permissions. It might look something like following.

1:  /accounts/**=ROLE_ADMINISTRATOR  

And the CSpaceSecurityMetadataResource looks as follows:


1:  import java.util.Collection;  
2:  import java.util.Properties;  
3:  import org.springframework.security.access.ConfigAttribute;  
4:  import org.springframework.security.access.SecurityConfig;  
5:  import org.springframework.security.web.FilterInvocation;  
6:  import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;  
7:  public class CSpaceSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {  
8:    private Properties urlProperties;  
9:    public Collection<ConfigAttribute> getAllConfigAttributes() {  
10:      return null;  
11:    }  
12:    public Collection<ConfigAttribute> getAttributes(Object filter)  
13:        throws IllegalArgumentException {  
14:      FilterInvocation filterInvocation = (FilterInvocation) filter;  
15:      String url = filterInvocation.getRequestUrl();  
16:      String method = filterInvocation.getHttpRequest().getMethod();  
17:      //get the roles for requested page from the property file  
18:      String urlPropsValue = urlProperties.getProperty(url);  
19:      StringBuilder rolesStringBuilder = new StringBuilder();  
20:      //fetch roles from property file that could invoke the given http method on given url
21:      return SecurityConfig.createListFromCommaDelimitedString(rolesStringBuilder.toString());  
22:    }  
23:    public boolean supports(Class<?> arg0) {  
24:      return true;  
25:    }  
26:    public void setUrlProperties(Properties urlProperties) {  
27:      this.urlProperties = urlProperties;  
28:    }  
29:    public Properties getUrlProperties() {  
30:      return urlProperties;  
31:    }  
32:  }  

Limitations :


Http Method

Unlike a web application, for the RESTful web service, it is important to know the http method (e.g. POST is used for creating representation for a resource) in addition to the URI (e.g. /accounts/) from the invocation context. Using this two part information, you should retrieve a list of roles (comma separated) that are allowed to invoke the given HTTP method on the given URI. Note that for this reason the example url.properties file shown earlier is not enough as it does not include any HTTP methods in permission definition.

Sub-resources and permission inheritance

According to the HATEOS principle, it is very common to expect that a representation received from a resource could include additional URIs to retrieve representations of related resources. For example, to retrieve the user profile associated with an account, one could invoke a GET request on URI /accounts/{accountid}/users/{userid}. Security administrator may want to inherit permissions for the users resource from the accounts resource. Using a flat permission model such as shown above in the url.properties, it is not possible to define inheritance.

Therefore, I am exploring if a URI could be considered a domain object and Spring ACL could be used to define permissions. Will keep you posted ...

Tuesday, March 9, 2010

Shutting down log4j

When I integrated Spring Security into CollectionSpace, the dynamic deployment of cspace-services.war stopped working. We use Cargo's JBoss plugin for maven to dynamically deploy war on a running server instance. The redeploy (undeploy + deploy) would fail with an error "Shutting down log4j"! Only restart of the server will allow to deploy the war. We use slf4 with log4j binding.

While the process of coming up to the following list of steps was long, the list itself is short.

1. In our environment, JBoss uses log4j, cspace-services.war uses log4j and Spring used it as well. However, Spring was shutting down log4j logger when our war was undeploying due to the following listener. Remove Log4jConfigListener from web.xml.

1:  <listener>  
2:   <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
3:  </listener>  

2. Do not package log4j jar with your web app. JBoss ships with log4j.jar that is available in the system classpath.

3. Have your own log RepositorySelector to separate your log from other applications (here JBoss). This also helps in stopping JBoss's RepositorySelector from jamming application's log entry onto its own. Here is how repository selector looked.

1:  package org.collectionspace.services.common.log;  
2:  import java.util.Hashtable;  
3:  import javax.naming.Context;  
4:  import javax.naming.InitialContext;  
5:  import javax.naming.NamingException;  
6:  import org.apache.log4j.Hierarchy;  
7:  import org.apache.log4j.Level;  
8:  import org.apache.log4j.spi.LoggerRepository;  
9:  import org.apache.log4j.spi.RepositorySelector;  
10:  import org.apache.log4j.spi.RootLogger;  
11:  /**  
12:   * CollectionSpaceLog4jRepositorySelector is a CollectionSpace  
13:   * specific log4j repository selector. See Ceki's solution  
14:   * for more details  
15:   * Courtsey Ceki Gulcu http://articles.qos.ch/sc.html  
16:   */  
17:  /** JNDI based Repository selector */  
18:  public class CollectionSpaceLog4jRepositorySelector implements RepositorySelector {  
19:    // key: name of logging context,  
20:    // value: Hierarchy instance  
21:    private Hashtable ht;  
22:    private Hierarchy defaultHierarchy;  
23:    public CollectionSpaceLog4jRepositorySelector() {  
24:      ht = new Hashtable();  
25:      defaultHierarchy = new Hierarchy(new RootLogger(Level.DEBUG));  
26:    }  
27:    // the returned value is guaranteed to be non-null  
28:    public LoggerRepository getLoggerRepository() {  
29:      String loggingContextName = null;  
30:      try {  
31:        Context ctx = new InitialContext();  
32:        loggingContextName = (String) ctx.lookup("java:comp/env/cspace-logging-context");  
33:      } catch (NamingException ne) {  
34:        // we can't log here  
35:      }  
36:      if (loggingContextName == null) {  
37:        return defaultHierarchy;  
38:      } else {  
39:        Hierarchy h = (Hierarchy) ht.get(loggingContextName);  
40:        if (h == null) {  
41:          h = new Hierarchy(new RootLogger(Level.DEBUG));  
42:          ht.put(loggingContextName, h);  
43:        }  
44:        return h;  
45:      }  
46:    }  
47:    /**  
48:     * The Container should remove the entry when the web-application  
49:     * is removed or restarted.  
50:     * */  
51:    public void remove(ClassLoader cl) {  
52:      ht.remove(cl);  
53:    }  
54:  }  

The JNDI environment entry was registered as follows in web.xml:

1:    <env-entry>  
2:      <description>Sets the logging context for the Tiger web-app</description>  
3:      <env-entry-name>cspace-logging-context</env-entry-name>  
4:      <env-entry-value>CSpaceLoggingContext</env-entry-value>  
5:      <env-entry-type>java.lang.String</env-entry-type>  
6:    </env-entry>  

Here is how we initialized it in our ServletContextLIstener

1:  public class CollectionSpaceServiceContextListener implements ServletContextListener {  
2:    @Override  
3:    public void contextInitialized(ServletContextEvent event) {  
4:  ...  
5:        LogManager.setRepositorySelector(new CollectionSpaceLog4jRepositorySelector(),  
6:            null);  
7:  ...  
8:    }  



4. Remove log4j.properties or log4j.xml from your war. We could still share log4j configuration with JBoss by adding categories into the jboss-log4j.xml. This is nice because you could dynamically change the log level using JMX mbeans provided by JBoss. If you package your own configuration in the war, you will need to redeploy it if you want to change log level or will have to build your own JMX Mbeans to change the level dynamically.

As I said before, the debugging took longer than writing this post. Hopefully, it is useful...

Sunday, March 7, 2010

What I would like to see in Spring Authorization...

I am trying to integrate Spring Security in CollectionSpace web services. I chose Spring Security because it is used quite widely in enterprise applications and has proven its utility and reliability. Also, it is a well thought out architecture that is easy to understand for the application developers. I like most of the features of Spring Security Authorization. This includes role-based access control, possibility for several types of voting in access decision manager and pre/post invocation hooks, etc.

In Spring Security, a resource to protect such as URI is treated differently from other resources (methods, domain objects, ...) both for permission administration as well as enforcement.  Easiest way to provision permissions for URI is by adding intercept-urls and corresponding expressions in security metadata for a filter in web application. This is metadata packaged with the war file.

1:  <bean id="filterSecurityInterceptor"  
2:      class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">  
3:   <property name="authenticationManager" ref="authenticationManager"/>  
4:   <property name="accessDecisionManager" ref="accessDecisionManager"/>  
5:   <property name="securityMetadataSource">  
6:    <security:filter-security-metadata-source>  
7:     <security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>  
8:     <security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>  
9:    </security:filter-security-metadata-source>  
10:   </property>  
11:  </bean>  

It perhaps is possible to define security metadata in database for these URIs so that permission could be changed dynamically after the webapp is deployed. I am trying to do exactly the same for CollectionSpace. I will explain the why later. However, I am disappointed to see statements such as "The first thing you should ask yourself is if you really need to do this. If an application requires securing, then it also requires that the security be tested thoroughly based on a defined policy." to defend their approach (of embedding permission expressions in the application itself). This seems like a short-sighted argument to me. It is a bad practice to embed permissions in the application package as it requires redeploying the application if permissions change dynamically as they often do, especially if the web application is a multi-tenant software service or exposes web services.

Secondly, while it is good to see permission enforcement for resources such as URI, method and AspectJ JoinPoint "described" as metadata that is not "hard coded" into the application, it is disappointing to see permissions ("securityMetadataSource") associated with these at the same location. I would have loved to see permission provisioning aspect separated out from the permission enforcement points (interceptors). The permissions enforcement points should only know about which resources to protect and not what permissions are associated with those resources. There seems to be a nicer way to delegate the permission evaluation using the PermissionsEvaluator interface but that seems only applicable to method security.

Why I need such flexibility?
  1. CollectionSpace is a multi-tenant application. For the same service URI, there could be different permissions per tenant. 
  2. There are lots of URIs that need to be protected for a web service and for a collection of web services in CollectionSpace. CollectionSpace web services are RESTful web services.
  3. Not all services might be packaged into a single war file. It would be good to centralize permission provisioning and administration out of the security interceptor meta data packaged with the war file.
Keep tuned for the next post describing how we do permission administration using Spring Security in CollectionSpace...

Wednesday, February 10, 2010

Spring security and JAAS provider

Recently, I checked in code (#1318, #1321) to integrate Spring Security into CollectionSpace. As you know, we already had implemented a working JAAS Login Module that acts as a default identity provider. The configuration of that login module is externalized into a JBoss application-policy in the login-config.xml. That is a good thing. Also, that login module inserts tenant information for the user into the security context. We plan to use this info. while making access control decisions in downstream request processing. So, my first task was to integrate that login module with Spring security, if it was possible.

I faced some problems (described at the end) but it was possible. The application-security.xml of our web application looks as follows.

1:  <beans xmlns="http://www.springframework.org/schema/beans"  
2:      xmlns:s="http://www.springframework.org/schema/security"  
3:      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
4:      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
5:      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">  
6:    <s:http use-expressions="true">  
7:      <s:intercept-url pattern="/**" access="permitAll" />  
8:      <s:http-basic />  
9:      <s:logout />  
10:    </s:http>  
11:    <s:authentication-manager>  
12:      <s:authentication-provider ref="jaasAuthenticationProvider" user-service-ref="userDetailsService"/>  
13:    </s:authentication-manager>  
14:    <bean id="jaasAuthenticationProvider"  
15:       class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">  
16:      <property name="loginContextName">  
17:        <value>cspace</value> <!-- value should be same as in application-policy in JBoss login-config.xml -->  
18:      </property>  
19:      <property name="loginConfig">  
20:        <value>/WEB-INF/login.conf</value> <!-- filler, not used at runtime -->  
21:      </property>  
22:      <property name="callbackHandlers">  
23:        <list>  
24:          <bean class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/>  
25:          <bean class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/>  
26:        </list>  
27:      </property>  
28:      <property name="authorityGranters">  
29:        <list>  
30:          <bean class="org.collectionspace.authentication.spring.CSpaceAuthorityGranter"/>  
31:        </list>  
32:      </property>  
33:    </bean>  
34:    <bean id="userDetailsService" class="org.collectionspace.authentication.spring.CSpaceUserDetailsService">  
35:    </bean>  
36:  </beans>  
Listing 1

As you see, authentication-manager refers to jaasAuthenticationProvider (#12). This JAAS authentication provider is using the same value cspace for loginContextName (#16-18) as the value of the attribute named name (#1 Listing 2) in application-policy of JBoss's login-config.xml as shown below. This is important. This is how Spring security framework finds out the required JAAS login module from the JBoss environment and ties it to its own JAAS authentication provider.

1:  <application-policy name="cspace">  
2:    <authentication>  
3:      <login-module code="org.collectionspace.authentication.jaas.CSpaceJBossDBLoginModule"  
4:             flag="required">  
5:        ...
6:      </login-module>  
7:    </authentication>  
8:  </application-policy>  
Listing 2

From application-security.xml in Listing 1, you may also notice that I have configured the following two entities for CollectionSpace:

  1. CSpaceAuthorityGranter (#30). This class returns a set of strings. Each entry in the set represents a string for a role which is represented as Java Principal by the JAAS provider. Spring uses this string to create a corresponding AuthorityGranter.
  2. CSpaceUserDetailsService (#34). Even if you do not have additional attributes for a user (e.g. user profile), this entity needs to be configured. It is useful to obtain application specific information attached to the user. For example, in CollectionSpace, this service could retrieve information such as first name, last name, contact details, etc. from services such as the Person sevice.
Problem

The CollectionSpace authentication service is packaged as a jar. This jar includes CS database realm classes, JAAS login module and the above mentioned Spring security specific classes. This jar is copied to JBoss's domain lib, e.g. (server/cspace/lib) and is available from system classpath at runtime.


The application-security.xml is packaged with the war file. This war file is built using maven. Maven could package all the Spring specific dependencies when the war is built.

At runtime, Spring framework would complain with the following:

"Cannot convert value of type org.collectionspace.authentication.spring.CSpaceAuthorityGranter to required type org.springframework.security.authentication.jaas.AuthorityGranter for property authorityGranters[0] : no matching editors or conversion strategy found"

To overcome this, I used the following dependency while building the authentication service jar.

1:      <spring.security.version>3.0.1.RELEASE</spring.security.version>  
2:      <dependency>  
3:        <groupId>org.springframework.security</groupId>  
4:        <artifactId>spring-security-core</artifactId>  
5:        <version>${spring.security.version}</version>  
6:        <scope>provided</scope>  
7:      </dependency>  
Listing 3


And the war was build using the following dependencies.

1:    <properties>  
2:      <spring.version>3.0.0.RELEASE</spring.version>  
3:      <spring.security.version>3.0.1.RELEASE</spring.security.version>  
4:    </properties>  
5:      <!-- dependencies on spring security & framework are runtime deps only -->  
6:      <!-- the following list is kept to make sure domain has these packages -->  
7:      <!-- in the cspace/lib directory -->  
8:      <dependency>  
9:        <groupId>org.springframework.security</groupId>  
10:        <artifactId>spring-security-core</artifactId>  
11:        <version>${spring.security.version}</version>  
12:        <scope>provided</scope>  
13:      </dependency>  
14:      <dependency>  
15:        <groupId>org.springframework.security</groupId>  
16:        <artifactId>spring-security-config</artifactId>  
17:        <version>${spring.security.version}</version>  
18:        <scope>provided</scope>  
19:      </dependency>  
20:      <dependency>  
21:        <groupId>org.springframework.security</groupId>  
22:        <artifactId>spring-security-web</artifactId>  
23:        <version>${spring.security.version}</version>  
24:        <scope>provided</scope>  
25:      </dependency>  
26:      <dependency>  
27:        <groupId>org.springframework.security</groupId>  
28:        <artifactId>spring-security-acl</artifactId>  
29:        <version>${spring.security.version}</version>  
30:        <scope>provided</scope>  
31:      </dependency>  
32:      <dependency>  
33:        <groupId>org.springframework</groupId>  
34:        <artifactId>spring-context</artifactId>  
35:        <version>${spring.version}</version>  
36:        <scope>provided</scope>  
37:      </dependency>  
38:      <dependency>  
39:        <groupId>org.springframework</groupId>  
40:        <artifactId>spring-web</artifactId>  
41:        <version>${spring.version}</version>  
42:        <scope>provided</scope>  
43:      </dependency>  
44:      <dependency>  
45:        <groupId>org.springframework</groupId>  
46:        <artifactId>spring-webmvc</artifactId>  
47:        <version>${spring.version}</version>  
48:        <scope>provided</scope>  
49:      </dependency>  
50:      <dependency>  
51:        <groupId>org.springframework</groupId>  
52:        <artifactId>spring-aop</artifactId>  
53:        <version>${spring.version}</version>  
54:        <scope>provided</scope>  
55:      </dependency>  
Listing 4

Note that I have used dependency scope for these dependencies. That means, I have to make these jars available at runtime, usually in the system classpath of JBoss. This is ugly, but if I did not do this, the Spring framework would complain with the error I mentioned earlier. In Spring security 3.0.1.RELEASE, it is coming from line #289 of org.springframework.beans.TypeConverterDelegate.java

My assumption here is that somehow the classes (esp. AuthorityGranter) pulled by maven while building the authentication provider jar and the war, both using default dependency scope, were not the same, even if I was using the same release versions for Spring security in both the poms. At one point I was using CI SNAPSHOTs for Spring security. Perhaps that might be the reason as the repository might be changing at the time. Dunno for sure.

If you know a better way to solve this problem, feel free to comment.

Tuesday, January 26, 2010

Authorization service in CollectionSpace

I have started design for the authorization service in CollectionSpace. While working on security services, I always provide some descriptions on terms used as well as core processes. Take a look at Authorization Service Description and Assumptions for more details about the service. There are some non-trivial requirements that I would like to highlight here.

  1. Support multiple tenants from the same service. That is roles and policies/permissions should be tenant-qualified. One tenant's collectionmanager role may not have the same privileges as the other tenant's collectionmanager role.
  2. Attribute-level access control. It appears that in the domain of collection management, attribute level access control might be more than uncommon requirement. How to enforce access control at attribute level in search functions so that performance is not degraded drastically is a big challenge.
Indeed, it is not in CollectionSpace's charter to build technology for authorization. So, we looked at a few options including XACML and Spring Security. I have taken rough notes on evaluation. Take a look and let me know if we missed something or have misinterpreted.

Tuesday, December 22, 2009

Authentication in multi-tenant service

Recently, I implemented authentication service in CollectionSpace. Apart from the multitenancy requirements, it is also expected that the tenants might have their own identity providers (e.g. CalNet, LDAP, Kerberos, etc.). It is also expected that a tenant might use more than one identity provider, e.g. CalNet for users involved with collection management and OpenID for research students perhaps.


Tenant-aware security context

I implemented a JAAS LoginModule in CollectionSpace (see here) that retrieves user's association with tenant(s) during the commit phase of the authentication process. This association is made at the time of provisioning an account for the user in CollectionSpace. During the commit phase, a group named Tenants is created and it contains one or more members each representing a tenant in the system to which user is associated with. I think this approach has the following advantages:
  1. User identifier remains independent of the user's association with a tenant.
  2. Because tenant is modeled as a group, access control permissions/policies could be written with explicit knowledge about the tenant. In other words, the permissions/policies could be tenant-qualified.
I will provide concrete example in subsequent post to explain how this works.