Thursday, June 25, 2015

Syncing Active Directory with OpenAM datastore using LSC

Lately I have been tasked to set up an OpenAM server that should authenticate user against an existing AD server.


I have found three usual approaches to this problem.

  • Use the AD as a OpenAM user store 
  • The AD authentication module can be used to authenticate user against AD. 
  • Use some kind tool could be used to synchronize AD and OpenAM user store.

The problem with the first one is that it require some changes to the AD which I am not allowed to implement.


The second one works fine if all you want to do is authenticate the user, as far as I've seen there aren’t any good ways to get more information about the user for example email address and phone number. The module can be configured to populate the default user store in OpenAM with user attributes from AD, but this will only happen on the first login. If the user changes email or phone number in the future the changes won't be reflected in OpenAM.


I ended up using both AD authentication module and synchronization. The authentication module for authentication directly against AD and synchronization for getting the attributes. For synchronization I used LSC.


In the next section I will describe the LSC configuration. This is not a beginners guide to LCS so if you are new to it, I recommend you checking out some of the tutorials here http://lsc-project.org/wiki/documentation/2.0/start


LSC configuration
In my OpenAM configuration different groups have different authorization so I want to sync both users and groups. 


This is accomplished by defining two tasks.


lsc.tasks = ADuser, ADgroup

Syncing users
First the properties for the srcService

lsc.tasks.ADuser.srcService = org.lsc.jndi.SimpleJndiSrcService

lsc.tasks.ADuser.srcService.baseDn = cn=Users

# Filter to list all entries to synchronize
lsc.tasks.ADuser.srcService.filterAll = (&(sAMAccountName=*)(objectClass=user)(memberOf=CN=OpenAMUsers,CN=Users,DC=capra,DC=no))

# Attributes to read from all entries used to match objects between source and destination
lsc.tasks.ADuser.srcService.pivotAttrs = sAMAccountName

# Filter to read one entry to synchronize, based on pivotAttrs above
# This filter may contain one or several pivotAttrs defined above, like "{attributeName}"
lsc.tasks.ADuser.srcService.filterId = (&(objectClass=user)(|(sAMAccountName={sAMAccountName})(sAMAccountName={uid})))

# Attributes to read from each entry used to read and write data
lsc.tasks.ADuser.srcService.attrs = mail cn sAMAccountName sn givenName

Short description. The filterAll attribute is used to pick out all users of interest. In this case I'm specifying objectType users and they should have an sAMAccountName. I have also placed all the users to be synced in a group called OpenAMUsers as the users to be synced is only a subset of the user base. 


The name to look at when matching users is the sAMAccountName and is specified in pivotAttrs. The filterId is a bit tricky In order to understand how to define this, I recommend reading  http://lsc-project.org/user-guide/#matching-up-entries. The attr is the attributes I would like to read from the source.

The dstService
lsc.tasks.ADuser.dstService = org.lsc.jndi.SimpleJndiDstService
lsc.tasks.ADuser.dstService.baseDn = ou=People

# Filter to list all entries to synchronize
lsc.tasks.ADuser.dstService.filterAll = (&(uid=*)(objectClass=inetOrgPerson))

# Attributes to read from all entries used to match objects between source and destination
lsc.tasks.ADuser.dstService.pivotAttrs = uid 

# Filter to read one entry to synchronize, based on pivotAttrs above
# This filter may contain one or several pivotAttrs defined above, like "{attributeName}"
lsc.tasks.ADuser.dstService.filterId = (&(objectClass=inetOrgPerson)(|(uid={sAMAccountName})(uid={uid})))

# Attributes to read from each entry used to read and write data
lsc.tasks.ADuser.dstService.attrs = mail cn uid objectClass sn givenName inetUserStatus

On the destination(OpenAM) all users lies under ou=People so base dn is ou=People.
FilterId sepecifies user of interest in the destination. The attrs in this case is the attributes I would like to update.


lsc.tasks.ADuser.dn = "uid=" + srcBean.getAttributeValueById("sAMAccountName") + ",ou=People"

In OpenAM the users are by default identified with the uid attribute. I thought it wise to keep this so the DN is concatenated with the account name and the user base dn.

Sync options specify how to update the info in destination.


# syncoption for the uid attribute: At creation the uid is set to sAMAccountName from src
lsc.syncoptions.ADuser.uid.action = K
lsc.syncoptions.ADuser.uid.objectClass.create_value = srcBean.getAttributeValueById("sAMAccountName")

#Insert objectClass
lsc.syncoptions.ADuser.objectClass.action = K
lsc.syncoptions.ADuser.objectClass.create_value = "iplanet-am-auth-configuration-service";"sunIdentityServerLibertyPPService";"sunAMAuthAccountLockout";"sunFederationManagerDataStore";"iplanet-am-managed-person";"iPlanetPreferences";"sunFMSAML2NameIdentifier";"person";"inetorgperson";"organizationalperson";"inetuser";"iplanet-am-user-service";"top"

#Set defualt sn
lsc.syncoptions.ADuser.sn.action = F
lsc.syncoptions.ADuser.sn.default_value = "noSN"

#Setting active user
lsc.syncoptions.ADuser.inetUserStatus.action = K
lsc.syncoptions.ADuser.inetUserStatus.default_value = "Active"

Pretty much straight forward, the uid is set to sAMAccountName, this is set to only update at creation time. This is the attribute to match users and it is not changed. 


The objectClass is updated at creation time with the classes users use in OpenAM. This is needed for OpenAM to recognize the users. 


For some reason the OpenAM data store schema demands SN to be set, so if it is not set in AD it is set to noSN. 


User status is set to Active. 


All attributes specified in the attrs settings with the same name in source and destination is automatically updated if nothing else is configured in sync options. In this case givenName, mail, cn.


Syncing groups
# Base DN for searches in the directory
lsc.tasks.ADgroup.srcService.baseDn = cn=Users

# Filter to list all entries to synchronize
lsc.tasks.ADgroup.srcService.filterAll = (&(cn=*)(objectClass=group)(memberOf=CN=OpenAMGroups,CN=Users,DC=capra,DC=no))

# Attributes to read from all entries used to match objects between source and destination
lsc.tasks.ADgroup.srcService.pivotAttrs = cn

# Filter to read one entry to synchronize, based on pivotAttrs above
# This filter may contain one or several pivotAttrs defined above, like "{attributeName}"
lsc.tasks.ADgroup.srcService.filterId = (&(objectClass=group)(cn={cn}))

# Attributes to read from each entry used to read and write data
lsc.tasks.ADgroup.srcService.attrs = cn member

As with the users, to make it easier to find the groups I want to sync, I have added the groups to a group, OpenAMGroups. As you can see in the filterAll property, only the groups in OpenAMGroups are synced.

The expression in the filterId becomes a lot easier to understand if the identifying attributes have the same name at source and destination. Again have a look at http://lsc-project.org/user-guide/#matching-up-entries for more information on filterId.


# Base DN for searches in the directory
lsc.tasks.ADgroup.dstService.baseDn = ou=groups

# Filter to list all entries to synchronize
lsc.tasks.ADgroup.dstService.filterAll = (cn=*)

# Attributes to read from all entries used to match objects between source and destination
lsc.tasks.ADgroup.dstService.pivotAttrs = cn

# Filter to read one entry to synchronize, based on pivotAttrs above
# This filter may contain one or several pivotAttrs defined above, like "{attributeName}"
lsc.tasks.ADgroup.dstService.filterId = (cn={cn})

# Attributes to read from each entry used to read and write data
lsc.tasks.ADgroup.dstService.attrs = cn uniqueMember objectClass

The attributes cn, uniqueMember and objectClass. UniqueMember is OpenAMs attribute for members and the object class needs to be set in order for the groups to be recognized by OpenAM.

lsc.tasks.ADgroup.dn = "cn=" + srcBean.getAttributeValueById("cn") + ",ou=groups"

New DN is concatenated.

# Set default delimiter for multiple values for an attribute.
# This is normally a semi-colon (;) but can be problematic when writing complex JavaScript
lsc.syncoptions.ADgroup.default.delimiter = $

The delimiter is set to be $ as semicolon is used in the more complex javascript expression below.

#Insert objectClass
lsc.syncoptions.ADgroup.objectClass.action = K
lsc.syncoptions.ADgroup.objectClass.create_value = "groupofuniquenames";"top"

Object class is set to standard values.

#members syncing
lsc.syncoptions.ADgroup.uniqueMember.action = F
lsc.syncoptions.ADgroup.uniqueMember.force_value = var uniqueMember = new Array();\
var members = srcBean.getAttributeValuesById("member").toArray();\
for (var i = 0; i < members.length; i++) {\
        var member = members[i];\
        member.replace(",CN=Users,DC=capra,DC=no", ",ou=people,dc=opensso,dc=java,dc=net").replace("CN", "uid");\
        uniqueMember.push(member);\
}\
members

Here is the best part, it shows some of the power in LSC.
What we need to do here is to take all the member attribute entries and change the dn before syncing.  


Every property value can be a javascript expression so we simply get the attributes, run them through a loop, do some string manipulation and return the new list.

This is the configuration I used. It works fine on OpenAM 10.0.0 and Windows Server 2008 R2.

If there are any questions or problems, feel free to post a comment.

No comments:

Post a Comment