Over the last decade or so, many large and mid-sized enterprise organizations have invested in identity federation and cross-domain single sign-on solutions that are based on the SAML2 standard from OASIS. With the trend towards cloud computing, and the emergence of PAAS offerings like Cloud Foundry, it only makes sense that we’d want to be able to leverage that investment as a way to authenticate to the cloud. In this post, I describe the configuration steps needed to enable a user to log in into a Cloud Foundry environment, using a SAML assertion issued from a Shibboleth Identity Provider (IdP).
As background, the Cloud Foundry side of the equation basically consists of three services: the (original) login-server, the new saml-login-server, and the uaa. In this post we focus only on the configuration needed for the saml-login-server. Each of these can be deployed as a WAR file in your favorite servlet container — Cloud Foundry uses Tomcat. On the Identity Provider side of the equation, we have the Shibboleth IdP service, which is also deployed as a WAR file.
There are basically five key items that need to be configured.
- Configure the saml-login-server via editing the login.yml as needed.
- Update the idp.xml file in the saml-login-server, and do a build/deploy.
- Start the saml-login-server, and use your browser to generate the required Service Provider (SP) metadata.
- Move the generated SP metadata over to the IdP.
- If needed, update the IdP configuration to release a suitable NameID attribute (e.g. corresponding to what was configured in login.yml, above).
We’ll go through each of these steps in a bit more detail below.
1. Configuring login.yml
First, we’ll need to edit the login.yml file to customize it to your local environment. Of course, this will include updates to the hostname and port numbers from their default values, as appropriate (I won’t detail all those routine steps here.) In addition, you should be sure to set a value for the login:entityBaseUrl. This will be the servlet context, e.g. the place where the Cloud Foundry saml-login-server is running. You’ll also need a value for the login:entityID key. This will be the unique name of this saml-login-server instance, within your SAML-based SSO environment. I chose the unimaginative designations shown below:
login: ...<snip>... # The entity base url is the location of this application # (The host and port of the application that will accept assertions) entityBaseURL: http://<hostname>:8080/saml-login-server # The entityID of this SP entityID: cloudfoundry-saml-login-server
Finally, you may want to set the value of the key login:saml:nameID. The default setting is “unspecified” as shown below:
At run time, this setting means that the Cloud Foundry saml-login-server will accept whatever <NameID> attribute the IdP chooses to send in the SAML assertion. Depending upon how your IdP is configured, this default configuration may be just fine. My IdP was initially configured to release only a transientID, and while this basically worked, it meant that my session was not linked to my existing Cloud Foundry user id, which was previously registered as my email (e.g. for doing the straight HTML form-based login via my username/password, or for doing a vmc login or a cf login prior to doing a cf push command line operation). For consistency in my configuration I changed this value to be emailAddress as shown below:
Now, the Cloud Foundry saml-login-server will specifically request that the <NameID> element contained in the SAML assertion returned from the IdP contain the user’s email address. This can then be correlated to the user’s existing account in the Cloud Foundry UAA database, when later doing OAuth token grants for applications.
2. Copy your idp.xml to the Cloud Foundry SAML login server
Metadata files are essentially the “secret sauce” of federated SSO. These files contain, among other things, the PEM-encoded X.509 certificates needed to establish digital trust in the messages exchanged via the SAML Web SSO protocol. Of course this step is needed in order to tell the saml-login-server about the IdP, and establish the first leg of that digital trust. You can move a copy of your local idp.xml file into the appropriate place in the saml-login-server deployment. I simply replaced the sample idp.xml that was supplied in the /src/main/resources/security directory when I cloned the saml-login-server project from GitHub.
Once you’ve updated these items, do a maven build, and deploy the WAR to your servlet container.
3. Generate the Service Provider Metadata
The Cloud Foundry saml-login-server is playing the role of the Service Provider (a.k.a. the Relying Party). We’ve already provided the saml-login-server with the metadata it needs in order to trust the IdP. Now we’ll need to give the IdP a copy of the saml-login-server’s SAML metadata. This step will make their digital trust relationship mutual. Point your browser at the URL for the metadata generator. In my deployment it looks like:
Where the leaf part of the resource URL (the part after “alias”) corresponds to the unique name used for the login:entityID key within the login.yml file, above. (BTW, this metadata generator resource is a nice convenience that is actually enabled via a bean provided by the underlying Spring Security SAML Extension).
Depending upon your browser, you may see the XML metadata rendered as a page or you may be prompted to save this file. In any case, you’ll want to save the file to a meaningful name such as cf-login-server-sp-metadata.xml, as you’ll need to put copy of this file on the IdP machine next.
4. Move the generated SP Metadata over to the IdP.
Using your favorite file transfer method, copy the SP Metadata file over to the IdP host. In a typical installation it needs to be placed in the location <IDP_HOME>/metadata.
Make sure you update the <IDP_HOME>/conf/relying-party.xml file to include this new Service Provider and refer to the corresponding metadata file at the correct location. My IdP happens to be running on a Windows server and the configuration looks like the following:
<metadata:MetadataProvider id="cloudfoundry-saml-login-server" xsi:type="metadata:FileBackedHTTPMetadataProvider" metadataURL="http://hostname:8080/saml-login-server/saml/metadata/alias/cloudfoundry-saml-login-server" backingFile="C:\shibboleth-identityprovider-2.3.5/metadata/cf-login-server-sp-metadata.xml"> </metadata:MetadataProvider>
5. Release the appropriate NameID and attributes from the IdP
Finally, we need to tell the IdP that it is OK to release the users email as the Name Identitfier in the SAML assertion that it issues. We’ll need to update two files in the IdP configuration.
First, you need to edit <IDP_HOME>/conf/attribute-resolver.xml and add the attribute definition for the users emailAddress as a nameID. A snippet from my configuration is shown below:
<!-- Name Identifier related attributes --> <resolver:AttributeDefinition id="transientId" xsi:type="ad:TransientId"> <resolver:AttributeEncoder xsi:type="enc:SAML1StringNameIdentifier" nameFormat="urn:mace:shibboleth:1.0:nameIdentifier"/> <resolver:AttributeEncoder xsi:type="enc:SAML2StringNameID" nameFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/> </resolver:AttributeDefinition> <resolver:AttributeDefinition xsi:type="ad:Simple" id="nameID_for_cf" sourceAttributeID="mail"> <resolver:Dependency ref="myLDAP" /> <resolver:AttributeEncoder xsi:type="SAML2StringNameID" xmlns="urn:mace:shibboleth:2.0:attribute:encoder" nameFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" /> </resolver:AttributeDefinition>
Notice that here I’ve chosen to release two NameID attributes, both a transientId and also the emailAddress. As noted above, we’ve chosen to configure the Cloud Foundry saml-login-server to request the emailAddress, so I’ve named that element appropriately, as per the XML attribute @id=”nameID_for_cf”. Of course this is just an arbitrary name in the XML, and could have been be set to anything. The ref=”myLDAP” must point to a corresponding DataConnector element, found in the same file, e.g.:
<resolver:DataConnector id="myLDAP" xsi:type="dc:LDAPDirectory" <...snip...> </resolver:DataConnector>
<!-- Release nameID_for_cf to enable SSO to Cloud Foundry --> <afp:AttributeFilterPolicy id="Cf_share_nameID"> <afp:PolicyRequirementRule xsi:type="basic:ANY"/> <afp:AttributeRule attributeID="nameID_for_cf"> <afp:PermitValueRule xsi:type="basic:ANY" /> </afp:AttributeRule> </afp:AttributeFilterPolicy>
Now, go ahead and restart your IdP and the saml-login-server and check for a clean startup. If all goes well, you should now be able to log in using either your existing Cloud Foundry user name and password, or a SAML assertion from your IdP.
To test this you can choose the link on the saml-login-server login page that says “Sign in with your organization’s credentials.” Clicking on that link should do an HTTP redirect, and after the dynamic discovery protocol completes, you’ll be looking at your Shibboleth IdP log in page. After you fill in your credentials and post the Shibboleth log in form, you’ll be redirected back to the Cloud Foundry log in page, with an opportunity to view your account profile. If you started the use case by initially visiting an application running within Cloud Foundry, you’ll be redirected back to the application page you requested, with your log in session established. For my testing I found it convenient to use the example applications found in the UAA samples directory.
Oh, yeah, ….one last thing… A common problem that I’ve encountered in configuring SAML in other situations is that the time of day settings between the IdP and the SP must be synchronized to within a reasonable tolerance, e.g. a few seconds. In production you are likely using NTP so this wouldn’t be a problem. However, when doing this in a development environment don’t just assume that you machines have the correct time. If you see an error indicating that your assertion has (immediately) expired, you’ll know why.