Musings of a PC

Thoughts about Windows, TV and technology in general

Tag Archives: security

Security and Email groups in LDAP

After working with Active Directory since 2000, I’ve clearly become a bit spoiled with the ability to create a group in AD that serves the purpose of both a security group and a distribution group with just one checkbox. Why do I say that? Because in LDAP, there isn’t a commonly used way of achieving that same goal.

LDAP, or Lightweight Directory Access Protocol, is an Internet protocol that services can use to look up information from a server. Active Directory makes use of LDAP which is why you’ll come across terms common to both such as schema.

For the UNIX world, the commonly used schemas are defined in RFCs. For example, the most common objectClass used to define a person – inetOrgPerson – is defined in RFC 2798. This is a really great class to use for storing personal information in an LDAP directory because it has attributes for all of the important stuff you might want to know about someone.

When it comes to groups, though, things get a bit tougher. There is posixGroup which is a good class to use for security needs because it stores a group ID, a description and the members of the group. Rather surprisingly, there isn’t an accepted standard for defining a distribution or email group. There are classes for defining groups of users, such as groupOfNames, groupOfUniqueNames and groupOfMembers. They each have their slight differences and which one(s) you use typically comes down to either personal preference or the tools you are going to be using to manage those groups.

Another curious aspect about groups in LDAP is that there are differences in how the members are represented. For example, posixGroup uses an attribute called memberUid and the value is just that – the uid of the member of the group. groupOfUniqueNames, by comparison, uses an attribute called uniqueMember and the value is the distinguishedName of the member. One of the benefits of using the distinguishedName is that it allows groupOfUniqueNames to contain other groups as members, which posixGroup does not.

So when it comes to trying to maximise the value of a group’s membership by using the group for dual-purposes, i.e. security and email, what can you do? One option is to define your own objectClass and add it to the schema on the LDAP server. That is essentially what Microsoft did but the problem then is that your tools probably won’t know how to work with that class unless you can modify the tools.

Given that the objectClasses do represent members in different ways makes any attempt to “merge” or “overlay” multiple objectClasses to get the desired result is also likely to fail.

For myself, in the end, I decided that I would use posixGroup as the definitive representation of a group and then have a script that reads the various posixGroup groups and creates groupOfUniqueNames groups to match those groups. I wrote the script in Perl and it is at the end of this blog.

Here is a bit more detail about how the script works and how I’ve got my LDAP server set up …

I have an organizationalUnit (OU) called groups, below which I have an OU called mailing and an OU called security. The idea should be clear but all of my posixGroup groups go into ou=security and the script creates the mailing groups in ou=mailing. The script can be used in two ways:

  1. Full scan of the security OU. It looks at each of the groups in turn and processes it.
  2. Processing of one security group by specifying the cn value on the command line. This functionality is primarily there for use with LDAP Account Manager Pro. This great web-based product allows the administrator to define custom scripts that get run at specified trigger points. In my case, I get it to run the script, passing the cn value, when a group is created or modified, thus ensuring the email group is kept up-to-date.

The logic behind processing a security group is as follows:

  • Get the attributes we need from the security group: modifyTimestamp, description, displayName, mail, owner and memberUid.
  • If there aren’t any members, we delete the corresponding email group. This is because groupOfUniqueNames has to have at least one member. If you want to use groupOfMembers instead, that restriction goes away and the script could be modified accordingly.
  • If the email group already exists and its modifyTimestamp is newer than that of the security group, we don’t do anything else because the implication is that it was created by the script after the security group was created/modified.
  • The next step is to delete the email group. This is done rather than trying to figure out the differences in group membership. If you want to get fancy with the code, go ahead but this works for me Smile.
  • The final step is to create a new email group, specifying the attributes and members retrieved from the security group.

A few notes about the attributes: the posixGroup class doesn’t allow you to specify a display name (displayName), email address (mail) or group owner (owner). To permit that, I use the objectClass extensibleObject which allows you to add any attribute defined in the schema. LDAP purists tend to frown on this because it could lead to errant attributes being added. If you are concerned, you could define your own objectClass as an auxiliary class in order to allow just those three attributes to be used. Alternatively, the script will work without them since displayName and owner aren’t strictly necessary and the script can auto-create an email address by adding the email domain to the end of the existing cn value.

For the email groups, I again use extensibleObject because groupOfUniqueNames doesn’t allow a display name or email address. The email address is clearly required if you want this to work as an email group and the display name may be required if you are, for example, syncing with Google (which was my requirement) and you want the group to have a “nicer” name than just the cn value. Again, if you don’t like the idea of allowing all attributes to be added, you could define your own objectClass and amend the script accordingly.

Final comments:

  • this is my first Perl script and I have been quite lazy in that I have hard-coded the various domain bits into the script. Feel free to improve and, if you want, share back!
  • I’ve not used SSL in the connection because the script runs directly on the LDAP server. It is quite straightforward to amend the script to use LDAPS and there are examples on the web on how to do that.
  • The script assumes, when converting from memberUid to uniqueMember that all of the UIDs exist in the same OU, namely ou=staff,ou=accounts,dc=example,dc=com. It should be fairly straightforward to extend the script so that it searches for the UID and finds the DN that way.

use strict;

use Net::LDAP;

# See if a group name has been passed on the command line, e.g. from

# LDAP Account Manager

my $groupMatch = "";

# $#ARGV is -1 if no parameters, 0 if 1 parameter, etc. We only

# look for one group name.

if ($#ARGV == 0)

{

$groupMatch = $ARGV[0];

}

# Create a connection to the LDAP server

my $ldap = Net::LDAP->new ( "<LDAP server>" ) or die $@;

my $mesg = $ldap->bind ( "account with appropriate write privs",

password => "account's password",

version => 3 );

my $result = $ldap->search ( base => "ou=security,ou=groups,dc=example,dc=com",

filter => "cn=*",

attrs => ['cn', 'description', 'displayName', 'mail', 'memberUid', 'owner', 'modifyTimestamp'] );

print "Got ", $result->count, " entries from the search.\n";

# Walk through the entries

my @entries = $result -> entries;

my $entr;

foreach $entr ( @entries ) {

my $thisCN = $entr->get_value("cn");

# Only process the group if either we are processing them

# all, or the group name matches.

if (($groupMatch eq "") || ($groupMatch eq $thisCN))

{

print "DN: ", $entr->dn, "\n";

my $deleteEmailGroup = 1;

my $buildEmailGroup = 1;

my $thisModify = $entr->get_value("modifyTimestamp");

my $thisDescription = $entr->get_value("description");

my $thisDisplayName = $entr->get_value("displayName");

my $thisMail = $entr->get_value("mail");

my $thisOwner = $entr->get_value("owner");

my $memberRef = $entr->get_value ( "memberUid", asref => 1 );

if ($memberRef == undef)

{

# No members means we don't build a new email group, regardless

# of timestamps, etc. We do still try to delete an existing email

# group though.

$buildEmailGroup = 0;

}

else

{

# We have members in the security group so now we check timestamps

# so that we only create a new email group if the security group has

# been modified more recently.

# See if the email group exists already and, if it does, when was it

# modified? There is no point in creating the new email group if it

# was modified after the security group.

my $emailGroup = $ldap->search ( base => "ou=mailing,ou=groups,dc=example,dc=com",

filter => "cn=$thisCN",

attrs => ['modifyTimestamp']);

if ($emailGroup->count == 1)

{

my @emailEntries = $emailGroup -> entries;

my $emailEntry = @emailEntries[0];

my $emailModify = $emailEntry->get_value("modifyTimeStamp");

if ($thisModify > $emailModify)

{

print "... security group is newer\n";

}

else

{

print "... email group is newer\n";

$deleteEmailGroup = 0;

$buildEmailGroup = 0;

}

}

else

{

print "... email group doesn't exist.\n";

}

}

if ($deleteEmailGroup)

{

print "  ... deleting old email group\n";

$mesg = $ldap->delete("cn=$thisCN,ou=mailing,ou=groups,dc=example,dc=com");

# If we got an error from that, print the error and don't try to

# create the replacement group

if ($mesg->code() != 0 && $mesg->code() != Net::LDAP::Constant->LDAP_NO_SUCH_OBJECT)

{

print "  ... error while deleting group: ", $mesg->error(), " (code ", $mesg->code(), ")\n";

$buildEmailGroup = 0;

}

}

if ($buildEmailGroup)

{

# If we have members in the group, create a new email group

my $entry = Net::LDAP::Entry->new();

$entry->dn("cn=$thisCN,ou=mailing,ou=groups,dc=example,dc=com");

$entry->add('cn' => $thisCN,

'objectClass' => [ 'groupOfUniqueNames', 'extensibleObject' ]

);

# If we have an email address set that, otherwise make one up

if ($thisMail)

{

$entry->add('mail' => $thisMail);

}

else

{

$entry->add('mail' => "$thisCN\@example.com");

}

# If we have a description, display name or owner, set them

if ($thisDescription)

{

$entry->add('description' => $thisDescription);

}

if ($thisDisplayName)

{

$entry->add('displayName' => $thisDisplayName);

}

if ($thisOwner)

{

$entry->add('owner' => $thisOwner);

}

# For each of the memberUid entries, add a uniqueMember attribute

# $memberRef is a reference to the array, so dereference it

my @members = @{ $memberRef };

foreach ( @members ) {

print "  ... adding $_\n";

$entry->add('uniqueMember' => "uid=$_,ou=staff,ou=accounts,dc=example,dc=com");

}

#         $entry->dump();

print "  ... creating group\n";

$mesg = $entry->update( $ldap );

if ($mesg->code() != 0)

{

print "  ... error while creating group: ", $mesg->error(), "\n";

}

}

print "\n";

}

}

$ldap->unbind;

Advertisements