Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Overview
Starting with AD FS in Windows Server 2016, you can run the cmdlet Install-AdfsFarm as a local administrator on your federation server, provided your Domain Administrator has prepared Active Directory. The script below in this article can be used to prepare AD. The steps are as follows:
- As Domain Administrator, run the script (or create the Active Directory objects and permissions manually).
- The script will return an AdminConfiguration object containing the DN of the newly created AD object
- On the federation server, execute the Install-AdfsFarm cmdlet while logged on as a local administrator, passing the object from #2 above as the AdminConfiguration parameter
Assumptions
- Contoso\localadmin is a non-Domain Admin builtin admin on the federation server
- Contoso\FsSvcAcct is a domain account that will be the AD FS service account
- Contoso\FsGmsaAcct$ is a gMSA account that will be the AD FS service account
- $svcCred is the credentials of the AD FS service account
- $localAdminCred is the credentials of the local (non DA) admin account on the federation server
Using a domain account as AD FS Service Account
Prepare AD
Run the following as domain administrator
PS:\>$adminConfig=(.\New-AdfsDkmContainer.ps1 -ServiceAccount contoso\fssvcacct -AdfsAdministratorAccount contoso\localadmin)
Sample Output
$adminconfig.DkmContainerDN
CN=9530440c-bc84-4fe6-a3f9-8d60162a7bcf,CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com
Create the AD FS Farm
On the federation server as a local admin, execute the following in an elevated PowerShell command window.
First, if the federation server admin is not using the same PowerShell session as the above domain admin, re-create the adminConfig object using the output from the above.
PS:\>$adminConfig = @{"DKMContainerDn"="CN=9530440c-bc84-4fe6-a3f9-8d60162a7bcf,CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com"}
Next, create the farm:
PS:\>$svcCred = (get-credential)
PS:\>$localAdminCred = (get-credential)
PS:\>Install-AdfsFarm -CertificateThumbprint AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00 -FederationServiceName "fs.contoso.com" -ServiceAccountCredential $svcCred -Credential $localAdminCred -OverwriteConfiguration -AdminConfiguration $adminConfig -Verbose
Using a gMSA as the AD FS Service Account
Prepare AD
PS:\>$adminConfig=(.\New-AdfsDkmContainer.ps1 -ServiceAccount contoso\FsGmsaAcct$ -AdfsAdministratorAccount contoso\localadmin)
Sample Output
$adminconfig.DkmContainerDN
CN=8065f653-af9d-42ff-aec8-56e02be4d5f3,CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com
Create the AD FS Farm
On the federation server as a local admin, execute the following in an elevated PowerShell command window.
First, if the federation server admin is not using the same PowerShell session as the above domain admin, re-create the adminConfig object using the output from the above.
PS:\>$adminConfig = @{"DKMContainerDn"="CN=8065f653-af9d-42ff-aec8-56e02be4d5f3,CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com"}
Next, create the farm: Note that the local computer account and the AD FS admin account need to be granted retrieve password and delegate to account rights on the gMSA.
PS:\>$localAdminObj = Get-ADUser "localadmin"
PS:\>$adfsNodeComputerAcct = Get-ADComputer "contoso_adfs_node"
PS:\>Set-ADServiceAccount -Identity fsgmsaacct -PrincipalsAllowedToRetrieveManagedPassword @( Add=$localAdminObj.SID.Value, $adfsNodeComputerAcct.SID.Value) -PrincipalsAllowedToDelegateToAccount @( Add=$localAdminObj.SID.Value, $adfsNodeComputerAcct.SID.Value)
PS:\>$localAdminCred = (Get-Credential)
PS:\>Install-AdfsFarm -CertificateThumbprint AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00 -FederationServiceName "fs.contoso.com" -Credential $localAdminCred -GroupServiceAccountIdentifier "contoso\fsgmsaacct$" -OverwriteConfiguration -AdminConfiguration $adminConfig
Script for preparing AD
The following PowerShell script can be used to accomplish the examples above
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[Parameter(Mandatory=$True)]
[string]$ServiceAccount,
[Parameter(Mandatory=$True)]
[string]$AdfsAdministratorAccount
)
$ServiceAccountSplit = $ServiceAccount.Split("\");
if ($ServiceAccountSplit.Length -ne 2)
{
Write-error "Specify the ServiceAccount identifier in 'domain\username' format"
exit 1
}
$AdfsAdministratorAccountSplit = $AdfsAdministratorAccount.Split("\");
if ($AdfsAdministratorAccountSplit.Length -ne 2)
{
Write-error "Specify the AdfsAdministratorAccount identifier in 'domain\username' format"
exit 1
}
#######################################
## Verify AD module is installed
#######################################
$m = "ActiveDirectory"
if (Get-Module | Where-Object {$_.Name -eq $m})
{
write-verbose "Module $m is already imported."
}
else
{
if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m})
{
Import-Module $m -Verbose
}
else
{
write-error "Module $m was not imported, install the Active Directory RSAT package and retry."
exit 1
}
}
push-location ad:
#######################################
## Generate random DKM container name
## The OU Name is a randomly generated Guid
#######################################
[string]$guid = [Guid]::NewGuid()
write-verbose ("OU Name" + $guid)
$ouName = $guid
$initialPath = "CN=Microsoft,CN=Program Data," + (Get-ADDomain).DistinguishedName
$ouPath = "CN=ADFS," + $initialPath
$ou = "CN=" + $ouName + "," + $ouPath
#######################################
## Create DKM container and assign default ACE which allows AD FS admin read access
#######################################
if ($pscmdlet.ShouldProcess("$ou", "Creating DKM container and assigning access"))
{
Write-Verbose ("Creating organizational unit with DN: " + $ou)
if ($AdfsAdministratorAccount.EndsWith("$"))
{
write-verbose "AD FS administrator account passed with $ suffix indicating a computer account"
$userNameSplit = $AdfsAdministratorAccount.Split("\");
$strSID = (Get-ADServiceAccount -Identity $userNameSplit[1]).SID
}
else
{
write-verbose "AD FS administrator account is a standard AD user"
$objUser = New-Object System.Security.Principal.NTAccount($AdfsAdministratorAccount)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
}
if ($null -eq (Get-ADObject -Filter {distinguishedName -eq $ouPath}))
{
Write-Verbose ("First creating initial path " + $ouPath)
New-ADObject -Name "ADFS" -Type Container -Path $initialPath
}
$acl = get-acl -Path $ouPath
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$adSecInEnum = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
$ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"GenericRead","Allow",$adSecInEnum
$acl.AddAccessRule($ace1)
set-acl -Path $ouPath -AclObject $acl
New-ADObject -Name $ouName -Type Container -Path $ouPath
}
#######################################
## Harden DKM container: disable inheritance, purge
## Authenticated Users and Pre-Windows 2000 Compatible Access.
## This prevents broad principals from reading thumbnailPhoto
## (where the DKM key material is stored on child Contact objects).
#######################################
if ($pscmdlet.ShouldProcess("$ou", "Disabling inheritance and purging Authenticated Users / Pre-Windows 2000"))
{
$acl = get-acl -Path $ou
# Block inheritance and discard all inherited ACEs.
$acl.SetAccessRuleProtection($true, $false)
# Purge Authenticated Users
$authenticatedUsers = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AuthenticatedUserSid, $null)
$acl.PurgeAccessRules($authenticatedUsers)
# Purge Pre-Windows 2000 Compatible Access
$preWin2k = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinPreWindows2000CompatibleAccessSid, $null)
$acl.PurgeAccessRules($preWin2k)
set-acl -Path $ou -AclObject $acl
}
#######################################
## Grant Domain Admins, Enterprise Admins, and SYSTEM full control.
## These are the approved principals for the DKM container.
#######################################
if ($pscmdlet.ShouldProcess("$ou", "Granting DA, EA, and SYSTEM GenericAll"))
{
$acl = get-acl -Path $ou
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$adSecInEnum = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
# Domain Admins
$domainSid = (Get-ADDomain).DomainSID
$domainAdminsSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainAdminsSid, $domainSid)
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $domainAdminsSid,"GenericAll","Allow",$adSecInEnum))
# Enterprise Admins (forest root domain)
$rootDomainSid = (Get-ADForest).RootDomain | Get-ADDomain | Select-Object -ExpandProperty DomainSID
$enterpriseAdminsSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $rootDomainSid)
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $enterpriseAdminsSid,"GenericAll","Allow",$adSecInEnum))
# SYSTEM
$systemSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null)
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $systemSid,"GenericAll","Allow",$adSecInEnum))
set-acl -Path $ou -AclObject $acl
}
#######################################
## Grant the following permission to the service account
# Read
# Create Child
# Write Owner
# Delete Tree
# Write DACL
# Write Property
#######################################
if ($ServiceAccount.EndsWith("$"))
{
write-verbose "service account passed with $ suffix indicating a gMSA"
$userNameSplit = $ServiceAccount.Split("\");
$strSID = (Get-ADServiceAccount -Identity $userNameSplit[1]).SID
}
else
{
write-verbose "service account is a standard AD user"
$objUser = New-Object System.Security.Principal.NTAccount($ServiceAccount)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
}
if ($pscmdlet.ShouldProcess("$strSID", "Granting GenericRead, CreateChild, WriteOwner, DeleteTree, WriteDacl and WriteProperty"))
{
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$adSecInEnum = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
$ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"GenericRead","Allow",$adSecInEnum
$ace2 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"CreateChild","Allow",$adSecInEnum
$ace3 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"WriteOwner","Allow",$adSecInEnum
$ace4 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"DeleteTree","Allow",$adSecInEnum
$ace5 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"WriteDacl","Allow",$adSecInEnum
$ace6 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"WriteProperty","Allow",$adSecInEnum
$acl = get-acl -Path $ou
$acl.AddAccessRule($ace1)
$acl.AddAccessRule($ace2)
$acl.AddAccessRule($ace3)
$acl.AddAccessRule($ace4)
$acl.AddAccessRule($ace5)
$acl.AddAccessRule($ace6)
$acl.SetOwner($strSID)
set-acl -Path $ou -AclObject $acl
}
#######################################
## Grant the following permission to the adfs admin account
# Read
# Create Child
# Write Owner
# Delete Tree
# Write DACL
# Write Property
#######################################
if ($AdfsAdministratorAccount.EndsWith("$"))
{
write-verbose "AD FS administrator account passed with $ suffix indicating a gMSA"
$userNameSplit = $AdfsAdministratorAccount.Split("\");
$strSID = (Get-ADServiceAccount -Identity $userNameSplit[1]).SID
}
else
{
write-verbose "AD FS administrator account is a standard AD user"
$objUser = New-Object System.Security.Principal.NTAccount($AdfsAdministratorAccount)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
}
if ($pscmdlet.ShouldProcess("$strSID", "Granting GenericRead, CreateChild, WriteOwner, DeleteTree, WriteDacl and WriteProperty"))
{
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$adSecInEnum = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
$ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"GenericRead","Allow",$adSecInEnum
$ace2 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"CreateChild","Allow",$adSecInEnum
$ace3 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"WriteOwner","Allow",$adSecInEnum
$ace4 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"DeleteTree","Allow",$adSecInEnum
$ace5 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"WriteDacl","Allow",$adSecInEnum
$ace6 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $strSID,"WriteProperty","Allow",$adSecInEnum
$acl = get-acl -Path $ou
$acl.AddAccessRule($ace1)
$acl.AddAccessRule($ace2)
$acl.AddAccessRule($ace3)
$acl.AddAccessRule($ace4)
$acl.AddAccessRule($ace5)
$acl.AddAccessRule($ace6)
$acl.SetOwner($strSID)
set-acl -Path $ou -AclObject $acl
$adminConfig = @{"DKMContainerDn"=$ou}
Write-Output $adminConfig
}
pop-location