Weekend Scripter: Use PowerShell for JIT Administration and PAM – Part 1


Summary: Guest blogger, Microsoft PFE, Ian Farr, talks about JIT administration with Windows PowerShell.

Microsoft Scripting Guy, Ed Wilson, is here. Welcome back guest blogger Ian Farr. To read more of Ian's previous guest posts, see these Hey, Scripting Guy! Blog posts. This weekend is a two-part blog, with today being Part 1. Here's Ian...

You may be asking yourself, "What’s JIT administration, and who’s PAM?" Let me explain…

The JIT stands for "just-in-time"—and not Just-in-Timberlake, as my colleague, Phil Lane, likes to insist! With Ed’s permission, let me introduce Phil:

“I started my IT career working for a blue-chip telecommunication company. I used to spend my days drinking coffee, writing batch files, and telling people they needed to reboot their computers. Fifteen years later, I’m working for the best company in the world, travelling the globe, drinking coffee, writing Windows PowerShell code, and telling people they need to reboot their computers! After all this time, I have the same mantra: “Anything can be fixed with an investigative mind and caffeine!”

Right.

Back to the interesting stuff...(No offence, Phil!)

JIT administration provides high-privileged access to resources, for a limited period of time, to accomplish a specific task. The acronym is new, the concept is not.

PAM stands for "privileged access management." PAM encompasses special administrative considerations for the high-privileged users and groups that you’ll find in any IT infrastructure.

JIT, PAM, and Active Directory

A lot of high-privileged groups in Active Directory (for example, Domain Admins) have members that have been on the list forever. This is bad. As a minimum, group membership should be regularly reviewed and adjusted. Ideally, high-privileged groups should remain empty with membership only granted when required. With JIT administration, you can ‘make it so’ (as Jeffrey Snover and someone else like to say).

Yes, Phil, I’m coming to the Windows PowerShell part…

Phil wrote an awesome JIT administration proof-of-concept Windows PowerShell script to ensure high-privileged group membership only exists for the duration of the task that requires elevation. I took that script, tweaked it, added more functionality, and turned it into an advanced function. Today’s post is a discussion of that function.

Here’s an overview of how the function works:

  • You specify your target user account, the target domain, the target high-privileged group, and the amount of time you want the user to be a member of the high-privileged group.
  • The function creates a security group as a dynamic object (more on this shortly) in Active Directory, assigns the user as a member of that group, and then nests the dynamic group in the designated high-privileged group.
  • At the end of the specified time, the dynamic group object is deleted by Active Directory, removing the high-privileged access.

Here’s the function:

            Set-ADUserJitAdmin

This function has these parameters:

  • UserDn…  The distinguished name of the user account to be granted high-privileged access
  • Domain…  The domain in which the privileged group resides
  • PrivGroup…  Either Domain Admins, Enterprise Admins, or Schema Admins
  • TtlHours…  The number of hours high-privileged access is granted for

And these switches:

  • CountDown…  A countdown, in seconds, of the time remaining before the dynamic group object is removed
  • ProtectedUser…  This makes use of some of the credential theft protections included in the later operating systems (more about this in Part 2)

The interesting bits

We use the Get-ADDomain cmdlet and some "old(ish) school" Active Directory scripting techniques to create an object that represents the Users container found in every Active Directory directory:

$DomainDn = (Get-ADDomain $Domain).DistinguishedName

$UsersContainerDn = "CN=Users,$DomainDn"

$UsersContainer = [ADSI]("LDAP://$UsersContainerDn")

After we have our System.DirectoryServices.DirectoryEntry object, we can use its Create() method to stage a group in the container. The $UserDN value is a parameter passed to the function. The group name references the target user.

$UserSamAccountName = ((Get-ADUser –Identity $UserDn).SamAccountName).ToUpper()

$DynamicGroupName = "Dynamic Group - $UserSamAccountName"

$DynamicGroup = $UsersContainer.Create("group","CN=$DynamicGroupName") 

When we have our new group stored as $DynamicGroup, we can start using some of its methods to define it as a dynamic object. First, the PutEx() method:

$DynamicGroup.PutEx(2,"objectClass",@("dynamicObject","group"))  

Here we set the objectClass attribute to two values. We can’t do this with the Active Directory cmdlets—hence the "old(ish) school" approach. In addition to being a group, our new object is a dynamic object.

The dynamic object has a time to live (TTL) value associated with it. When the TTL expires, Active Directory automagically removes the object for us. Here’s how we set the TTL:

$DynamicGroup.Put("msDS-Entry-Time-To-Die",(Get-Date).AddHours($TtlHours -1))

The msDS-Entry-Time-To-Die attribute lets us define how long the group will be in existence. We do this by adding the number of hours supplied to the -TtlHours parameter to the current date and time. Notice that we include -1 in the calculation. This is to account for an idiosyncrasy in setting the attribute.

The next Put() operations define additional characteristics of the group:

$DynamicGroup.Put("sAMAccountName",$DynamicGroupName) 

$DynamicGroup.Put("displayName",$DynamicGroupName) 

$DynamicGroup.Put("description","Temporary group to grant time-bound membership of `'$PrivGroup`' to `'$UserSamAccountName`'") 

With these options set, we use the SetInfo() method to write our object back to Active Directory.

$DynamicGroup.SetInfo() 

Phil realized that we could use the dynamic object functionality to provide time-bound membership of a high-privilege group. Smart.

We add the target user account to our new dynamic group object and then nest that dynamic group object into the target privileged group. The $PrivGroup value is a parameter passed to the function.

Add-ADGroupMember -Identity "CN=$DynamicGroupName,$UsersContainerDn" -Members $UserDn

Add-ADGroupMember -Identity $PrivGroup -Members "CN=$DynamicGroupName,$UsersContainerDn"

Time-bound membership

Here’s what happens when we execute the function with the –CountDown and –Verbose parameters:

Image of command output

The –CountDown switch spins up a do while loop that makes use of a constructed attribute called entryTTL and the Write-Progress cmdlet:

       do {

            #Get the TTL of the dynamic group

            $TTL = (Get-ADGroup -Identity $DynamicGroupName -Properties entryTTL).entryTTL

            #Spin up a progress bar for the countdown

            Write-Progress -Activity "Countdown until `'CN=$DynamicGroupName,$UsersContainerDn`' removed..." `

                           -Status "Seconds remaining: $TTL" `

                           -PercentComplete ($TTL/($TtlHours * 3600) *100)

            #Wait a second...

            Start-Sleep -Seconds 1

        } while ($TTL -gt 0) 

The calculation that is supplied to the –PercentComplete parameter starts us at 100% and reduces—we get a diminishing progress bar. The value stored in entryTTL is an integer that represents the number of seconds left until the dynamic object expires. Start-Sleep for 1 means that every time we use Get-ADGroup to query the entryTTL value, it will have reduced by one second. Love it!

Let’s check the membership of Domain Admins with the help of Get-ADGroupMember (with and without the –Recursive parameter):

Image of command output

Notice we have our dynamic group object, Dynamic Group – MASTERCHIEF, nested in Domain Admins. Our dynamic group object contains the target user account, Master Chief. As a result, we have time-bound, high-privileged group membership—that is, when the countdown reaches 0, ‘Dynamic Group – MASTERCHIEF’ is deleted, and by association, ‘Master Chief’ is removed from Domain Admins. Sweet.

Note  The target user account should be an account that is ONLY used by a system administrator for high-privileged tasks, and only from a trusted, secured, and audited administrative host. With JIT administration, the target users should spend most of their time inactive and languishing in low privilege. Of course, we need an elevated account to run the function.

Tomorrow, I’ll discuss the additional credential theft functionality provided by using the –ProtectedUser switch.

~Ian

Thanks Ian (and Phil). Looking forward to tomorrow’s post.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (3)

  1. @Dusty… definitely compliment!

    @Adrian… good spot – function updated

  2. Does JIT and PAM supplement or compete with jEA?

  3. Adrian Rodriguez says:

    Great script! I was playing with it though and it looks like you may need to use UTC time for the msDS-Entry-Time-to-Die attribute. So in other words, replace (Get-Date).AddHours($TtlHours -1) with [datetime]::UtcNow.AddHours(1).

Skip to main content