Friday, October 2, 2009

Oracle Entitlements Server 10gR3 and Coherence 3.5 Integration

The following is intended to show the current integration points around Coherence and the Oracle Entitlements Server (OES) product. The will show how a Coherence application can be configured to call out to an OES Security Module for an entitlement decision by the OES engine. The granularity of a resource provided by Coherence is to the cache-name which can be used to determine if a specific authenticated user has access to a named cache in the grid.

The value of this solution is that someone could enforce distributed policies on a named cache in the data grid so that not just “anyone” can access their data. The policies would be centrally configured and then automatically distributed to all of the endpoints (OES SSMs) for enforcement at the client level. This would eliminate the need to update the client side Coherence permissions.xml file on each individual client which could be a large maintenance task. If someone is interested in securing access to a named cache in Coherence this can achieved by following the general guide at Coherence Security Framework and implementing the com.tangosol.net.security.AccessController interface to call out to an OES SSM (Security Service Module). In this case the RMI-SSM is best used for performance reasons.

The AccessController class can be generic in nature and the code provided later on could be easily reused. However the configuration for the Coherence side would be specific to the SSM, its location, and other configuration variables for OES.


Currently only the name of the cache is exposed as a resource that one can write policies on from the Coherence ClusterPermission object. Coherence does not currently give you the name of the Object key if one has to goto that level.


“How-To” secure a Coherence Application with OES SSM:

It is assumed that one has already installed and configured the OES product (OES Admin Server and OES SSMs) and Oracle Coherence product prior to this.   NOTE:  A Jdeveloper sample project is available upon request. :)

OES Setup:

1. The following step only needs to be done once per machine. The reason is that the OES 10gR3 installation kit does not do this out-of-the-box during installation. Create the rmi-ssm directory structure by running the OES_HOME\ales32-ssm\webservice-ssm\adm\rmi-adm\create_rmi_ssm.(bat/sh) utility. This will create an OES_HOME\ales32-ssm\rmi-ssm directory with all of the material needed for this SSM.

2. Create an instance of an RMI-SSM by running the OES_HOME\ales32-ssm\rmi-ssm\adm\ConfigTool.(bat/sh) utility which use a configuration file to automatically create the SSM instance and configure this in the OES Admin Server which has the necessary policies. A sample “myssm_config.properties” file is located in the JDeveloper Project under the “OES_RMI_Setup” directory.

3. Build the Username Identity Asserter. This is used in this example to assert the identity passed in from the Coherence application as valid. It is assumed that the authentication done on the client side is valid and OES is being strictly used as an authorization engine. Follow the instructions listed in the OES_HOME\ales32-ssm\rmi-ssm\examples\SampleProviders\UsernameAsserter directory. You will need to copy this JAR file to the Admin Server side and the RMI-SSM instance.

4. Configure the Username Identity Asserter in the “CoherenceSSM” entry on the OES Admin Server (which was just created in step 2). Login to: https://localhost:7010/asi (default user/password is: admin/password) and look on the left-hand side under Security Configurations -> Service Control Managers -> adminconfig -> CoherenceSSM -> Authentication Providers to create a new instance of “Username Identity Asserter”. It will look like the following:



5. OES does need to know what the name of the user is since policies are written based on a Subject. For this example, we can enter the names of our users in the “asi” identity directory since the RMI-SSM points to this. In this case it will be the user names created in the keystore for the Coherence side of things (steve, larry, and bill are the sample users).

6. Create a sample group (Coherence_Group), put the user names from the “asi” identity directory into this group, and later (step 8) create a policy on the “testCache” resource which only allows members of this group access. This is what it would look like in the Entitlements Administration Console (https://localhost:7010/entitlementsadministration):



7. Create the resource tree under the binding name of the “CoherenceSSM” you just created. For example it would look like this: CoherenceSSM/testCache and CoherenceSSM/__ASTR_ (this is the literal value for the “*” that can be passed in from Coherence as well).

8. Create the Authorization Policy on both of those resources (testCache and __ASTR_). For this example I allowed the “Coherence_Group” access to these resources only. This group contained “steve”, “larry”, and “bill”. This is what the authorization policy will look like on the CoherenceSSM/testCache:



9. Copy the “pdpproxy” directory (OES_HOME\ales32-ssm\rmi-ssm\instance\CoherenceSSM\pdpproxy) to a separate location since the Coherence “client” application will need access to it (or you can modify the command line parameters to point to this). In this case it is part of the JDeveloper project already. You may need to modify the pdpproxy\PDPClientConfiguration.properties to make sure the names in this file match your environment.

10. Modify the “security.properties” which is also on the Coherence client side (in the JDeveloper project) so that the name matches what you have configured. In this case it would be “CoherenceSSM”.


Coherence Setup:

1. Create a sample Coherence application. (This is already in the JDeveloper project and called “com.oracle.oes.coherence.client.OESCoherenceClient”.)

2. Add a runAs() method around what you are trying to secure. Example:
PrivilegedAction action = new PrivilegedAction() {

public Object run() {

// All processing here is with access rights assigned to the Subject

CacheFactory.ensureCluster();

// create or get a named cache called mycache

NamedCache myCache = CacheFactory.getCache("testCache");

// put key, value pair into the cache.

myCache.put("key1", "Hello world");

System.out.println("Client Code: Inside runAs() end");

return null;

}

};


3. Modify the run configuration in JDeveloper to include the coherence configuration and the OES configuration. An example of this is located in the JDeveloper project (in the *.jpr file) and look for the “Security_Run_Storage” section.

4. Make sure there is a “-Djava.security.auth.login.config=” flag which points to a JAAS configuration file. This configuration file contains the keystore used in this example for Authentication from the Coherence application. In the JDeveloper project look at the “Coherence_Keystore.conf” file and make sure that the information in this file points to the correct location of the Keystore.jks file (this will be created in step6).

5. Modify the tangosol-coherence.xml file and make sure it points to the AccessController class which contains the code to call the OES RMI-SSM. For example:

com.oracle.oes.coherence.impl.OES_AccessController

……..

6. Create a keystore with some users configured. You can run the following set of commands to create the keystore (assuming you have Java 1.5 executable set in your environment):

keytool -genkey -v -keystore ./keystore.jks -storepass password -alias steve -keypass password -dname CN=steve,OU=MyUnit

keytool -genkey -v -keystore ./keystore.jks -storepass password -alias larry -keypass password -dname CN=larry,OU=MyUnit

keytool -genkey -v -keystore ./keystore.jks -storepass password -alias bill -keypass password -dname CN=bill,OU=MyUnit

keytool -genkey -v -keystore ./keystore.jks -storepass password -alias dave -keypass password -dname CN=dave,OU=MyUnit


Testing:

1. Start the RMI-SSM. For example: “OES_HOME\ales32-ssm\rmi- ssm\instance\CoherenceSSM\bin\WLESrmi.bat console” and make sure this starts successfully.

2. Start JDeveloper, import the JDeveloper project, and modify the classpaths to point to the correct installation location of OES and Coherence on your machine.

3. Right click and run the “com.oracle.oes.coherence.client.OESCoherenceClient”. If everything is configured correctly this will invoke the AccessController class with the authenticated user “steve” (“com.oracle.oes.coherence.impl.OES_AccessController”), contact the RMI-SSM (which is already running), and render an authorization decision. If the client is “hanging” at initializing the Security Services Framework, check that the PDPClientConfiguration.properties is configured on the command line of the Coherence client and also enable debugging in the AccessController and OES_Authorization_Impl code by setting the DEBUG value to true. This will be the output within JDeveloper:

Oracle Coherence Version 3.4.1/407

Grid Edition: Development mode

Copyright (c) 2000-2008 Oracle. All rights reserved.

Client Code: Inside runAs() begin: security action

2009-02-04 11:33:53.926/3.345 Oracle Coherence GE 3.4.1/407 (thread=Cluster, member=n/a): Service Cluster joined the cluster with senior service member n/a

2009-02-04 11:33:57.176/6.595 Oracle Coherence GE 3.4.1/407 (thread=Cluster, member=n/a): Created a new cluster "cluster:0x30D1" with Member(Id=1, Timestamp=2009-02-04 11:33:53.66, Address=169.254.25.129:8088, MachineId=26952, Location=machine:SPOZ03,process:4724, Edition=Grid Edition, Mode=Development, CpuCount=2, SocketCount=1) UID=0xA9FE19810000011F422373FC69481F98

2009-02-04 11:33:57.222/6.641 Oracle Coherence GE 3.4.1/407 (thread=Main Thread, member=1): Loaded cache configuration from file "C:\spoz\coherence\OES_COH\coherence-cache-config.xml"

Subject is:[CN=steve, OU=MyUnit]

clusterPermission actions are:join, and ServiceName is:DistributedCache

(com.tangosol.net.ClusterPermission service=DistributedCache,cache=* join)

Cache Name is:*

accessResult is:true for

subject:[CN=steve, OU=MyUnit]

resource:*

2009-02-04 11:33:57.910/7.329 Oracle Coherence GE 3.4.1/407 (thread=DistributedCache, member=1): Service DistributedCache joined the cluster with senior service member 1

Subject is:[CN=steve, OU=MyUnit]

clusterPermission actions are:join, and ServiceName is:DistributedCache

(com.tangosol.net.ClusterPermission service=DistributedCache,cache=testCache join)

Cache Name is:testCache

accessResult is:true for

subject:[CN=steve, OU=MyUnit]

resource:testCache

Client Code: Inside runAs() end

Value in cache is Hello world

Process exited with exit code 0.

4. Change the code and pass in the user “dave” who is in the keystore however not part of the Coherence_Group for the entitlements policy as shown in the earlier screenshot. When the client is run one will see the following messages in JDeveloper and the AccessControlException is thrown as per the Coherence checkPermission API:

accessResult is:false for

subject:[CN=dave, OU=MyUnit]

resource:*

Deny and throw exception

Exception in thread "Main Thread" java.security.AccessControlException: Insufficient rights to perform the operation (com.tangosol.net.ClusterPermission service=DistributedCache,cache=* join) for Subject:[CN=dave, OU=MyUnit]

5. You can enable debugging on the RMI SSM instance to determine why access was or was not granted for the named cache.


Coherence Client Code Example:

package com.oracle.oes.coherence.client;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.net.security.Security;
import java.security.PrivilegedAction;
import javax.security.auth.Subject;
import com.oracle.oes.coherence.impl.OES_AccessController;

import java.security.AccessControlException;

public class OESCoherenceClient {
public OESCoherenceClient() {
}

public static void main(String[] args) {


if (args.length != 1) {
System.out.println("Please supply a user name");
System.exit(0);
}

String sName = args[0].toString();

char[] acPassword = null;
acPassword = "password".toCharArray();

System.out.println("*** Starting Client ***");
// It is assume that the main application has the username/password already from the "client"
Subject subject = Security.login(sName, acPassword);

// The coherence client only has to pass in their credentials and configure the tangosol-coherence.xml
// file to use the com.oracle.oes.coherence.impl.OES_AccessController class in the class-name of the
// access-controller section of that XML file. Each client does not need to know or reimplement the
// OES code. They would just need to wrap their coherence calls inside a runAs() method as per
// normal JAAS security.

// Do something with the cache where an entitlement decision is rendered by OES based on the
// subject, resource (testCache here), and the action/permission.
PrivilegedAction action = new PrivilegedAction() {
public Object run() {
// All processing here is taking place with access rights assigned to the corresponding Subject
System.out.println("Client Code: Inside runAs() begin: security action");
CacheFactory.ensureCluster();

// create or get a named cache called mycache
NamedCache myCache = CacheFactory.getCache("testCache");
// put key, value pair into the cache.
myCache.put("key1", "Hello world");
System.out.println("Client Code: Inside runAs() end");
return null;
}
};
Security.runAs(subject, action);

// Access the cache since it is already secured above for the same named cache
NamedCache myCache = CacheFactory.getCache("testCache");
System.out.println("Value in cache is " + myCache.get("key1"));

}
}


Example of AccessController Implementation for Integration with OES:


package com.oracle.oes.coherence.impl;

import com.bea.security.*;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.ClusterPermission;

import com.tangosol.run.xml.SimpleParser;

import com.tangosol.run.xml.XmlDocument;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;
import com.tangosol.util.LiteSet;
import com.tangosol.util.Resources;
import com.tangosol.util.SafeHashMap;
import java.io.*;
import java.net.URL;
import java.security.*;
import java.security.cert.CertPath;
import java.security.cert.X509Certificate;

import java.util.*;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;


public class OES_AccessController implements com.tangosol.net.security.AccessController {

private boolean DEBUG = false;
private boolean nonOESDebug = false;
private java.security.KeyStore m_store;
private XmlElement m_xmlPermits;
private Map m_mapPublicKey;
public static final String PROPERTY_CONFIG = "tangosol.security.config";
public static final String KEYSTORE_TYPE;
public static final String SIGNATURE_ALGORITHM;
public static final Signature SIGNATURE_ENGINE;
public static final String BOUND_SSM_NAME = "CoherenceSSM/";

// OES
private OES_Authorization_Impl oesEng = new OES_Authorization_Impl();
private SecurityRuntime rt = null;
private PolicyDomain pd = null;
private AuthenticationService atnSvc = null;
private AuthorizationService atzSvc = null;

private void debugPrintPrivate (String s) {
if (nonOESDebug) System.out.println(s);
}

private void debugPrint (String s) {
if (DEBUG) System.out.println(s);
}

public OES_AccessController(File fileKeyStore, File filePermits)
throws IOException, AccessControlException
{
System.out.println("************************ IN Access Controller************");

m_mapPublicKey = new SafeHashMap();
if(!filePermits.exists() || !filePermits.canRead())
throw new IOException("Permission file is not accessible: " + filePermits.getAbsolutePath());
try
{
KeyStore store = KeyStore.getInstance(KEYSTORE_TYPE);
store.load(new FileInputStream(fileKeyStore), null);
m_store = store;
}
catch(Exception e)
{
System.out.println("Failed to load keystore: " + fileKeyStore.getAbsolutePath()+" exception:"+e.toString());
}
try
{
m_xmlPermits = (new SimpleParser()).parseXml(new FileInputStream(filePermits));
}
catch(Exception e)
{
System.out.println( "Failed to load permissions: " + filePermits.getAbsolutePath()+" exception:"+e.toString());
}

//
// Setup the services which call the OES SSM (PDP) and returns results to this Java class for enforcement (PEP)
// This will use the configuration file specified in the -Dpdp.configuration.properties.location=
// flag set on the coherence client...
//

String pdname = oesEng.tryGetPolicyDomainName();
System.out.println("--> pdname returned from the security.properties file is:"+pdname);

rt = oesEng.initializeSSM(pdname);
if (rt == null) {
System.out.println("Failed to initialize the setup to the OES SSM");
System.exit(-1);
}

// Fetch our policy domain from the runtime
pd = oesEng.tryGetPolicyDomain(rt, pdname);
if (pd == null) {
System.out.println("Failed to get the policy domain "+pdname+" for the OES SSM");
System.exit(-2);
}

// Get the authentication service from the policy domain so that the identity (subject passed here)
// can be asserted with the sample "User Name" Identity Asserter. OES will assume that authentication
// has already taken place successfully.
atnSvc = oesEng.tryGetAuthenticationService(pd);
if (atnSvc == null) {
System.out.println("Failed to get the Authentication service for the OES SSM");
System.exit(-3);
}

// Get the authorization service from the policy domain
atzSvc = oesEng.tryGetAuthorizationService(pd);
if (atzSvc == null) {
System.out.println("Failed to get the Authorization service for the OES SSM");
System.exit(-4);
}

}


/**
* The checkPermission API is the main method exposed by Coherence 3.4 where OES can plug-in and render
* a decision (grant/deny) based on the subject, action, resource passed in. The accessResult value is
* what is returned from the OES engine. If a deny is returned, then an exception is thrown as per the
* Coherence API documentation. See:
* http://download.oracle.com/otn_hosted_doc/coherence/340/com/tangosol/net/security/AccessController.html
*/
public void checkPermission(ClusterPermission clusterPermission,
Subject subject) {
// This same method can be called multiple times from a Coherence client. For example:
// joining the cluster, try to join a cache, etc...
// The current permissions from Coherence are: ALL, CREATE, DESTROY, JOIN, NONE
// Currently the most granular information one can get from Coherence is the name of the
// cache and not the actual object. See:
// http://download.oracle.com/otn_hosted_doc/coherence/340/com/tangosol/net/ClusterPermission.html

debugPrint("Subject is:"+subject.getPrincipals());
String actionATZ = clusterPermission.getActions();

debugPrint("clusterPermission actions are:"+actionATZ+", and ServiceName is:"+clusterPermission.getServiceName());
debugPrint(clusterPermission.toString());

// Get the actual name of the cache to pass to OES as the resource
String cacheNameATZ = clusterPermission.getName();
int cachePos = cacheNameATZ.indexOf("cache=");
cacheNameATZ = cacheNameATZ.substring(cachePos+6);
debugPrint("Cache Name is:"+cacheNameATZ);

// How should we best handle the "*" as a resource? This can either be added as a resource
// in OES or it can be ignored if desired
//if (cacheNameATZ.equals("*")) {
// debugPrint("Cache is actually a * here... ");
//}

// Start authentication which will assert the identity passed in. The Identity Asserter configured in
// OES for this RMI-SSM will let all identities passed since we are relying on the calling application
// to have been authenticated by some means (keystore, OAM, etc). OES needs some identity in which
// entitlements policies can be written
AuthenticIdentity ident = oesEng.tryAuthenticate(atnSvc, subject);
if (ident == null) {
System.out.println("Failed to authenticate the identiy within the OES SSM");
System.exit(-5);
}

HashMapContext appContext = new HashMapContext();
RuntimeResource resource = new RuntimeResource(BOUND_SSM_NAME+cacheNameATZ, "exampleResource");
RuntimeAction action = new RuntimeAction(actionATZ, "exampleAction");

// Call the OES authorization engine with the identity, resource, action and any hashmap context needed
AccessResult accessResult = oesEng.tryAuthorize(atzSvc, ident, resource, action, appContext);
System.out.println("accessResult is:"+accessResult.isAllowed()+" for\n\t subject:"+subject.getPrincipals()+"\n\t resource:"+resource);

// if the result is a DENY throw an exception. Otherwise it is a grant and do nothing since this is a void method...
if (!accessResult.isAllowed()) {
System.out.println("Deny and throw exception");
throw new AccessControlException("Insufficient rights to perform the operation "+clusterPermission+" for Subject:"+subject.getPrincipals());
} // of if

} // of checkPermissions


//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

/**
/* All helper methods for this AccessController to do the work of encrypt and decrypt
* information from the keystore where the identity and passwords are stored. This is essentially
* what the DefaultController does in Coherence. There is no OES code from this point forward.
*/
public SignedObject encrypt(Object o, Subject subjEncryptor)
throws IOException, GeneralSecurityException
{

Set setPrivateCreds = subjEncryptor.getPrivateCredentials();
if(setPrivateCreds == null)
throw new GeneralSecurityException("Subject without private credentials");
for(Iterator iter = setPrivateCreds.iterator(); iter.hasNext();)
{
Object oCred = iter.next();
PrivateKey keyPrivate = null;
if(oCred instanceof PrivateKey)
keyPrivate = (PrivateKey)oCred;
else
if(oCred instanceof X500PrivateCredential)
keyPrivate = ((X500PrivateCredential)oCred).getPrivateKey();
if(keyPrivate != null)
return encrypt((Serializable)o, keyPrivate);
}

throw new GeneralSecurityException("Not sufficient credentials");
}

public Object decrypt(SignedObject so, Subject subjEncryptor,
Subject subjDecryptor) throws ClassNotFoundException, IOException, GeneralSecurityException {
debugPrintPrivate("In decrypt for my AccessController class");
PublicKey keyPublic;
Iterator iter;

keyPublic = (PublicKey)m_mapPublicKey.get(subjEncryptor);
if(keyPublic != null)
return decrypt(so, keyPublic);
Set setKeys = null;
if(subjDecryptor != null)
{
Set setDecryptorCreds = subjDecryptor.getPublicCredentials();
if(setDecryptorCreds != null && equalsMostly(subjDecryptor, subjEncryptor))
setKeys = extractPublicKeys(setDecryptorCreds);
}
if(setKeys == null)
setKeys = findPublicKeys(subjEncryptor);
iter = setKeys.iterator();
debugPrintPrivate("about to loop...");
do {
if(!iter.hasNext()) {
break; /* Loop/switch isn't completed */
} else {
keyPublic = (PublicKey)iter.next();
Object o;
o = decrypt(so, keyPublic);
m_mapPublicKey.put(subjEncryptor, keyPublic);
return o;
}
// throw new GeneralSecurityException("Failed in looping for credentials");
} while (true);

throw new GeneralSecurityException("Failed to match credentials for " + subjEncryptor);
}

protected SignedObject encrypt(Serializable o, PrivateKey keyPrivate)
throws IOException, GeneralSecurityException
{
return new SignedObject(o, keyPrivate, SIGNATURE_ENGINE);
}

protected Object decrypt(SignedObject so, PublicKey keyPublic)
throws ClassNotFoundException, IOException, GeneralSecurityException
{
if(so.verify(keyPublic, SIGNATURE_ENGINE))
return so.getObject();
else
throw new SignatureException("Invalid signature");
}

protected boolean equalsMostly(Subject subject1, Subject subject2)
{
debugPrintPrivate("In equalsMostly... hardcode since this equals() method listed doesn't resolve to anything in public Coherence docs");
//return equals(subject1.getPrincipals(), subject2.getPrincipals()) && equals(subject1.getPublicCredentials(), subject2.getPublicCredentials());
return true;
}

protected Set extractPublicKeys(Set setPubCreds)
{
Set setCerts = extractCertificates(setPubCreds);
Set setKeys = new LiteSet();
Certificate cert;
for(Iterator iter = setCerts.iterator(); iter.hasNext(); setKeys.add(cert.getPublicKey()))
cert = (Certificate)iter.next();

return setKeys;
}

protected Set extractCertificates(Set setPubCreds)
{
Set setCerts = new LiteSet();
Iterator iter = setPubCreds.iterator();
do
{
if(!iter.hasNext())
break;
Object oCred = iter.next();
if(oCred instanceof CertPath)
{
CertPath certPath = (CertPath)oCred;
List listCert = certPath.getCertificates();
if(!listCert.isEmpty())
setCerts.add(listCert.get(0));
} else
if(oCred instanceof Certificate)
{
Certificate cert = (Certificate)oCred;
setCerts.add(cert);
} else
if(oCred instanceof Certificate[])
{
Certificate acert[] = (Certificate[])oCred;
if(acert.length > 0)
setCerts.add(acert[0]);
} else
{
CacheFactory.log("Unsupported credentials: " + oCred.getClass(), 2);
}
} while(true);
return setCerts;
}

protected Set findPublicKeys(Subject subject)
throws GeneralSecurityException
{
java.security.KeyStore store = m_store;
Set setCerts = extractCertificates(subject.getPublicCredentials());
Set setPpals = new LiteSet();
Set setKeys = new LiteSet();
Iterator iter = setCerts.iterator();
do
{
if(!iter.hasNext())
break;
java.security.cert.Certificate cert = (java.security.cert.Certificate)iter.next();
if(store.getCertificateAlias(cert) != null && (cert instanceof X509Certificate))
{
X509Certificate certX509 = (X509Certificate)cert;
setPpals.add(new X500Principal(certX509.getIssuerDN().getName()));
setKeys.add(cert.getPublicKey());
}
} while(true);
if(!setPpals.containsAll(subject.getPrincipals()))
{
CacheFactory.log("Unable to verify the Principal set: " + subject.getPrincipals(), 2);
setKeys.clear();
}
return setKeys;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

static
{
String sConfig = System.getProperty("tangosol.security.config");
XmlDocument xml = null;
String sKeystoreType = "JKS";
String sAlgorithm = "SHA1withDSA";
if(sConfig != null && sConfig.length() > 0)
{
URL url = Resources.findResource(sConfig, null);
Throwable e = null;
if(url != null)
try
{
xml = XmlHelper.loadXml(url.openStream());
}
catch(Throwable t)
{
e = t;
}
if(xml == null)
{
System.out.println("Unable to load DefaultController configuration file \"" + sConfig + "\";");
if(e != null)
System.out.println("e is:"+e);
System.out.println("Using default configuration.");
}
}
try
{
if(xml == null)
xml = XmlHelper.loadXml(com.tangosol.net.security.DefaultController.class, "ISO-8859-1");
sKeystoreType = xml.getSafeElement("keystore-type").getString(sKeystoreType);
sAlgorithm = xml.getSafeElement("signature-algorithm").getString(sAlgorithm);
}
catch(Throwable e) {System.out.println("In throwable for static?"); }
Signature engine;
try
{
engine = Signature.getInstance(sAlgorithm);
}
catch(Exception e)
{
throw new ExceptionInInitializerError(e);
}
KEYSTORE_TYPE = sKeystoreType;
SIGNATURE_ALGORITHM = sAlgorithm;
SIGNATURE_ENGINE = engine;
}

}


OES Authorization Client Code:


package com.oracle.oes.coherence.impl;

import com.bea.security.*;

import java.io.*;

import java.util.Enumeration;
import java.util.Properties;

import javax.security.auth.Subject;

/**
* This class does the work of connecting to the OES SSM and providing the Coherence Client
* (in the checkPermissions method) an easy way to call the isAccessAllowed API from OES.
*/
public class OES_Authorization_Impl {

public OES_Authorization_Impl() {
}

private boolean DEBUG = false;

// To enable quick performance stats on ATZ calls for a maximum looping number
// disable debug on the RMI-SSM and this will be in sub-millisecond time after the
// first ATZ call.
private boolean QUICK_PERF = false;
private int MAX_LOOP = 100;

// Default policy domain name however it will read the security.properties file in
// the working directory for the actual name.
public static final String DEFAULT_CONFIGURATION_ID = "asiadmin";

// Token type expected by the User Name Identity Asserter configured in the OES SSM
private static String USERID_TOKEN_TYPE = "USERID_TOKEN";

protected String tryGetPolicyDomainName() {
// Check for the standard system property
String configId = System.getProperty("wles.realm");

Properties props = new Properties();
if (configId == null) {
try {
props.load(new BufferedInputStream(new FileInputStream("security.properties")));

String realmName = props.getProperty("wles.realm");
if (realmName != null) {
configId = realmName;
} else {
String realm1Name = props.getProperty("wles.realm.1");
if (realm1Name != null) {
configId = realm1Name;
} else {
configId = DEFAULT_CONFIGURATION_ID;
}
}
} catch (java.io.IOException e) {
// File does not exist - ignore and set configId to default value
configId = DEFAULT_CONFIGURATION_ID;
}
}
return configId;
}

protected SecurityRuntime initializeSSM(String configId) {
SecurityRuntime rt = null;

// Initialize this applications configuration
debugPrint("Initializing the Security Runtime for configId--> "+configId);
AppConfig cfg = new AppConfig("Java API Example Application");

cfg.useConfiguration(configId);
//
// Add this application naming definitions to the config
try {
// default file name located in the working directory of this project. This
// API call is required with the contents of this file.
cfg.addNameAuthorityDefinitionFile("exampleNames.xml");

} catch (FileNotFoundException fnfExc) {
System.out.println(fnfExc.getLocalizedMessage());
return rt;
}


debugPrint("Cfg AppName is:"+cfg.getApplicationName());
debugPrint("Cfg Client UID is:"+cfg.getClientUID());
String list[] = cfg.getPolicyDomainURLs();

if (list != null ) {
for (int i=0;i Access Allowed: " + String.valueOf(accessResult.isAllowed()));
debugPrint("---> Decision Time: " + accessResult.getDecisionTime().toString());

// By default the data is returned separated by the rule which generated it (i.e. one response context per rule).
// The "getMergedContexts" method will merge all the response contexts into a single context.
HashMapContext responseContext =
(HashMapContext)collector.getMergedContexts();
if (responseContext != null) {
if (responseContext.size() != 0) {
AppContextElement[] res =
responseContext.getElements(responseContext.getNames());
for (int i = 0; i < responseContext.size(); i++) {
debugPrint(" Response context: " +
res[i].getName() + "=" +
res[i].getValue());
}
} else {
debugPrint(" Response context has 0 elements");
}
} else {
debugPrint(" Response context is NULL");
}
collector.clear();

} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
}

return accessResult;
}

private void debugPrint(String s) {
if (DEBUG)
System.out.println(s);
}

} // of class

Friday, September 11, 2009

Oracle Entitlements Server (OES) Using Oracle Coherence for Access to Distributed Attribute Data

The value of this solution is that someone can use the data in the grid to create specific entitlement policies. The data used for the entitlement could be updated at anytime using a Coherence application to dynamically change the result of a policy at run-time based on business needs or conditions.

This use-case can be done for accessing distributed attribute data in a cache grid. Many end-users already use Oracle Coherence and re-using the information from a Coherence Grid with OES would strengthen a distributed SOA environment. These name/value pairs from a Coherence Grid can be used as attributes within Oracle Entitlements Server (OES).


How does this currently work?

OES can use the Oracle Coherence product to access an Oracle Coherence Data Grid. At startup of the OES Security Module, the Coherence product will startup and connect to a configured Coherence cluster containing distributed data. Once Coherence connects, it automatically gets a copy of all the data already within that grid. The data can be retrieved with a custom OES Attribute Retriever for use in Authorization Policy decisions for an application. See the diagram below which shows how this works "Out-of-the-Box" today:



Current Solution for OES/Coherence Attribute Retrievers

This use-case can currently be solved using OES and Coherence. In the description below a WebLogic Portal application was used with OES, a custom attribute retriever and Coherence in a sample domain/test environment.


NOTE:
  • Any application can be used for testing and a "WebLogic Portal" application is not necessary. It is used as an example here of what it would look like.


  1. Setup you WLS Domain:

  2. Add the coherence jar files to the setDomainEnv.bat/sh script and make sure you specify a host for the Coherence Cluster being used as mentioned above:

    set COHERENCE_OPTIONS=-Dtangosol.coherence.localhost=169.254.25.129
    set JAVA_OPTIONS=%JAVA_OPTIONS% %COHERENCE_OPTIONS%

    @REM SET THE CLASSPATH
    set COHERENCE_DIR=\coherence-3.3.1\lib
    set COHERENCE_LIBS=%COHERENCE_DIR%\tangosol.jar;%COHERENCE_DIR%\coherence.jar;%COHERENCE_DIR%\coherence-web.jar
    set CLASSPATH=%PRE_CLASSPATH%;%WEBLOGIC_CLASSPATH%;%POST_CLASSPATH%;%WLP_POST_CLASSPATH%;%COHERENCE_LIBS%

    NOTE: Coherence is using the default cache-config.xml file which is bundled within the coherence jar files. The implication of this is that Coherence is not tune for performance and if one wants to override this, then a -Dtangosol.coherence.cacheconfig= flag would need to be applied to the java start line for WebLogic Server pointing to the coherence configuration file of choice.


  3. Run a client to load the data into the coherence grid. In this case the only cluster member of the Coherence Grid is the Coherence Server started within the container. The idea is that there is *already* an existing Coherence Grid with Distributed Data which one will connect to.


  4. Start the WLS Domain which has its security enforced by OES with the startWebLogic.cmd/sh file. Within OES an Attribute Retriever is used which gets the requested attribute from the Coherence Grid. See the sample Attribute Retriever Code below.


  5. Make sure the Attribute Retriever and attribute being used is configured within the Authorization Provider of the WLS container.


  6. Test your sample application to see if your policy which has the attribute being retrieved will retrieve it from the Coherence Grid. In this example, a Portal application was used and I modified the following resource within the OES Administration Console to check the value of the attribute we are retrieving:



  7. Resources: BEAPortalSample/wlp/portalAndALES_WAR/com_bea_p13n/Page/QL_portal_Education_and_Support
    Constraint: checkIncominAttrRet(accesspage, execPage) and coh_AccessPage = "OK"
    Subjects: USER:beaID:weblogic


  8. Access the Portal application URL and login.


  9. If the Coherence Grid is running, then the value of "OK" should be returned in the Attribute Retriever


  10. As a test, change the value in the Grid for the "coh_AccessPage" attribute to something other than "OK".


  11. Logout of the Portal application and log back in. This time you will see a different result and the user not authorized to see the page. If you are having problems with the authorization, please review the previous blog on Troubleshooting authorization decisions.





OES Custom Attribute Retriever Code


The Coherence added code is in bold:

package com.bea.ales.sample2;

import com.bea.security.providers.authorization.asi.AttributeRetriever;
import weblogic.security.spi.Resource;
import weblogic.security.service.ContextHandler;

import javax.security.auth.Subject;
import java.util.*;

// Modify the build path for this application to include
// the tangosol.jar and coherence.jar files within one's environment.
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;

/**
* Implementation for performing attribute retrieval.
* This plugin can get to remote repositories to figure out the value
* of a certain attribute at runtime. Multiple retrievers may be
* registered for the same attribute name, they will be called in
* order until one returns a non-null result.
*/
public class CohAttributeRetriever implements AttributeRetriever {

private static final String cohaccessPage = "coh_AccessPage";
private String[] attributes = {cohaccessPage };
private NamedCache myCache = null;


// “Prime” the Coherence information within the constructor
// of the AttributeRetriever
public CohAttributeRetriever() {

CacheFactory.ensureCluster();
myCache = CacheFactory.getCache("mycache");
}
/**
* Returns the names of attributes handled by this class.
* indicates that the retriever will be considered capable of
* handling any attribute name.
*
* @return the names associated with this object
*/
public String[] getHandledAttributeNames() {
return attributes;
}

/**
* Retrieve the value of the named attribute.
* Additional authorization request data
* is made available to allow for more complex attribute retrieval.
*
* @param name the name of the needed attribute
* @param subject the subject associated with the request
* @param roles the role membership of the subject
* @param resource the resource associated with the request
* @param contextHandle the context associated with the request
* @return the attributes value, or null if not found
*/
public Object getAttributeValue(String name,
Subject subject,
Map roles,
Resource resource,
ContextHandler contextHandle) {

// Set default value
String attrValue = "no";

if (name.equals(cohaccessPage)) {

try {

// The only call to the Coherence Named Cache to get
// whatever attribute is needed from the Coherence Grid

attrValue = (String)myCache.get(cohaccessPage);

System.out.println("--> COH Value is \"" + attrValue + "\"");
} catch (Exception e) {
System.out.println("E:"+e.getLocalizedMessage()+" "+ e);
}
finally {}
return attrValue;
}

// default value returned...
return attrValue;
}

}//end of AttributeRetriever

Friday, September 4, 2009

Step by Step Details on Debugging an Oracle Entitlements Server (OES) Security Module Instance

The following details how-to troubleshoot authorization failures in an Oracle Entitlements Server Security Module Instance. The OES product is available for download from the following link.


What is OES? From the OES documentation: "Oracle Entitlements Server provides fine-grained entitlement management solution that secures critical applications with performance and reliability. By combining centralized policy management with distributed policy decision-making and enforcement, it allows you to rapidly adapt to changing business requirements. Typical uses include fine-grained entitlements for application functionality, dynamic data redaction and privacy at the source, and controlling access to web service endpoints."


Troubleshooting:

If the issue at hand is troubleshooting in development “why” a particular policy was either a GRANT or DENY then the best thing to do would be to enable debugging with the SSM instance’s log4j.properties file which is located in the “config” directory of the SSM instance. Once that is done, restart the SSM instance, run the application in question to exercise the policy, and then look into the SSM “instance” log directory for the system_console.log file. Open this file up in a text editor, scroll to the bottom and look for the policy decision in question to understand why a policy evaluated the way it did.


Step by Step Details on Debugging an SSM Instance:

Enable debugging on the RMI-SSM to see verbose debug logging as the application is run. This is a good place to look in order to determine why something is or is not authorized. To enable debugging in this example, goto the OES_HOME\ales32-ssm\rmi-ssm\instance\\config directory. In the following example there is an RMI-SSM instance called "CoherenceSSM" which one will see referenced.

  1. Edit the log4j.properties file and uncomment these lines: log4j.logger.com.bea.security.providers.authorization = DEBUG log4j.logger.com.wles.util.DebugStore=DEBUG
  2. Clear out the log files in BEA_HOME\ales32-ssm\rmi-ssm\instance\CoherenceSSM\log
  3. Re-start the SSM instance.
  4. View the “system_console.log” file under BEA_HOME\ales32-ssm\rmi-ssm\instance\CoherenceSSM\log
  5. This will show an example of an incorrect authorization because the user “dave” is not part of the Coherence_Group for this resource:

  6. 2009-02-03 21:18:49,969 [RMI TCP Connection(2)-141.144.104.221] DEBUG com.wles.util.DebugStore - queryAccess: DebugStore:
    ========== Policy Evaluation Info ==========
    RequestResource is: //app/policy/CoherenceSSM/__ASTR_
    UserInfo:
    Name: //user/asi/dave/
    Groups: //sgrp/asi/allusers/
    Resource Present: true
    Roles Granted: NONE
    Role Mapping Policies: NONE
    ATZ Policies: NONE
    ========== Policy Evaluation Info ==========
    2009-02-03 21:18:49,969 [RMI TCP Connection(2)-141.144.104.221] DEBUG com.bea.security.providers.authorization.asi.ARME.engine.ARME - unlock policy lock for read
    2009-02-03 21:18:49,969 [RMI TCP Connection(2)-141.144.104.221] DEBUG com.bea.security.providers.authorization.asi.AuthorizationProviderImpl - result is UNKNOWN
    2009-02-03 21:18:49,969 [RMI TCP Connection(2)-141.144.104.221] INFO com.bea.security.providers.authorization.asi.AccessResultLogger - Subject Subject:
    Principal: asi
    Principal: dave
    privilege join resource //app/policy/CoherenceSSM/__ASTR_ result ABSTAIN


  7. This will show an example of an correct authorization because the user “steve” IS part of the Coherence_Group for this resource:
    2009-02-03 21:21:57,969 [RMI TCP Connection(4)-141.144.104.221] DEBUG com.wles.util.DebugStore - queryAccess: DebugStore:


  8. ========== Policy Evaluation Info ==========
    RequestResource is: //app/policy/CoherenceSSM/testCache
    UserInfo:
    Name: //user/asi/steve/
    Groups: //sgrp/asi/Coherence_Group/ //sgrp/asi/allusers/
    Resource Present: true
    Roles Granted: NONE
    Role Mapping Policies: NONE
    ATZ Policies:
    1. Result: true; Policy Type: grant
    Privilege: any
    Resource: //app/policy/CoherenceSSM/testCache
    Subject: //sgrp/asi/Coherence_Group/
    Constraints: NONE
    Delegator: null

    ========== Policy Evaluation Info ==========
    2009-02-03 21:21:57,969 [RMI TCP Connection(4)-141.144.104.221] DEBUG com.bea.security.providers.authorization.asi.ARME.engine.ARME - unlock policy lock for read
    2009-02-03 21:21:57,969 [RMI TCP Connection(4)-141.144.104.221] DEBUG com.bea.security.providers.authorization.asi.AuthorizationProviderImpl - result is GRANT
    2009-02-03 21:21:57,969 [RMI TCP Connection(4)-141.144.104.221] DEBUG com.bea.security.providers.authorization.asi.AccessResultLogger - Subject Subject:
    Principal: asi
    Principal: steve
    Principal: Coherence_Group
    privilege join resource //app/policy/CoherenceSSM/testCache result PERMIT