Check for locked out Active Directory user via Ruby

At work, I’ve been working on a lot of automation lately and I ran into a seemingly simple problem that ended up being a bit more complicated than I had first imagined. I have been collaborating on a project that we’re using for auditing Active Directory users and groups and tracking changes to those groups via some simple automation. While that project is interesting in its own right, my boss and I agreed that tackling another helpful automation problem would help our entire IT team: determining if user accounts are locked. I’ve been pushing #ChatOps hard at work through Lita, so adding a plugin for our bot to work with Active Directory seemed only logical.

Context out of the way, making Ruby work with LDAP is a solved problem, many times over. Thankfully, Active Directory exposes most everything you’d want via LDAP, so with a few helper methods, building a few objects tailored to this task was easy work. We quickly discovered that each Active Directory user has a handy attribute called lockoutTime, and even some helpful hints via the interwebs that we just need to check if that value is 0 (meaning the user isn’t locked out) or any other value (indicating, naturally, that they are locked out). Well, this would be a pretty crappy blog post if that was the end, but it wasn’t.

Our basic testing of “Lock yourself out, check if the bot knows you’re locked out, then have someone unlock you, then have the bot check” worked just fine. That said, Active Directory had a gotcha buried deep in there, and it took a couple hours to stumble upon it. A user that is locked out but doesn’t have an admin unlock them will only be locked for so long. This duration — aptly named lockoutDuration in Active Directory’s LDAP parlance — is available by querying the LDAP Base DN (usually something like DC=yourdomain,DC=com). Oddly, this is stored as a negative integer, whose value is expressed in units of 1/10th of a microsecond (or, put another way, 100 nanoseconds). To complicate things further, this is returned by Ruby as a BER encoded string (of the Net::BER::BerIdentifiedString class to be precise). If that isn’t strange enough, when the lockoutTime attribute isn’t 0, it is the timestamp of the user lock-out based on Microsoft’s epoch for Active Directory timestamps. Microsoft’s Active Directory epoch is 1601-01-01 at 00:00, and timestamps are measured in this 100 nanosecond unit as well.

So this means we need to:

  • Determine a consistent unit of measurement (we’ll call this t)
  • Calculate the difference between Microsoft’s epoch time and everyone else’s (we’ll call this epoch_delta)
  • To do this, we need to calculate how many units of t there are between 1601-01-01 and 1970-01-01. Thankfully, Ruby’s Time provides a way to do this quickly:

  • Determine the value of the user’s lockoutTime attribute (we’ll call this locktime)

  • Pull the value of the domain’s lockoutDuration attribute (let’s call this duration)

  • Get the current time in units of t (we’ll call this now):

With all that information, we can apply the following calculation / logic:

Here is where you can see this actual formula applied in practice.