LibrarySites.Banner

Using Sitecore with Microsoft Active Directory Lightweight Directory Services (AD LDS)

I received a question recently inquiring whether or not Sitecore is able to use Active Directory Lightweight Directory Services (AD LDS) as a Membership Provider. Not knowing much about AD LDS but having some familiarity with Active Directory, I started performing some research to determine first "what is AD LDS?", and how it might be leveraged for use with Sitecore.

What is Active Directory Lightweight Directory Services (AD LDS)?

As it turns out, AD LDS was formerly named Active Directory Application Mode (ADAM), but was renamed to AD LDS with the release of Windows Server 2008. AD LDS is a Lightweight Directory Access Protocol (LDAP) directory service that provides flexible support for directory-enabled applications, without the dependencies that are required for Active Directory Domain Services (AD DS). AD LDS provides much of the same functionality as AD DS, but it does not require the deployment of domains or domain controllers. It gives you the capabilities of a multi-master LDAP directory that supports replication without some of the extraneous features of an Active Directory domain controller (domains and forests, Kerberos, trusts, etc.). AD LDS is typically used in situations where you need an LDAP directory but don't want the administration overhead of AD (e.g. authentication for web applications or SQL databases). Its schema can also be fully customized without impacting the AD schema.

AD LDS uses the concept of instances, similar to that of instances in SQL. What this means is one AD LDS server can run multiple AD LDS instances (databases). This is another differentiator from Active Directory: a domain controller can only be a domain controller for one domain. In AD LDS, each instance runs on a different set of ports. The default instance of AD LDS listens on 389 (similar to AD). You can run multiple instances of AD LDS concurrently on a single computer, with an independently managed schema for each AD LDS instance.

Active Directory (AD) typically provides directory services for both the Windows Server operating systems and for directory-enabled applications. For the server operating system, AD stores critical information about the network infrastructure, users and groups, network services, and so on. In this role, AD must adhere to a single schema throughout an entire forest. AD LDS, on the other hand, provides directory services specifically for directory-enabled applications. AD LDS does not require or rely on AD domains or forests. However, in environments where AD exists, AD LDS can use AD for the authentication of Windows security principals.

AD LDS can also be synchronized with AD in the event that you wish to store user data in AD LDS that you can't or don't want to store in AD. Your application will point to the AD LDS instance for this user data, but you probably don't want to manually create all of these users in AD LDS when they already exist in AD. There are tools and methods available to aid with this synchronization that are beyond the scope of this blog post. 

See the "Further Reading" section at the end of this post for more resources concerning AD LDS.

Installing Active Directory LDS

I used the following article as an installation guide for AD LDS:

http://www.thegeekispeak.com/archives/28

I was able to use the article mostly as written, but here are a few notes about installation for the purposes of this post:

  • The LDS partition name should be something unique and follow the normal distinguished name syntax. I used dc=sitecore,dc=security,dc=lds. When translated to a domain name, this would be sitecore.security.lds.
  • I used the default option of Network Service Account
  • I added all available schemas to the Selected LDIF files section
  • All other settings used default values/selections

Configuring Active Directory LDS for Sitecore

This is where things started to get interesting. Sitecore already has an Active Directory module available for using AD as a membership/role/profile provider. Because AD and AD LDS share many similarities in both schema and function, I had hoped I could just plug in the Sitecore AD module, point it to my LDS instance and all would be well. While I found success using the AD role provider within the Sitecore AD module against LDS, I did encounter some issues when trying to use the AD membership provider against LDS.

Primarily, the issues I encountered revolved around schema differences between AD and LDS. Fortunately, a couple of the issues can be resolved simply through configuration/schema adjustments. The final change, however, requires reverse-engineering and a code change.

After you have AD LDS installed, there are a few basic procedures you need to perform:

Import the MS-adamschemaw2k8.ldf file using LDIFDE

This will add the sAMAccountName and ms-DS-User-Account-Control-Computed attributes to the base schema and allow them to be used by user objects in LDS. These attributes are used and referenced by the AD membership provider in the Sitecore AD module. While it is possible to configure the AD module to reference the userDisplayName attribute for usernames instead of the sAMAccountName attribute, the default is sAMAccountName and it doesn't hurt to import the full MS-adamschemaw2k8 schema in the event you'll be synchronizing LDS with AD. Also, the Sitecore AD Role Provider explicitly uses sAMAccountName when retrieving roles and role names.

  • Open a command prompt on the LDS server with administrator privileges.
  • Navigate to c:\windows\adam
  • Run the following command: ldifde -i -u -f ms-adamschemaw2k8.ldf -s localhost:389 -j . -c "cn=Configuration,dc=X" #configurationNamingContext
  • The import should only take a minute or two
  • After it has completed, open ADSI Edit (found under Administrative Tools)
  • Connect to your LDS instance (e.g. localhost:389)
  • After connecting, right click on the LDS server node and select Update Schema Now. This will clear the schema cache and load the schema changes you imported.

Create Users container

  • Using ADSI Edit, connect to your LDS instance (e.g. localhost:389)
  • Once connected, right click on the root container (e.g. DC=sitecore,DC=security,DC=lds), then select New -> Object...
  • Choose container from the class list
  • Name the object Users. Technically, you can name the object something other than Users if you'd like, but this will be the location where users are stored and defined.

Create an Administrator User

You need to create a user that is a member of the LDS Administrators role in order to connect to your LDS instance from Sitecore. This user doesn't need to correspond to a Sitecore content author or administrator, the user is essentially only needed for communication with LDS via the membership provider.

  • Right click on the Users container you created, then select New -> Object...
  • Choose user from the class list
  • Provide a username for the user object when prompted and ignore the More Attributes option
  • Right-click on the user object you just created and select Reset Password... use a strong password containing mixed case alphanumeric characters and special characters.
  • Right-click on the user object again and select Properties
  • Scroll to the msDS-UserAccountDisabled attribute and set the value of the attribute to FALSE, then 
  • Scroll to the sAMAccountName attribute and set the value of the attribute to the name of the user object you created
  • Scroll to the userPrincipalName attribute and set the value of the attribute to the name of the user object you created

Assign User to Administrator Role

  • Expand the Roles container
  • Right click on the Administrators object, then select Properties
  • Scroll to the Members attribute and click the Edit button
  • In the editor dialog, click Add DN...
  • When prompted, provide the distinguished name (DN) for the user you created earlier. The DN will be something like this: CN=myuser,CN=Users,DC=sitecore,DC=security,DC=lds
  • Ok out of the dialogs

Password Operations

Password operations such as setting a password on a new user or resetting a password for an existing user are not allowed, by default, over an unsecured connection (i.e. non-SSL and the connectionProtection attribute on the membership provider definition is set to None). To allow password operations over an unsecured connection, you will need to perform the following steps on the LDS server (courtesy of http://stackoverflow.com/questions/5616080/active-directory-lds-exception). Note: this is only relevant if you use an unsecured (i.e. non-SSL-encrypted) connection.

  • Open a command prompt in administrator mode
  • Type cd %windir%
  • Type dsmgmt and press enter
  • Type the following commands pressing enter after each line:
    • ds behavior
    •   connections
    •     connect to server localhost:389
    •     quit
    •   allow passwd op on unsecured connection
    •   quit
    • quit

This will allow you to perform password operations over an unsecured connection.

Using the Sitecore Active Directory Module

 

Installation

You can install the Sitecore Active Directory Module normally according to the documentation found on SDN: 
http://sdn.sitecore.net/Products/AD/AD11/Documentation.aspx

After that, pull down the code found here and compile it (note: this code is compiled against the Sitecore AD Module v1.1):
https://bitbucket.org/aweber1/sitecore-security-extensions 

Copy/reference the compiled Sitecore.SecurityExtensions.dll output to your solution.

Configuration

To use the LDS provider modifications, you'll need to change the membership provider you defined in the web.config as part of the Active Directory module installation. Change the type attribute from LightLDAP.SitecoreADMembershipProvider to Sitecore.SecurityExtensions.LDS.SitecoreLdsMembershipProvider, Sitecore.SecurityExtensions, as shown below:

 

<system.web>
    <membership>
        <providers>
            <add name="ad" type="Sitecore.SecurityExtensions.LDS.SitecoreLdsMembershipProvider, Sitecore.SecurityExtensions" ... />
        </providers>
    </membership>
</system.web>

 

Important: you'll need to use the credentials of the LDS Administrator user you created earlier within the LDS membership provider configuration element. If you don't, you'll likely see an error logged in the Sitecore log stating you're attempting to use an invalid username or password.

Completion

At this point, you should be able to login to the Sitecore desktop and interact with LDS using the User Manager and Role Manager as expected. I should note that my testing of this customized membership provider primarily consisted of standard CRUD operations - in other words, as always exercise caution using this without thorough testing of your own. 

Code Notes

Although there is quite a bit of code in the LDS membership provider project, the vast majority of it was copied and pasted from the Sitecore AD module via Reflector. Really, the only change I had to make was to the LightLDAP.SitecoreADMembershipProvider.GetMembershipUserFromSearchResult method. Unfortunately, just to support that minor change, all of the other code in the project is required due to a number of relevant methods and classes being marked as private, internal, or not virtual. Below is a summary of the change I made:

namespace Sitecore.SecurityExtensions.LDS
{
    public class SitecoreLdsMembershipProvider : MembershipProvider
    {
        .
        .
        .
         
        private MembershipUser GetMembershipUserFromSearchResult(ISearchResult res)
        {
            .
            .
            .
            //Original code:
            //if ((DataHelper.GetInt(res, ObjectAttribute.UserAccountControlComputed) & 0x10) != 0)            
            if ((DataHelper.GetInt(res, "msDS-User-Account-Control-Computed") & 0x10) != 0)
            {
                isLockedOut = true;
            }
            .
            .
            .
        }
         
        .
        .
        .
    }
}

As you'll notice, the change involved simply renaming the msDS-User-Account-Control-Computed to the proper name. Ideally, this could have been handled through configuration or even if the method was protected virtual instead of private. Alas, Reflector to the rescue!

I should also note that the simplicity of this change belies the volume of effort that was involved in determining why the Sitecore AD module code didn't work with LDS out-of-the-box. I wouldn't wish troubleshooting obscure COM and Active Directory errors upon anyone :)

Other Notes

  • If you receive an 'Invalid skip value in paging input' exception in the Sitecore User Manager, check the Sitecore log file for an exception occurring immediately before the invalid skip value exception. The invalid skip value exception is a result of no users being returned from the LDAP user query. The membership provider may swallow and log a separate exception that can be used to determine why no users are being returned from the LDAP user query.
  • The msDS-User-Account-Control-Computed attribute is attached to user objects via the systemMayContain attribute of the ms-DS-Bindable-Object object - which the user object indirectly inherits. In the Sitecore AD module, the msDS-User-Account-Control-Computed attribute is referenced as msDSUserAccountControlComputed but for LDS needs to be referenced as msDS-User-Account-Control-Computed
  • The SitecoreADRoleProvider.GetAllRoles() method explicitly uses the sAMAccountName attribute to get role names, it is not configurable to use the UserPrincipalName attribute (or any other attribute). Therefore, ensure that at least one role defined in LDS has the sAMAccountName attribute set to a non-empty value, else you'll receive an exception in the Sitecore Role Manager. Also, ensure that any roles defined in LDS that you also wish to view/select in Sitecore have a non-empty value in the sAMAccountName attribute.
  • The SitecoreADRoleProvider uses a different method of connecting to and retrieving data from LDS than the SitecoreADMembershipProvider. Therefore, when configuring the two providers via web.config, you will need to use credentials for a LDS Administrator user as the connectionUsername and connectionPassword attributes for the membership provider. For the role provider, you will need to use credentials for a local administrator account on the server hosting LDS.
  • I mentioned this earlier, but it bears repeating: Password operations such as setting a password on a new user or resetting a password for an existing user are not allowed, by default, over an unsecured connection (i.e. non-SSL and the connectionProtection attribute on the membership provider definition is set to None). To allow password operations over an unsecured connection, you will need to perform the following steps on the LDS server (courtesy of http://stackoverflow.com/questions/5616080/active-directory-lds-exception). Note: this is only relevant if you use an unsecured (i.e. non-SSL-encrypted) connection.
  • The attribute naming issue I encountered and which required code adjustments has been brought to the attention of the Sitecore AD module product team. They will be considering changes to the AD module to support LDS in a future release.

Further Reading

AD LDS Operations Guide:
http://technet.microsoft.com/en-us/library/cc816635(v=ws.10).aspx

Synchronizing AD and AD LDS:
http://blogs.technet.com/b/askds/archive/2012/11/12/adamsync-101.aspx
http://technet.microsoft.com/en-us/library/cc794836%28v=ws.10%29.aspx

Installing and configuring AD LDS:
http://www.thegeekispeak.com/archives/28
http://www.thegeekispeak.com/archives/64

More information:
http://www.thegeekispeak.com/ad-lds-101