Using Azure AD Privileged Identity Management with Active Directory roles (such as domain admin)

I just put my Azure AD Group Writeback Script on Github, and figured it was time to do something I know many have requested from Microsoft to deliver, but that is still missing; Using Azure AD Privileged Identity Management to control access to Active Directory built-in groups such as Domain Admin, Schema Admin and Enterprise Admin.

To keep this blog post as short as possible, I will not be covering things such as setting up Active Directory, configuring Azure AD Connect and so on. Also, this section on github covers what you need to know for authenticating the Azure AD Group Writeback Script to Azure AD for none-Azure environments, so I will only cover that briefly.

What we will do in this blog post is:

Let us start by creating a few privileged groups in the Azure Portal – “AD – Domain Admins” and “AD – Enterprise Admins”.

Notice that no roles are assigned

Next, for both groups, we open the group properties, find “Privileged access” and click “Enable privileged access”:

Now, I add two active assignments, just to have a few members to actually sync when setting up the AAD Group Writeback Script. These will be converted to eligible later.

Now that our two groups have been created, let us set up the AAD Group Writeback Script. Start by either downloading the Writeback Script repo or installing git and cloning the repo like this:

git clone "https://github.com/goodworkaround/AAD-Group-Writeback-Script.git" "c:\git\AAD-Group-Writeback-Script"
cd c:\git\AAD-Group-Writeback-Script

Next, make sure that the Active Directory PowerShell Module and the Azure AD PowerShell Module is installed using PowerShell:

Install-WindowsFeature RSAT-AD-PowerShell
Install-Module AzureAD

Start PowerShell and launch the script provided here (which is the same as the one below this line):

function New-WriteBackScriptInstallation {
    [CmdletBinding()]

    Param
    (
        [Parameter(Mandatory=$false,
                   Position=0)]
        $ConfigFile = ".\Run.config"
    )

    $appName = "AzureAD to AD group writeback script"

    Install-Module AzureAD | Out-Null
    Connect-AzureAD | Out-Null

    $requiredGrants = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]::new(
        "00000003-0000-0000-c000-000000000000", # Microsoft Graph
        @(
            [Microsoft.Open.AzureAD.Model.ResourceAccess]::new("5b567255-7703-4780-807c-7be8301ae99b","Role") 
            [Microsoft.Open.AzureAD.Model.ResourceAccess]::new("df021288-bdef-4463-88db-98f22de89214","Role") 
        )
    )

    Write-Verbose "[Change] Creating the app registration '$appName'" -Verbose
    $app = New-AzureADApplication -DisplayName $appName -RequiredResourceAccess $requiredGrants
    $sp = New-AzureADServicePrincipal -AppId $app.appid
    $key = New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId -EndDate (get-date).AddYears(100)

    $url = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/$($app.AppId)/isMSAApp/"
    Write-Verbose "Go to the url that already is put on the clipboard and click 'Grant admin consent':`n`n$url" -Verbose
    $url | Set-Clipboard
    Read-host -Prompt "Click enter when done"

    if(Test-Path $ConfigFile) {
        $ConfigFile2 = ".\{0}.config" -f [guid]::NewGuid()
        Write-Verbose "File $ConfigFile already exists, writing to $ConfigFile2"
        $ConfigFile = $ConfigFile2
    }

    $_config = [ordered] @{
        AuthenticationMethod = "ClientCredentials"
        ClientID = $app.appid
        EncryptedSecret = "$($key.Value | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString)"
        TenantID = "$((Get-AzureADCurrentSessionInfo).TenantId.ToString())"
        DestinationOU = "OU=AAD Group writeback,DC=contoso,DC=com"
        ADGroupObjectIDAttribute = "info"
        AADGroupScopingMethod = "PrivilegedGroups"
        GroupDeprovisioningMethod = "PrintWarning"
    } | ConvertTo-Json
    set-content $ConfigFile -Value $_config

    Write-Host "Created file $ConfigFile"
}
New-WriteBackScriptInstallation

Authenticate to Azure AD with an account with Global Admin, Cloud Application Admin or Applicaiton Admin role (need to be able to create an app registration, and consent to it):

Grant the app registration the required access, which is to read all users and groups: Click enter on the script window, and it should return that it has created a file for you. This is a config file that should work – almost, just by changing a few settings πŸ™‚ The authentication stuff is now correct, and you only need to update the DestinationOU in your script. Here is my final configuration, where only line 6 and 9 has changed (DestinationOU and GroupDeprovisioningMethod):

{
    "AuthenticationMethod":  "ClientCredentials",
    "ClientID":  "e0736d2a-d385-42b0-80f6-60a11d817996",
    "EncryptedSecret":  "01000000d08c9ddf0115d1118c7a00c04fc297eb01000000be3e7b5924a98f4cbf5ebc0e1d25f1130000000002000000000003660000c00000001000000091f23fe77a40a5c4ac2ebe88e97e9ccc0000000004800000a0000000100000009d6fb3fd22c6ae7a74768b9d8d440767600000003580e05d35304ee9fed6b2e3ec5473e09109664fe14420959eb0ad7f37581c9466ab443509764ab3f6c160e7140a6ccdbb5b1ce5b5d80c6b6ac290bb8ead530085d91ef6b7f54e91a936d08a37dbf0e37526f69ccff49842e7c106d382e4ae5f14000000aa145ebe1fcffd99e6eca8d669b67a9858b6bbd8",
    "TenantID":  "937a3b05-1264-406f-ade1-3f4a42d4e26f",
    "DestinationOU":  "OU=AAD Privileged Groups,DC=contoso,DC=com",
    "ADGroupObjectIDAttribute":  "info",
    "AADGroupScopingMethod":  "PrivilegedGroups",
    "GroupDeprovisioningMethod":  "Delete"
}

Running Run.ps1 with -WhatIf and -Verbose now shows what is going to happen when:

.\Run.ps1 -WhatIf -Verbose 
VERBOSE: Successfully received access token
VERBOSE:  - oid:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - aud:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - iss:             https://sts.windows.net/937a3b05-1264-406f-ade1-3f4a42d4e26f/
VERBOSE:  - appid:           e0736d2a-d385-42b0-80f6-60a11d817996
VERBOSE:  - app_displayname: AzureAD to AD group writeback script
VERBOSE:  - roles:           Group.Read.All User.Read.All
VERBOSE: Getting all scoped groups
VERBOSE: Found 2 groups in scope
VERBOSE: Starting Save-ADGroup
VERBOSE:  - Processing AADGroup 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:   - Creating group 'AD - Domain Admins' in AD
What if: Performing the operation "New" on target "CN=AD - Domain Admins (22fe80d8-674f-48ef-83b9-6539a7df9f03),OU=AAD Privileged Groups,DC=contoso,DC=com".
VERBOSE:  - Processing AADGroup 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE:   - Creating group 'AD - Enterprise Admins' in AD
What if: Performing the operation "New" on target "CN=AD - Enterprise Admins (8b264bee-f405-46f7-b1be-b7f1bf4ef72d),OU=AAD Privileged Groups,DC=contoso,DC=com".
VERBOSE: Save-ADGroup finished
VERBOSE: Processing all memberships
VERBOSE:  - Processing group 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
WARNING: Unable to find AD group for AAD group 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:  - Processing group 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
WARNING: Unable to find AD group for AAD group 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE: Determining whether there are AD groups to deprovision
VERBOSE: No groups that should be deprovisioned

This looks good! Let’s just run it without WhatIf, before starting to invoke it as a scheduled task every 5 minutes (or you know, if you want to be a bit more sure that the script is working, run it manually and see what it does, like I will do in this blog post)

.\Run.ps1 -Verbose 
VERBOSE: Successfully received access token
VERBOSE:  - oid:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - aud:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - iss:             https://sts.windows.net/937a3b05-1264-406f-ade1-3f4a42d4e26f/
VERBOSE:  - appid:           e0736d2a-d385-42b0-80f6-60a11d817996
VERBOSE:  - app_displayname: AzureAD to AD group writeback script
VERBOSE:  - roles:           Group.Read.All User.Read.All
VERBOSE: Getting all scoped groups
VERBOSE: Found 2 groups in scope
VERBOSE: Starting Save-ADGroup
VERBOSE:  - Processing AADGroup 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:   - Creating group 'AD - Domain Admins' in AD
VERBOSE:  - Processing AADGroup 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE:   - Creating group 'AD - Enterprise Admins' in AD
VERBOSE: Save-ADGroup finished
VERBOSE: Processing all memberships
VERBOSE:  - Processing group 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:   - Adding member to AD group 'AD - Domain Admins (22fe80d8-674f-48ef-83b9-6539a7df9f03)': CN=Donald Duck,OU=User accounts,DC=contoso,DC=com
VERBOSE:   - Adding member to AD group 'AD - Domain Admins (22fe80d8-674f-48ef-83b9-6539a7df9f03)': CN=Dolly Duck,OU=User accounts,DC=contoso,DC=com
VERBOSE:  - Processing group 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE: Determining whether there are AD groups to deprovision
VERBOSE: No groups that should be deprovisioned

That’s it. Our AD now looks like this! We can not add the “AD – Domain Admins” group to our Domain Admins group to achieve global admin permissions.

Note 1: The adminSDHolder process will mess with the permissions of the group when you add it to Domain Admins, so make sure that you are running the Run.ps1 script as a user that has access to those kinds of groups

Note 2: Large ADs with many sites will experience that replication is an issue. This is where you could consider using this script to populate groups in a bastion forest. I am looking at adding support for msDs-ShadowPrincipal objects, just like MIM PAM does πŸ™‚

We are now ready to convert Dolly and Donald to Eligible Assignments. Go back to the Azure portal, on the AD – Domain Admins group and click “Update”, and change the assignments to eligible:

When running Run.ps1 now, we should see the users being removed from the group in AD:

VERBOSE: Successfully received access token
VERBOSE:  - oid:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - aud:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - iss:             https://sts.windows.net/937a3b05-1264-406f-ade1-3f4a42d4e26f/
VERBOSE:  - appid:           e0736d2a-d385-42b0-80f6-60a11d817996
VERBOSE:  - app_displayname: AzureAD to AD group writeback script
VERBOSE:  - roles:           Group.Read.All User.Read.All
VERBOSE: Getting all scoped groups
VERBOSE: Found 2 groups in scope
VERBOSE: Starting Save-ADGroup
VERBOSE:  - Processing AADGroup 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:  - Processing AADGroup 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE: Save-ADGroup finished
VERBOSE: Processing all memberships
VERBOSE:  - Processing group 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:   - Removing member from AD group 'AD - Domain Admins (22fe80d8-674f-48ef-83b9-6539a7df9f03)': CN=Dolly Duck,OU=User accounts,DC=contoso,DC=com
VERBOSE:   - Removing member from AD group 'AD - Domain Admins (22fe80d8-674f-48ef-83b9-6539a7df9f03)': CN=Donald Duck,OU=User accounts,DC=contoso,DC=com
VERBOSE:  - Processing group 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE: Determining whether there are AD groups to deprovision
VERBOSE: No groups that should be deprovisioned

Sweet, now let’s look at Donald’s experience in all of this:

First he signs into the Azure Portal, and searches for “PIM” (which can be a favorite of course, directly linking to PIM)

He then goes to “Active just in time”

He goes to Privileged Access Groups, and acitivates his “AD – Domain Admins” group

The role is now active

Now, when Run.ps1 is again triggered, we see that Donald is added to the “AD – Domain Admins” AD group:

VERBOSE: Successfully received access token
VERBOSE:  - oid:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - aud:             e67bebd1-7f97-4837-9379-bd3869c7d814
VERBOSE:  - iss:             https://sts.windows.net/937a3b05-1264-406f-ade1-3f4a42d4e26f/
VERBOSE:  - appid:           e0736d2a-d385-42b0-80f6-60a11d817996
VERBOSE:  - app_displayname: AzureAD to AD group writeback script
VERBOSE:  - roles:           Group.Read.All User.Read.All
VERBOSE: Getting all scoped groups
VERBOSE: Found 2 groups in scope
VERBOSE: Starting Save-ADGroup
VERBOSE:  - Processing AADGroup 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:  - Processing AADGroup 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE: Save-ADGroup finished
VERBOSE: Processing all memberships
VERBOSE:  - Processing group 'AD - Domain Admins' (22fe80d8-674f-48ef-83b9-6539a7df9f03)
VERBOSE:   - Adding member to AD group 'AD - Domain Admins (22fe80d8-674f-48ef-83b9-6539a7df9f03)': CN=Donald Duck,OU=User accounts,DC=contoso,DC=com
VERBOSE:  - Processing group 'AD - Enterprise Admins' (8b264bee-f405-46f7-b1be-b7f1bf4ef72d)
VERBOSE: Determining whether there are AD groups to deprovision
VERBOSE: No groups that should be deprovisioned

There are a few configuration options available for the script that I have not mentioned here. Please visit github for checking those out.

Good luck!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s