sub-title

Also check Orama's Quora and Orama's GitHub
I shall not claim to know so much, but only that I learn new things everyday

Sunday, 24 April 2022

System Administration Made Easy – Automating the Directory Service using Python and LDAP

Introduction

Today’s System Administrator is lucky that many of the repetitive and boring system administration tasks can be automated – but only if they have the skills to do automation. In whatever Operating System environment one is working, automation possibilities are abundant using various tools.

First, Python – a multi-purpose language that can be used to solve any problem that has a logical solution – is a handy tool for the System Admin. Aside from Python, one can use BASH (for Linux) or PowerShell (for Windows). Python is my favorite.

In this post, I will talk about Automating the Active Directory. The main motivation is simple: I have witnessed way too many instances where System Admins waste a lot of time by manually doing trivial tasks that must be automated in this era –  sometimes even justifying the hiring of temporary staff to accomplish such tasks.

Since Management may not know that such tasks could be automated, they go ahead and even approve temporary hires. What a waste of resources!

Let me digress a bit and say something different. If a System Admin still manually checks each of their several servers one by one and records the status in an Excel template, then something is not right. I still see this happening. Imagine checking 20 servers every morning and recording the free disk space, free memory, running services, etc. Using Python, I once developed a network monitoring tool similar to Nagios that does the above in a more versatile manner, and saves a lot of valuable time, but that is not the story for today.


The Typical Problems


The background is that the organization for which I work has 3,000 employees. On a Windows platform, each of the 3,000 employees has a user account in the Active Directory, which they use for accessing the network.

Problem 1

The 3,000 accounts were temporarily grouped by department into nine groups as Group1, Group2, Group3 … Group9. Now, the System Admin wants to re-group the accounts using the nine actual departmental names, e.g. Technical. Marketing, Sales, etc, Also at the end of every month, Management would like to get an automated email with a list of all user accounts that were added or disabled during that month, including the groups to which they belong.

Problem 2

Out of the 3,000 staff, 500 have expired employment contracts, and therefore their domain (or computer) accounts have to be disabled. The task is to disable all user accounts for staff whose employment contracts have expired. This is possibly a more interesting problem to solve because the staff data is available in a separate operational database (e.g. in the ERP system). The ERP database contains user account names which is the same as the AD account name.

Problem 3

There is a routine exercise that all the 3,000 staff are mandated to perform at the end of every month. Some staff, however, do not comply in a prompt manner, while some others do not comply at all. In order to enforce compliance, management wants to implement a system which automatically denies defaulting staff access to the corporate network. Disabling such staff account would be an acceptable penalty according to Management, but it would have other undesirable consequences. Therefore, Management has approved that such defaulters be denied the rights to access only certain specific systems within the corporate network. The rights can be restored after the staff has complied. This way, staff would be compelled to comply. A database for the monthly exercise exists, and it is easy at any one time to filter out staff who have either complied or not.


Surprise, Surprise


You would be surprised at how many System Admins would go native and manually change the account groups one by one (for Problem 1), or manually disable accounts (for Problem 2), or manually modify access rights (Problem 3).

Yes, I mean it, because I have interacted with and interrogated System Admins, and most (in fact all) of the ones I interacted with would go native. This begs the question: where does one get the time to do such a manual task? But it happens nonetheless.

On the other hand, I bet there are equally many System Admins who would go the professional way of automation.


Directory Service and LDAP


Since Problems 2 and 3 are more interesting – and very similar – we shall see how to solve Problem 3 here using Python and LDAP.

First, a Directory Service manages network resources. LDAP (Lightweight Directory Access Protocol) is a protocol for accessing and querying a Directory Service. LDAP does querying of the Directory Service database using LDAP queries.

The Directory Service for Windows is called Active Directory (AD). AD provides an interface for organizing and managing objects on a shared network—meaning desktop and laptop computers, devices, printers, and services, as well as user and user groups. Embedded within this, users or groups of users are assigned a set of privileges that afford them access to information and objects in the directory.

ADs are structured around domains, trees, and forests. At the lowest level, domains contain sets of objects. Domains are defined as a logical group of network objects, such as computers, devices, or users, that share the same AD database.

The importance of AD cannot be overemphasized. Knowing how to query the AD is a great asset for the System Admin. Understanding LDAP query is a first step in automating the AD.

Some very useful examples of LDAP query (courtesy of this website) are below:

1. List AD objects with common name=rorama and surname=orama: (&(cn=rorama)(sn=orama))
2. List all users named richard: (&(objectClass=user)(objectCategory=person)(cn= richard))
3. Search for administrators in groups Domain Admins, Enterprise Admins: (objectClass=user)(objectCategory=Person)(adminCount=1)
4. List disabled user accounts: (objectCategory=person)(objectClass=user)(useraccountcontrol:1.2.840.113556.1.4.803:=16)
5. List users with Password Never Expires option enabled: (objectcategory=user)(userAccountControl:1.2.840.113556.1.4.803:=65536)
6. List users with an email: (objectcategory=person)(mail=*)
7. List users with empty email: (objectcategory=person)(!mail=*)
8. List users with department Marketing: (&(objectCategory=person)(objectClass=user)(department=Marketing))
9. List users who are members of a group DataManagersGroup: (&(objectclass=user)(samacccountname=*)(MemberOf=CN= DataManagersGroup,OU=Groups,OU=UK,DC=theitbros,DC=com))
10. List groups that a user belongs to: (&(objectCategory=group)(member=CN=rorama,OU=Employees,DC=theitbros,DC=com))
11. List all disabled computer accounts: (&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=2))
12. List all Windows 10 computers: (objectCategory=computer)(operatingSystem=Windows 10*) 
13. List all Domain Controllers: (&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))

14. List all member domain servers (except DCs: (&(objectCategory=computer)(operatingSystem=*server*)(!userAccountControl:1.2.840.113556.1.4.803:=8192)) 
15. List all MS SQL Server instances in AD: (&(objectCategory=computer)(servicePrincipalName=MSSQLSvc*)) 
16. List groups created in a specific period: (objectCategory=group)(whenCreated>=20200101000000.0Z&<=20201201000000.0Z&)

17. List empty groups: (objectCategory=group)(!member=*)
18. List color printers on a specific print server published in the AD: (uncName=*lon-prnt*)(objectCategory=printQueue)(printColor=TRUE)

See this for userAccountControl


The Algorithm for solving  Problem 3


The pseudocode for the algorithm for solving Problem 3 is as follows:
1. connect to the database
2. select all staff who have not complied with the monthly requirement
3. connect to the AD service
4. empty all members of the group named Defaulters 5. loop through the list of staff who have not complied 6. in each iteration: add the user account to the group named Defaulters
7. disconnect the AD service 8. use Group Policy (Software Restriction Policy, SRP) to separately configure access rights for the group named Defaulters

#. you can replace 5 and 6 with one line: add all members got from bullet 2 to the group named Defaulters

This solution is very effective, yet it can be both simple and complex at the same time. It also helps to bridge the "possibility" divide where some techies usually claim that something is impossible (yet it is possible) to achieve programmatically, leading them to resort to manual ways of doing things.


The Code for solving  Problem 3 using Python and LDAP

Since I implemented the above solution for a specific requirement, parts of my Python/LDAP scripts need generalization before sharing. Also, as I become cleverer everyday, I learn how to improve my algorithms, so I have made some code changes in the original project.

Below is a slightly revised version but not tested after revision. With minimal changes, it should be able to solve Problem 3 (and even Problems 1 and 2). Note that I have not included all required imports.

# 1. connect to the database
# pip install pyodbc
import pyodbc cnxn = pyodbc.connect('DRIVER={SQL Server};SERVER=database_server_name;DATABASE=database_name;UID=***;PWD=***') cursor = cnxn.cursor() # 2. select all staff who have not complied with the monthly requirement (defaulters) query = cursor.execute(""" select username from database """) defaulters = [r.username for r in query.fetchall()] #list of defaulters # 3. connect to the AD service
# pip install ldap3
import sys, ldap3
from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, AUTO_BIND_NO_TLS, SUBTREE, MODIFY_REPLACE server = Server(ad_server_name, get_info=ALL) conn = Connection(server, user='{}\\{}'.format(domain_name, user_name), password=password, authentication=NTLM, auto_bind=True) conn.bind() # don't forget to unbind connection after finishing 4. empty all members of the group named Defaulters (identified as defaultersDn) from ldap3.extend.microsoft.removeMembersFromGroups import ad_remove_members_from_groups as removeMembers defaultersDn = "CN=Defaulters,OU=Defaulters,DC=***,DC=local" #list of users who are members of Defaulters conn.search('dc=***,dc=local'.format(domain_name), "(&(objectClass=user)(memberOf=Defaulters))") originalDefaulters = [user.distinguishedName for user in conn.entries] removeMembers(conn, originalDefaulters, [defaultersDn], True) 5. loop through the list of staff who have not complied (defaulters) 6. in each iteration: add the user account to the group named Defaulters (identified as defaultersDn) from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addMembers newDefaulters = [] for row in defaulters:     conn.search('dc=***,dc=local'.format(domain_name), "(&(objectClass=person)(sAMAccountName=%s))" % row, attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES])     if conn.entries[0].userAccountControl == 512: #if account is active/enabled
        newDefaulters.append(str(e.distinguishedName))
        #if instead you wanted to disable user accounts, then you would use the following to modify accounts
        #conn.modify(str(e.distinguishedName), {'department': [(MODIFY_REPLACE, ['Sales'])], 'userAccountControl': [(MODIFY_REPLACE, [2])]}) #2 means account disabled; what does 514 mean?? #newDefaulters = *** #find a shortcut to replace the above loop addMembers(conn, newDefaulters, [defaultersDn], True)


7. disconnect the AD service
conn.unbind()
8. use Group Policy (Software Restriction Policy, SRP) to separately configure access rights for the group named Defaulters


Conclusion


If you look around yourself in any environment, there are too many of such problems as above that require automation, yet in today’s world you will still find techies doing manual stuff. The power of Python for scripting and automation is limitless. Virtually, you can automate anything that follows a logical sequence.

Having worked in many environments to-date, I have seen that these are common problems that are abundant everywhere. The solutions lie in acquiring the right skills and being innovative as a problem-solver.

Finally, this is just another classic example of why I chose Python instead of R for data analytics. With R knowledge, I would not be able to solve such system administration problems. But with Python, the limit is beyond the sky.

No comments:

Post a Comment