Monday, June 17, 2013

Credential Store Framework

Sometimes you have the need for credentials (username/password) in your ADF, or other Fusion Middleware, application. I've seen numerous solutions with property files, web.xml context parameters, deployment plans, etc. Most of these run into problems with SysOps or anyone else worried with security. You don't want these credentials scattered around in plain text files and you don't want developers to know the passwords for each environment. This is better left to configuration by a sys-admin after deployment.

Not everybody seems to know Oracle Fusion Middleware, more specifically Oracle Platform Security Services (OPSS), provides a great solution with the Credentials Store Framework. This is a set of APIs that applications can use to create, read, update, and manage credentials securely.

A credential store is a repository of security data (credentials) that can hold user name and password combinations, tickets, or public key certificates. A credential store can be file-, LDAP- (Oracle Internet Directory), or DB-(Oracle RDBMS)based. A file-based credential store, also referred to as wallet-based and represented by the file cwallet.sso, is the out-of-the-box credential store.

The Credentials Store Framework also limits which application (or components thereof) are allowed to retrieve or modify credentials. This allows for a very secure setup where only trusted libraries that go through extensive auditing are allowed to retrieve credentials.

This post describes the basic steps to get started with the Credentials Store Framework, but more information can be found in the official documentation (Fusion Middleware Application Security Guide):

The Credential Store is a collection of maps, each with its own name. Each of these maps can store and retrieve credentials or certificates based on a unique key. It's common practice to name each map specific for an application so we can grant as little privileges as possible to this map (and keys).

Step 1 - Add Credentials to a Credential Store

The first step in this example is to add credentials to a credential store. You can use WLST or Fusion Middleware Control. When using the embedded weblogic server that comes with JDeveloper you will have to use WLST scripting as it doesn't have Fusion Middleware Control. I'll explain both approaches below.

Creating credentials with WLST

Ensure the (integrated) WebLogic server is running and start WLST with ORACLE_HOME/oracle_common/common/bin/wlst

Connect to the WebLogic Server from WLST with the correct credentials and t3 URL, for example:
connect('weblogic', 'weblogic1', 't3://localhost:7101')

create a credential with createCred:
createCred(map="mapName", key="keyName", user="secretUsername", password="secretPassword", desc="Description")

I advice to keep the name of the map equal to the adfAppUID as defined in the META-INF/adf-config.xml file of your application:
<adf-config xmlns="http://xmlns.oracle.com/adf/config"
            xmlns:config="http://xmlns.oracle.com/bc4j/configuration"
            xmlns:adf="http://xmlns.oracle.com/adf/config/properties">
  ....
  <adf:adf-properties-child xmlns="http://xmlns.oracle.com/adf/config/properties">
    <adf-property name="adfAppUID"
                  value="ApplicationName.com.redheap.sample"/>
  </adf:adf-properties-child>
</adf-config>

An example would be:
createCred(map="ApplicationName.com.redheap.sample", key="workflowRMI", user="weblogic", password="forYourEyesOnly", desc="Account for RMI calls to Human Workflow Service")

You can use updateCred and deleteCred for other operations.

As a final step you can check if your newly create credential is available in the store:
listCred(map="mapName", key="keyName")

Creating credentials with Fusion Middleware Control

An alternative to WLST scripting is to use the Fusion Middleware Control web interface. The steps are similar although you have to create the map itself first and then create a key within that map. Keep in mind that the credential store is for the entire weblogic domain and not for a specific server or cluster. The credential store can be managed through Domain > Security > Credentials. For more details see Managing Credentials with Fusion Middleware Control
Editing credentials using Fusion Middleware Control


Step 2 - Authorising an a application to retrieve credentials

Credentials from the Credential Store cannot simply be retrieved by any application. A system administrator first has to grant privileges through system policies.

Integrated WebLogic Server in JDeveloper

When developing with a local JDeveloper and WebLogic installation it is easiest to use relaxed security and to give all applications read-access to all credentials. This removes the need to grant permissions to each (test) application over and over again. For a very relaxed policy shutdown your Integrated WebLogic server and edit its system-jazn-data.xml file. On windows this is typically located at %APPDATA%\JDeveloper\system11.x.x.x.x.x.x\DefaultDomain\config\fmwconfig\system-jazn-data.xml. Add the following grant as a child of jazn-data/system-policy/jazn-policy (be very sure to check the location as there is also a admin-policy under system-policy and that's not the one you want):
<grant>
  <grantee>
    <codesource>
      <url>file:/-</url>
    </codesource>
  </grantee>
  <permissions>
    <permission>
      <class>oracle.security.jps.service.credstore.CredentialAccessPermission</class>
      <name>context=SYSTEM,mapName=*,keyName=*</name>
      <actions>read</actions>
    </permission>
  </permissions>
</grant>

Managed WebLogic Server

With a properly managed server you don't want to grant every application access to all credentials. Here you want to limit the grants as much as possible. You can add a grant directly in the config file or through Fusion Middleware Control.

Manually granting directly in jazn-data.xml

You can add the necessary grant as a child of jazn-data/system-policy/jazn-policy in DOMAIN_HOME/config/fmwconfig/jazn-data.xml (again be very sure to check the location as there is also a admin-policy under system-policy and that's not the one you want):
<grant>
 <grantee>
  <codesource>
   <url>file:${oracle.deployed.app.dir}/ApplicationName${oracle.deployed.app.ext}</url>
  </codesource>
 </grantee>
 <permissions>
  <permission>
   <class>oracle.security.jps.service.credstore.CredentialAccessPermission</class>
   <name>context=SYSTEM,mapName=CredentialMapName,keyName=CredentialKeyName</name>
   <actions>read</actions>
  </permission>
 </permissions>
</grant>

Replace ApplicationName with the name of the deployed application. This is the same as the directory that is created in .../user_projects/domains/DomainName/servers/ServerName/tmp/_WL_USER. Also replace CredentialMapName and CredentialKeyName with the names of the credential map and key to identify which credential can be read by the application.

Adding using Fusion Middleware Control

A less error prone method is to use Fusion Middleware Control to add the grant. Navigate to Domain > Security > System Policies in Fusion Middleware Control. Then press Create... and use the following properties:
  • Grant to: codebase
  • Codebase: file:${oracle.deployed.app.dir}/ApplicationName${oracle.deployed.app.ext} (replace ApplicationName with the name of the application, see manual granting for more details)
  • Press the Add button for permissions
    • Check Select here to enter details for a new permission. This is a tiny checkbox at the bottom of the popup to start creating a new permission.
    • Permission Class: oracle.security.jps.service.credstore.CredentialAccessPermission
    • Resource Name: context=SYSTEM,mapName=CredentialMapName,keyName=CredentialKeyName (replace CredentialMapName and CredentialKeyName with the name of the map and key used when creating the credentials)
    • Permission Actions (if available): read
WARNING: After making these changes all Managed Servers AND the Admin Server have to restarted to activate these changes.

TIP: More information on the syntax of the codebase URL, and the use of wildcards, can be found in Appendix B - File-Based Identity and Policy Store Reference

Step 3 - Retrieving Credentials in Application

In Java we need to use the AccessController to execute the code to retrieve credentials from the store with elevated privileges. The code itself will use oracle.security.jps.service.credstore.CredentialStore to retrieve the credentials. See the simplified example below for a factory class that can create a Connection object with credentials form the credential store. Most connection objects won't expose the credentials used for that connection to their user. This means you can package this factory class in a separate library and only allow that library access to the credential store. That way you only have to audit/review this single library and not all other application code for places where the credentials might leak out.

import java.security.AccessController;

import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

import oracle.adf.share.ADFContext;

import oracle.security.jps.JpsException;
import oracle.security.jps.service.JpsServiceLocator;
import oracle.security.jps.service.ServiceLocator;
import oracle.security.jps.service.credstore.PasswordCredential;
import oracle.security.jps.service.credstore.CredentialStore;

public final class ConnectionFactory {

    /**
     * Retrieves credentials from the Credential Store where the current
     * application UID is used as the name of the credential map.
     * This method must be called through AccessController.doPrivileged
     * @param key name of the key in the credential map to retrieve
     * @return PasswordCredential if exists, null otherwise
     * @throws JpsException
     */
    private PasswordCredential _readCredentials(String key) throws JpsException {
        ServiceLocator locator = JpsServiceLocator.getServiceLocator();
        CredentialStore store = locator.lookup(CredentialStore.class);
        // always use application UID as name for the credential map to ensure
        // each application uses its own map and credentials aren't shared
        String map = ADFContext.getCurrent().getADFApplicationUID();
        return (PasswordCredential)store.getCredential(map, key);
    }

    /**
     * Retrieves credentials from the Credential Store by invoking
     * {@link #_readCredentials} as a privileged action.
     * @param key name of the key in the credential map to retrieve
     * @return PasswordCredential if exists, null otherwise
     * @throws JpsException
     */
    private PasswordCredential readCredentials(final String key) {
        PasswordCredential credentials;
        PrivilegedExceptionAction<PasswordCredential> action =
            new PrivilegedExceptionAction<PasswordCredential>() {
            public PasswordCredential run() throws JpsException {
                return _readCredentials(key);
            }
        };
        try {
            credentials = AccessController.doPrivileged(action);
        } catch (PrivilegedActionException e) {
            throw new RuntimeException(e);
        }
        return credentials;
    }

    /**
     * Factory method to create connection with credentials from Credential
     * Store.
     * @return new connection
     */
    public Connection createWorkflowConnection() {
        // use fixed key so the user of this factory is not allowed to
        // create a connection based on any arbitrary credential
        PasswordCredential cred = readCredentials("workflowRMI");
        // TODO: create and return connection with information from
        //       cred.getName() and cred.getPassword();
        return null;
    }
}

If the oracle.security.jps.* classes are not available to your project be sure to include the JRF Client or JRF Runtime library in your project settings.

TIP: If you miss the necessary system policy grant your application won't be allowed to retrieve the credentials. The raised AccessControlException is normally not shown to the end user or logged to your console. Add -Djps.auth.debug=true to your application startup parameters (and restart weblogic) to display these errors in your console. If you also want to see which codebase is used to retrieve the credentials, you can also add -Djps.auth.debug.verbose=true but be warned that this also logs every successful access so your console will quickly fill up. This way we found out that the codebase for one of our sample applications running on Windows was file:/C:/Users/xxx/AppData/Roaming/JDeveloper/system11.x.x.x.x.xx.x/DefaultDomain/servers/DefaultServer/tmp/_WL_user/ApplicationName/36wlms/war/WEB-INF/lib/credentials-1.0.jar. Together with the previous information on wildcards in the codebase URL this can help you fine tune the system policies.

Keep in mind that declarative ADF Connections (the ones you can edit in the JDeveloper IDE) can be changed through Fusion Middleware Control or WLST as described by Edwin Biemond from AMIS. The Oracle Credential Store Framework is more for situations where you are programmatically creating connections to backend systems or other situations where you need credentials. If you have any remaining questions just post them below and I'll try to answer them. 

10 comments:

  1. Hi, I'm migrating content and structure in WebCenter Content 11g R1 (11.1.1.7) and see in the logs the next error messages:

    !csMasterKeysReadError!csErrorInvokingCSFMethod,getCredentialMap!syServiceRuntime,
    java.security.AccessControlException: access denied

    (oracle.security.jps.service.credstore.CredentialAccessPermission
    context=SYSTEM\,mapName=IDCCS\,keyName=* read)
    intradoc.common.ServiceException: !csErrorInvokingCSFMethod,getCredentialMap
    at idc.conversion.jps.CSFKeyLoader.readKeys(CSFKeyLoader.java:172)
    at intradoc.conversion.SecurityObjects.getMasterKeys(SecurityObjects.java:1211)
    at intradoc.conversion.CryptoPasswordUtils.loadMasterKeys(CryptoPasswordUtils.java:613)
    at intradoc.conversion.CryptoPasswordUtils.loadMasterKeyForCategory(CryptoPasswordUtils.java:618)
    at sitestudiopublisher.SSPServiceHandler.handleStartup(Unknown Source)
    at sitestudiopublisher.SSPFilters.doFilter(Unknown Source)
    at intradoc.shared.PluginFilters.filterWithAction(PluginFilters.java:114)
    at intradoc.shared.PluginFilters.filter(PluginFilters.java:68)

    My configuration is on cluster (2 nodes).

    Thanks for you reply.

    ReplyDelete
  2. I see this error also when restart weblogic server.

    Thanks.

    ReplyDelete
  3. Hi, I solved the problem, see the forum https://forums.oracle.com/message/10297929

    Thanks.

    ReplyDelete
    Replies
    1. Sorry for missing your comments, but good to hear the problem is solved

      Delete
  4. Hi,
    In createCred example it should be key instead of keyname

    ReplyDelete
  5. Hello - I have custom class loader which has code which is accessing the DB based credential Store.I have given the credentialAccessPermission to entire code-base as described in the blog.However, I still get credentialAccessPermission exception.Posted the stacktrace below.
    [JpsAuth] Check Permission
    PolicyContext: [null]
    Resource/Target: [context=SYSTEM,mapName=cybersource,keyName=transactionSecurityKey]
    Action: [read]
    Permission Class: [oracle.security.jps.service.credstore.CredentialAccessPermission]
    Result: [FAILED]
    Evaluator: [ACC]
    Failed ProtectionDomain:ClassLoader=atg.nucleus.ServicesManifestClassLoaderService$ClassLoaderServiceOwnedServicesManifestClassLoader@d4067c1 (files=all my jars here which are loaded by custom classloader, parent=weblogic.utils.classloaders.GenericClassLoader@67d09fe2 finder: weblogic.utils.classloaders.CodeGenClassFinder@c48e96f annotation: CloudCommerce-store@)
    CodeSource=
    Principals=total 0 of principals
    Permissions=(
    ("java.util.PropertyPermission" "line.separator" "read")
    ("java.util.PropertyPermission" "java.vm.specification.version" "read")
    ("java.util.PropertyPermission" "java.vm.version" "read")
    ("java.util.PropertyPermission" "java.vendor.url" "read")
    ("java.util.PropertyPermission" "java.vm.specification.vendor" "read")
    ("java.util.PropertyPermission" "java.vm.name" "read")
    ("java.util.PropertyPermission" "os.name" "read")
    ("java.util.PropertyPermission" "java.vm.vendor" "read")
    ("java.util.PropertyPermission" "path.separator" "read")
    ("java.util.PropertyPermission" "os.version" "read")
    ("java.util.PropertyPermission" "java.specification.name" "read")
    ("java.util.PropertyPermission" "os.arch" "read")
    ("java.util.PropertyPermission" "java.version" "read")
    ("java.util.PropertyPermission" "java.class.version" "read")
    ("java.util.PropertyPermission" "java.vendor" "read")
    ("java.util.PropertyPermission" "file.separator" "read")
    ("java.util.PropertyPermission" "java.vm.specification.name" "read")
    ("java.util.PropertyPermission" "java.specification.version" "read")
    ("java.util.PropertyPermission" "java.specification.vendor" "read")
    ("java.net.SocketPermission" "localhost:1024-" "listen,resolve")
    ("java.lang.RuntimePermission" "stopThread")
    ("oracle.security.jps.service.keystore.KeyStoreAccessPermission" "stripeName=system,keystoreName=trust,alias=*" "read")
    )

    ReplyDelete
    Replies
    1. The list of active permissions don't even show the extra grant you added to system-jazn-data.xml. We had the same the other day when we added the grant to the wrong location in the system-jazn-data.xml. Be absolutely sure you add it to jazn-policy which is a child of system-policy which is a child of the root jazn-data. Be aware there is also an admin-policy under the system-policy and that's not the one you want.
      Also be sure to make the changes to this file while weblogic is shut down. It seems to write its own state to this file un shutdown so if you edit it while WLS is running your changes are lost. To be sure make the changes while WLS is shutdown, start and stop WLS and double check if the grant is still there. If it is, restart WLS and try you application again.

      Delete
  6. Hi,
    I am getting java.security.AccessControlException: access denied ; While adding User Principle to Application role. Can you please help me to find out solution


    (oracle.security.jps.service.policystore.PolicyStoreAccessPermission Context:APPLICATION Context Name:CSSApp Actions:addPrincipalToAppRole)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
    at java.security.AccessController.checkPermission(AccessController.java:546)
    at oracle.security.jps.util.JpsAuth$AuthorizationMechanism$3.checkPermission(JpsAuth.java:463)
    at oracle.security.jps.util.JpsAuth.checkPermission(JpsAuth.java:523)
    at oracle.security.jps.util.JpsAuth.checkPermission(JpsAuth.java:549)
    at oracle.security.jps.internal.policystore.util.OpssAuth.checkAdminPermission(OpssAuth.java:69)
    at oracle.security.jps.internal.policystore.entitymanager.impl.ApplicationRoleManagerImpl.checkPermission(ApplicationRoleManagerImpl.java:3709)
    at oracle.security.jps.internal.policystore.entitymanager.impl.ApplicationRoleManagerImpl.addPrincipalToAppRole(ApplicationRoleManagerImpl.java:2611)

    ReplyDelete
  7. What are you trying to do when you get this error? Is this during application deployment?

    ReplyDelete

Comments might be held for moderation. Be sure to enable the "Notify me" checkbox so you get an email when the comment is accepted and I reply.