Tuesday, June 29, 2010
Oracle Access Manager (OAM) and the SSO Synchronization Filter
Today I’d like to explore the SSO Sync Filter in a little more detail. The filter is implemented as a system filter for WebLogic 11g. It works in conjunction with the OAM Identity Asserter for WebLogic. The filter is only active when the OAM Identity Asserter is configured in a security realm.
It works by comparing the value of the OAM_REMOTE_USER header set by the OAM webgate to the value of the user principal name. If they are consistent then the filter lets the request through, but if they are inconsistent then the filter invalidates the WLS/JSESSION session and redirects the user back to the same URL which should either result in a user challenge or the establishment of a new WLS session with the same identity contained in the OAM session.
Note, that the SSO Sync Filter has no knowledge of OAM policies. Its view of what is or isn’t protected is based only on the headers it sees in the request. If no OAM_REMOTE_USER header is found, then the filter assumes that the request is for an unprotected resource and just passes it through. On the other hand, resources protected by the OAM anonymous authentication scheme will be considered protected by the filter since an OAM_REMOTE_USER header should always be present for such resources and set to the value of a real authenticated user or the configured anonymous user identity.
Given the functionality described above, it should be apparent that the filter is a great aid in helping to address session synchronization issues that can occur in an OAM/SSO enabled environment including issues around single logouts and session timeouts.
The documentation for this valuable Fusion Middleware component can be found here: http://download.oracle.com/docs/cd/E15523_01/core.1111/e10043/osso.htm#CHDHDBED
In my next post, I’ll discuss how to address the issue of JSESSIONID Cookie Overriding in SSO enabled environments.
Monday, June 21, 2010
Troubleshooting OES 10gR3 cp4 Installation of WLS SM
SCM Won't Start
In order for the SCM to run, it needs to have to separate sockets - one for "public" and one for "private" requests. You can see the SCM configuration in ales32-scm/apps/scm-asi/SAR-INF/config.xml. By default the installer creates one listener for the hostname on port 7013 and another on 127.0.0.1 on port 7013. If in your environment the hostname maps to 127.0.0.1, then these are the same socket, and when the SCM tries to start the second listener, you'll get a BIND Exception. The way to fix this is to create a second loopback adapter, with a fixed IP address, and change the 127.0.0.1 to that IP address. This will fix the SCM start-up problem.
WLS Server with WLS SM won't start - non JRF Domain
After running the config tool, and all of the policies have been created, the WLS server won't start with an error like "User weblogic is not authorized to boot the server". This means that, most likely, the policies have not been distributed to the WLS Server, so you need to force a policy distribution. The surefire way to do this is as follows:
- In the ales32-ssm/wls-ssm/instance/instancename/work/runtime remove the state.chk
- Remove all of the files in ales32-ssm/wls-ssm/instance/instancename/work/runtime/policyA
- Remove all of the files in ales32-ssm/wls-ssm/instance/instancename/work/runtime/policyB
- At this point in ales32-ssm/wls-ssm/instance/instancename/work/runtime all you should have two empty directories, policyA and policyB
- Log into the asi console, navigate to "Deployment", "Deployment Status" and click on the trash can next to the instance of SM. This won't hurt anything, just force the SM when it starts to re-register with the admin and get fresh policy.
If you did it right, when you restart the WLS domain, you'll get a warning about a "missing state.chk" which means that you're getting fresh policy, and the server should boot normally.
WLS Server with WLS SM won't start - JRF Domain
The most common example of a JRF domain is a SOA Suite Domain. A JRF domain is using OPSS and OPSS requires at least one LDAP authentication provider. The config tool for the WLS SM creates a WLS realm that only has the OES RDBMS authenticator. In this case, even if you get passed the previous issue, you'll hit an error like:
<Jun 21, 2010 4:35:06 PM EDT> <Error> <Security> <BEA-090892> <The dynamic loading of the OPSS java security policy provider class oracle.security.jps.internal.policystore.JavaPolicyProvider failed due to problem inside OPSS java security policy provider. Exception was thrown when loading or setting the JPSS policy provider. Enable the debug flag -Djava.security.debug=jpspolicy to get more information. Error message: oracle.security.jps.JpsException: [PolicyUtil] Exception while getting default policy Provider>
<Jun 21, 2010 4:35:06 PM EDT> <Critical> <WebLogicServer> <BEA-000386> <Server subsystem failed. Reason: weblogic.security.SecurityInitializationException: The dynamic loading of the OPSS java security policy provider class oracle.security.jps.internal.policystore.JavaPolicyProvider failed due to problem inside OPSS java security policy provider. Exception was thrown when loading or setting the JPSS policy provider. Enable the debug flag -Djava.security.debug=jpspolicy to get more information. Error message: oracle.security.jps.JpsException: [PolicyUtil] Exception while getting default policy Provider
weblogic.security.SecurityInitializationException: The dynamic loading of the OPSS java security policy provider class oracle.security.jps.internal.policystore.JavaPolicyProvider failed due to problem inside OPSS java security policy provider. Exception was thrown when loading or setting the JPSS policy provider. Enable the debug flag -Djava.security.debug=jpspolicy to get more information. Error message: oracle.security.jps.JpsException: [PolicyUtil] Exception while getting default policy Provider
at weblogic.security.service.CommonSecurityServiceManagerDelegateImpl.loadOPSSPolicy(CommonSecurityServiceManagerDelegateImpl.java:1394)
at weblogic.security.service.CommonSecurityServiceManagerDelegateImpl.initialize(CommonSecurityServiceManagerDelegateImpl.java:1018)
at weblogic.security.service.SecurityServiceManager.initialize(SecurityServiceManager.java:875)
at weblogic.security.SecurityService.start(SecurityService.java:141)
at weblogic.t3.srvr.SubsystemRequest.run(SubsystemRequest.java:64)
Truncated. see log file for complete stacktrace
Caused By: oracle.security.jps.JpsRuntimeException: oracle.security.jps.JpsException: [PolicyUtil] Exception while getting default policy Provider
at oracle.security.jps.internal.policystore.PolicyDelegationController.<init>(PolicyDelegationController.java:256)
at oracle.security.jps.internal.policystore.PolicyDelegationController.<init>(PolicyDelegationController.java:248)
at oracle.security.jps.internal.policystore.JavaPolicyProvider.<init>(JavaPolicyProvider.java:130)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
Truncated. see log file for complete stacktrace
Caused By: oracle.security.jps.JpsException: [PolicyUtil] Exception while getting default policy Provider
at oracle.security.jps.internal.policystore.PolicyUtil.getDefaultPolicyStore(PolicyUtil.java:675)
at oracle.security.jps.internal.policystore.PolicyDelegationController.<init>(PolicyDelegationController.java:254)
at oracle.security.jps.internal.policystore.PolicyDelegationController.<init>(PolicyDelegationController.java:248)
at oracle.security.jps.internal.policystore.JavaPolicyProvider.<init>(JavaPolicyProvider.java:130)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
Truncated. see log file for complete stacktrace
Caused By: java.security.PrivilegedActionException: oracle.security.jps.JpsException: [PolicyUtil] Unable to obtain default JPS Context!
at java.security.AccessController.doPrivileged(Native Method)
at oracle.security.jps.internal.policystore.PolicyUtil.getDefaultPolicyStore(PolicyUtil.java:622)
at oracle.security.jps.internal.policystore.PolicyDelegationController.<init>(PolicyDelegationController.java:254)
at oracle.security.jps.internal.policystore.PolicyDelegationController.<init>(PolicyDelegationController.java:248)
at oracle.security.jps.internal.policystore.JavaPolicyProvider.<init>(JavaPolicyProvider.java:130)
Truncated. see log file for complete stacktrace
Caused By: oracle.security.jps.JpsException: [PolicyUtil] Unable to obtain default JPS Context!
at oracle.security.jps.internal.policystore.PolicyUtil$1.run(PolicyUtil.java:637)
at oracle.security.jps.internal.policystore.PolicyUtil$1.run(PolicyUtil.java:622)
at java.security.AccessController.doPrivileged(Native Method)
at oracle.security.jps.internal.policystore.PolicyUtil.getDefaultPolicyStore(PolicyUtil.java:622)
at oracle.security.jps.internal.policystore.PolicyDelegationController.<init>(PolicyDelegationController.java:254)
Truncated. see log file for complete stacktrace
Caused By: oracle.security.jps.service.idstore.IdentityStoreException: Failed to create identity store service instance idstore.ldap.provider:idstore.ldap. Reason: No Default or LDAP Authenticator configured on WLS.
at oracle.security.jps.internal.idstore.ldap.LdapIdentityStoreProvider.getIdStoreConfig(LdapIdentityStoreProvider.java:227)
at oracle.security.jps.internal.idstore.ldap.LdapIdentityStoreProvider.getInstance(LdapIdentityStoreProvider.java:108)
at oracle.security.jps.internal.idstore.ldap.LdapIdentityStoreProvider.getInstance(LdapIdentityStoreProvider.java:59)
at oracle.security.jps.internal.core.runtime.ContextFactoryImpl.findServiceInstance(ContextFactoryImpl.java:139)
at oracle.security.jps.internal.core.runtime.ContextFactoryImpl.getContext(ContextFactoryImpl.java:170)
Truncated. see log file for complete stacktrace
Caused By: oracle.security.jps.JpsRuntimeException: No Default or LDAP Authenticator configured on WLS
So, you need an LDAP authentication provider. The quick and dirty way to fix this is by editing the config.xml. You can simply cut and paste the DefaultAuthenticator from the other realm (myrealm) in the file and add it before the OES RDBMS authenticator
<sec:authentication-provider xsi:type="wls:default-authenticatorType">
<wls:use-retrieved-user-name-as-principal>true</wls:use-retrieved-user-name-as-principal>
</sec:authentication-provider>
<sec:authentication-provider xmlns:ext="http://www.bea.com/ns/weblogic/90/security/extension" xsi:type="ext:database-authenticatorType">
<n1:name xmlns:n1="http://www.bea.com/ns/weblogic/90/security">DatabaseAuthenticator</n1:name>
<ext:jdbc-driver-class-name>oracle.jdbc.driver.OracleDriver</ext:jdbc-driver-class-name>
<ext:jdbc-connection-url>jdbc:oracle:thin:@localhost:1521:xe</ext:jdbc-connection-url>
<ext:database-user-login>oes10gR3cp4</ext:database-user-login>
<ext:identity-scope>RootOrg!defaultOrg!defaultUsers</ext:identity-scope>
<ext:database-user-password-encrypted>{AES}uUvyzqwh98bA/POG9/jhP6ITfnptEYE0RbkaZMofsC4=</ext:database-user-password-encrypted>
</sec:authentication-provider>
This should get the server started. Now, what I like to do is to have the Default Authenticator 1st, and have it set to sufficient. Keep the RDBMS authenticator, and also have it as sufficient. By doing this, if you want to use tools like Eclipse to deploy applications to a WLS domain protected by OES, you won't run into the issue of Eclipse de-serializing the OES principals. Trust me, this is the simplest set-up.
Summary
First all, Chris helped me with some of these pointers, but he's in Europe helping a customer, so I didn't want to wait. Full credit to Chris for wading through many of these issues first. I hope that the pointers help you get OES 10gR3 CP4 WLS SM running smoothly. If you have some pointers of your own, please post them here.
Friday, June 18, 2010
Securing WebLogic WebServices with Oracle Entitlements Server and Oracle Web Services Manager
Overview
This solution uses OES 10gR3CP4 protecting a JAX-WS webservice running inside of WLS 10.3.2 (11gR1SP). The domain is a JRF domain, which means that I can use OWSM to protect the web-service. I installed and configured the WLS SM and used the config tool to create a scoped application - scoped in this case means that I'm going to use the entitlementsadministration GUI - apps and orgs. I created in Eclipse the worlds simplest Web Service and deployed it. The webservice is nothing more than a "helloWorld". Once deployed, I could use the WLS admin console to define the policies for web-service.
Notice that I applied two policies...and authentication policy using oracle/wss10_saml_token_service_policy, and then the custom policy owsmac/owsmac_oes_AuthorizationPolicy. NOTE: The oracle/wss10_saml_token_service_policy has no message protection and has to be used in a production environment in conjunction with network or transport security. You'll see why when you look at the sample messages.
Why use OES Custom Assertion for OWSM?
Since the endpoint that we're protecing in a JAX-WS webservice, it is protected by the WLS SM by default. As we discussed previously, the container first checks the access controls policy for the URL and then the WebService. With OWSM in the picture, the sequence is:
- URL Authorization Check - the resource is type=>url<, application=sample_service, contextPath=/sample_service, uri=/Service1Service, httpMethod=GET. You can do authorization only on the URL of the endpoint. Nothing in the body
- Call OWSM - calls OES Custom Assertion - we'll discuss more in a second what you can do
- WebServices Authorization Check - the resource is type=>webservices<, application=sample_service, contextPath=/sample_service, webService=Service1Port, method=hello, signature={java.lang.String}. Notice you have a lot more information like the Port and the operation as well as the signature of the method. Also, if you use attribute retriever in OES, you should be able to access a lot of the relevent appContext elements. The list is pretty expansive.
This means that without doing any custom work, you get two cracks at the request, and with OES in place and the WLS SM, there is quite a bit you can do. So why use the OES OWSM custom assertion? These are the additional use cases I've come up with:
- You don't want to use OES to secure the WLS resources - when I did this solution last year at OOW, I installed the WLS SM, but also had the DefaultAuthorizer and used the OES Adjudicator to pull the two together. In this case, OES won't get called for URL and Webservices resource types
- Policy Based access to SOAP:Body and SOAP:Env - If you use OOTB WLS SM to secure web-services, then you have to code a custom attribute retriever to operate on the SOAP Message. This means that if there are changes, then you'll need to re-code. The approach of having the XPath defined in the OES policy allows for changes to be made w/o coding
- You want to update the message - In the updated version of the OES Custom Assertion, you can write policy that updates the values. What is the use case for this - outbound datamasking at the perimeter. For example, you can't control what the web service is going to return, but you want to make sure that the contents don't expose any PII. You can write a policy that will mask it.
- You want to do authorization on the response - This is related to the previous point, but it allows you to do one more authorization check based upon the information in the response. This makes sense if the authorization cannot be determined until after invoking the operation. These really only makes sense in read operations, since the authorization failure is happending "presumably" after the transaction has been committed.
If you have any of the use cases above, read on and I'll explain a little more about the policies in OES.
Modelling the Policies for the OES Custom Assertion
The OES custom assertion works with 4 actions - request_lookup, execute_request, response_lookup, and execute_response. They are performed in that order. All of the magic happens in the responses (i.e. report_as function in the constraints). The values of the report_as in the lookup phases are assumed to be XPath queries that should be performed. The resulting values are then sent down in the execute phase. If the user is granted access, any report_as values returned that reference the attributes passed in are assumed to be updates which the OWSM assertion then applies.
For example, the constraint report_as("hello_out","*****") in the execute_response method will replace the value of the hello_out attribute (defined as the XPath in the response_lookup privilege) with ****.
I used the Vordel SOAP Box to test, including the creation of the unsigned SAML assertion required by the oracle/wss10_saml_token_service_policy. Basically, the policies allow any requests except if the hello in message is abc or if the response is Hello abcd - which means that the input of abcd will cause authorization to fail. Otherwise, the response is masked with ****. I also DENY any requests is the authentication method in the SAML assertion is urn:oasis:names:tc:SAML:1.0:am:unspecified. The XPath can be used on either the header of the body of the SOAP message.
I've included below the export from PolicyIX. It shows all of the authorization policies in detail.
Reference: Policy Export For Sample Application
<?xml version="1.0" encoding="UTF-8"?>
<xb:policy_propagation xmlns:xb="http://policypropagation.ales.com/xmlbean">
<xb:policy_propagation_data_v2>
<xb:scopes>
<xb:application_entry value="RootOrg!defaultOrg!sample_service" boundSSM="oes10gR3cp4wlsssm">
<xb:admin_roles>
<xb:admin_role_entry value="AppAdmin" isPrimary="true" description="Primary Application Admin Role of current Application">
<xb:admin_role_privileges>
<xb:admin_role_privilege_entry object="action" action="adminmanage"/>
<xb:admin_role_privilege_entry object="authorizationPolicy" action="adminmanage"/>
<xb:admin_role_privilege_entry object="authorizationPolicyReport" action="adminmanage"/>
<xb:admin_role_privilege_entry object="directory" action="adminview"/>
<xb:admin_role_privilege_entry object="extension" action="adminmanage"/>
<xb:admin_role_privilege_entry object="group" action="adminview"/>
<xb:admin_role_privilege_entry object="policyDistribution" action="adminmanage"/>
<xb:admin_role_privilege_entry object="policySimulator" action="adminmanage"/>
<xb:admin_role_privilege_entry object="resource" action="adminmanage"/>
<xb:admin_role_privilege_entry object="role" action="adminmanage"/>
<xb:admin_role_privilege_entry object="rolePolicy" action="adminmanage"/>
<xb:admin_role_privilege_entry object="rolePolicyReport" action="adminmanage"/>
<xb:admin_role_privilege_entry object="user" action="adminview"/>
</xb:admin_role_privileges>
</xb:admin_role_entry>
</xb:admin_roles>
<xb:resources>
<xb:resource_entry value="//resources/SOAPMessage" isVirtualResoureAllowed="true"/>
<xb:resource_entry value="//resources/SOAPMessage/Service1Service" isVirtualResoureAllowed="true"/>
<xb:resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port" isVirtualResoureAllowed="true"/>
<xb:resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello" isVirtualResoureAllowed="true"/>
<xb:resource_entry value="//resources/url" isVirtualResoureAllowed="true"/>
<xb:resource_entry value="//resources/webservices" isVirtualResoureAllowed="true"/>
</xb:resources>
<xb:actions>
<xb:action_entry value="any"/>
<xb:action_entry value="execute_request"/>
<xb:action_entry value="execute_response"/>
<xb:action_entry value="request_lookup"/>
<xb:action_entry value="response_lookup"/>
</xb:actions>
<xb:dynamic_attributes>
<xb:dynamic_attribute_entry name="auth_method" type="string"/>
<xb:dynamic_attribute_entry name="hello_in" type="string"/>
<xb:dynamic_attribute_entry name="hello_out" type="string"/>
</xb:dynamic_attributes>
<xb:roles>
<xb:role_entry value="Anonymous" parent=""/>
</xb:roles>
<xb:policies>
<xb:membership_rule_entry>
<xb:policy_effect value="grant"/>
<xb:policy_roles>
<xb:policy_role_entry value="Anonymous"/>
</xb:policy_roles>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/url"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
</xb:membership_rule_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="deny"/>
<xb:policy_actions>
<xb:policy_action_entry value="execute_response"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
<xb:policy_constraint value="sys_defined ( hello_out ) and hello_out = "Hello abcd""/>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="deny"/>
<xb:policy_actions>
<xb:policy_action_entry value="execute_request"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
<xb:policy_constraint value="sys_defined ( hello_in ) and hello_in = "abc""/>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="deny"/>
<xb:policy_actions>
<xb:policy_action_entry value="execute_request"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
<xb:policy_constraint value="sys_defined ( auth_method ) and auth_method = "urn:oasis:names:tc:SAML:1.0:am:unspecified""/>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="grant"/>
<xb:policy_actions>
<xb:policy_action_entry value="response_lookup"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
<xb:policy_constraint value="report_as ( "hello_out" , "body:.//tns:helloResponse/return" )"/>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="grant"/>
<xb:policy_actions>
<xb:policy_action_entry value="any"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/url"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="grant"/>
<xb:policy_actions>
<xb:policy_action_entry value="request_lookup"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
<xb:policy_constraint value="report_as ( "hello_in" , "body:.//tns:hello/arg0" )"/>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="grant"/>
<xb:policy_actions>
<xb:policy_action_entry value="request_lookup"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
<xb:policy_constraint value="report_as ( "auth_method" , "header:.//saml:AuthenticationStatement/@AuthenticationMethod" )"/>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="grant"/>
<xb:policy_actions>
<xb:policy_action_entry value="execute_request"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage/Service1Service/Service1Port/hello"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
<xb:policy_constraint value="sys_defined ( hello_in ) and report_as ( "hello_in" , "*****" )"/>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="grant"/>
<xb:policy_actions>
<xb:policy_action_entry value="execute_response"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/SOAPMessage"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
</xb:authorization_policy_entry>
<xb:authorization_policy_entry>
<xb:policy_effect value="grant"/>
<xb:policy_actions>
<xb:policy_action_entry value="any"/>
</xb:policy_actions>
<xb:policy_resources>
<xb:policy_resource_entry value="//resources/webservices"/>
</xb:policy_resources>
<xb:policy_subjects>
<xb:policy_group_entry name="allusers" directory="defaultUsers" scope="RootOrg!defaultOrg"/>
</xb:policy_subjects>
</xb:authorization_policy_entry>
</xb:policies>
</xb:application_entry>
</xb:scopes>
<xb:security_configuration_data>
<xb:scms>
<xb:scm_entry name="adminconfig">
<xb:ssms>
<xb:ssm_entry name="asiadmin"/>
<xb:ssm_entry name="oes10gR3cp4wlsssm"/>
</xb:ssms>
</xb:scm_entry>
</xb:scms>
</xb:security_configuration_data>
</xb:policy_propagation_data_v2>
</xb:policy_propagation>
Wednesday, June 16, 2010
Do I Need to Secure My Service?
Let’s take the simplest case possible from a security vantage point: a synchronous web service being called from a limited number of trusted internal clients (let’s say web applications). Because the web service is synchronous we don’t have to worry about the request sitting in a queue unprotected. Likewise, because the web service is being called from a limited number of internal clients we might be inclined to care less, if at all, about ensuring that users/clients are authorized to call the service. Finally, we will assume that there is no requirement to authenticate a “user” of the client invoking the service.
At the same time we will assume that the service is a “high value” service such that exposing it to the “outside” without security would be a mistake.
So, does such a service need security?
I think to answer this question we have to look at all the possible security concerns for such a service and how they can be addressed with or without explicit security. Usually, when a customer implies that their service doesn’t mean security what they are really saying is that the boundaries of their physical network providing all the security they need. So, let’s look and see to what extent that may be true.
The security concerns for an internal, synchronous service are as follows:
1) Client trust: ensuring that only authorized users/clients are invoking the service.
There are two types of potentially unauthorized clients to worry about, internal clients and external clients.
To protect against external client access physical network boundaries may be sufficient.
However, in my experience an increasing number of customers are not comfortable relying on their physical network.
Without additional security, the only protection against unauthorized internal access to your services is the trust and good will of the users that have access internally.
Two-way SSL is a straight forward, easy to deploy security solution that you can add to your web service that provides strong protection against all types of unauthorized client access. Note though, that one-way SSL does nothing to help with this case.
2) Service trust: ensuring that the service being invoked by consumers of the service is authentic; that it is not a Trojan horse service.
The physical network does provide some level of protection here in that a malicious Trojan service would have to be accompanied by IP address spoofing or DNS hacking to do damage. The greater danger might be the standing up and advertising of an unauthorized service for a SOA phishing attack.
Here SSL (technically just one-way) can help ensure that only authorized services are being called by clients.
3) Message integrity: ensuring the message has not been tampered with.
The physical network can limit the potential for capture, alter, and replay attacks to internal users but SSL can be added to eliminate all risk associated with capturing messages over the wire.
4) Message confidentiality: ensuring that the information in the message cannot be intercepted and read.
The physical network can limit the potential for message capture to internal users but SSL can be used to eliminate all risk of capture over the wire.
5) Tracking and reporting: ensuring that the ability exists to track specific requests/transactions to a specific user/client.
Without additional steps the only tracking mechanism provided by the physical network is the IP address of the client which may or may not be of use.
When you add 2-way SSL with each client getting a unique client certificate, you now have definitive record of which client made which request.
6) Identity propagation
If there is a requirement to propagate the identity of the user of a web application onto the web service then there is potentially additional security required.
Given the assumptions made at the beginning that the consumers of our service are trusted internal clients, we will assume that we can trust them to propagate whatever identity they want. Still, the propagation has to be done in some fashion.
Here we have 3 options:
1) The identity could be added through a custom HTTP header. This is easy on the client and secure if we trust the client and are using 2-way SSL at the transport (the header can be encrypted with symmetric cryptography for added security). On the downside it is very non-standard and will most likely require some custom work on the service side to accept an identity from a custom header.
2) The use of a real WSS Security header such as a SAML assertion. Using SAML with the bearer confirmation method (http://fusionsecurity.blogspot.com/2009/09/bearer-confirmation-method-huh-what-is.html) provides a more standard way of propagating a user identity. With bearer, no messy signing or encryption is required and given our assumptions the security of bearer is probably sufficient if 2-way SSL is being used. The only gotcha is you have to have client and server side stacks that can both “speak” SAML bearer.
3) Many customers ask/talk about just putting the user identity in the body of the message. In many ways this is the easiest thing to do. I also think it makes sense if the business logic of the service will actually make use of the user identity information; as would be the case for a “process insurance selection” or “purchase ticket” service. The only thing to understand is that putting user identity information in the message body means including it in the schema and explicitly coding how to retrieve it in the service. If you rely on WSS security for user propagation then the user identity can be left out of the schema and the service can be coded just to get the identity from the container.
When identity propagation comes up in these conversations, the discussion usually centers on using SAML vs. just putting the user info in the message body. The key deciding factors here are whether the user info will be used by the business logic of the service, whether you want to include the user info in the schema or not, and whether you are comfortable with the code of the service itself retrieving the user identity from the message or whether you’d rather rely on getting the user identity from the container.
Summary
On a very restrictive physical network where trust in the users that have access to the network is very high, it may be OK to deploy a high value web service without additional security. However, most of the time it is a good idea to utilize 2-way SSL to provide strong transport level security.
If identity propagation is required a judgment call needs to be made on how to propagate the identity and whether or not to do so through transport security, WSS message level security, or just in the body of the message.
Monday, June 14, 2010
Oracle Service Bus, SOA Suite Human Workflow Web Services and SAML
A customer recently was looking for some guidance on how to apply SAML and OSB to SOA Suite Human Workflow Web Services. I'd never looked at those particular web-services, but applying SAML to OSB and SOA Suite in 11g is pretty straightforward - OWSM...right? Well...this really depends on the requirements and which particular services are being used. In this post, I'll explain a little bit about the identity propagation options for the Human Workflow Web Services, and layout two different approaches to deploying OSB and SAML to protect them.
Identity Propagation Options for Human Workflow Web Services
The customer that I'm working with was particularly interested in the TaskService and TaskQueryServices. After looking at the documentation, I started by pulling down the WSDL. Most of the WSDL and dependent schemas are XML representations of the Workflow Services, but there is one part that caught my attention:
<service name="TaskQueryService">
<port name="TaskQueryServicePortSAML" binding="tns:TaskQueryServiceSOAPBinding">
<soap:address location="http://192.168.56.102:18001/integration/services/TaskQueryService/TaskQueryService2/*"/>
</port>
<port name="TaskQueryServicePort" binding="tns:TaskQueryServiceSOAPBinding">
<soap:address location="http://192.168.56.102:18001/integration/services/TaskQueryService/TaskQueryService"/>
</port>
</service>
The service has two ports - TaskQueryServicePort and TaskQueryServicePortSAML. They both have the same binding, so they support the same messages, just two different ports. Digging a little deeper, I took a look at the WSDL on the SAML Port by going to http://192.168.56.102:18001/integration/services/TaskQueryService/TaskQueryService2?ORAWSDL and look what I found....OWSM policy!
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:oralgp="http://schemas.oracle.com/ws/2006/01/loggingpolicy" xmlns="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="wss10_saml_token_service_policy" xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy" orawsp:displayName="Wss10 SAML Token Service Policy" xmlns:orasp="http://schemas.oracle.com/ws/2006/01/securitypolicy" orawsp:description="This policy authenticates users using credentials provided in SAML tokens in the WS-Security SOAP header. The credentials in the SAML token are authenticated against a SAML login module. This policy can be applied to any SOAP-based endpoint. " orawsp:attachTo="binding.server" Name="oracle/wss10_saml_token_service_policy" orawsp:category="security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" orawsp:local-optimization="check-identity">
<orasp:wss10-saml-token orawsp:Silent="false" orawsp:name="WSSecurity SAML Token" orawsp:Enforced="true" orawsp:category="security/authentication">
<orasp:saml-token orasp:is-encrypted="false" orasp:is-signed="false" orasp:confirmation-type="sender-vouches" orasp:version="1.1"/>
</orasp:wss10-saml-token>
</wsp:Policy>
People know, that I'm not really a WS-Policy purist of really even much of a fan, so I really like the ?ORAWSDL "trick" to help figure our which OWSM policy is being applied. From looking at the ORAWSDL, we've learned that the SAML end-point is protected by the oracle/wss10_saml_token_service_policy. This is a simple WS-Security SAML 1.0 Sender Vouches policy. Putting OSB 11gR1 in front of this service should be a pretty simple application of the OWSM/OSB integration. This is the one of the two approaches I'll explain in a little bit.
If there are two ports, and the 2nd port supports SAML for identity propagation, what does the 1st port use? The answer is a proprietary token. Before everyone goes BOOOOOOO, if you look at the services that are exposed through this end-point, they are synchronous, single-party, RPC style services. If all the requests are made over SSL, then there is no real advantage or requirement for message level security. I suspect, that this proprietary token was at some point an HTTP Cookie, and was probably just passed around in the HTTP transport header...also a well established (read: WebSSO) practice. I was at a different customer a few weeks back and we came to a very similar recommendation to use a transport token instead of message level security and SAML. Every environment is different, but this other way of doing identity propagation is reasonable for this situation. The token itself has been SOAP-a-sized (made SOAP friendly?) by being passed in a com:workflowContext element in all of the messages. Here's a sample:
This is an example of the authenticate call that returns the token for a user authenticated by their username and password. The really interesting part is what's commented out in the com:credential element - onBehalfOfUser. That's right - the proprietary token has impersonation built it. This actually goes beyond what is available in the SAML based identity propagation. The second solution will show how to use SAML with the proprietary token including the onBehalfOf functionality.
Solution 1 - OSB proxy calling Human Workflow Web Service as Business Service using SAML
Once you figure out what's going on with the Human Workflow Web Services, fronting it with OSB 11g and OWSM is pretty straight forward. The basic idea is that you're going to create a business service from the SAML Port, and then configure OSB to use OWSM when calling the business service. One of the nice things about OWSM is that picking the corresponding client policy for a service policy is pretty straight forward.
The only thing you really need to consider is are you going to set-up OSB as an active intermediary or as a pass-through. In either case, you create a proxy-service based on the business service, and configure the OWSM policy oracle/wss10_saml_token_service_policy. This policy uses a SAML Sender-Vouches, but there is no message protection. This means that the message itself is not signed. Notice, looking back at the policy, the assertion itself isn't signed or encrypted either, so we'll need to do something at the transport/network level to ensure that the request is authentic. If there is a trusted network between OSB and SOA suite, then you may not need anything else. You can use WebLogic's Connection Filters as an additional software firewall. If this is not sufficient, then you can set-up 2-way SSL between OSB and SOA Suite to ensure that the request is valid. OSB still uses Service Key Providers to determine the Certificate to use for making 2-way SSL calls, even if OWSM message level policies are applied. Why not just change the policy protecting the services to require that the message or assertion is signed? These end-points are not proper JAX-WS Web Services running in WebLogic Server...there are just servlets, so there is no simply way to change the policies. I think the best approach here is to use 2-way SSL.
Solution 2 - OSB Proxy calling Human Workflow Web Service using Proprietary Token
But Josh, I thought this was doing to be about using SAML? It is. OSB can still expose an endpoint protected by a SAML policy, but the call to Human Workflow Web Service is going to be done using the existing token identity propagation mechanism. The trick is that the proxy pipeline is going to make a Java callout to get the token and add it into the message. The key to the whole solution is the getWorkflowContextForAuthenticatedUser method. This call will return the token for the user. But what if OSB and SOA Suite are running on two separate domains? So this is a remote call, but the OSB and SOA suite domains have to be trusted. The simplest is to just set-up Global Trust. This means that the two domains will trust eachother's signed Principals. There are a few more subtle points that can best be explained by showing the Java callout code and the pipeline.
First, let's look at the pipeline. Conceptually, we have to make the Java callout, get the token, and then add the token to the request in the com:token element of the message. It's pretty simple, and there are far betten XPath people than I, but this is how I did it.
- Assign $body//com:onBehalfOfUser/text() to a variable - we'll talk more about this in a second.
- Make the Java Callout. The most important point is that the call requires a Service Account of type passthrough. This makes sure that the caller's identity is passed down so we know who the user that we're requesting a token for. We're passing in the onBehalfOfUser from above as a parameter. This method returns the token as a String.
- Assign $body/child::node() to the variable operationName. This is the child node of the body...which is the operation request. The com:workflowContext needs to be added to the first child of this element.
- Insert the following as the first child of operationNode
<com:workflowContext>
<com:credential>
<com:identityContext>jazn.com</com:identityContext>
</com:credential>
<com:token/>
<com:locale>en_US</com:locale>
<com:timeZone>US/Eastern</com:timeZone>
</com:workflowContext> - Replace ./com:workflowContext/com:token in operationNode with $token - the value returned from the Java callout
- Replace child::node() in $body with opertaionNode
This is the complete pipeline:
Now the Java callout. The code itself is actually pretty simple, but there was some tricky stuff with classloading. I started off trying to load the Client Jar Files into OSB directly, but was getting some very wierd ClassNotFoundExceptions with weblogic.jndi.WLInitialContextFactory. I decided after some fiddling that I was just going to load the bpm-services.jar
from the system classpath. This means modifying the setDomainEnv.sh to add the jar at start-up. This works really well, except that since the jar contains EJBs, they workflow services don't get deployed on the server. This is fine since for this customer OSB and SOA Suite are two separate domains. I'm sure there is a different way to fix the classloading but I was under a deadline, so this is the best for now.
Now that we have the set-up out of the way, this is the code for the callout
public static String getToken(final String runAsUser) throws Exception {
System.out.println("RunAsUser="+runAsUser);
Subject theUser = weblogic.security.Security.getCurrentSubject();
System.out.println("The User IS="+theUser);
if (theUser==null || theUser.toString().trim().equals("Subject:")) {
System.out.println("No user....");
return "";
} else {
System.out.println("'"+theUser.toString().trim()+"'");
}
System.out.println("Calling to get token for "+theUser+" from t3://localhost:8001");
Object token = weblogic.security.Security.runAs(theUser, new PrivilegedAction() {
@Override
public String run() {
try {
Class clientFactoryClass = ClassLoader.getSystemClassLoader().loadClass("oracle.bpel.services.workflow.client.WorkflowServiceClientFactory");
Method factoryMethod = clientFactoryClass.getMethod("getWorkflowServiceClient", new Class [] {String.class, oracle.bpel.services.workflow.client.config.WorkflowServicesClientConfigurationType.class, Logger.class});
WorkflowServicesClientConfigurationType wscct =
new WorkflowServicesClientConfigurationType();
Listservers = wscct.getServer();
ServerType server = new ServerType();
server.setDefault(true);
server.setName("default");
servers.add(server);
RemoteClientType rct = new RemoteClientType();
rct.setServerURL("t3://localhost:18001");
rct.setInitialContextFactory("weblogic.jndi.WLInitialContextFactory");
rct.setParticipateInClientTransaction(false);
server.setRemoteClient(rct);
IWorkflowServiceClient client =
(IWorkflowServiceClient)factoryMethod.invoke(clientFactoryClass, new Object[] { WorkflowServiceClientFactory.REMOTE_CLIENT, wscct, null});
ITaskQueryService tqs = client.getTaskQueryService();
IWorkflowContext wfContext = tqs.getWorkflowContextForAuthenticatedUser();
if (runAsUser==null) {
return wfContext.getToken();
} else {
IWorkflowContext onBehalfOfCtx = tqs.authenticateOnBehalfOf(wfContext, runAsUser);
return onBehalfOfCtx.getToken();
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
});
System.out.println("The token is "+token);
return (String)token;
}
The code itself is really pretty simple. You do need to pull the Subject using the always handy weblogic.security.Security.getCurrentSubject() call, and then invoke the PriviledgedAction. This is required so that when the client running inside of OSB makes the call to SOA Suite, the user's identity is passed, and a token for that user is returned. Again, you have to make sure that you've configured the Java callout with the passthrough service account. Finally, we check if a runAsUser (onBehaldOfUser) is passed, and if it is then instead of returning the token based upon the user's identity in SAML, you get a token onBehalf of the passed in user. In my testing, using the OOTB SAML capabilities, the information in the com:workflowContext was not processed, including the onBehalfOfUser information, so without a username and password, I think using the token based approach is the only way to leverage this functionality with SAML.
Summary
In laying out the two approaches, the simpler OOTB approach will work if you don't need the onBehalfOfUser functionality and you can secure the link between OSB and SOA Suite either at the network or transport level using 2-way SSL. The more complicated approach has the restriction of SOA Suite and OSB being in separate domains, but does have the advantage of supporting onBehalfOf. I would also make sure that for this approacj, there is transport or network security between the OSB and SOA suite since the token (though encrypted) could be replayed. There could be performace issues with the constant creation of the com:workflowContext objects, so its not hard to imagine that the Java callout is extended to use Coherence for caching of Subjects to tokens.
All and all a very good first "real test" for my new "huggable server" - multiple SOA and OSB domains + IDE + Browser without breaking a sweat. I can't but help feel that this solution can be improved, or somehow simplified. Is all of the complexity of the custom approach warranted? What do you think?
References
Edward Biedmond's Blog
Tuesday, June 8, 2010
Installing Oracle IdM 11g on Enterprise Linux
- OEL 5 (available from linux.oracle.com)
- Oracle database 11g R2 (from this page on www.oracle.com)
- WebLogic Server (from this page on www.oracle.com)
- The IdM stack (Identity Management from this page on www.oracle.com)
Thursday, June 3, 2010
Impersonation and OES
So, for starters, let's define the use case. A user is logged in and wants to act as another user for some brief period of time - like for a few minutes to troubleshoot an issue. Once the impersonation is established all of the authorization should be calculated against the impersonatee (person being impersonated). In the audit logs, you should see both the impersonator and impersonatee. Optionally, there may be rules that are additionally calculated for the impersonator - example: DENY policies that are enforced even if the user is impersonating, like PII.
Just to clarify, this use case is different than delegation - which is similar except that delegation (in my vocabulary) has a longer duration than impersonation. The classic delegation use case is vacation - example: during the week I'm away, let Bob perform this privilege that I have. OES supports this type of model OOTB.
Impersonation is really more about tokens than it is about authorization, but there are some services that OES exposes that can be pretty helpful in this regard. The CredentialMappingService can be used to generate a token for a given user. You need a custom credential mapper that does two things:
1. Check if the current Subject is authroized to impersonate the other user. Looking at the signature of the getCredentials method, you can see that the actual CredentialMapper is going to get passed the Subject, the alias of the user to create, and then the RuntimeAction and RuntimeResource. This makes it pretty easy from inside of the CM to make a call to the AuthorizationService to check and see if the impersonation is allowed. I'd probably use the action "impersonate" and add the passed in Action to the AppContext.
2. Generate a token that the user can then use to impersonate. This is a little trick but basically you need to create an encrypted token that contains the name of the user - or a reference to a session - that has the name of the user. You also want to - either in the token or by reference - has access to the name of the person that is the impersonator - the Subject passed the CredentialMapperService.
So, now that you have the token, the next step is when the token is presented, to establish a JAAS Subject for the user being impersonated. This is a custom identity asserter. This identity asserter needs to be able to decrypt the token and set up the user name and the impersonator as callbacks. Since impersonator is not a standard callback, you need to pair the identity asserter with a custom login module that will make the callbacks. The user is added as a regular WLSUser. As for the impersonator, I would add them as a custom principal that extends WLSUser...like ImpersonatorUser.
With both of these added to the JAAS Subject, OES can perform authorization, and will use the regular user. If you want to have access to the impersonator, you'll need a custom attribute retriever - getImpersonator that basically pulls the ImpersonatorPrincipal from the JAAS Subject, and returns the name. Since the JAAS subect contains the impersonator name as well as the user, Audit logs will have access to both.
When the user is done impersonating, just have them logout, and log back in as themselves. There are more advanced cases when you can actually stop the impersonation. In that case, you have to extend the solution in a few ways. The first is that you need to persist the original JAAS Subject - either in the token or by reference - so that it can be restored. The second - is to have the identity asserter be able to establish the JAAS Subject of that impersonator. The third is to be able to generate either a token for the impersonator or the impersonatee based on the token type passed in the CredentialMapperService.
This is the mechanics of using the OES services, but these are just API calls. How would you get these APIs called? I think it depends on the context that you're attempting to impersonate. If this is a web-application, then this looks like a good fit for a ServletAuthenticationFilter. The tokens can be passed as HTTP cookies. For a services environment, this looks like a good use case for a custom STS and WS-Trust - wst:onBehalfOf - seems to fit very nicely here.
I left out a lot of the nitty gritty details (read: no sample), but I think this is enough to get people started. You'll have to apologize - my flight out of Atlanta (via Philadelphia) to Boston - is getting ready to board. Let me know if you like this approach and are interested in more details.
P.S.
BEAT LA
BEAT LA
BEAT LA
Take that Wayne!
Wednesday, June 2, 2010
Oracle Access Manager and Kerberos (/Integrated Windows Authentication) with fallback to HTML forms
- a Windows domain
- a server in the domain with an IIS web server and a Web Gate installed/configured
- a workstation in the domain (call this one workstation1)
- workstation NOT in the domain (call this one workstation2)
- using IIS' 401 error page
- using XmlHttpRequest and XDomainRequest
- using Flash
URL url = new URL("http://testmachine.testdomain.com/IWAProtected/");Make /IWAProtected/ protected by IIS' Integrated Windows Authentication, put that code into an Applet and add the Applet to an HTML page elsewhere on your site and you'll be able to tell whether the user is logged into the domain or not.
URLConnection urlConn = url.openConnection();
urlConn.setAllowUserInteraction(false);
urlConn.setUseCaches(false);
DataInputStream dis = new DataInputStream(urlConn.getInputStream());
data = new String();
String s;
while ((s = dis.readLine()) != null) {
System.out.println("Read data" + s);
data += s + "\n";
}
dis.close();