Monday, August 1, 2011

Coherence Security using Access Controller

A detailed understanding of the Clustered Access Controller and Coherence is available here. Though steps for configuring Default Access Controller are available on the Coherence website but step-by-step guide on how to use it in real world is mentioned below:

*********************
Step 1: Create a class MyAccessController that implements AccessController Interface. The methods that should be implemented are:

- checkPermission(ClusterPermission clusterPermission, Subject subject)
The checkPermission method is used to authorise the security credentials of the subject for a particular action such as, join, create or destroy a clustered cache.

- SignedObject encrypt(Object object, Subject subject)
Encrypts the specified object using the private key extracted from the keystore specified in the constructor for MyAccessController.

- Object decrypt(SignedObject signedObject, Subject subject, Subject subject2)
Decrypts the specified SignedObject using the public credentials extracted from the keystore specified in the constructor for MyAccessController.

- Constructor: public MyAccessController(String keyStoreName, String alias,String password, File permissionFile)
* @param keyStoreName: File location of the keyStore
* @param alias: The Alias whose keys will be used
* @param password: The Password for the Alias
* @param permissionFile: The Permissions granted to the Alias

The complete class Implementation of the MyAccessController is as below:

public class MyAccessController implements AccessController {

/**
* The Default Controller will be used to implement the check permissions method
*/
public static DefaultController defaultController;

/** This is the signature algorithm that will be used with
* our keys to encrypt and decrypt SignedObject instances.
*/
public static final String SIGNATURE_ALGORITHM = "SHA1withDSA";

/** The PrivateKey used for encryption
*/
private PrivateKey privateKey;

/** The public key used for decryption
*/
private PublicKey publicKey;


public MyAccessController() {
super();
}

/*** Create a new ClusterAccessController using the keys
* * from the specified keystore.
* @param keyStoreName
* @param alias
* @param password
* @param permissionFile
*/

public MyAccessController(String keyStoreName, String alias,String password, File permissionFile) {

try {
// Extract the keys from the keystore
InputStream fileStoreStream;

File f = new File(keyStoreName);

if (f.exists()) {
fileStoreStream = new FileInputStream(f);
} else {
fileStoreStream =getClass().getResourceAsStream(keyStoreName);
}

if (fileStoreStream == null) {
throw new IllegalArgumentException("keystore file does not exist");
}

KeyStore store = KeyStore.getInstance("JKS");
store.load(fileStoreStream, null);
//Extract the Private Key for the Alias
privateKey =(PrivateKey) store.getKey(alias, password.toCharArray());
//Extract the Public Key for the Alias
publicKey = store.getCertificate(alias).getPublicKey();
//Intialize the Default Controller for implementing checkPermission method
defaultController = new DefaultController(f,permissionFile);
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw Base.ensureRuntimeException(e,"Error in ConfigurableClusterAccessController constructor");
}
}

/**
* @param clusterPermission
* @param subject
*/
@Override
public void checkPermission(ClusterPermission clusterPermission, Subject subject) {
//Implement your authoriation module or use the deafult Controller modeule
//based on the permissions file.
defaultController.checkPermission(clusterPermission, subject);
}

/**
* @param object
* @param subject
* @return
* @throws IOException
* @throws GeneralSecurityException
*/
@Override
public SignedObject encrypt(Object object, Subject subject) throws IOException, GeneralSecurityException {
return new SignedObject((Serializable) object, privateKey, Signature.getInstance(SIGNATURE_ALGORITHM));
}

/**
* @param signedObject
* @param subject
* @param subject2
* @return
* @throws ClassNotFoundException
* @throws IOException
* @throws GeneralSecurityException
*/
@Override
public Object decrypt(SignedObject signedObject, Subject subject, Subject subject2)
throws ClassNotFoundException,
IOException,
GeneralSecurityException {
if (!signedObject.verify(publicKey, Signature.getInstance(SIGNATURE_ALGORITHM))) {
throw new SignatureException("Unable to verify SignedObject");
}
return signedObject.getObject();
}
}

*********************
Step 2: Create the keystore.jks using java keytool utility for various aliases:

keytool -genkey -v -keystore <keystore.jks file location> -storepass password -alias admin
-keypass password -dname CN=Administrator,O=MyCompany,L=MyCity,ST=MyState

keytool -genkey -v -keystore <keystore.jks file location> -storepass password -alias manager
-keypass password -dname CN=Manager,OU=MyUnit

keytool -genkey -v -keystore <keystore.jks file location> -storepass password -alias worker
-keypass password -dname CN=Worker,OU=MyUnit

*********************
Step 3: Create a permission file as mentioned below:

<?xml version='1.0'?>
<permissions>
<grant>
<principal>
<class>javax.security.auth.x500.X500Principal</class>
<name>CN=Manager,OU=MyUnit</name>
</principal>

<permission>
<target>*</target>
<action>all</action>
</permission>
</grant>

<grant>
<principal>
<class>javax.security.auth.x500.X500Principal</class>
<name>CN=Worker,OU=MyUnit</name>
</principal>

<permission>
<target>cache=common*</target>
<action>join</action>
</permission>
<permission>
<target>service=invocation</target>
<action>all</action>
</permission>
</grant>
</permissions>

The above permission file is configured to allow managers to perform all the actions and the workers to join cache starting with name "common" and the invocation services.

*********************
Step 4: Create a JAAS configuration file coherence-jass.config as below:

// LoginModule Configuration for Oracle Coherence(TM)
Coherence {
com.tangosol.security.KeystoreLogin required
keyStorePath="<keystore.jks file location>
};

*********************
Step 5: Modify the tangosol-coherence-override.xml file to include:
<security-config>
<!-- Security is defaulted to true -->
<enabled system-property="tangosol.coherence.security">true</enabled>
<!-- The name of the JAAS login module to use - This is still the same as the Coherence default -->
<login-module-name system-property="coherence.security.loginmodule">Coherence</login-module-name>
<!-- Configure the access controller to use to authorise cluster membership -->
<access-controller>
<class-name>com.sample.MyAccessController</class-name>
<init-params>
<init-param id="1">
<param-type>java.lang.String</param-type>
<param-value>C:\Oracle\Coherence\Files\Security\Manager\keystore.jks</param-value>
</init-param>
<init-param id="2">
<param-type>java.lang.String</param-type>
<param-value>manager</param-value>
</init-param>
<init-param id="3">
<param-type>java.lang.String</param-type>
<param-value>password</param-value>
</init-param>
<init-param id="4">
<param-type>java.io.File</param-type>
<param-value system-property="tangosol.coherence.security.permissions">C:\Oracle\Coherence\Files\Security\permission.xml</param-value>
</init-param>

</init-params>
</access-controller>
<callback-handler>
<class-name>com.sun.security.auth.callback.TextCallbackHandler</class-name>
</callback-handler>
</security-config>

*********************
Step 6: When DefaultCacheServer runs it contains a loop that periodically calls CacheFactory.ensureService for each service that is configured with autostart=true and this loop runs every few seconds. As the ensureService call needs to be secured Coherence will call our Login Module to obtain credentials for each service in each loop. The solution to this is to run DefaultCacheServer itself with credentials already applied; i.e. wrap it inside a PrivilegedAction as mentioned below:

/* This class is
a wrapper around {@link com.tangosol.net.DefaultCacheServer} and will
* run a normal Coherence Cache Server wrapped in a {@link java.security.PrivilegedExceptionAction}
* and hence withing the scope of a {@link javax.security.auth.Subject}.
*
* @author Neeraj Jain
*/
public class JaasDefaultCacheServer {

static Subject subject;

/**
* @param args
* @throws Exception
*/
public static void main(final String[] args) throws Exception {
JaasDefaultCacheServer.startMain(args);
}

/**
* @throws Exception
*/
public static void start() throws Exception {
Security.runAs(subject, new PrivilegedExceptionAction() {
public Object run() throws Exception {
DefaultCacheServer.start();
return null;
}
});
}

/**
*/
public static void shutdown() {
DefaultCacheServer.shutdown();
}

/**
* @throws Exception
*/
public static void startDaemon() throws Exception {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println("Uncaught exception from Thread " + t.getName());
e.printStackTrace();
System.exit(1);
}
});

Security.runAs(subject, new PrivilegedExceptionAction() {
public Object run() throws Exception {
DefaultCacheServer.startDaemon();
return null;
}
});
}

/**
* @param args
* @throws Exception
*/
public static void startMain(final String[] args) throws Exception {
subject = Security.login("manager", ("password").toCharArray());
Security.runAs(subject,new PrivilegedExceptionAction() {
public Object run() throws Exception {
DefaultCacheServer.main(args);
return null;
}
});
}
}

*********************
Step 7: Use JaasDefaultCacheServer to start the Cache Nodes using the following arguments:

java -server -cp "%COHERENCE_HOME%/lib/coherence.jar:%COHERENCE_HOME%/lib/security/coherence-login.jar" -Dtangosol.coherence.override="C:\Oracle\Coherence\Files\Security\tangosol-coherence-override.xml" -Djava.security.auth.login.config="C:\Oracle\Coherence\Files\Security\coherence-jaas.config" com.sample.JaasDefaultCacheServer


***************************
Let me quickly take you through what happens behind the scene ->

A. When you start the JaasDefaultCacheServer, it will look for the Login Configuration. In the above example, we are using the "com.tangosol.security.KeystoreLogin" that comes with the Coherence product "coherence-login.jar". Whichever Login Module you use, specify in the "coherence-jass.config"

B. During the "login" call Coherence utilizes JAAS that runs on the caller's node to authenticate the caller. This means that,

subject = Security.login("manager", ("password").toCharArray()); will look into the keystore specified in "coherence-jass.config" for authentication. This authentication will provide us with the subject.

C. Once the local authentication is successful, it uses the local Access Controller to determine:

- Determine whether the local caller has sufficient rights to access the protected clustered resource (checkPermission method in MyAccessCotroller);
- Encrypt the outgoing communications regarding the access to the resource with the caller's private credentials retrieved during the authentication phase B above(encrypt method in MyAccessController);
- Decrypt the result of the remote check using the requestor's public credentials (decrypt method in MyAccessController);
- In the case that access is granted verify whether the responder had sufficient rights to do so. (checkPermission method in MyAccessCotroller)

Other Coherence security implementations will be available in future posts.

No comments:

Search This Blog