Automate the Publishing of a Custom ADFS Authentication Provider
Building and deploying a custom ADFS external authentication provider can be tedious. In this post, we will address these concerns by creating automated build and deploy scripts with Powershell. This post will not go into the details of how to create an ADFS external authentication provider. You can read about custom ADFS authentication providers in detail via the following excellent blog posts:
- Build a Custom Authentication Method for AD FS in Windows Server 2012 R2
- External authentication providers in AD FS in Windows Server 2012 R2: Overview
- How to create a Custom Authentication Provider for Active Directory Federation Services on Windows Server 2012 R2
Getting Setup
The full source for the code used in this post is available on Github.
I’m developing this with Visual Studio 2015 (VS 2012 should be OK), targeting .NET Framework 4.5, and I’m using Powershell v5 (although Powershell v3 or higher should be fine).
Goals
-
Automatically sign all assemblies that could be referenced by our provider. All assemblies must be signed so that they can be installed into the GAC on the ADFS server. If this is not done, our provider will not be able to locate the referenced assemblies. The assembly signing process should occur on every build of our provider.
-
Allow for a developer to deploy the provider to the ADFS server with zero manual steps. This is very important!. As far as I know, there is no way to remotely debug a custom provider. So, for us developers of these providers, we must make our code changes locally, deploy them to our test ADFS server, and then observe (not debug, just observe) our changes. This is an incredible annoyance and is also a serious detriment to productivity. As a result, I really want the developer to be able to rapidly deploy and enable the custom provider to speed up their dev time and to stay sane. I will detail the various steps that I think make up a full deploy a bit further on in this post.
Deployment must be fast and thorough to keep developers productive (and sane)
What Constitutes a Full Provider Deployment?
The following are described as if they are happening on the ADFS server.
- Disable any currently installed instances of the provider
- Attempting to remove an enabled provider via Powershell will throw errors and not work
- Remove the provider from ADFS
- Remove the provider’s assembly and dependent assemblies from the GAC
- Delete all provider files (.dll’s, built artifacts, etc)
- Copy newly built provider files from developer computer
- Add the provider’s assembly and dependent assemblies to the GAC
- Add the provider to ADFS
- Enable the provider in ADFS
- Ensure relevant ADFS windows services are up and running.
After a full deployment, the developer should merely have to refresh the browser in which they are testing their website in order to be exposed to their latest changes to the provider.
Step 1 - Assembly signing
I’ve added a NuGet package to the project to get a 3rd party assembly reference, specifically the Twilio package because sending SMS messages during the custom authentication processes could occur. Adding the Twilio NuGet package to the provider project will install two assemblies, Twilio.Api.dll
and RestSharp.dll
- but they are not signed assemblies! Because our provider’s assembly is signed, it can only use/reference other signed assemblies. To get around this, we can disassemble the 3rd party assemblies, sign them, and recompile them (see: Signing an Unsigned Assembly).
This can be accomplished a number of different ways, but I think that a Post-Build event in the provider project is an ideal method. This assures that all assemblies will be signed when a developer does a build from within Visual Studio or if a sys admin does a full deploy and MSBuild is manually used to build the provider project.
Let’s start with a batch file and project Post-Build event. Add a postbuild.bat
file to the provider project, and add this Post-Build event:
call "$(ProjectDir)postbuild.bat" $(TargetDir)
Here are the contents of postbuild.bat
set targetdir = %1
# First create all the strong key file
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\sn.exe" -k %targetdir%Twilio.Api.snk
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\sn.exe" -k %targetdir%RestSharp.snk
# decompile the DLLS to IL so we can add strong names
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\ildasm.exe" /all /out="%targetdir%Twilio.Api.il" %targetdir%Twilio.Api.dll
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\ildasm.exe" /all /out="%targetdir%RestSharp.il" %targetdir%RestSharp.dll
# delete the dll files so we can compile them
del %targetdir%Twilio.Api.dll /F /Q
del %targetdir%RestSharp.dll /F /Q
#recompile the dlls with strong names
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" /dll /Resource=%targetdir%Twilio.Api.res /out=%targetdir%Twilio.Api.dll /Key=%targetdir%Twilio.Api.snk %targetdir%Twilio.Api.il
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" /dll /Resource=%targetdir%RestSharp.res /out=%targetdir%RestSharp.dll /Key=%targetdir%RestSharp.snk %targetdir%RestSharp.il
#cleanup
@echo off
pushd %targetdir%
for /f %%F in ('dir /b /a-d ^| findstr /vile ".dll .Resources.resources .config"') do del "%%F"
Building the project will now get our assembly signing job done. However, this approach has the downside of not being generic. If a developer adds another NuGet package or assembly reference, they would have to update the postbuild.bat
file accordingly. That’s not cool. We can convert this postbuild action to Powershell and make it better.
postbuild.ps1
[Cmdletbinding()]
param(
[string]$targetdir,
[string]$providerAssemblyName
)
# Disassemble, sign, and recompile all 3rd party assemblies
Get-ChildItem -Path $targetdir -Include *.dll -Recurse | Where-Object { $_.Name -ne "$($providerAssemblyName).dll"} |
Select-Object -ExpandProperty FullName | Format-DllPaths | Invoke-Sn | Invoke-Ildasm | Invoke-Ilasm > $null
#cleanup
Remove-Item -Path "$($targetdir)*" -Exclude *.dll,*.Resources.resources -Recurse -Force > $null
I’ve omitted the implementation of the functions used above, but you can view the full source on Github. With this new Powershell script, we can change our Post-Build event to now be this:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -file "$(ProjectDir)Scripts/postbuild.ps1" $(TargetDir) $(ProjectName)
Step 2 - Deployment
The Powershell scripts for the deployment make use of Powershell Remoting, so make sure that is set up beforehand. I’m not going to post all of the scripts’ contents here (somewhat lengthy), but please [feel free to look over them on Github][https://github.com/ryanwischkaemper/adfs-mfa-provider/tree/master/DemoAuthenticationProvider/Scripts/ADFSProviderPublisher]. They cover and satisfy the goals that I laid out earlier, as well as the various steps that constitute a full deployment. Of course, there is always room for improvement, and I eagerly welcome feedback. Also, the scripts only target a single server, but they could fairly easily be adapted to target multiple servers.
Using these scripts, a developer can quickly configure a target ADFS server (and several other related config params) in the Publish-AuthProvider.ps1
file and then execute that same file to quickly and entirely deploy their provider! Hope this helps out with your workflows when developing these things.