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!

2 Comments

  1. Permalink
  2. michael Permalink

    Hi, very nice article and trying to follow up. below an extraction of a powershell lambda i’m trying to convert ldap3 python.

    I tried it but i’m stucked due to lack of python knowledge. Could you assist me with this please?

    Kind Regards, Michael

    # PowerShell script file to be executed as a AWS Lambda function.
    #
    # When executing in Lambda the following variables will be predefined.
    # $LambdaInput – A PSObject that contains the Lambda function input data.
    # $LambdaContext – An Amazon.Lambda.Core.ILambdaContext object that contains information about the currently running Lambda environment.
    #
    # The last item in the PowerShell pipeline will be returned as the result of the Lambda function.
    #
    # To include PowerShell modules with your Lambda function, like the AWS.Tools.S3 module, add a “#Requires” statement
    # indicating the module and version. If using an AWS.Tools.* module the AWS.Tools.Common module is also required.

    #Requires -Modules @{ModuleName=’AWS.Tools.Common’;ModuleVersion=’4.0.5.0′}

    # Uncomment to send the input event to CloudWatch Logs
    # Write-Host (ConvertTo-Json -InputObject $LambdaInput -Compress -Depth 5)
    ####### Edit these Variables
    # Gets todays Date
    $date = Get-Date

    # Number of days it’s been since the computer authenticated to the domain
    $days = “-90”

    # Sets a description on that object so other admins know why the object was disabled
    $description = “Disabled by CCOE on $date due to inactivity for 90 days.”

    # This is the OU you are searching for Stale Computer accounts
    $ou = “OU=AWS,OU=xx,OU=Tenants,DC=xx,DC=xx”

    # This is where the disabled accounts get moved to.
    #$disabledOU = “OU=Disabled_Computers,DC=signalwarrant,DC=local”

    # Finding Stale Computers
    $findcomputers = Get-adcomputer –filter * -SearchBase $ou -properties cn, LastLogonDate |
    Where {$_.LastLogonDate -le [DateTime]::Today.AddDays($days) -and ($_.lastlogondate -ne $null) }

    # Disable the Stale Computer Accounts
    $findcomputers | set-adcomputer -Description $description –passthru | Disable-ADAccount

    # Find all the Stale Computer Accounts we just disabled
    #$disabledAccounts = Search-ADAccount -AccountDisabled -ComputersOnly -SearchBase $ou

    # Move the Disabled accounts to $disabledOU
    #$disabledAccounts | Move-ADObject -TargetPath $disabledOU

    Reply

Leave a Reply