Automate Secondary ADFS Node Installation and Configuration

Introduction

Additional nodes in an ADFS farm are required to provide redundancy incase your primary ADFS node goes offline. This ensures your ADFS service is still up and servicing all incoming requests. Additional nodes also help in load balancing the incoming traffic, which provides a better user experience in cases of high authentication traffic.

Overview

Once an ADFS farm has been created, adding additional nodes is quite simple and mostly relies on the same concepts for creating the ADFS farm. I would suggest reading my previous blog Automate ADFS Farm Installation and Configuration as some of the steps we will use in this blog were documented in it.

In this blog, I will show how to automatically provision a secondary ADFS node to an existing ADFS farm. The learnings in this blog can be easily used to deploy more ADFS nodes automatically, if needed.

Install ADFS Role

After provisioning a new Azure virtual machine, we need to install the Active Directory Federation Services role on it.  To do this, we will use the same Desired State Configuration (DSE) script that was used in Automate ADFS Farm Installation and Configuration. Please refer to the section Install ADFS Role in the above blog for the steps to create the DSE script file InstallADFS.ps1.

Add to an existing ADFS Farm

Once the ADFS role has been installed on the virtual machine, we will create a Custom Script Extension (CSE) to add it to the ADFS farm.

In order to do this, we need the following

  • certificate that was used to create the ADFS farm
  • ADFS service account username and password that was used to create the ADFS farm

Once the above prerequisites has been met, we need a method for making the files available to the CSE. I documented a neat trick to “sneak-in” the certificate and password files onto the virtual machine by using Desired State Configuration (DSE) package files in my previous blog. Please refer to Automate ADFS Farm Installation and Configuration under the section Create ADFS Farm for the steps.

Also note, for adding the node to the adfs farm, the domain user credentials are not required. The certificate file will be named adfs_certificate.pfx  and the file containing the encrypted adfs service account password will be named adfspass.key.

Assuming that the prerequisites have been satisfied, and the files have been “sneaked” onto the virtual machine, lets proceed to creating the CSE.

Open Windows Powershell ISE and paste the following.

param (
  $DomainName,
  $PrimaryADFSServer,
  $AdfsSvcUsername
)

The above shows the parameters that need to be passed to the CSE where

$DomainName is the name of the Active Directory domain
$PrimaryADFSServer is the hostname of the primary ADFS server
$AdfsSvcUsername is the username of the ADFS service account

Save the file with a name of your choice (do not close the file as we will be adding more lines to it). I named my script AddToADFSFarm.ps1

Next, we need to define a variable that will contain the path to the directory where the certificate file and the file containing the encrypted adfs service account password are stored. Also, we need a variable to contain the key that was used to encrypt the adfs service account password. This will be required to decrypt the password.

Add the following to the CSE file

$localpath = "C:\Program Files\WindowsPowerShell\Modules\Certificates\"
$Key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)

Next, we need to decrypt the encrypted adfs service account password.


$adfspassword = Convertto-SecureString -String (Get-Content -Path $($localpath+"adfspass.key")) -key $key
$AdfsSvcCreds = New-Object System.Management.Automation.PSCredential($($DomainName+"\"+$AdfsSvcUsername), $adfspassword)

Now, we need to import the certificate into the local computer certificate store. To make things simple, when the certificate was exported from the primary ADFS server, it was encrypted using the adfs service account password.

After importing the certificate, we will read it to get its thumbprint.


#install the certificate that will be used for ADFS Service
Import-PfxCertificate -Exportable -Password $adfspassword -CertStoreLocation cert:\localmachine\my -FilePath $($localpath+"adfs_certificate.pfx")
#get thumbprint of certificate
$cert = Get-ChildItem -Path Cert:\LocalMachine\my | ?{$_.Subject -eq "CN=fs.adfsfarm.com, OU=Free SSL, OU=Domain Control Validated"}

Up until now, the steps are very similar to creating an ADFS farm. However, below is where the steps diverge.

Add the following lines to add the virtual machine to the existing ADFS farm


#Configure ADFS
Import-Module ADFS
Add-AdfsFarmNode -CertificateThumbprint $cert.thumbprint -ServiceAccountCredential $AdfsSvcCreds -PrimaryComputerName $PrimaryADFSServer

You now have a custom script extension file that will add a virtual machine as a secondary node to an existing ADFS Farm.

Below is the full CSE


<#AddToADFSFarm.ps1
#>
param (
$DomainName,
$PrimaryADFSServer,
$AdfsSvcUsername
)
#the adfs service account password is encrypted and stored in a local folder
$localpath = "C:\Program Files\WindowsPowerShell\Modules\Certificates\"
$Key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)
#lets get the password and decrypt it
#get the adfs password first
$adfspassword = Convertto-SecureString -String (Get-Content -Path $($localpath+"adfspass.key")) -key $key
$AdfsSvcCreds = New-Object System.Management.Automation.PSCredential($($DomainName+"\"+$AdfsSvcUsername), $adfspassword)
#install the certificate that will be used for ADFS Service
Import-PfxCertificate -Exportable -Password $adfspassword -CertStoreLocation cert:\localmachine\my -FilePath $($localpath+"adfs_certificate.pfx")
#get thumbprint of certificate
$cert = Get-ChildItem -Path Cert:\LocalMachine\my | ?{$_.Subject -eq "CN=fs.adfsfarm.com, OU=Free SSL, OU=Domain Control Validated"}
#Configure ADFS
Import-Module ADFS
Add-AdfsFarmNode -CertificateThumbprint $cert.thumbprint -ServiceAccountCredential $AdfsSvcCreds -PrimaryComputerName $PrimaryADFSServer

All that is missing now is the method to bootstrap the scripts described above using Azure Resource Manager templates.

Below is the ARM template that can be used to install ADFS role on the a virtual machine and then add this virtual machine as a secondary node to the ADFS farm


{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('ADFS02VMName'),'/InstallADFS')]",
"apiVersion": "2015-05-01-preview",
"location": "[resourceGroup().location]",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', parameters('ADFS02VMName'))]",
],
"properties": {
"publisher": "Microsoft.Powershell",
"type": "DSC",
"typeHandlerVersion": "2.19",
"autoUpgradeMinorVersion": true,
"settings": {
"ModulesUrl": "[variables('InstallADFSPackageURL')]",
"ConfigurationFunction": "[variables('InstallADFSConfigurationFunction')]",
"Properties": {
"MachineName": "[parameters('ADFS02VMName')]",
"DomainName": "[parameters('domainName')]",
"AdminCreds": {
"UserName": "[parameters('adminUserName')]",
"Password": "PrivateSettingsRef:AdminPassword"
}
}
},
"protectedSettings": {
"Items": {
"AdminPassword": "[parameters('adminPassword')]"
}
}
}
},
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('ADFS02VMName'),'/AddToADFSFarm')]",
"apiVersion": "2015-05-01-preview",
"location": "[resourceGroup().location]",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', parameters('ADFS02VMName'),'/extensions/InstallADFS')]"
],
"properties": {
"publisher": "Microsoft.Compute",
"type": "CustomScriptExtension",
"typeHandlerVersion": "1.4",
"autoUpgradeMinorVersion": true,
"settings": {
"fileUris": [
"[variables('AddToADFSFarmScriptUrl')]"
],
"commandToExecute": "[concat('powershell.exe -file AddToADFSFarm.ps1',' -DomainName ',parameters('domainName'),' -PrimaryADFSServer ',parameters('ADFS01VMName'),' -AdfsSvcUsername ',parameters('AdfsSvcUsername'))]"
}
}
},

In the above ARM template, the parameter ADFS02VMName refers to the hostname of the virtual machine that will be added to the ADFS Farm.

Listed below are the variables that have been used in the ARM template above


"repoLocation": "https://raw.githubusercontent.com/user/folder/&quot;,
"InstallADFSPackageURL": "[concat(parameters('repoLocation'), 'InstallADFS.zip')]",
"InstallADFSConfigurationFunction": "InstallADFS.ps1\\InstallADFS",
"AddToADFSFarmScriptUrl": "[concat(parameters('repoLocation'), 'AddToADFSFarm.ps1')]",

The above method can be used to add as many nodes to the ADFS farm as needed.

I hope this comes in handy when creating an ARM template to automatically deploy an ADFS Farm with additional nodes.