Saturday, September 4, 2010

Hands-on WSRP Security in Oracle Fusion Middleware

Introduction

This article shows how to securely propagate a user identity to a portlet, have this identity asserted and authorized to view the portlet.

WSRP (Web Services Remote Portlets) are web services. In Oracle Fusion Middleware, the way of enabling identity propagation and authentication is via OWSM (Oracle Web Services Manager) policies. The concept is no different that securing any regular web service.

Portlets authorization is handled by the OPSS (Oracle Platform Security Services) layer once authentication is done.


Note: What follows has been implemented in Oracle Fusion Middleware 11g PS1 stack (JDeveloper, WLS, Web Center, OWSM and OPSS)

OWSM policies

OWSM  is the component that implements web services security and allows for runtime enforcement and declarative policy attachment within Oracle Fusion Middleware.

OWSM provides policies for different types of use case scenarios. For example, there are policies supporting SAML token profile, kerberos, WSS1.0, SSL, etc. I will soon write an article covering OWSM in detail.

For WSRP, let’s pick a SAML token profile with WSS1.0. It basically means that the portlet will require a SAML token in the incoming SOAP header and the whole message will be digitally signed and encrypted according to WSS1.0 standards.

On the portlet producer side, we’ll attach wss10_saml_token_with_message_protection_service_policy. And on the consumer side, we’ll attach wss10_saml_token_with_message_protection_client_policy.

The policy attached to the consumer adds the SAML token to the SOAP header and performs message signing/encryption. The policy attached to the provider verifies the signature, decrypts the message, retrieve the SAML token and delegates the assertion to the OPSS layer, which verifies its validity against the configured identity store.

Securing the Producer

Authentication

The policy wss10_saml_token_with_message_protection_service_policy needs to be attached to the portlet producer. Here I will show how to do it by updating oracle-webservices.xml. Do notice this can also be accomplished via Oracle Enterprise Manager.

oracle-webservices.xml is a packaging-time artifact, meaning it is not available in JDeveloper during design-time. Therefore, to edit the file, first deploy your application to an ear file, extract the file, update it and repackage it into the ear file.

The update itself is very simple. Look for port-component name=”WSRP_v2_Markup_Service” and add the highlighted snippet shown below:

<port-component name="WSRP_v2_Markup_Service" style="document"
                                  bindingQName="{urn:oasis:names:tc:wsrp:v2:bind WSRP_v2_Markup_Binding_SOAP"
                                  enabled="true"
                                  schemaValidateInput="false">
   
<policy-references>
     <policy-reference enabled="true" 
                                       uri="oracle/wss10_saml_token_with_message_protection_service_policy"
                                       category="security"/>
   </policy-references>

   <operations>
     ...
   </operations>
   <!-- start:deployment time generated info -->
   <deployment>
    <tie-class-name>oasis.names.tc.wsrp.v2.bind.runtime.WSRP_v2_Markup_Binding_SOAP_Tie</tie-class-name>
    <service-qname namespaceURI="urn:oasis:names:tc:wsrp:v2:wsdl"
localpart="WSRP_v2_Service"/>
    <soap-version>soap1.1</soap-version>
   </deployment>
   <!-- end:deployment time generated info   -->
   <servlet-link>WSRP_v2_Markup_Service</servlet-link>
 </port-component>



Note: The portlet producer application needs to be deployed in order to be called by consumers. You cannot “Click & Run” a secured portlet application from JDeveloper.


Key store and Credential store configuration

This is a necessary step only because the OWSM policies we’re using require message-level protection.

Both key store and credential store are WLS domain wide artifacts and WLS expects them to be found under $DOMAIN_HOME/config/fmwconfig folder.

In a nutshell, the key store contains the signing and encryption keys used to sign/encrypt/decrypt messages. Each key and the key store itself are password protected. They are also referred by an alias. These aliases and the corresponding passwords are stored in the credential store. When access to key store is required, the credential store is first queried for the necessary aliases and passwords.


Key store contents

The key store is created by using JDK’s keytool command. Assuming JDK is on your $PATH, type keytool in a command prompt and you will see the command options.

To list the contents of the key store, type:

keytool –list –v –keystore <keystore_filename> -storepass <keystore password>


How to create a certificate key

In order to protect the symmetric key used to actually encrypt the SOAP message, we need a PKI certificate in the key store. For the sake of this example, let's have this certificate under an alias called “orakey”, with password equals to “welcome1”.

keytool -genkeypair -keyalg RSA -alias orakey -keypass welcome1 -keystore default-keystore.jks -storepass welcome1 -validity 3600


Note: JDK 6 is required to generate a key store compatible with OWSM in FMW 11g.


Credential store contents

A credential store is split into maps. Each map contains a set of keys. Each key points to the actual credential. The aliases created in the key store must have a corresponding credential in the credential store. For example, if we have named our alias as “orakey” in the key store, there must be a credential named “orakey” in the credential store. Such credential is pointed by a predefined key names.

For OWSM, these key names are sign-csf-key and enc-csf-key, which are used to sign and encrypt/decrypt messages. Another important key is keystore-csf-key, which holds the key store alias and password and is used to open the key store.

OWSM uses a predefined credential map named oracle.wsm.security.


How to populate the credential store

The credential store can be populated through WLST or Oracle Enterprise Manager.

In WLST, execute these online commands:

wls:/DefaultDomain/serverConfig> createCred(map="oracle.wsm.security", key="keystore-csf-key", user="owsm", password="welcome1", desc="Keystore key")
wls:/DefaultDomain/serverConfig> createCred(map="oracle.wsm.security", key="enc-csf-key", user="orakey", password="welcome1", desc="Encryption key")
wls:/DefaultDomain/serverConfig> createCred(map="oracle.wsm.security", key="sign-csf-key", user="orakey", password="welcome1", desc="Signing key")

Such commands update your domain level credential store. Normally, it corresponds to cwallet.sso under $DOMAIN_HOME/config/fmwconfig.


Verifying key store and credential store configuration in jps-config.xml

Open jps-config.xml located at $DOMAIN_HOME/config/fmwconfig and check whether the following entry exists. This should be out of box configured.

<serviceInstance location="./" provider="credstoressp" name="credstore"> 
  <description>File Based Credential Store Service Instance</description>
 </serviceInstance>
 <serviceInstance name="keystore" provider="keystore.provider" location="./default-keystore.jks">
   <description>Default JPS Keystore Service</description>
   <property name="keystore.type" value="JKS"/>
   <property name="keystore.csf.map" value="oracle.wsm.security"/>
   <property name="keystore.pass.csf.key" value="keystore-csf-key"/>
   <property name="keystore.sig.csf.key" value="sign-csf-key"/>
   <property name="keystore.enc.csf.key" value="enc-csf-key"/>
 </serviceInstance>


Also, make sure the “default” context references the key store and credential store service instances above:

<jpsContext name="default">
   <serviceInstanceRef ref="credstore"/>
   <serviceInstanceRef ref="policystore.xml"/>
   <serviceInstanceRef ref="audit"/>
   <serviceInstanceRef ref="idstore.ldap"/>
   <serviceInstanceRef ref="keystore"/>
 </jpsContext>




Note: If this configuration is already in place, you don’t need to restart your WLS domain. Otherwise, make the changes and restart your WLS domain.


Authorization


Portlets authorization is delegated to OPSS layer, i.e., the decision whether or not a portlet is available to a user is done against the OPSS policy store. Portlets are just one way of exposing local ADF task flows to remote applications. There’s a component called portlet brigde that is responsible for providing the glue between portlets and task flows. It basically makes possible exposing a task flow as a portlet.

In the example below, the policy store is file-based. Normally it is represented by system-jazn-data.xml located under $DOMAIN_HOME/config/fmwconfig folder. That can be easily changed to be and LDAP-based one.

Once the right task flow permissions are already in place in the policy store, there’s only one more permission that needs to be added, and it should be granted to the authenticated-role:

<permission> 
  <class>oracle.adf.controller.security.TaskFlowPermission</class>
   <name>/WEB-INF/adfp-portlet-bridge-container.xml#adfp-portlet-bridge-container</name>
   <actions>view</actions>
 </permission>


Here’s an example of a complete set of grants required for portlet authorization. In the example, the propagated user must be granted approle1 application role in order to view the portlet (assuming /WEB-INF/my-task-flow.xml#my-task-flow is taskflow exposed as a portlet).

<jazn-policy> 
  <grant>
     <grantee>
       <principals>
         <principal>
           <class>oracle.security.jps.internal.core.principals.JpsAuthenticatedRoleImpl</class>
           <name>authenticated-role</name>
         </principal>
       </principals>
     </grantee>
     <permissions>
       <permission>
         <class>oracle.adf.controller.security.TaskFlowPermission</class>
         <name>/WEB-INF/adfp-portlet-bridge-container.xml#adfp-portlet-bridge-container</name>
         <actions>view</actions>
       </permission>
     </permissions>
   </grant>
   <grant>
     <grantee>
       <principals>
         <principal>
           <class>oracle.security.jps.service.policystore.ApplicationRole</class>
           <name>approle1</name>
         </principal>
       </principals>
     </grantee>
     <permissions>
       <permission>
         <class>oracle.adf.controller.security.TaskFlowPermission</class>
         <name>/WEB-INF/my-task-flow.xml#my-task-flow</name>
         <actions>view</actions>
       </permission>
     </permissions>
   </grant>
 </jazn-policy>


Securing the Consumer


Securing a portlet consumer basically means enabling identity propagation. This is done during portlet producer registration in JDeveloper. When doing it, make sure you select/enter the right information in step 5 at WSRP Portlet Producer Registration wizard, as shown below:





Note: the configuration made here is written to connections.xml


Key store and Credential store configuration


Within the portlet consumer WLS domain, both key store and credential store can be the same used by the portlet producer. Have them at $DOMAIN_HOME/config/fmwconfig, as explained previously.

It is true we have a potential security issue here, since the certificates used by WSS are the same across WLS domains, and I’ll cover how to harden it in a next article.

10 comments:

  1. On the Portlet Producer side, do I need to first configure the 'Token Profile' as 'WSS 1.0 SAML Token with Message Protection' when I register the WSRP Portlet Producer?

    ReplyDelete
  2. Hi Bill, which JDev build are you using? I didn't have to do that. In fact, I did it manually by updating oracle-webservices.xml.

    ReplyDelete
  3. Build JDEVADF_11.1.1.3.PS2_GENERIC_100408.2356.5660

    I think I'm just confused on which application I'm invoking the WSRP Connection Wizard from. It appears that I should be invoking the portlet producer wizard from the 'Application Resources - Connections' (as opposed to the 'Resource Palette') associated with my WebCenter Application (the consumer) and not from my Fusion application (the producer). Is this correct?

    Also when I 'Create Portlet Entry' on my Task Flow in my Fusion application none of my application role information defined for the task flow is populated in the portlet.xml file. The only role that appears is the 'valid-users' role. Is this correct?

    ReplyDelete
  4. Bill, I misunderstood your first question. You're referring to "Securing the Consumer" section. That's done from the client's perspective. You're basically registering a portlet producer in your consumer app.
    You don't see the app roles in portlet.xml. Your application roles are defined in jazn-data.xml. valid-users is a JavaEE role, acting as a glue between JavaEE and OPSS, but that deserves a brand-new article. :-)

    ReplyDelete
  5. Andre, I finally got it working - actually I have it working using the integrated WebLogic Server and JDeveloper. I just deploy my source portlet application using the admin console rather then 'point and deploy'.

    There is just one issue that needs a little clarification in your article. When I create a Portlet Producer (from a Task Flow exposed as a portlet), the User Categories page in the wizard only displays role-names that are defined in the producer's portlet-xml file and not the roles defined in the jazn-data.xml file. So I add the relevant application roles to the portlet.xml file manually. Otherwise I don't get anything displayed in the User Categories page.

    ReplyDelete
  6. Andre, I'm having an 'access denied' problem after deploying my portlet producer app to my development WebCenter Spaces server. Both the WebCenter Spaces page and the portlet producer are running within the same domain. The message I'm getting in the logs is: oracle.fabric.common.PolicyEnforcementException: access denied (oracle.wsm.security.WSIdentityPermission resource=webcenter assert). Could this be caused by by both the cosumer and producer using the same keystore?

    ReplyDelete
  7. Not really Bill.

    This looks like a new requirement (or bug) in FMW PS2. Add the following code-source grant in your client application jazn-data.xml and redeploy it, or directly edit the WLS domain system-jazn-data.xml. Add it to the outermost <jazn-policy> element in that file, wehre code-source grants are kept.

    <grant>
    <grantee>
    <codesource>
    <url>file:${common.components.home}/modules/oracle.wsm.agent.common_11.1.1/wsm-agent-core.jar</url>
    </codesource>
    </grantee>
    <permissions>
    <permission>
    <class>oracle.wsm.security.WSIdentityPermission</class>
    <name>resource=webcenter</name>
    <actions>assert</actions>
    </permission>
    </permissions>
    </grant>

    ReplyDelete
  8. Andre, adding the codesource grant worked. Thanks for your help!

    ReplyDelete
  9. Andre, thanks to your post, I finally got WSRP security with authenticated users. :-)

    My problem resides now on anonymous users. I have configured the policy on the producer's oracle-webservice.xml, as well as "anonymous" as the default user on the consumer.

    I also followed instructions (http://download.oracle.com/docs/cd/E14571_01/webcenter.1111/e12405/wcadm_portlet_prod.htm#WCADM326) to add the "strict-authentication" flag and the proper grant to the policy store on the producer side.

    The portlet renders as "Portlet unavailable", and the log shows the following exception:

    Caused by: javax.xml.rpc.soap.SOAPFaultException: FailedAuthentication : Não foi possível autenticar o token de segurança.
    at oracle.j2ee.ws.client.StreamingSender._raiseFault(StreamingSender.java:668)
    at oracle.j2ee.ws.client.StreamingSender._sendImpl(StreamingSender.java:474)
    at oracle.j2ee.ws.client.StreamingSender._send(StreamingSender.java:147)
    at oracle.portlet.wsrp.v2.soap.runtime.WSRP_v2_Markup_Binding_SOAP_Stub.initCookie(WSRP_v2_Markup_Binding_SOAP_Stub.java:359)
    at oracle.portlet.wsrp.v2.WSRP_v2_Markup_PortTypeJaxbToSoap.initCookie(WSRP_v2_Markup_PortTypeJaxbToSoap.java:676)
    at oracle.portlet.wsrp.v2.ServerToWSRPv2.initCookie(ServerToWSRPv2.java:18294)
    at oracle.portlet.client.connection.wsrp.ActivityServerWrapper.initCookie(ActivityServerWrapper.java:2410)
    ... 23 more

    I've searched the Oracle docs and the web, and found absolutely nothing about this error. Am I missing something? Any help would be much appreciated!

    Tomy Inhauser
    (Jdeveloper 11.1.1.3.0 with Integrated WLS)

    ReplyDelete

Note: Only a member of this blog may post a comment.