Introduction
After introducing Azure Multi-Factor Authentication (MFA) for use with Virtual Private Network (VPN) or Remote Desktop Gateway (RDGW) solutions. I finally wrote some articles about it over at Transition a Highly Available RD Gateway to Use the NPS Extension for Azure MFA – Phase I and Transition a highly available RD Gateway to use the NPS Extension for Azure MFA – Phase II. In these, I explain how to transition a highly available RDWG environment to include MFA via Microsoft’s NPS Extension for Azure MFA. A question or need that always comes up is how to easily exclude users with VPN or RDGW access from Azure MFA. It is typically only a temporary measure for one or a couple of users who have forgotten, broken, lost their phone, or have Authenticator App issues.
Telecommuters exist thanks to secure remote access (Photo by Teona Swift)
With many MFA solutions, you can exclude users from MFA temporarily for a certain amount of time, work with offline access codes, etc. But here, the NPS Extension for Azure MFA is a bolt-on to IAS/RADIUS, with workloads that do not offer a UI for OTPs or other codes. That limits us in what we can do. Still, we can do something. So, while there is no perfect solution, let me share a good workaround I have implemented for a service desk.
Requirements
The key here is that usually, MFA is mandatory for all users at all times. If a user in Azure does not have MFA enabled, generally via a conditional policy, they cannot gain access. Exemptions to this policy are only temporary and for approved use cases. They are also only to affect the VPN or RDGW access. We also do not want the service desk to have to change Conditional Access policies or such in Azure itself.
You can easily disable the NPS Extension for Azure MFA on the NPS Servers by renaming a couple of registry keys (see the articles referenced above for more details) and restarting the NPS service. But that means there is no MFA for anyone connecting, which is not what you want.
Setting the value of REQUIRE_USER_MATCH (in HKLM\SOFTWARE\Microsoft\AzureMfa )exempts a user from MFA when that user account does not have MFA enabled in Azure. Do note that changes to these values require a restart of the NPS Service to become active. But this misses the mark. It works for any user in that situation, which is not OK as we don’t want people who don’t have MFA enabled to slip through. Another factor is that we don’t want any changes made to the Conditional Access policy for this. As we mandate MFA for use with the NPS Extension for Azure MFA configuration, disabling MFA for a user in Azure is also not what we want. Next to that, there are other apps we might not want to exclude from MFA, and we have no option in Azure to disable MFA for this extension only per user. The service desk does not usually have the mandate and rights to do this anyway.
The best “workaround” we could implement for this use case
In an environment with a secure and highly available RDGW deployment, we came up with the below solution that guaranteed the following:
- It is local to the RDGW (or VPN) Servers, so this requires no extra rights in Active Directory Domain Services or Azure Active Directory
- You can bypass MFA for one or more users while the others still fall under the MFA requirement
- You do not need to change anything to the working NPS Extension for Azure MFA configuration. Additionally, you do not need to touch the NPS servers for this. This workaround is local to the RDWG servers.
Step by step
On every RDGW server in your load-balanced farm, do the following. First, create a new policy and call it something sensible like “EXCLUDE USERS FROM MFA.”
Overview tab
- For now, leave it disabled
- For the “type of network server,” choose “Remote Desktop Gateway.”
The overview tab
Conditions tab
On the conditions tab, add the following:
- Enter the “User Name” pattern to match. In our example, we allow three users, and any or all of these will be allowed. Usually, these are set to none existing users to avoid any unintended MFA bypass when the policy is enabled. The regular expression us | for an OR condition. Note that we need to escape the special “\” with an extra “\” character as that has a meaning in regular expressions. Here I use REALM\\user1|REALM\\user2|DATAWISETECH\\billythekid. Note that only the 3rd is an actual user in the lab domain. The rest are “inert” placeholders.
- Allow the “NAS Port Type” of Virtual (VPN)
- Add some day and time restrictions. For example, while we exempt a user or users from the MFA requirement, this will only work on weekdays between 07:00 and 19:00.
The conditions tab
Bonus
Here is a PowerShell script that does this for you on the two RDGW servers. Note that you must include credentials with the Invoke-Command if you run the script under an account with no admin permissions to make changes on the remote servers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<em>#A string array of users we want to exempt from Azure MFA for VPN and RDGW access</em> <em>$users = ("REALM\\user1", "REALM\\user6", "DATAWISETECH\\billythekid")</em> <em>#Whether the connection request policy is disabled - when creating the policy, we want it disabled</em> <em>$CRPDISABLED = $True</em> <em>#Initialize variables</em> <em>$BuildUserNameString = ''</em> <em>$counter = $Null</em> <em>$Servers = ("RDGW01", "RDGW02")</em> <em>#build the exempted user string</em> <em>foreach ($User in $Users) {</em> <em>$counter = $counter + 1</em> <em>$counter</em> <em>if ($counter -ne $users.count) {</em> <em>Write-Host -ForegroundColor green</em> <em>$BuildUserNameString += $User + "|"</em> <em>}</em> <em>Else {</em> <em>Write-Host -ForegroundColor yellow</em> <em>$BuildUserNameString += $User</em> <em>}</em> <em>}</em> <em>#Check to see what state the policy needs to have</em> <em>if ($CRPDISABLED) { $State = 'DISABLE' }else { $State = 'ENABLE' }</em> <em>#build the NPS netsh command to execute</em> <em>$NetShellToRun = "Netsh NPS add crp name = ""EXCLUDE USERS FROM MFA"" state = ""DISABLE"" processingorder = ""7"" policysource = ""1"" conditionid = ""0x1"" conditiondata = ""$BuildUserNameString"" conditionid = '0x3d' conditiondata = ""^5$"" conditionid = ""0x1006"" conditiondata = ""1 07:00-19:00; 2 07:00-19:00; 3 07:00-19:00; 4 07:00-19:00; 5 07:00-19:00"""</em> <em>#run the NPS netsh command</em> <em>Invoke-command -ComputerName $Servers -ScriptBlock {Invoke-Expression $using:NetShellToRun}</em> |
Be warned; the netsh command cannot reorder the policies for you. Instead, you need to select an “empty” order number and then move your connection request policy to the desired spot to have it evaluated correctly.
Messing around with the GUI and netsh for configuring and creating the policies can rarely lead to configuration file corruption. So have an export of your config, a checkpoint, and a backup to recover if needed. Although, that is something you should always have. Any time you work in your production environment, you must have a backup plan for when things go wrong.
Settings tab
On the settings tab under Authentication
- Check “Authenticate Requests on this server.” That means that when the conditions match, the Authentication is not sent off the NPS servers with the NPS Extension for Azure MFA. Still, the user is authenticated locally and has to meet the local network policy.
The settings tab
That is the default. If you used the above script, this configuration is also correct.
Finally
Move the policy to sit before the “REQUESTS TOWARDS CENTRAL NPS SERVERS” policy. If the conditions match, the Authentication will be handled locally and not sent to the NPS Servers. If they do not, the NPS server will take care of this for us. The remote NPS servers require Azure MFA, which is still a hard requirement for any user we did include in our user name condition in our “EXCLUDE USERS FROM MFA” policy.
Order your new Connection Request Policy correctly!
Suppose you have disabled or removed the original local NPS network policy for RDGW (the Client Access Policy in RDG W terms), which determines who can log on via the RDGW server, what features are available, etc. In that case, you need to enable it or recreate one.
Ensure you have a functional and enabled local Client Access Policy (Network Policy) on the NPS Server
Remember that when you transition to a solution that leverages the NPS Extension for Azure MFA, you no longer use the local policy but handle all that on the NPS Servers. So here we need it back to take care of the user(s) we exempted from MFA.
To test the policy, put in the user name of one of your users. Enable the policy and try it! When you have configured everything correctly, you will find that the user(s) configured in the EXCLUDE USERS FROM MFA connection request policy should be able to use RDWG without an MFA prompt.
Some tips on using this policy
Once the need for bypassing MFA for a user is over, remove them from the list.
When there is no need for anyone to bypass MFA, keep this policy disabled. For example, forgetting to remove a user from the condition disabling the policy will prevent anyone from circumventing MFA by mistake.
You’ll need the discipline to do this correctly to prevent any MFA bypass mistake. One way of helping with this is automating it. Below is a script the service desk can run against the NPS servers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<em>#A string array of users we want to exempt from Azure MFA for VPN and RDGW access</em> <em>$users = ("REALM\\inertuser1", "REALM\\inertuser2", "DATAWISETECH\\billythekid")</em> <em>#Whether the connection request policy is disabled.</em> <em>$CRPDISABLED = $False</em> <em>#Initialize variables</em> <em>$BuildUserNameString = ''</em> <em>$counter = $Null</em> <em>$Servers = ("RDGW01", "RDGW02")</em> <em>#build the exempted user string</em> <em>foreach ($User in $Users) {</em> <em>$counter = $counter + 1</em> <em>#$counter</em> <em>if ($counter -ne $users.count) {</em> <em>Write-Host -ForegroundColor green</em> <em>$BuildUserNameString += $User + "|"</em> <em>}</em> <em>Else {</em> <em>Write-Host -ForegroundColor yellow</em> <em>$BuildUserNameString += $User</em> <em>}</em> <em>}</em> <em>#Check to see what state the policy needs to have</em> <em>if ($CRPDISABLED) { $State = 'DISABLE' }else { $State = 'ENABLE' }</em> <em>#build the NPS netsh command to excute</em> <em>$NetShellToRun = "Netsh NPS set crp name = ""EXCLUDE USERS FROM MFA"" state = ""$State"" conditionid = ""0x1"" conditiondata = ""$BuildUserNameString"" conditionid = ""0x3d"" conditiondata = ""^5$"" conditionid = ""0x1006"" conditiondata = ""1 07:00-19:00; 2 07:00-19:00; 3 07:00-19:00; 4 07:00-19:00; 5 07:00-19:00"""</em> <em>#Invoke-Expression $NetShellToRun</em> <em>#run the NPS netsh command</em> <em>Invoke-command -ComputerName $Servers -ScriptBlock {Invoke-Expression $using:NetShellToRun}</em> |
As an added fail-safe, you can have a scheduled task to disable the policy to the MFA bypass every evening at 20:00 hours. If the exemption must exist longer than the current workday, it has to be enabled again intentionally by the service desk the next day. With creativity, you can add the users to a text file and have the configuration executed by a service account, so the service desk personnel does not need admin access to the RDGW servers.
Conclusion
Well, that concludes my solution for the often requested ability to bypass NFA for a specific user or users when introducing NPS Extension for Azure MFA. Of course, this isn’t perfect, but it helps. Just remember to manage this well as you are, for whatever reason, introducing more risk into your environment. I hope this helps you out when the need arises.