Tag: knowhow

Cross-Domain Single Sign-On

A couple of blog posts ago I’ve been detailing how regular cookie based Single Sign-On (SSO) works. I believe now it is time to have a look at this again, but make it a bit more difficult: let’s see what happens if you want to achieve SSO across different domains.

So let’s say that you have an OpenAM instance on the login.example.com domain and the goal is to achieve SSO with a site on the cool.application.com domain. As you can see right away, the domains are different, which means that regular SSO won’t be enough here (if you don’t see why, have a read of my SSO post).

Cross-Domain Single Sign-On

We already know now that in the regular SSO flow the point is that the application is in the same domain as OpenAM, hence it has access to the session cookie, which in turn allows the application to identify the user. Since in the cross-domain scenario the session cookie isn’t accessible, our goal is going to be to somehow get the session cookie from the OpenAM domain to the application’s domain, more precisely:
We need to have a mechanism that is implemented by both OpenAM and the application, which allows us to transfer the session cookie from one place to the other.
Whilst this sounds great, it would be quite cumbersome for all the different applications to implement this cookie sharing mechanism, so what else can we do?

Policy Agents to the rescue

For CDSSO OpenAM has its own proprietary implementation to transfer the session ID across domains. It’s proprietary, as it does not follow any SSO standard (for example SAML), but it does attempt to mimic them to some degree.
OpenAM has the concept of Policy Agents (PA) which are (relatively) easily installable modules for web servers (or Java EE containers) that can add extra security to your applications. It does so by ensuring that the end-users are always properly authenticated and authorized to view your protected resources.
As the Policy Agents are OpenAM components, they implement this proprietary CDSSO mechanism in order to simplify SSO integrations.

Without further ado, let’s have a look at the CDSSO sequence diagram:

cdsso-web-sequence

A bit more detail about each step:

  1. The user attempts to access a resource that is protected by the Policy Agent.
  2. The PA is unable to find the user’s session cookie, so it redirects the user to…
  3. …the cdcservlet. (The Cross-Domain Controller Servlet is the component that will eventually share the session ID value with the PA.)
  4. When the user accesses the cdcservlet, OpenAM is able to detect whether the user has an active session (the cdcservlet is on OpenAM’s domain, hence a previously created session cookie should be visible there), and…
  5. when the token is invalid…
  6. we redirect to…
  7. …the sign in page,
  8. which will be displayed to the user…
  9. …and then the user submits her credentials.
  10. If the credentials were correct, we redirect the user back to…
  11. …the cdcservlet, which will…
  12. …ensure…
  13. …that the user’s session ID is actually valid, and then…
  14. …displays a self-submitting HTML form to the user, which contains a huge Base64 encoded XML that holds the user’s session ID.
  15. The user then auto-submits the form to the PA, where…
  16. …the PA checks the validity…
  17. …of the session ID extracted from the POST payload…
  18. …and then performs the necessary authorization checks…
  19. …to ensure that the user is allowed to access the protected resource…
  20. …and then the PA creates the session cookie for the application’s domain, and the user either sees the requested content or an HTTP 403 Forbidden page. For subsequent requests the PA will see the session cookie on the application domain, hence this whole authentication / authorization process will become much simpler.

An example CDSSO LARES (Liberty Alliance RESponse) response that gets submitted to PA (like in step 15 above) looks just like this:

<lib:AuthnResponse xmlns:lib="http://projectliberty.org/schemas/core/2002/12" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ResponseID="sb976ed48177fd6c052e2241229cca5dee6b62617"  InResponseTo="s498ed3a335122c67461c145b2349b68e5e08075d" MajorVersion="1" MinorVersion="0" IssueInstant="2014-05-22T20:29:46Z"><samlp:Status>
<samlp:StatusCode Value="samlp:Success">
</samlp:StatusCode>
</samlp:Status>
<saml:Assertion  xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:lib="http://projectliberty.org/schemas/core/2002/12"  id="s7e0dad257500d7b92aca165f258a88caadcc3e9801" MajorVersion="1" MinorVersion="0" AssertionID="s7e0dad257500d7b92aca165f258a88caadcc3e9801" Issuer="https://login.example.com:443/openam/cdcservlet" IssueInstant="2014-05-22T20:29:46Z" InResponseTo="s498ed3a335122c67461c145b2349b68e5e08075d" xsi:type="lib:AssertionType">
<saml:Conditions  NotBefore="2014-05-22T20:29:46Z" NotOnOrAfter="2014-05-22T20:30:46Z" >
<saml:AudienceRestrictionCondition>
<saml:Audience>https://cool.application.com:443/?Realm=%2F</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AuthenticationStatement  AuthenticationMethod="vir" AuthenticationInstant="2014-05-22T20:29:46Z" ReauthenticateOnOrAfter="2014-05-22T20:30:46Z" xsi:type="lib:AuthenticationStatementType"><saml:Subject   xsi:type="lib:SubjectType"><saml:NameIdentifier NameQualifier="https://login.example.com:443/openam/cdcservlet">AQIC5wM2LY4SfcxADqjyMPRB8ohce%2B6kH4VGD408SnVyfUI%3D%40AAJTSQACMDEAAlNLABQtMzk5MDEwMTM3MzUzOTY5MTcyOQ%3D%3D%23</saml:NameIdentifier>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
<lib:IDPProvidedNameIdentifier  NameQualifier="https://login.example.com:443/openam/cdcservlet" >AQIC5wM2LY4SfcxADqjyMPRB8ohce%2B6kH4VGD408SnVyfUI%3D%40AAJTSQACMDEAAlNLABQtMzk5MDEwMTM3MzUzOTY5MTcyOQ%3D%3D%23</lib:IDPProvidedNameIdentifier>
</saml:Subject><saml:SubjectLocality  IPAddress="127.0.0.1" DNSAddress="localhost" /><lib:AuthnContext><lib:AuthnContextClassRef>http://www.projectliberty.org/schemas/authctx/classes/Password</lib:AuthnContextClassRef><lib:AuthnContextStatementRef>http://www.projectliberty.org/schemas/authctx/classes/Password</lib:AuthnContextStatementRef></lib:AuthnContext></saml:AuthenticationStatement></saml:Assertion>
<lib:ProviderID>https://login.example.com:443/openam/cdcservlet</lib:ProviderID></lib:AuthnResponse>

If you watch carefully you can see the important bit right in the middle:

<saml:NameIdentifier NameQualifier="https://login.example.com:443/openam/cdcservlet">AQIC5wM2LY4SfcxADqjyMPRB8ohce%2B6kH4VGD408SnVyfUI%3D%40AAJTSQACMDEAAlNLABQtMzk5MDEwMTM3MzUzOTY5MTcyOQ%3D%3D%23</saml:NameIdentifier>

As you can see the value of the NameIdentifier element is the user’s session ID. Once the PA creates the session cookie on the application’s domain you’ve pretty much achieved single sign-on across domains, well done! πŸ˜‰

P.S.: If you are looking for a guide on how to set up Policy Agents for CDSSO, check out the documentation.

Certificate authentication over REST

A little bit of background

Amongst the various different authentication mechanisms that OpenAM supports, there is one particular module that always proves to be difficult to get correctly working: Client certificate authentication, or Certificate authentication module as defined in OpenAM. The setup is mainly complex due to the technology (SSL/TLS) itself, and quite frankly in most of the cases the plain concept of SSL is just simply not well understood by users.

Disclaimer: I have to admit I’m certainly not an expert on SSL, so I’m not going to deep dive into the details of how client certificate authentication itself works, instead, I’m just going to try to highlight the important bits that everyone should know who wants to set up a simple certificate based authentication.

The main thing to understand is that client cert authentication happens as part of the SSL handshake. That is it… It will NOT work if you access your site over HTTP. The authentication MUST happen at the network component that provides HTTPS for the end users.
Again, due to SSL’s complexity there are several possibilities: it could be that SSL is provided by the web container itself, but it is also possible that there is a network component (like a load balancer or a reverse proxy) where SSL is terminated. In the latter case it is quite a common thing for these components to embed the authenticated certificate in a request header for the underlying application (remember: the client cert authentication is part of the SSL handshake, so by the time OpenAM is hit, authentication WAS already performed by the container).

Now this is all nice, but how do you actually authenticate using your client certificate over REST?

Setting it all up

Now some of this stuff may look a bit familiar to you, but for the sake of simplicity let me repeat the exact steps of setting this up:

  • Go to Access Control – realm – Authentication page and Add a new Module Instance called cert with type Certificate
  • Open the Certificate module configuration, and make sure the LDAP Server Authentication User/Password settings are correct.
  • Generate a new self signed certificate by following this guide, but make sure that in the CSR you set the CN to “demo”. The resulting certificate and private key for me was client.crt and client.key respectively.
  • Create PKCS#12 keystore for the freshly generated private key and certificate:
    openssl pkcs12 -export -inkey client.key.org -in client.crt -out client.p12
  • Install the PKCS#12 keystore in your browser (Guide for Firefox)
  • Enable Client Authentication on the container. For example on Tomcat 7.0.53 you would have to edit conf/server.xml and set up the SSL connector like this:
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
     maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
     keystoreFile="/Users/aldaris/server.jks" keystorePass="changeit"
     truststoreFile="/Users/aldaris/trust.jks" truststorePass="changeit"
     clientAuth="true" sslProtocol="TLS" />
    
  • Add your user’s public certificate into the truststore (since the container performs the authentication, it needs to trust the client):
    keytool -import -keystore /Users/aldaris/trust.jks -file client.crt -alias demo

    In cases when the truststoreFile attribute is missing from the Connector settings, Tomcat by default falls back to the JVM’s truststore, so make sure you update the correct truststore.

  • Restart the container

Now if you did everything correctly, you should be able to go to /openam/UI/Login?module=cert and the browser should ask for the client certificate. After selecting the correct certificate you should get access to OpenAM right away.

It’s time to test it via REST

For my tests I’m going to use curl, I must say though that curl on OSX Mavericks (7.30) isn’t really capable to do client authentication, hence I would suggest installing curl from MacPorts (7.36) instead.
To perform the login via REST one would:

$ curl -X POST -v -k --cert-type pem --cert client.crt --key client.key.org "https://openam.example.com:8443/openam/json/authenticate?authIndexType=module&authIndexValue=cert"

And if you want to authenticate into a subrealm:

$ curl -X POST -v -k --cert-type pem --cert client.crt --key client.key.org "https://openam.example.com:8443/openam/json/subrealm/authenticate?authIndexType=module&authIndexValue=cert"

Simple as that. πŸ˜‰

One-Time Passwords – HOTP and TOTP

About One-Time Passwords in general

One-Time Passwords (OTP) are pretty much what their name says: a password that can be only used one time. Comparing to regular passwords OTP is considered safer since the password keeps on changing, meaning that it isn’t vulnerable against replay attacks.

When it comes to authentication mechanisms, usually OTP is used as an additional authentication mechanism (hence OTP is commonly referred to as two factor authentication/second factor authentication/step-up authentication). The main/first authentication step is still using regular passwords, so in order your authentication to be successful you need to prove two things: knowledge and possession of a secret.

Since the OTP is only usable once, memorizing them isn’t quite simple. There is two main way of acquiring these One-Time Passwords:

  • hardware tokens: for example YubiKey devices, which you can plug in to your USB port and will automatically type in the OTP code for you.
  • software tokens: like Google Authenticator, in this case a simple Android application displays you the OTP code which you can enter on your login form.

There is two main standard for generating One-Time Passwords: HOTP and TOTP, both of which are governed by the Initiative For Open Authentication (OATH). In the followings we will discuss the differences between these algorithms and finally we will attempt to use these authentication mechanisms with OpenAM.

Hmac-based One-Time Password algorithm

This algorithm relies on two basic things: a shared secret and a moving factor (a.k.a counter). As part of the algorithm an HmacSHA1 hash (to be precise it’s a hash-based message authentication code) of the moving factor will be generated using the shared secret. This algorithm is event-based, meaning that whenever a new OTP is generated, the moving factor will be incremented, hence the subsequently generated passwords should be different each time.

Time-based One-Time Password algorithm

This algorithm works similarly to HOTP: it also relies on a shared secret and a moving factor, however the moving factor works a bit differently. In case of TOTP, the moving factor constantly changes based on the time passed since an epoch. The HmacSHA1 is calculated in the same way as with HOTP.

Which one is better?

The main difference between HOTP and TOTP is that the HOTP passwords can be valid for an unknown amount of time, while the TOTP passwords keep on changing and are only valid for a short window in time. Because of this difference generally speaking the TOTP is considered as a more secure One-Time Password solution.

So how does OpenAM implement OTP?

In OpenAM currently there is two authentication module offering One-Time Password capabilities: HOTP and OATH. So let’s see what is the difference between them and how to use them in practice.

HOTP authentication module

The HOTP module – as you can figure – tries to implement the Hmac-based One-Time Password algorithm, but it does so by circumventing some portions of the specification. Let me try to summarize what’s different exactly:

  • The shared secret isn’t really shared between the user and OpenAM, actually it is just a freshly generated random number, each HOTP code will be based on a different shared secret.
  • The moving factor is statically held in OpenAM, and it just keeps on incrementing by each user performing HOTP based authentications.
  • A given HOTP code is only valid for a limited amount of time (so it’s a bit similar to TOTP in this sense).

These differences also mean that it does not work with a hardware/software token since the OTP code generation depends on OpenAM’s internal state and not on shared information. So in order to be able to use the generated OTP code, OpenAM has to share it somehow with the user: this can be via SMS or E-mail or both.

So let’s create an example HOTP module using ssoadm:

openam/bin/ssoadm create-auth-instance --realm / --name HOTP --authtype HOTP --adminid amadmin --password-file .pass

Now we have an HOTP module in our root realm, let’s configure it:

openam/bin/ssoadm update-auth-instance --realm / --name HOTP --adminid amadmin --password-file .pass -D hotp.properties

Where hotp.properties contains:

sunAMAuthHOTPAuthLevel=0
sunAMAuthHOTPSMSGatewayImplClassName=com.sun.identity.authentication.modules.hotp.DefaultSMSGatewayImpl
sunAMAuthHOTPSMTPHostName=smtp.gmail.com
sunAMAuthHOTPSMTPHostPort=465
sunAMAuthHOTPSMTPUserName=example
sunAMAuthHOTPSMTPUserPassword=secret123
sunAMAuthHOTPSMTPSSLEnabled=SSL
sunAMAuthHOTPSMTPFromAddress=demo@example.com
sunAMAuthHOTPPasswordValidityDuration=5
sunAMAuthHOTPPasswordLength=8
sunAMAuthHOTPasswordDelivery=E-mail
sunAMAuthHOTPAutoClicking=true
openamEmailAttribute=mail

NOTE: The names of the service attributes can be found in the OPENAM_HOME/config/xml/amAuthHOTP.xml file.

At this stage I’ve modified the built-in demo user’s e-mail address, so I actually receive the OTP codes for my tests.

Last step is to create an authentication chain with the new HOTP module:

openam/bin/ssoadm create-auth-cfg --realm / --name hotptest --adminid amadmin --password-file .pass
openam/bin/ssoadm add-auth-cfg-entr --realm / --name hotptest --modulename DataStore --criteria REQUISITE --adminid amadmin --password-file .pass
openam/bin/ssoadm add-auth-cfg-entr --realm / --name hotptest --modulename HOTP --criteria REQUIRED --adminid amadmin --password-file .pass

Here the first command created the chain itself, the other two commands just added the necessary modules to the chain.

We can test this now by visiting /openam/UI/Login?service=hotptest . Since I don’t like to take screenshots you just have to believe me that the authentication was successful and I’ve received my HOTP code in e-mail, which was in turn accepted by the HOTP module. πŸ™‚

OATH authentication module

The OATH module is a more recent addition to OpenAM which implements both HOTP and TOTP as they are defined in the corresponding RFCs. This is how they work under the hood:

  • Both OTP mode retrieves the shared secret from the user profile (e.g. it is defined in an LDAP attribute of the user’s entry).
  • HOTP retrieves the moving factor from the user profile.
  • TOTP does not let a user to authenticate within the same TOTP window (since a given TOTP may be valid for several time windows), hence it stores the last login date in the user profile.

For the following tests I’ve been using the Google Authenticator Android app to generate my OTP codes.

Testing OATH + HOTP

First set up the authentication module instance:

openam/bin/ssoadm create-auth-instance --realm / --name OATH --authtype OATH --adminid amadmin --password-file .pass
openam/bin/ssoadm update-auth-instance --realm / --name OATH --adminid amadmin --password-file .pass -D oathhotp.properties

Where oathhotp.properties contains:

iplanet-am-auth-oath-auth-level=0
iplanet-am-auth-oath-password-length=6
iplanet-am-auth-oath-min-secret-key-length=32
iplanet-am-auth-oath-secret-key-attribute=givenName
iplanet-am-auth-oath-algorithm=HOTP
iplanet-am-auth-oath-hotp-window-size=100
iplanet-am-auth-oath-hotp-counter-attribute=sn
iplanet-am-auth-oath-add-checksum=False
iplanet-am-auth-oath-truncation-offset=-1

NOTE: here I’m using the givenName and the sn attributes for my personal tests but in a real environment I would use dedicated attributes for these of course.

Again let’s create a test authentication chain:

openam/bin/ssoadm create-auth-cfg --realm / --name oathtest --adminid amadmin --password-file .pass
openam/bin/ssoadm add-auth-cfg-entr --realm / --name oathtest --modulename DataStore --criteria REQUISITE --adminid amadmin --password-file .pass
openam/bin/ssoadm add-auth-cfg-entr --realm / --name oathtest --modulename OATH --criteria REQUIRED --adminid amadmin --password-file .pass

In order to actually test this setup I’m using a QR code generator to generate a scannable QR code for the Google Authenticator with the following value:

otpauth://hotp/Example:demo@example.com?secret=JBSWY3DPK5XXE3DEJ5TE6QKUJA======&issuer=Example&counter=1

NOTE: The Google Authenticator needs the secret key in Base32 encoded format, but the module needs the key to be in HEX encoded format (“48656c6c6f576f726c644f664f415448”) in the user profile.

After all these changes bring up /openam/UI/Login?service=oathservice and just simply ask for a new HOTP code on your phone, you should see something like this:

Google Authenticator - HOTP

Enter the same code on the login screen and you are in, yay. πŸ™‚

Testing OATH + TOTP

For testing TOTP I’ll be reusing the authentication modules and chains from the OATH HOTP test:

openam/bin/ssoadm update-auth-instance --realm / --name OATH --adminid amadmin --password-file .pass -D oathtotp.properties

Where oathtotp.properties contains:

iplanet-am-auth-oath-auth-level=0
iplanet-am-auth-oath-password-length=6
iplanet-am-auth-oath-min-secret-key-length=32
iplanet-am-auth-oath-secret-key-attribute=givenName
iplanet-am-auth-oath-algorithm=TOTP
iplanet-am-auth-oath-size-of-time-step=30
iplanet-am-auth-oath-steps-in-window=2
iplanet-am-auth-oath-last-login-time-attribute-name=sn

We need to generate a new QR code now with the following content:

otpauth://totp/Example:demo@example.com?secret=JBSWY3DPK5XXE3DEJ5TE6QKUJA======&issuer=Example

Again, let’s test it at /openam/UI/Login?service=oathtest and this time we should have a slightly different UI on the Google Authenticator:

Google Authenticator - TOTP

As you can see there is a nice indicator now showing us how long this TOTP code will remain valid.

I think now we know about OTP codes more than enough, hope you’ve found this article useful. πŸ˜‰

Identity Repositories

Identity Repositories (or Data Stores if you’d like) are almost the most important part of OpenAM. They enable you to manage identities within your organization (to a certain degree), and are involved in many processes in general:

  • during authentication it is often necessary to contact the IdRepo to get informations about the user (like the e-mail address, so an OTP code can be sent out)
  • after authentication takes place the user’s profile is looked up (for example to check that the user account is not inactivated)
  • once you have a session you can easily configure a PA to retrieve profile attributes in HTTP headers
  • when you authenticate via SAML, the IdRepo is contacted to retrieve the values for the SAML attributes in the assertion
  • etc

Some (boring) background about IdRepos

As you can see IdRepo is quite an important part of the product, so let’s try to get a better understanding of it.
In general, a given identity repository basically represents a single source of identity data. In this case identity can mean users, groups (and with ODSEE it can even mean roles and filtered roles). While it is common to use a regular relational database to store application data, in enterprise systems it is much more common to use some LDAP-capable directory server (OpenDJ, Active Directory, etc) instead. Since OpenAM is meant to be used as an enterprise solution it has been designed to work mainly with directory servers, but it is not limited to those (there is also Database IdRepo implementation, and with some patience any other sort of implementation can be written just as well). To make it simple let’s only talk about directory servers for now…

So as you already can guess, OpenAM has an LDAPv3Repo named IdRepo implementation which basically should be able to communicate with any LDAPv3 compliant directory server. While this sounds great, this also means that the implementation has grown quite big with time. There are parts for example where special handling is required for a given directory server type (yes, I’m talking about AD)..
In case of versions prior to 10.2.0, the LDAPv3Repo implementation has been using the quite old Netscape LDAP SDK to perform the LDAP operations. Although this solution works, I must say that support for the Netscape SDK is pretty much non-existent (and on top of that the whole SDK has been basically repackaged within OpenAM), so when it comes to debugging, it can become quite difficult to tackle down a given problem (again, we are talking about legacy code here). Because of this, and the fact that the OpenDJ team has started to develop an own LDAP SDK (which by the way ROCKS!), we decided to drop Netscape from the product. The good news is that this change already starts with 10.2.0 (though unfortunately there will be still parts of OpenAM using the old SDK, but hopefully not for too long).

So how do IdRepos work?

You can define any amount of Identity Repositories (Data Stores) within a realm, the only caveat is that all of the configured IdRepos will be contacted whenever an operation needs to be performed. You may ask why, well here it is:
OpenAM uses IdRepos to abstract away from identity repository differences, and within a realm it uses AMIdentityRepository to abstract away from the multiple IdRepo concept. This means basically that OpenAM only knows about the single AMIdentityRepository, which then knows everything about the different IdRepo configurations within the realm. To complicate things AMIdentityRepository is using IdServices to access all the IdRepo implementations, so let’s see the 4 different IdServices implementation for reference:

  • IdServicesImpl – the most basic implementation, this one just looks through the IdRepo list and executes the given operation on all of them. At the end it combines the results from the different IdRepos.
  • IdCachedServicesImpl – this implementation stores operation results in a cache, and when a given requested data cannot be found in the cache (which by the way is invalidated based on persistent search results), it just asks IdServicesImpl to contact the IdRepos for it.
  • IdRemoteServicesImpl – a remote implementation which is using JAX-RPC to contact an OpenAM server (which in turn will use Id(Cached)ServicesImpl) to perform operations remotely
  • IdRemoteCachedServicesImpl – as its name suggests: caches on the client side and asks OpenAM via IdRemoteServicesImpl if there is a cache miss.

Because IdRepos are implemented this way, you can hopefully see why we say that OpenAM wasn’t meant to be used for identity management. Creating/reading/updating/deleting entries from all the configured IdRepos cannot always fulfill all the requirements, and the expected behavior is poorly defined (for example what happens if you run a delete on an entry that only exists in one IdRepo?).

Alright, alright, but what’s new in OpenAM 10.2.0?

We have implemented a new IdRepo implementation, which is only using OpenDJ LDAP SDK to perform LDAP operations, keeping in mind that the behavior should be similar to the old Netscape-based IdRepo implementation. The main changes however are:

  • The LDAP failover code has been rewritten, and it should be much more reliable with the new release.
  • In the old version the user search attribute was misused, and it was basically handled as the naming attribute of the entry (i.e. first attribute name in the DN), this has been changed, so now the user search attribute is only used when performing search operations in the IdRepo, and the already existing naming attribute setting is used as naming attribute now.
  • Two new option has been introduced: heartbeat interval and heartbeat timeunit. These settings control the interval for sending out heartbeat search requests for idle connections. This means that if the connection is not idle, there won’t be any heartbeat request either.
  • Because of this the idle timeout setting has been removed. When there is a network component closing idle connections after a certain period of time, you should just configure the heartbeat interval correctly, and that should basically prevent the connection becoming idle.
  • Data store level cache has been removed (not implemented).

I think we can all agree on the fact that 10.2.0 will be an exciting release. πŸ™‚

Java EE agent internals

Not so long ago I’ve been working on JBoss v7 agent, so let me try to sum up some of my findings with Java EE agents in general.
A Java EE agent basically stands from 4 main components:

  • Installer
  • Container specific JAAS integration
  • Java EE Filter implementation
  • Agent application

Let’s go through each one of these and see how they really work.

Agent installer

As its name suggest, the agent installer helps to install the Policy Agent (PA) on the chosen Java EE container. The main behavior of the installer is defined within config/configure.xml file. This file is basically a big XML descriptor of the followings:

  • interactions – what should be asked from the user when performing install/custom-install/migrate/uninstall
  • tasks – what tasks needs to be performed as part of the agent installation, things like backup, creating directory layout, etc

So basically this XML describes how the installer should work in general. Now let’s go even more deeper…

Interactions

During an agent installation all user input is being stored in a thing called IStateAccess (well sometimes there is a persistent=”false” interaction which isn’t persistent, but that part I don’t fully grasp yet πŸ™‚ ).
As part of an interaction it is possible to set up different validators (even custom ones), so for example this makes it possible that a given agent is actually installed on a supported container version. For example with JBoss we are running “JBOSS_HOME/bin/instancename.(bat|sh) –version” command to get back the version number of the JBoss instance, parse it and if it matches, we let the installer further.

Tasks

Tasks are usually just a well defined “change” that needs to be performed by the installer. Implementing a task is not just performing a given change, rollback is just as important part of it. In order to make an agent work, it is very likely that some container specific configuration file needs to be modified (for example to configure/enable the JAAS integration), the necessary file paths are all calculated based on the user input during the interactions (IStateAccess).
Usually an agent installer performs the following tasks:

  • creating backups for the container’s configuration files
  • perform modifications to the container configuration
  • create agent directory layout (i.e. creating the Agent_00x folder structure)
  • encrypt the agent profile’s password and save it in the agent bootstrap config
  • generate the configuration files based on the values provided to the installer
  • in certain scenarios: deploy agentapp.war in the container
  • in case of custom-install: create agent profile if needed

As you can probably see there is nothing super magical about the agent installer, its sole purpose is basically to make the deployer’s life easier by automating this whole process.
I feel we heard enough about the installer for now, so let’s go a bit further and see what the JAAS integration is really all about.

Container specific JAAS integration

JAAS in general isn’t that new really, it’s been part of JRE 1.4 already, so let’s look at it very shortly (here is a longer version):

  • You can implement a custom authentication module
  • You can define authentication “chains” using built-in/custom modules in a configuration file

Well hopefully this concept doesn’t sound too new for you, since the whole OpenAM authentication system is based on JAAS. πŸ˜‰
As an extra, application servers tend to have the concept of “JAAS realm”, which is basically a collection of users (to be sparse as much as possible). Unfortunately there is not much standard around implementing JAAS support for application servers, so this part of the agents is different per container (and sometimes even across container versions).

Advantages of JAAS

Well the main advantage of JAAS is that it can help making the OpenAM integration as less intrusive as possible, but there are others as well:

  • Retrieving the logged in user’s name is quite simple, you can call request#getRemoteUser() or request#getUserPrincipal().
  • By setting up Java EE Security it is possible to define roles in the application’s descriptor files and later on in your application you can just simply check against these using request#isUserInRole(String) (authorization).
  • In case you later on decide to use an LDAP based JAAS module instead, the idea is that you can simply switch out the JAAS login module (of course you’ll need to configure it to work similarly to OpenAM), without actually modifying any part of your application code.
  • While this is already good enough, I think JAAS really pays off when EJBs are used in applications. In that case you can use the @DeclareRoles and @RolesAllowed annotations and magically your EJB method will be protected, only those in the configured role can actually invoke the method (anything else results in failure), which sounds quite neat. πŸ™‚

So what about agents?

The Java EE agents support JAAS, they integrate well with the container and within the agent configuration it is possible to set up role mapping based on pretty much any arbitrary data (session property, profile attribute). To enable JAAS support though you must ensure that you configure the agent in J2EE_POLICY or ALL mode. While it is possible to define security-constraints in web.xml to protect access to pages, usually that’s a bit cumbersome, instead it’s probably simpler to just define OpenAM policies (i.e. use the agent in ALL mode), and only use JAAS for the previously described goodies.

Java EE Filter implementation

The agent itself is implemented as a Java EE filter, which is (obviously) standard, so this part is common for all the agents and as such it is part of the agent SDK. When the filter intercepts a given request, it will basically go through a list of “TaskHandlers” and execute them in a predefined order. If a given TaskHandler returns with an AmFilterResult, then the processing stops and the result will be handled. Here is a small example subset of the available TaskHandlers:

  • CDSSOTaskHandler – detects if the user needs to authenticate using CDSSO and redirects to CDCServlet if necessary
  • CDSSOResultTaskHandler – processes the incoming LARES response and creates the cookie on the application’s domain
  • URLPolicyTaskHandler – checks the currently visited URL against configured URL policies in OpenAM, and blocks access if necessary
  • InitialPDPTaskHandler – saves POST data if the user hasn’t authenticated yet, so later on the POST can be resubmitted once authentication took place.
  • XSSDetectionTaskHandler – checks incoming request parameters for malicious characters

Since the agent is implemented as a filter, you can see how it can alter the outcome of a given incoming request. If the user doesn’t have session yet, it will prevent further processing of the page, and redirect straight to OpenAM instead. This however means that in order to protect the application fully, you must set the agent’s filter as the first one, otherwise it may be possible that a filter earlier in the chain alters the response, or returns earlier, which could basically prevent the agent to protect the application properly.

Agent application

And here is the last component which is pretty simple, the agent application. The purpose of this application is to provide a unique contextroot for the agent, so the agent can receive notifications from OpenAM, and also LARES responses in case of CDSSO. To be fair the agentapp only has the agent’s filter defined, and that basically handles everything. If there is an incoming notification, then the filter just checks if the requested URL is actually the notification URL, and processes it if so.
As a sidenote I would mention that deploying agentapp.war is not necessary when using global web.xml with Tomcat 6. That is simply because the global web filter already includes the agent filter, hence any incoming request to the /agentapp context will be catched by the filter already.

Conclusion

Agents are fun, but that doesn’t mean they are not complex. πŸ™‚

Single Sign-On – the basic concepts

What is Single Sign-On?

A good buzzword at least, but on top of that it’s a solution which lets users authenticate at one place and then use that same user session at many completely different applications without reauthenticating over and over again.
To implement SSO, OpenAM (as most other SSO solutions) uses HTTP cookies (RFC 6265 [1]) to track the user’s session. Before we go into any further details on the how, let’s step back first and get a good understanding first on cookies. Please bear with me here, believe me when I say that being familiar with cookies will pay out at the end.

Some important things to know about cookies

By using cookies an application can store information at the user-agent (browser) across multiple different HTTP requests, where the data is being stored in a name=value basic format. From the Cookie RFC we can see that there are many different extra fields in a cookie, but out of those you will mostly only run into these:

  • Domain – the domain of the cookie where this cookie can be sent to. In case the domain is not present, it will be handled as a host based cookie, and browsers will only send it to that _exact_ domain (so no subdomains! Also beware that IE may behave differently…[2]).
  • Max-Age – the amount of time the cookie should be valid. Of course IE doesn’t support this field, so everyone uses “Expires” instead with a GMT timestamp.
  • Path – a given URL path where the cookie applies to (for example /openam)
  • Secure – when used, the cookie can be only transferred through HTTPS, regular HTTP requests won’t include this cookie.
  • HttpOnly – when used, the cookie won’t be accessible through JavaScript, giving you some protection against XSS attacks

Let’s go into a bit more detail:

  • If you create a cookie with Domainexample.com“, then that Cookie will be available for an application sitting at foo.example.com as well, and basically for all other subdomains, BUT that very same cookie won’t be available at badexample.com nor at foo.badexample.com, because badexample.com does not match the example.com cookie domain. Browsers will only send cookies with requests made to the corresponding domains. Moreover browser only will set cookies for domains where the response did actually come from (i.e. at badexample.com the browser will discard Set-Cookie headers with example.com Domain).
  • Browsers will discard cookies created by applications on TLDs (Top Level Domain, like .co.uk or .com), also the same happens with cookies created for IP addresses, or for non valid domains (like “localhost”, or “myserver”), the Domain has to be a valid FQDN (Fully Qualified Domain Name) if present.
  • If you do not specify Max-Age/Expires, the cookie will be valid until the browser is closed.
  • In order to clear out/remove a cookie you need to create a cookie with the same name (value can be anything), and set the Expires property to a date in the past.
  • In case you request a page, but a Set-Cookie is coming out of a subsequent request (i.e. resource on the page – frame/iframe/etc), then that cookie is considered as a third party cookie. Browsers may ignore third party cookies based on their security settings, so watch out.
  • Containers tend to handle the cookie spec a bit differently when it comes to special characters in the cookie value. By default when you create a cookie value with an “=” sign in it for example, then the cookie value should be enclosed with quotes (“). This should be done by the container when it generates the Set-Cookie header in the response, and in the same way when there is an incoming request with a quoted value, the container should remove the quotes, and only provide the unquoted value through the Java EE API. This is not always working as expected (you may get back only a portion of the actual value, due to stripping out the illegal characters), hence you should stick with allowed characters in cookie values.

Funky IE problems

  • IE does not like cookies created on domain names that do not follow the URI RFC [3]
  • IE9 may not save cookies if that setting happened on a redirect. [4]

How is all of this related to SSO?

Well, first of all as mentioned previously OpenAM uses cookie to track the OpenAM session, so when you do regular SSO, then the sequence flow would be something like this:

ssov2

And here is a quick outline for the above diagram:

  • User visits application A at foo.example.com, where the agent or other component realizes that there is no active session yet.
  • User gets redirected to OpenAM at sso.example.com, where quite cleverly the cookie domain was set to example.com (i.e. the created cookie will be visible at the application domain as well)
  • User logs in, as configured OpenAM will create a cookie for the example.com domain.
  • OpenAM redirects back to the application at foo.example.com, where the app/policy agent will see the session cookie created, and it will then validate the session, upon success, it will show the protected resource, since there is no need to log in any more.

Oh no, a redirect loop!

In my previous example there are some bits that could go wrong and essentually result in a redirect loop. A redirect loop 90+% of the time happens because of cookies and domains – i.e. misconfiguration. So what happens is that OpenAM creates the cookie for its domain, but when the user is redirected back to the application, the app/agent won’t be able to find the cookie in the incoming request, but authentication is still required, so it redirects back to OpenAM again. But wait, AM already has a valid session cookie on its domain, no need for authentication, let’s redirect back to the app, and this goes on and on and on. The most common reasons for redirect loops:

  • The cookie domain for OpenAM does not match at all with the protected application.
  • The cookie domain is set to sso.example.com, instead of example.com, and hence the cookie is not available at foo.example.com .
  • The cookie is using Secure flag on the AM side, but the protected application is listening on HTTP.
  • Due to some customization possibly, the AM cookie has a path “/openam” instead of the default “/”, so even if the cookie domain is matching the path won’t match at the application.
  • You run into one of the previously listed IE quirks. πŸ™‚
  • Your cookie domain for OpenAM isn’t actually an FQDN.

So how does OpenAM deal with applications running in different domains? The answer is CDSSO (Cross Domain Single Sign-On). But let’s discuss that one in the next blog post instead. Hopefully this post will give people a good understanding of the very basic SSO concepts, and in future posts we can dive into the more complicated use-cases.

References

Custom Auth module – Configuration basics

If you want to create a custom OpenAM auth module or a service, then you probably going to end up writing a configuration XML. This XML describes to OpenAM what kind of UI elements to render on the admin console, and what values should be stored in the configuration store for the given module.
Let’s take a look at the following sample XML:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ServicesConfiguration
PUBLIC "=//iPlanet//Service Management Services (SMS) 1.0 DTD//EN"
"jar://com/sun/identity/sm/sms.dtd">

<ServicesConfiguration>
    <Service name="sunAMAuthMyModuleService" version="1.0">
        <Schema
serviceHierarchy="/DSAMEConfig/authentication/sunAMAuthMyModuleService"
            i18nFileName="amAuthMyModule"
            revisionNumber="1"
            i18nKey="sunAMAuthMyModuleServiceDescription">

            <Organization>
                <AttributeSchema name="sunAMAuthMyModuleAuthLevel"
                                 type="single"
                                 syntax="number_range" rangeStart="0"
                                 rangeEnd="2147483647"
                                 i18nKey="a500">
                    <DefaultValues>
                        <Value>0</Value>
                    </DefaultValues>
                </AttributeSchema>

                <SubSchema name="serverconfig" inheritance="multiple">
                    <AttributeSchema name="sunAMAuthMyModuleAuthLevel"
                                     type="single"
                                     syntax="number_range" rangeStart="0"
                                     rangeEnd="2147483647"
                                     i18nKey="a500">
                        <DefaultValues>
                            <Value>0</Value>
                        </DefaultValues>
                    </AttributeSchema>
                </SubSchema>
            </Organization>
        </Schema>
    </Service>
</ServicesConfiguration>

What you should know about authentication module service XMLs:

  • According to this piece of code the service name HAS to start with either iPlanetAMAuth or sunAMAuth, and HAS to end with Service (like sunAMAuthMyModuleService in our case)
  • the serviceHierarchy attribute is /DSAMEConfig/authentication/<service-name>
  • in the i18nFileName attribute you need to add the name of a properties file, which is on OpenAM classpath (like openam.war!WEB-INF/classes). This internationalization file will be used by OpenAM to lookup the i18nKeys for the various items in the XML.
  • You should put the module options into an Organization Schema, this will make sure that the module will be configurable per realm.
  • All the attribute schema definitions should be also listed under a SubSchema element (this will allow you to set up two module instances based on the same module type with different configurations).

The AttributeSchema element contains informations about a single configuration item (what name should OpenAM use to store the parameter in the configstore, what kind of UI element needs to be rendered, what restrictions does the attribute have, etc).

Available types for an attribute:

  • single -> singlevalued attribute
  • list -> multivalued attribute
  • single_choice -> radio choice, only one selectable value
  • multiple_choice -> checkboxes, where multiple item could be selected
  • signature -> unknown, possibly not used
  • validator -> if you want to use a custom validator for your attribute, you need to include the validator itself (beware of OPENAM-974).

Available uitypes for an attribute:

  • radio -> radiobutton mostly for displaying yes-no selections
  • link -> this is going to be rendered as a link, where the href will be the value of the propertiesViewBeanURL attribute
  • button -> unknown, not used
  • name_value_list -> generates a table with add/delete buttons (see Globalization settings for example)
  • unorderedlist -> a multiple choice field in which you can dynamically add and remove values. The values are stored unordered.
  • orderedlist -> a multiple choice field in which you can dynamically add and remove values. The values are stored ordered.
  • maplist -> a multiple choice field in which you can add/remove key-value pairs
  • globalmaplist -> same as maplist, but it allows the key to be empty.
  • addremovelist -> basically a palette where you can select items from the left list and move them to the right list

Available syntaxes for an attribute:

  • boolean -> can be true or false
  • string -> any kind of string
  • paragraph -> multilined text
  • password -> this will tell the console, that it should mask the value, when it’s displayed
  • encrypted_password -> same as the password syntax
  • dn -> valid LDAP DN
  • email
  • url
  • numeric -> its value can only contain numbers
  • percent
  • number
  • decimal_number
  • number_range -> see rangeStart and rangeEnd attributes
  • decimal_range -> see rangeStart and rangeEnd attributes
  • xml
  • date

NOTE: Some of these syntaxes aren’t really used within the product, so choose it wisely.

Other than these, there is also the i18nKey attribute, which basically points to i18n keys in the referred i18nFile configured for the service. This is used when the config is displayed on the admin console.
This should cover the basics for authentication module service configuration I think. πŸ˜‰

Session Quota basics

What is Session Quota?

Session Quota provides a way to limit the number of active sessions for a given user. Basically in the session service you can configure how many active sessions could a user have. For simplicity let’s say that the active session limit is set to 5. If a user would log in for the 6th time, then based on the session quota settings a QuotaExhaustionAction gets executed, which will decide what happens with the new and the existing sessions.

How session quota is evaluated?

When session quota is enabled, OpenAM tracks the user sessions mapped by universal IDs, hence it can easily detect the number of active sessions for a given user (this mapping is only available when session quota is enabled or SFO is being used). There is 3 way how the session quota can be evaluated:

  • Single Server Mode: if there is only one OpenAM node (with or without a site), then the quota will be evaluated per that one server.
  • Local Server Only Mode: When the advanced property “openam.session.useLocalSessionsInMultiServerMode” is set to true, OpenAM will only evaluate session quota per local server (so a user can end up having number of servers * number of allowed concurrent sessions). This option has been introduced to provide a certain level of support for multiserver environments without SFO (see OPENAM-875)
  • Session Failover Mode: In multiserver setup when using SFO it is possible to enforce deployment-wide session quota

So we now know how OpenAM detects if the session quota is being exceeded by a given user, but we don’t know yet what to do when that happens. Here comes QuotaExhaustionAction into the picture. This interface allows deployers to customize the behavior of OpenAM for these cases. To understand this a bit more let’s look at the built-in session quota exhaustion actions:

  • DENY_ACCESS: basically it will deny the access for the new session, but will keep the old session alive.
  • DESTROY_NEXT_EXPIRING: destroys the next expiring session (based on min(full, idle) for all the sessions), and lets the new session live.
  • DESTROY_OLDEST_SESSION: destroys the session with the lowest expiration time, and lets the new session live.
  • DESTROY_OLD_SESSIONS: destroys all the existing sessions, and lets the new session live.

As you can see there are many different ways to handle session quota exhaustion, and it really depends on the requirements, which method really fits. In case none of these are good for you, you can simply implement a custom QuotaExhaustionAction, for that you can find the built-in implementations here, but there is also a sample GitHub project.
Once you are ready with your custom implementation follow the installation steps on the GitHub project README.

How to enable session quota?

On the admin console go to Configuration -> Global -> Session page and:

  • Set the number of “Active User Sessions” to your choosen value.
  • Turn ON “Enable Quota Constraints”.
  • Select the preferred exhaustion action under the “Resulting behavior if session quota exhausted” option.

Quite possibly you need to restart OpenAM for the changes to take effect, but after then it should work just as you would want it to. πŸ˜‰

How to develop custom auth module

NOTE: if you’re looking for a quick way to learn how to develop auth modules, then you may want to read this or this instead.

In the OpenAM world an authentication module is responsible for authenticating a user, but sometimes the method of authentication is not that simple, as we think. For example the OpenAM gives out-of-the-box support for LDAP, Cert, SPNEGO token, etc. based authentication, but what happens if you need to authenticate (for some reason) from a WebService, this is when you’re probably going to end up developing a custom authentication module.

OpenAM is using JAAS, so if you don’t know what JAAS is, probably you need to read this, this is going to help you understand the basic concepts of the Auth API.

To create an authentication module, you’re going to need the followings:

  • Configuration-UI descriptor XML with localization
  • Callback-descriptor XML for login page UI
  • Some java code for authentication logic
  • maybe some JSP
  • and lot’s of lot’s of OpenAM container restarts πŸ™‚

So based on these needs I’m going to write a few more posts in this area, probably in the following structure:

  • Configuration basics
    • Config UI elements
    • Validation of configuration
  • Login UI basics
    • Callbacks
    • Dynamic Callback-handling
  • How to write the Java code
  • Gotcha’s, best practices
  • How to install auth module

I hope you will find these articles useful, some of them are new stuff for me too, so it may going to need some time to write them, but they’re going to (Forge)ROCK! πŸ˜€