Hey there folks! It’s been a long while since I’ve had some time to write a blog post, but happy to be back. Over the last year I’ve moved to a new city and started a new position, so I’ve spent most of my time learning many new things. Some of that learning came with a project where we needed to sync user data from a G Suite domain to Active Directory. Surprisingly, a quick Google search for this led to several results with the question of how to do this, but not many (if any) results involving the solution. Most results offered a solution for the opposite (syncing AD to G Suite). Google even built a tool to help with this, called Google Cloud Directory Sync. This is likely because most people are using Active Directory as their IdP, and G Suite purely as an email/collaboration/productivity suite, and thus they want to sync passwords or user attributes from Active Directory over. But what if G Suite is the IdP, and you’re not using Active Directory for that purpose?
PowerShell or Python?
My primary development environment these days is made up of Python 3 and AWS Lambda to run the code. I still dabble with MacAdmin things here and there, but I am primarily responsible for automating internal (or external SaaS) tools together. However, I have some experience with writing PowerShell scripts for minor Windows automation tasks. So while I have some familiarity, and am actually quite impressed with PowerShell for the most part, my comfort zone is in Python.
The Challenges of PowerShell
If you’re like me, you probably instantly thought PowerShell was the go-to tool to perform this task. I knew it had Active Directory modules that would probably allow us to only write a few lines of code, there is even a cmdlet called
New-ADUser that seems to fit the bill. However, the issue with this approach was, how were we going to run it in AWS Lambda? AWS does have PowerShell Core support, but not being as familiar with PowerShell, I wasn’t sure if a) this supported all the modules/cmdlet’s I would need, and b) how we would go about connecting to the Domain Controller’s, as I think most of those cmdlets assume a domain-bound system is running them. The other problem then became, if we go with PowerShell, how do we interact with the Google Admin API?
Surprisingly, someone had done some pretty extensive work to bring most (if not all) of the Google Admin API calls over to PowerShell and made the module PSGSuite. This looked (and still looks) very cool and very promising. But again, my unfamiliarity with PowerShell just made this task feel a bit daunting and like I wouldn’t be able to get what I needed done in a timely fashion.
The Challenges of Python
The challenges of Python were far less to start. For one thing, I know how to deploy and setup a Python-based AWS Lambda. Secondly, I know the language well enough and work with the Google API near daily, so it felt like a very low bar for entry. The issue or challenge with Python, was how were we going to talk with AD? The first few Google results brought up things like pyAD which is a Python library that is built specifically on top of the ADSI interfaces, which are only supported on Windows. There was another package called active_directory that also seemed like it could do the job, but again was built for Windows. It wasn’t until I read a few forums that someone suggested using the ldap3 python library to talk with AD over LDAP. It was at this point when I did an (almost literal) :facepalm:. I had worked with Active Directory far more extensively in previous jobs, and had done some AD automation via Python and LDAP. The difference, however, was that I was always retrieving data. It never occurred to me that, of course, this may also work to write data…
Once I had played a bit with the LDAP3 library, and confirmed that indeed I could manipulate objects in AD via Python, this became a much easier and somewhat more fun task.
The basic flow of the code would be:
- Pull all users from G Suite
- Pull all users from our target OU in Active Directory
- Iterate through our G Suite users and create ldap-friendly formatted JSON objects with the information we cared about (First name, Last name, UPN, etc.)
- Check that the user wasn’t already in the list of users returned from Active Directory
- Create user in Active Directory
As the G Suite API is very well documented, I’m not going to include the code on how to authenticate and retrieve users with it. But I have documented the code that performs the LDAP bits into a Gist that you can find here: https://gist.github.com/jbaker10/4d03616910b86a5f7e24bbc0dab37023
NOTE: Not all of this code may make sense for your environment. For example, the
create_username function strips dashes (
-) out of emails when making the username for AD, that may not be desired in your org. Please read ALL of the code first before using this in a production system.
Interesting Lessons Learned
Microsoft has some constraints in place where certain actions are not allowed over an unencrypted connection (i.e. just LDAP, not LDAPS). This means that you cannot create or modify a user without the connection being over either TLS or SSL via LDAPS. Simple enough, and we should all be using SSL everywhere we can as it is.
The hardest part about this whole process was not with creating the users in Active Directory, but creating them as enabled accounts in Active Directory. We already discussed needing to be connected via an encrypted channel to create or modify users, but I could not for the life of me get the user to be created and be enabled. I confirmed I was setting a password that complied with AD’s password policy, that all the necessary bits were set correctly, but still the users were created as Disabled. It wasn’t until I went to try and manually enable the user via the GUI that I got a prompt that a user could not be enabled without a password set. Ok 🤔 but I know I’m sending the password in the LDAP payload, so what’s the issue? Turns out, Microsoft uses a very specific encoding to set the
UnicodePwd LDAP attribute: UTF-16-LE. You’ll also find the same looking through the ldap3 source code on how the
ad_modify_password method works: https://ldap3.readthedocs.io/_modules/ldap3/extend/microsoft/modifyPassword.html#ad_modify_password
Once that was in place, bingo, bango, bongo – we were in business!
We were now able to quickly and automatically provision new Active Directory user accounts with random passwords that were enabled by default.
I hope this helps someone else out and makes some project a bit easier for you! Feel free to comment or let me know if something isn’t working for you, while I’m in no way an LDAP/AD/G Suite expert, I’m happy to try and help.