Using ldap3 python module to manage Active Directory

I have found myself in situations where I have needed to manage Active Directory objects programatically, but have not been able to use the Powershell Active Directory module because pwsh / Jenkins workspaces do not support these functions because of the .Net requirements.

My solution to this problem was to use the python3 module ldap3.

https://ldap3.readthedocs.io/en/latest/

This post will provide simple practical examples of each of the below scenarios:

  • Binding with an AD server
  • Searching for objects
  • Querying object attributes
  • Creating groups
  • Adding users to groups
  • Deleting groups
  • Adding OU’s

Binding with an AD server

A bind to the LDAP server is required prior to executing any other commands. I suggest using the use use_ssl=True switch with the ldaps port set to 636 in order to secure communications.

import ldap3
from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups
server = Server('172.16.10.50', port=636, use_ssl=True)
conn = Connection(server, 'CN=ldap_bind_account,OU=1_Service_Accounts,OU=0_Users,DC=TG,DC=LOCAL','Passw0rds123!',auto_bind=True)

print(conn)
ldaps://172.16.10.50:636 - ssl - user: CN=ldap_bind_account,OU=1_Service_Accounts,OU=0_Users,DC=TG,DC=LOCAL - not lazy - bound - open - <local: 192.168.1.12:54408 - remote: 172.16.10.50:636> - tls not started - listening - SyncStrategy - internal decoder

Searching for user objects

Using the basic search function allows you to return a boolean value as to whether the AD object is present. objectclass can be changed to search the object type required (group,computer etc)

conn.search('DC=TG,DC=Local','(&(objectclass=user)(sAMAccountName=bobtest))')
True

To query the AD object in more depth you can call result and response_to_json() to a parseable set of details about the returned object.

response = json.loads(conn.response_to_json())
print(response)
{'entries': [{'attributes': {}, 'dn': 'CN=Bob Test,OU=1_Standard_Users,OU=0_Users,DC=TG,DC=LOCAL'}]}

Querying group membership

Sometimes it is neccessary to return additional attributes within an object. This requires the use of the ‘attributes’ element within the search function. Add an extra element to the search string as follows:

attributes=[‘<attributeName’])

Where <attributeName> is the relevant AD attribute ie accountExpires / logonCount etc etc

The example below pulls back the ‘memberOf’ attribute of an object.

conn.search('DC=TG,DC=Local','(&(objectclass=user)(sAMAccountName=bobtest))', attributes=['memberOf'])
True
response = json.loads(conn.response_to_json())
print(response)
{'entries': [{'attributes': {'memberOf': ['CN=DL_VRA_ADMIN,OU=0_Groups,DC=TG,DC=LOCAL']}, 'dn': 'CN=Bob Test,OU=1_Standard_Users,OU=0_Users,DC=TG,DC=LOCAL'}]}

Creating Groups

Creating security groups is a simple process using the ‘add’ function. The key components to adding a group are:

  • object_class: In this example we use ‘group’ to create a group, but the same principals would apply when adding users, simply change the class value.
  • attributes: A basic array of object attributes should be assembled. This can be as basic or as advanced as neccessary.
  • A new distinguished name for the group which requires creating.
dl_shortname = 'DL_EXAMPLE_SEC_GROUP'
dl_group_dn = 'CN=' + dl_shortname + ',OU=0_Groups,DC=TG,DC=LOCAL'
object_class = 'group'
attr = {
          'cn': dl_shortname,
          'description': 'This is a dummy description',
          'groupType':'-2147483644',
          'sAMAccountName': dl_shortname
}
conn.add(dl_group_dn , object_class , attr)
True
print(conn.result)
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'type': 'addResponse'}

Once the add function has been ran, printing the result will provide you with a status code. Throughout the ldap3 module, a return code of 0 is success.

Adjusting User Group Membership

ldap3 includes an extended Microsoft module which makes adding or removing users from groups a quick process. First, make sure you import the functions at the top of your script.

from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups
from ldap3.extend.microsoft.removeMembersFromGroups import ad_remove_members_from_groups as removeUsersInGroups

Once the function is imported (call it whatever you so wish), it can be called with a one liner

addUsersInGroups(conn, dn, group_dn)
or    
removeUsersInGroups(conn, dn, group_dn,fix=True)

where;

  • conn is the connection to the LDAP server established at the top of the page
  • dn is the name of the user account which requires its membership adjusting (in DN format)
  • group_dn is the name of the group to add or remove the user account (in DN format)

Deleting Groups

Following the format of the previous functions, the delete function allows objects to be removed from your directory. The only attribute required for succesful object removal is a group DN.

shortname = 'DL_EXAMPLE_SEC_GROUP'
group_dn = 'CN=' + shortname + ',OU=0_Groups,DC=TG,DC=LOCAL'
conn.delete(group_dn)

print(conn.result)
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'type': 'delResponse'}

Adding OU’s

Creating OU objects uses the .add function, much like creating and removing groups above. The main difference is that an OU doesn’t require additional attributes adding.

ou_path = 'OU=1_Dummy_OU,OU=0_Users,DC=TG,DC=LOCAL'
object_class = 'organizationalUnit'
    
conn.add(ou_path, object_class)
print(conn.result)
{'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'type': 'addResponse'}
if conn.result['result'] == 0:
    print('OU Object Created Successfully')

Hopefully these snippets give peeps some useful working examples of ldap3!

Leave a Reply