I was recently asked to demonstrate a use case for securing Software-as-a-Service (SaaS) offerings, in particular GoogleApps. The concept is fairly straight forward - there are a growing number of hosted online services that coporations and individuals may subscribe to instead of self-hosting or using desktop applications. GoogleApps is one example, offering hosted email (GMail), online document authoring, management and sharing (Google Docs), website authoring (Google Sites) and shared calendar services (Google Calendar).
Subscribers to GoogleApps Premier Edition associate a GoogleApps account with a DNS domain, then configure user accounts for one or more users within that domain. Default authentication for a user to their GoogleApps account is via a username and password managed by GoogleApps itself. The focus of this article is to look at an alternate SSO offering from GoogleApps which leverages SAML 2.0, and how to configure Tivoli Federated Identity Manager (TFIM) as the Identity Provider to a GoogleApps account. This is particularly useful for corporations who offer employees an authentication portal today and would like to maintain that portal as a single sign-on for users to bridge to cloud-based services such as GoogleApps.
GoogleApps offers single sign-on services via a very basic subset of SAML 2.0. For the SAML-savvy reader, here's the highlights:
- Single sign-on is achieved with either SP-initiated or unsolicited IDP-initiated SAML 2.0 single sign-on.
- For SP-initiated, the HTTP Redirect binding is used for the AuthnRequest message with no signatures from Google.
- The single sign-on response uses the HTTP POST binding (also commonly known as browser-post) and a digital signature is required on the assertion.
- The GoogleApps assertion consumer service expects the user's GoogleApps account email address as the NameID in the SAML Subject.
One of the major advantages of using a browser-post profile for single sign-on is that only the browser needs to be able to contact both the identity provider and service provider URL's. There are no configured SOAP backchannels, and no other direct communication is needed between GoogleApps and the partner identity provider. This makes the solution work very well for scenarios where the identity provider is located on a VPN or other non-public server that only the user's browser can access (e.g. an intranet environment).
This diagram (linked from the Google SSO page) describes the message flow for the SAML 2.0 browser-post SSO when it is SP-initiated:
The IDP-initiated flow is very similar, just a subset starting at the browser and clicking on a link at the identity provider which invokes step 5 onward.
An example of the SAML assertion used in the flow (with the signature removed for readability) is shown here. Note that the NameID element within the subject carries the GoogleApps identity during the single sign-on:
<saml:Assertion ID="Assertion-uuid44763d73-011f-1874-91d0-fc038de2ff05" IssueInstant="2009-02-05T03:23:33Z" Version="2.0"> <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"> https://myidp.ibm.com/FIM/sps/saml20idp/saml20 </saml:Issuer> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="uuid4476409b-011f-14a2-8e01-fc038de2ff05"> ... removed for brevity ... </ds:Signature> <saml:Subject> <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"> firstname.lastname@example.org </saml:NameID> <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> <saml:SubjectConfirmationData InResponseTo="nkdbjghneepijibkinhhoinhnplcoadbncdpnkkk" NotOnOrAfter="2009-02-05T03:33:33Z" Recipient="https://www.google.com/a/weeden.org/acs" /> </saml:SubjectConfirmation> </saml:Subject> <saml:Conditions NotBefore="2009-02-05T03:13:33Z" NotOnOrAfter="2009-02-05T03:33:33Z"> <saml:AudienceRestriction> <saml:Audience>google.com/a/weeden.org</saml:Audience> </saml:AudienceRestriction> </saml:Conditions> <saml:AuthnStatement AuthnInstant="2009-02-05T03:23:33Z" SessionIndex="uuid4475d11b-011f-1fd8-972d-fc038de2ff05" SessionNotOnOrAfter="2009-02-05T04:23:32Z"> <saml:AuthnContext> <saml:AuthnContextClassRef> urn:oasis:names:tc:SAML:2.0:ac:classes:Password </saml:AuthnContextClassRef> </saml:AuthnContext> </saml:AuthnStatement> </saml:Assertion>
The rest of this entry will focus on the technical configuration requirements to get the SSO working at both GoogleApps and on Tivoli Federated Identity Manager.
SAML 2.0 Configuration Overview
SAML 2.0 integrations require the sharing of trusted configuration data (typically a set of identifiers, URL endpoints and signing certificates) between the parties involved in the federation. Integration between TFIM and GoogleApps is no different, and the following sections provide written guidelines for what it takes to get GoogleApps and TFIM to interoperate. Configuration is necessary at both Google and on the TFIM server. The information is really reference-level, and not a tutorial for the completely uninitiated. Prior experience with SAML 2.0 and TFIM are pre-requisites to get the most out of these sections.
After configuring your GoogleApps domain account and establishing some users, single sign-on is enabled via the Advanced Tools of the GoogleApps control panel. This is an all-or-nothing approach. Either single sign-on is enabled (to ONE idp-partner only), or local authentication is used. Unlike other product-based SAML 2.0 offerings GoogleApps doesn't deal with standard metadata document formats, and instead prompts for the minimal necessary sub-components needed for service provider enablement. The configuration panel has settings with the following properties:
|Enable Single Sign-on||A checkbox that must be selected for single sign-on to be used.|
|Sign-in page URL||This should be the single sign-on service URL for the TFIM SAML 2.0 identity provider federation, somthing like: |
|Sign-out page URL||A URL your users should be redirected to if they click Logout on the GoogleApps pages. It can be any URL, in this exampe I've pointed it to the logout URL of the identity provider which is a Tivoli Access Manager WebSEAL server: |
|Change password URL||A URL where users should be redirected to if they select the change password option from GoogleApps. In this example I've pointed it to the WebSEAL change password functionality: |
|Verification Certificate||This is a control that allows you to upload a PEM-formatted text file containing the public signing verification key for signed SAML assertions sent from the identity provider. This should be the public key matching the signing certificate configured for assertions in the TFIM SAML 2.0 identity provider federation.|
|Use a domain specific issuer||This is an optional checkbox that controls the entity ID that google will send in AuthnRequest's (and what is expected in the AudienceRestrictionCondition of assertions). I recommend it is always checked so that the same identity provider may be used to provide single sign-on services to more than one GoogleApps domain.|
That's really all there is to configuring GoogleApps as a SAML 2.0 SSO Service Provider for Tivoli Federated Identity Manager. Obviously the URL's may vary for your own TFIM installation. Now when you access a GoogleApps URL like http://docs.google.com/a/<yourdomain.com> you will be redirected to the sign-in page URL with an AuthnRequest using the HTTP redirect binding.
There is however some configuration data needed about the GoogleApps service provider that is to be shared with the TFIM identity provider. TFIM expects this data in a standard SAML 2.0 metadata XML file. As GoogleApps doesn't provide such a file, it must be constructed manually. The file format is fairly trivial, and an example you can use as a template is shown below. The two variable portions are the pieces where you see yourdomain.com.
<?xml version="1.0" encoding="UTF-8" ?> <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="google.com/a/yourdomain.com"> <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat> <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.google.com/a/yourdomain.com/acs" index="0" isDefault="true" /> </md:SPSSODescriptor> <md:Organization> <md:OrganizationName xml:lang="en">GoogleApps</md:OrganizationName> <md:OrganizationDisplayName xml:lang="en">Google Apps</md:OrganizationDisplayName> <md:OrganizationURL xml:lang="en" /> </md:Organization> <md:ContactPerson contactType="technical"> <md:Company>Your Information</md:Company> <md:GivenName /> <md:SurName /> <md:EmailAddress /> <md:TelephoneNumber /> </md:ContactPerson> </md:EntityDescriptor>
Tivoli Federated Identity Manager is configured with a SAML 2.0 identity provider, with the following noteable characteristics (assumes knowledge of TFIM SAML configuration options):
- Tivoli Federated Identity Manager 6.2, fixpack 3 or later should be used. At the time of writing this fixpack is not yet available publically so if your need is more urgent, please contact me to discuss options. A solution can be made to work with earlier versions of TFIM however it requires manual provisioning of persistent name identifiers in the TFIM alias service.
- When using the TFIM federation wizard to create SAML 2.0 identity provider configuration, use a Basic Web SSO profile. In reality only the single sign-on service will be used, so you can later disable single logout if you wish.
- On the Signature Options panel, do NOT check the box which requires signatures for incoming messages. This is important for SP-initiated SSO as Google does not sign AuthnRequest messages.
- Still on the Signature Options panel, leave the radio button selection for outgoing messages on the default: Typical set of outgoing SAML messages and assertions are signed. The signing key you select should be the private key matching the ceritificate uploaded to GoogleApps SSO configuration.
- All other options are set at their default values.
The most important part of the remaining federation configuration is the identity mapping options. Google requires the email address of the GoogleApps user be transmitted in the NameID element of the SAML Subject. This lends itself to the email address NameID format, and the TFIM mapping rule is responsible for populating the STSUniversalUser principal name with this email. Following is an example XSLT mapping rule for TFIM which will work as-is if the user names of the TFIM environment exactly match the GoogleApps usernames. You will also see a commented out section which allows you to do a different identity mapping where just the email address suffix is modified (or added) to the TFIM usernames (e.g. TFIM-user = shane (or email@example.com), GoogleApps-user = firstname.lastname@example.org).<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xalan" xmlns:stsuuser="urn:ibm:names:ITFIM:1.0:stsuuser" xmlns:mapping-ext="com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils" extension-element-prefixes="mapping-ext" version="1.0"> <xsl:strip-space elements="*" /> <xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes" /> <!-- Initially we start with a copy of the document. --> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()" /> </xsl:copy> </xsl:template> <!-- the google apps domain - UPDATE with your own --> <xsl:variable name="googledomain" select="'@domain.com'" /> <!-- the current user --> <xsl:variable name="username" select="//stsuuser:Principal/stsuuser:Attribute[@name='name']/stsuuser:Value" /> <!-- the current user's shortname (before the '@') --> <xsl:variable name="usershortname" select="substring-before($username, '@')" /> <!-- build the google apps target username from the current user (PICK A TECHNIQUE) --> <!-- simple 1:1 identity mapping example --> <xsl:variable name="googleid" select="$username" /> <!-- this is a trickier demo version that uses the user's current username and just adds the google apps domain name <xsl:variable name="googleid"> <xsl:choose> <xsl:when test="$usershortname and string-length($usershortname) > 0"> <xsl:value-of select="concat($usershortname,$googledomain)" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat($username,$googledomain)" /> </xsl:otherwise> </xsl:choose> </xsl:variable> --> <!-- This template replaces the Principal name with one that contains the google apps email address as name identifier. --> <xsl:template match="//stsuuser:Principal"> <stsuuser:Principal> <stsuuser:Attribute name="name" type="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"> <stsuuser:Value> <xsl:value-of select="$googleid" /> </stsuuser:Value> </stsuuser:Attribute> </stsuuser:Principal> </xsl:template> <xsl:template match="//stsuuser:AttributeList"> <stsuuser:AttributeList> <stsuuser:Attribute name="AuthnContextClassRef" type="urn:oasis:names:tc:SAML:2.0:assertion"> <stsuuser:Value>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</stsuuser:Value> </stsuuser:Attribute> </stsuuser:AttributeList> </xsl:template> </xsl:stylesheet>
After configuring the federation and partner, there are some manual settings which MUST be made to the TFIM federation configuration file (feds.xml) to enable successful interoperability with GoogleApps:
- In the identity provider's self configuration, there is a multi-valued property called SAML2.NameIDFormat. Add an additional value for urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified. This is necessary for SP-initiated SSO to work, as the GoogleApps SP sends an AuthnRequest with the unspecified NameID format.
- Again in the identity provider's self configuration, add a new property called SAML2.DefaultNameIDFormat with a value of urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress. This will instruct the federation to use the emailAddress NameID format by default when it receives an AuthnRequest with the unspecified format. Again this is only needed to make SP-initiated SSO work properly.
These manual configuration updates should be possible with the TFIM command-line interface, and when that is verified I will update the blog entry with the relevant commands.For now the recommendation is to manually edit <websphere_profile_root>/config/itfim/<tfim_domain>/etc/feds.xml.
After the configuration file is updated, either restart the WebSphere server, or simply use the TFIM console to navigate to the Domain Management -> Runtime Node Management panel, and Reload Configurations for the domain.
Configuration is now complete, and you should be able to SSO between TFIM and your GoogleApps domain.
Invoking Single Sign-on
The SP-initiated SSO flow is invoked simply by trying to access the target application, for example:http://docs.google.com/a/<yourdomain.com>
The IDP-initiated flow is a little different as you need to tell the TFIM IDP which SSO profile and NameID format to use. A typical IDP-initiated SSO link would look something like:https://myidp.ibm.com/FIM/sps/saml20idp/saml20/logininitial?RequestBinding=HTTPPost&NameIdFormat=email&PartnerId=google.com%2Fa%2Fyourdomain.com&Target=https://docs.google.com/a/yourdomain.com
There are a variety of extensions and variants possible to the solution described above. Consider some of these:
- TFIM can easily be configured to act as an IDP aggregator, allowing bridging of different SSO protocols from different authentication domains, or proxying to various different alternate IDP's of the user's choice whilst still being configured with a 1:1 SAML 2.0 relationship to a particular GoogleApps domain. E.g. You may allow users to login to YOUR authentication portal with OpenID, Information Cards or other SSO protocols and then they are transparently signed on to GoogleApps via SAML 2.0.
- TFIM could be used as a SaaS offering itself - essentially offering hosted delegated cloud security services.
- If you have a large body of corporate users, any suspect only a subset of them will leverage GoogleApps, you could use the GoogleApps Provisioning API from within a TFIM identity provider mapping rule to do just-in-time provisioning of the users to GoogleApps rather than a bulkload or manually provisioning all the accounts. This would be a very effective component of a complete corporate solution and demonstrates the flexibility of the TFIM identity mapping solution. The TFIM identity mapping plug-in point could also be used to invoke a Tivoli Directory Integrator (TDI) and/or Tivoli Identity Manager (TIM) workflow for more complex provisioning scenarios.
This article demonstrates how a simple SAML 2.0 single sign-on flow can be used to secure access to GoogleApps cloud services from Tivoli Federated Identity Manager. This is just the tip of the iceberg for cloud services security, but has the very desirable attributes of being a simple integration, and will work out of the box in small volumes without any custom code development. Larger deployments would require some auto-provisioning integration, however all the required building blocks are already in place. Other cloud services offerings like Microsoft Azure services also have federation gateways in place, and it seems like a natural capability for all the cloud vendors to offer. Whilst each may use it's own federation protocol, Tivoli Federated Identity Manager with it's best-of-breed broad range of protocol offerings is uniquely positioned to offer seamless authentication portal integration to any concurrent set of cloud services.