A bit of history first,
I was looking into automating user account management at my place of work. We have quite a large number of user accounts that change often, being K-12 school district, and have a few systems, each of which tracks its own set of user accounts. These primarily include our Student Information System, Powerschool, a Microsoft Active Directory domain (we are primarily a Windows-PC based network), and our Google Apps for Education domain. We looked into various single-sign on implementations and they were deemed either too expensive, too complex to implement, or lacking in certain features that we required.
It has always bothered me that creation of new user accounts required separate entry of the same data into multiple databases. This manual process can be unnecessarily slow and prone to consistency issues.
So I decided to write some code that synchronizes the important information between the disparate databases. The language I chose for this, mostly out of desire to learn it, is Python and the operating system is Linux. Being a larger school district, we do have special licensing with Microsoft which basically allows us an unlimited number of virtual server 2008 or 2012 R2 servers, as well as MSSQL Server databases and development tools for .NET … Naturally, from the perspective of rapid development, C# or VB/.NET would probably be the first choice because of it’s robust libraries for integration with Active Directory. I’m guessing Powershell would actually do a really good job of this too. That said, I wanted this tool to be flexible enough to work with (or at least be modified to work with) other LDAP databases on free and open source platforms as well as with proprietary solutions. That, along with my desire to expand my Linux OS skills and a new programming language led to the Linux/Python implementation decision.
All had been going wonderfully.. I used the cx_oracle Python module to integrate with the Powerschool database and pull the data into the accounts database which I have hosted on a MySQL/Ubuntu VM. I had teacher and student account information syncing from Oracle to the accounts database and I had implemented a module using python-ldap to create user accounts and populate them into the correct AD security groups and OUs based on what information had been entered on the users into Powerschool.
At this point I ran into the dilemma this article is about. Some of our user accounts need to be set so they cannot change their own passwords. Unfortunately, this particular option, as I found out while trying to implement, is not directly manipulable through setting a specific LDAP attribute.
In Active Directory, the “user cannot change password” option is controlled by an access control list. Access control lists of this type are seen in use in a lot of Windows-based solutions – they’re usually attached to NTFS-based file level security, but are also used in Active Directory to define access and auditing rights on objects. They are typically attached to a larger structure, called a “Security Descriptor”, which contains additional security information (such as the owner) on the object. Whenever you modify permissions to an object, you are modifying the one of the two ACLs located within the Security Descriptor.
The issue I ran into with Python over Linux is a generic LDAP connection does not provide you with Microsoft-specific methods for modifying the security descriptor attribute. Your options for doing this appear to be limited to using ADSI’s WinNT provider, which provides an LDAP-like interface for querying AD with additional functionality, or to use Win32 COM calls. While these options ARE doable with Python, they are only doable with Python running on a Windows machine.
Being somewhat stubborn about sticking with the Linux accounts server option, I did some digging and found that Microsoft does have some excellent documentation on how a security descriptor is structured. I also discovered that, when opening an LDAP query with an account with domain administrative privileges (Unfortunately… This may turn some admins off to the idea), you can directly query the security descriptor attribute, ‘nTSecurityDescriptor’ from AD objects.
Microsoft’s information on security structures, including security descriptors, ACLs and more, can be found at the following link:
With this knowledge at hand, I set about writing some Python that is able to read and modify Active Directory’s nTSecurityDescriptor attribute as queried through a standard LDAP connection. I want to post the code here, in the hopes that some other folks may find this useful in the future. As it is currently implemented, I have successfully used it to set/unset the ‘user cannot change password’ flag for AD user accounts.
First of all, as I mentioned, the account querying AD must be a member of ‘Domain Admins’ in order to query the nTSecurityDescriptor attribute. I tried locating the specific permission necessary but was unable to. Use of a service account with ‘Domain Admin’ rights may be unacceptable for certain applications. I won’t go into too much detail on mitigating the risk in using an account with admin rights, as there are other individuals who would be more qualified than I to do so, however I have taken the following steps:
- I use LDAPS for the connection.
- I set a very strong password on the service account, and this is the only purpose I have for this particular service account.
- The server running the queries is located on the same network segment as the Active Directory domain controller that it is connecting to. Ideally this network is isolated from any public, non-administrative network traffic.
Secondly, this code should not be considered ‘production’ code. First of all, I am not a professional developer by any means, and I’m sure those of you who are will recognize that when you see how I’ve structured the code (I’m sure there must be more efficient, better ways to implement this). If you use this code in your environment, plan on testing it thoroughly. Additionally, I’ve implemented enough to set the ‘user cannot change password’ flag as well as set permissions directly on an object. I have not implemented inherited/inheritable permissions, though I leave room for it to be implemented in the future should the need arise.
Anyway, on to the code:
The actual code I use to set the user cannot change password flag (ADCon is an instance of a helper class that I used to establish some common methods for querying Active Directory via python-ldap):
The implementation of setUserCannotChangePasswordFlag is as follows:
Three different Access Control Entries (ACEs, individual entries in an ACL) on a user object control their ability to change their own password. One grant ACE explicitly grants permission for users to change their passwords. Two deny ACEs will take the place of the grant ACE when the ‘user cannot change password’ option is selected.
The code uses the following modules to manipulate the nTSecurityDescriptor. These modules are as follows:
SDOperations.py – Methods that take the security descriptor byte structure as queried from LDAP. You can use the aclBytes() method to return one of the ACL byte structures contained within the security descriptor byte structure:
ACLOperations.py – Methods for reading and manipulating ACL byte structures:
ACEOperations.py – Methods for reading and manipulating ACE byte structures:
SIDOperations.py – Methods for reading and manipulating Security Identifier byte structures:
One last important thing to note: python-ldap’s modifymodlist method does not play nicely when pushing the nTSecurityDescriptor back up to AD. You must force a ‘replace’ operation by manually creating a replace modlist and calling ldap.modify with that. See as follows for how I implemented that:
Hopefully somebody out there will find this useful.. If you have any questions feel free to drop me a comment.