Category Archives: Blog

LDAP SSL/TLS Config for Shibboleth IdP

Shibboleth IdP LDAPS configuration made easy.

LDAP SSL/TLS Config for Shibboleth IdP

Many (if not most) of our clients utilize LDAP as the authentication source for Shibboleth IdP.

That said, we all too often encounter environments without properly configured encryption of the connection. All-to-often this arises because of uncertainty about the options and how to properly configure SSL/TLS for the IdP.

Shibboleth supports two mechanisms for encrypted connections:

TLS

The most basic connection is TLS (transport layer security). Confusingly, this is usually referred to in logs and configs as SSL (even outside of Shibboleth), but does not refer to true SSL by itself. As a protocol, SSL has long been deprecated, but we still use the term colloquially to refer to TLS.

The Shibboleth configuration options (in the ldap.properties file) for TLS look something like this:

idp.authn.LDAP.ldapURL       = ldaps://ldap.example.org:636
idp.authn.LDAP.useStartTLS   = false
idp.authn.LDAP.useSSL        = true

We've set idp.authn.LDAP.useSSL to true to indicate that our IdP is to connect via TLS (I know, it's silly nomenclature), and our ldapURL includes both the protocol specification as ldaps:// and the port number that's typically used for LDAPS connections (TCP/636).

Start TLS

The second connection type is called StartTLS. In this mode, the connection starts out as clear text communication over the standard port (TCP/389), during which the IdP indicates to the LDAP server that it should communicate via a secure connection. It's at this point that TLS negotiation occurs, and afterwards communication is secure.

The configuration in Shibboleth looks like this:

idp.authn.LDAP.ldapURL       = ldap://ldap.example.org:389
idp.authn.LDAP.useStartTLS   = true
idp.authn.LDAP.useSSL        = false

Note that the major differences are that we've explicitly flagged to use StartTLS as opposed to SSL, and the LDAP URL includes the non-secure port number (TCP/389) as well as a regular ldap:// protocol identifier.


There are advantages to either choice. StartTLS can make networking firewall rules simpler, but not all LDAP deployments support it, and so it's not a universal mechanism.

What's important is that it doesn't matter which mechanism you choose, so long as you do choose one.

No matter how secure you think the internal network is between the IdP server and the LDAP server, you don't want to trust the data between the two traveling in the clear.

Releasing NameID with a Specified Format

Releasing NameID with a Specified Format

A frequent task for ADFS Identity Provider administration is onboarding a new Relying Party Trust and releasing to that relying party a particular set of attributes. Frequently, service providers will request a particular attribute take the form of a Name Identifier (NameID), formatted accordingly.

Name Identifiers are special attributes that come within the SAML <subject> element. For example,

<Subject>
    <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
      user.name@idmengineering.com
    </NameID>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <SubjectConfirmationData InResponseTo="_31dde5dfce9c812303ed02d73fb382e9"
                                 NotOnOrAfter="2019-11-06T18:41:11.977Z"
                                 Recipient="https://sp.example.com/SAML2/POST" />
    </SubjectConfirmation>
</Subject>

Here, I've released an email address for my user with the format: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress. Let's re-create the above SAML response <subject>.

Example: E-mail Address as NameID

For the remainder of this discussion, we'll assume that you've configured a claims-aware relying-party trust within ADFS. We now want to configure a NameID to be released from a particular LDAP attribute. Here's how we'll achieve the above result with ADFS.

Note: These instructions should work for ADFS 2.0 and up.

  1. From the ADFS Management Console, select Trust Relationships > Relying Party Trusts.

  2. Highlight the relying party which you are trying to configure, and under Actions on the right hand side pane, select Edit Claim Rules.

  3. Click Add Rule.

  4. The default claim rule template is Send LDAP Attributes as Claims so you should select Next.

  5. Give the rule a meaningful name. We'll first be establishing a claim that we can later use to release the attribute with a particular format, so I've chosen to call this rule "Make Email Address Available for NameID".

  1. Select your attribute store, which will most likely be Active Directory.

  2. Select the attribute that you wish to release as the NameID. Here I will select Email Addresses.

  3. And select the outgoing claim type as E-mail Address.

  • Note: Do NOT select Name ID as the outgoing claim type here if you wish to specify the format. Selecting this will send the user's name address as the Name ID, however, there will be no formatting information, i.e. <NameID>user.name@idmengineering.com</NameID> would appear within the <subject> element of the SAML Response.
  1. Click Finish.

  2. Now, we'll add a rule to Transform that claim into a properly formatted NameID. Select Add Rule once more from the Edit Claim Rules dialog.

  3. And this time, change the Claim rule template: drop-down to Transform an Incoming Claim, then select Next.

  4. Once again, give this rule a meaningful name, like "Transform Email to NameID".

  5. Select as the Incoming Claim Type whatever claim you chose to issue in the previous rule. In this case, that's E-Mail Address.

  1. Select Name ID as the outgoing claim type.

  2. And now, you can specify the Name ID Format that you wish to use. The following table lists the Outgoing Name ID Format selections available within ADFS, and the corresponding format identifier URI that will appear within the SAML <subject>.

Outgoing NameID Format Format Identifier URI
UPN http://schemas.xmlsoap.org/claims/UPN
Email urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
Common Name http://schemas.xmlsoap.org/claims/CommonName
Unspecified urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
X.509 Subject Name urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
Windows Qualified Domain Name urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
Kerberos Principal Name urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
Entity Identifier urn:oasis:names:tc:SAML:2.0:nameid-format:entity
Persistent Identifier urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
Transient Identifier urn:oasis:names:tc:SAML:2.0:nameid-format:transient

Bold entries represent the most commonly requested formats.

Cautionary Note About urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified

The urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified format is unique. Formally, this is what the Service Provider should list within their metadata if they do not care what Name ID Format an IdP uses. In practice, however, many IdP administrators assume that this specification in an SP's metadata means that they are REQUIRED to release a NameID in this format. This is not the case.

It is because of this assumption, however, that ADFS allows one to release a Name ID in this format.

Persistent vs. Transient -or- Welcome to SAML 2.0

For a good discussion of the concepts behind Name Identifiers, see this post from the Shibboleth Wiki. In particular, name identifiers have a number of characteristics which define them, including:

  • Persistence - whether a given name id is intended to be used across many sessions.
  • Revocability - whether a given name id can be revoked.
  • Re-assignability - whether a given name id, once revoked, may be reassigned to a different subject.
  • Opaqueness - whether a relying party can positively identify the subject from a given name id.
  • Targetability - whether a given name id is intended for a specific relying party.
  • Portability - whether a given name id is usable across security domains.
  • Global - whether a given name id value is globally unique.

The SAML 2.0 spec therefore defines two primary formats:

Type Characteristics
Persistent A persistent, revocable, reassignable, opaque, targeted and portable identifer.
Transient A non-persistent, non-revocable, opaque, non-portable, non-targeted, global identifier.

which broadly encompass the whole of these Name IDs, and provide a broad basis for (importantly) opaque identifiers. Both NameIDs result in hashed values being sent, but one will always be different for every user and every session (transient), and the other will always be consistent for a given user and service. But importantly, neither will be easily associated to a given user based only on the Name ID value.

Configuring Reloadable Services for Shibboleth


Configuring Reloadable Services for Shibboleth IdP

Shibboleth Identity Provider Version 3 introduced the ability to reload individual services within the Shibboleth framework. Prior to IdP v3, if you wanted to onboard a new Service Provider by adding new <MetadataProvider> and <RelyingParty> elements, you would be required to restart the servlet container. Now, nearly every class of change that you may need to make to the Shib IdP can be done without restarting Jetty.

Reloadable Services

There are two functional methods to achieve this, either a direct request to the administrative handler URL:

http(s)://idp.example.org/idp/profile/admin/reload-service?id=[SOME SERVICE]

or by utilizing the included sample shell script which will make this call for you:

$ /opt/shibboleth-idp/bin/reload-service.sh -id [SOME SERVICE]

In each case [SOME SERVICE] represents the name of the Shibboleth service that you would like to reload, i.e. one of the service identifiers (id) listed below.

Access Control

In order to be able to make requests to the reloadable services identified above, you must ensure that you have white-listed the IP address of the host making the call. In the case of the reload-service.sh script, that includes ensuring that the interface of the IdP server itself has been whitelisted.

To do this, ensure that the IP addresses are listed within /opt/shibboleth-idp/conf/access-control.xml as in the following example:

<entry key="AccessByIPAddress">
    <bean id="AccessByIPAddress" parent="shibboleth.IPRangeAccessControl"
        p:allowedRanges="#{ {'127.0.0.1/32', '::1/128'} }" />
</entry>

Services

The following services can be reloaded:

Reloadable Service id Function
shibboleth.RelyingPartyResolverService RelyingPartyConfiguration resources for a new or migrated installation.
shibboleth.MetadataResolverService MetadataConfiguration resources.
shibboleth.AttributeResolverService AttributeResolverConfiguration resources.
shibboleth.AttributeFilterService AttributeFilterConfiguration resources.
shibboleth.NameIdentifierGenerationService NameIDGenerationConfiguration resources.
shibboleth.ReloadableAccessControlService AccessControlConfiguration resources.
shibboleth.ReloadableCASServiceRegistry Resources containing ServiceRegistry beans to be reloaded.

Example: Onboarding a new SP

In the following examples, we will only use reload-service.sh, however you can easily adjust the call to the HTTP GET as above.

After you've added the SP's <MetadataProvider> element to /opt/shibboleth-idp/conf/metadata-providers.xml you should first reload that service:

[root@idp.example.org shibboleth-idp]# ./bin/reload-service.sh -id shibboleth.MetadataResolverService

Configuration reloaded for 'shibboleth.MetadataResolverService'

Then, after you have added an appropriate attribute filter policy for this entity in attribute-filter.xml, you should reload that service:

[root@idp.example.org shibboleth-idp]# ./bin/reload-service.sh -id shibboleth.AttributeFilterService

Configuration reloaded for 'shibboleth.AttributeFilterService'

Lastly, presuming you need to configure a <RelyingPartyOverride> for this entity to adjust the particulars of the single sign on integration, you should reload that service:

[root@idp.example.org shibboleth-idp]# ./bin/reload-service.sh -id shibboleth.RelyingPartyResolverService

Configuration reloaded for 'shibboleth.RelyingPartyResolverService'

Common Issues

The most common issue we encounter with reloadable services is that calls to reload-service.sh fail, either because Access Control was not established properly, or because the IDP cannot properly make calls to http(s)://localhost/idp which is the default IDP_BASE_URL environment variable.

Setting up IDP_BASE_URL within your Shibboleth service's startup (typically /etc/default/jetty) script usually resolves this issue. For example, a typical startup script might need to look something like this:

export INST_BASE=/opt
export JAVA_HOME=$INST_BASE/java
export JAVA=$JAVA_HOME/bin/java
export JETTY_HOME=$INST_BASE/jetty
export JETTY_BASE=$INST_BASE/idp_jetty
export IDP_HOME=$INST_BASE/shibboleth-idp
export IDP_BASE_URL=http://idp.example.org/idp

Setting up a PHP OAuth Client

Setting up a PHP OAuth Client

Our firm usually recommends PHPLeague's OAuth2 client for PHP integrations. There's is sample code in the README.md, but basically

  • Include the relevant Client Key and Secret in the $Provider element, i.e.

      $provider = new \League\OAuth2\Client\Provider\GenericProvider([
      'clientId'                => 'fake-id-741648bdf4e3ffc8e1e3607898e16870',
      'clientSecret'            => 'fake-secret-548755e9e3717974f7a378cd25b24e85',
      'redirectUri'             => 'https://app.example.com/callback.php',
      'urlAuthorize'            => 'https://oauth.server.com/oauth2/authz/',
      'urlAccessToken'          => 'https://oauth.server.com/oauth2/access/',
      'urlResourceOwnerDetails' => 'https://oauth.server.com/userinfo/',
    ]);
    
  • Make the call to getAuthorizationURL() with the $Provider, which will redirect the user to the login page, and return back to your code with ?code=[stuff]. To include OIDC support, make sure you specify the OIDC scope (as well as any others that you care about):

    $options = [
      'scope' => ['openid profile email']
    ];
    

    and call getAuthorizationUrl with the $options array, like:

    $authorizationUrl = $provider->getAuthorizationUrl($options);
    
  • You can then use the value in $_GET['code'] to get an access token from the identity provider, like:

    $accessToken = $provider->getAccessToken('authorization_code', [
      'code' => $_GET['code']
    ]);
    
  • using that token you'll be able to use getAuthenticatedRequest's along with the $accessToken as needed if you need to pull something from a secured API on the identity server:

    $request = $provider->getAuthenticatedRequest(
      'GET',
      'https://oauth.server.com/some-api-endpoint',
      $accessToken
    );
    $entitlementResponse = $provider->getParsedResponse($request);
    
  • you can also just user information about the $resourceOwner that is returned (because of the scopes that we chose:

    $resourceOwner = $provider->getResourceOwner($accessToken)->toArray();
    

The access token has expiry information, etc. and there are functions for refreshing the token as needed. All in all it's really simple to do with PHP.

I avoid JS for OAuth, but I'm pretty sure that's just a personal preference. There are good javascript libraries as well.

Note: Loosely based off of this StackOverflow answer from the author.