Using Windows Firewall to restrict access to Windows Azure instances

Summary: If you ever have a need to restrict access to your Windows Azure deployment to known IP address ranges, you can do this by programmatically modifying the Windows Firewall. You’ll need to do this both at startup, and whenever your role topology changes, as the Windows Azure guest agent also likes to modify firewall rules.

When you deploy your PaaS cloud service to Windows Azure, it’s on the internet. This means it can be accessed by anyone who is also on the internet, subject to your application’s authorisation logic. Normally this is a good thing—we build and deploy apps so people can use them. However sometimes you may want to restrict access to certain known client machines. Some of the reasons I’ve seen for this include:

  • You’re working on an unreleased, super-secret application that you don’t want anyone to stumble upon until you’re good and ready.
  • You have multiple deployments for different environments (e.g. development, test, UAT) which should each only be accessed by certain people but you don’t want to change the app’s authorisation logic.
  • Your application is only designed for internal users and you don’t want to allow remote access. In this case you should also use a federated identity solution to authorise individual users, but if you’re really paranoid you may want to prevent these users from accessing the app when away from the corporate network.
  • Even though you’re happy for anyone to access the application’s public UI, you want some additional security (beyond standard user authentication) for admin services, such as RDP or an administrative UI running on a different port.

If you’re only interested in blocking traffic to IIS-hosted services, one option is to use IIS’s “IP and Domain Restrictions” feature to restrict access as described in Liam Cavanagh's Blog. This is a good option as the IP restrictions are configuration-based, and Windows Azure won’t do anything that interferes with your settings. However as Liam points out, this capability depends on a Windows feature that isn’t installed by default, so you’ll need to write a startup script to install the “IP and Domain Restrictions” feature.

However this solution will not work if you want to restrict access to something that’s not IIS hosted, such as RDP. In this case you need to step it up a level by modifying the Windows Firewall which runs locally on each Windows Azure VM. Normally it’s pretty easy to configure Windows Firewall to only allow access to a given port to specific IP ranges: you set up a single rule for that port and set the scope to your IP ranges; any traffic from other IP addresses will be automatically blocked as there is no matching firewall rule. Unfortunately this is a bit more complex in Windows Azure, as the fabric will automatically configure a whole bunch of firewall rules (helpfully named after random GUIDs) that open certain ports to all IP addresses. You can still add additional rules if you want, but due to the way that firewall rules combine, you can’t easily block access once another rule has already granted it.

The most practical option I found was to modify the existing firewall rules to add the IP range restrictions. There are a few different APIs to do this, but the easiest way is to use the Network Security PowerShell cmdlets. These run on Windows Server 2012, so make sure you set your Windows Azure osFamily attribute to “3”. Here’s a simple PowerShell script that modifies all existing rules for two ports (80 and 3389) to restrict them to specific IP addresses and ranges:

 # SetFirewallRestrictions.ps1
$date = Get-Date
Write-Host "Updating firewall rule restrictions at " $date "`r`n"
$allowedRanges = ('3.2.1.0/24', '1.2.3.4') 
Get-NetFirewallPortFilter | ? LocalPort -eq '80' | Get-NetFirewallRule | Set-NetFirewallRule -RemoteAddress $allowedRanges
Get-NetFirewallPortFilter | ? LocalPort -eq '3389' | Get-NetFirewallRule | Set-NetFirewallRule -RemoteAddress $allowedRanges

To make it easier to call the script and log the output, let’s also create a .cmd file to wrap it:

 rem SetFirewallRestrictions.cmd
cd /d %~dp0
Powershell set-executionpolicy remotesigned -force
Powershell .\SetFirewallRestrictions.ps1 >> SetFirewallRestrictions.out.log 2>> SetFirewallRestrictions.err.log

So now we have our scripts, it’s just a matter of including them into our Windows Azure cloud service role and calling them at the appropriate time. The obvious place to do this is at role startup, so let’s add a startup task to the ServiceDefinition.csdef file. Remember to run it as elevated since we’re playing with security settings. Also note the <Runtime> element which I’ll get to in a moment.

 <ServiceDefinition name="FirewallTest" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2013-03.2.0">
  <WebRole name="WebRole1" vmsize="Small">
   ...  
    <Startup>
      <Task commandLine="Startup\SetFirewallRestrictions.cmd" executionContext="elevated" taskType="simple" />
    </Startup>
    <Runtime executionContext="elevated" />
  </WebRole>
</ServiceDefinition>

With the startup script in place, Windows Azure will automatically update the firewall settings with your IP range restrictions just as the instance starts. So we’re done, right? Actually not quite. After some testing, we found that our custom firewall rules were occasionally overwritten after the instance has been running for a while. After further investigation we discovered that the Windows Azure Guest Agent (an app which the fabric installs on your VM to carry out various cloudy tasks) recreates the firewall rules whenever the role is scaled – yes, even on the instances that were already running. So in order to keep our IP address restrictions, we need to reapply our firewall script when the role’s topology changes. Important: This solution is based on experimentation, not insider knowledge of exactly how this part of the platform works. It’s possible that there are other times when the firewall rules are overwritten. Use this solution at your own risk and test thoroughly.

In order to reapply the firewall rules when the app is scaled, it’s necessary to subscribe to the RoleEnvironment.Changed event in your RoleEntryPoint-derived class. In the event handler we can launch the same script again. The <Runtime executionContext=”elevated” /> entry we put in above ensures that this code runs as admin.

 public class WebRole : RoleEntryPoint
{
    public override bool OnStart()
    {
        RoleEnvironment.Changed += RoleEnvironment_Changed;
        return base.OnStart();
    }

    void RoleEnvironment_Changed(object sender, RoleEnvironmentChangedEventArgs e)
    {
        if (e.Changes.Any(ch => ch is RoleEnvironmentTopologyChange))
        {
            var processStartInfo = new ProcessStartInfo(@"Startup\SetFirewallRestrictions.cmd");
            var process = Process.Start(processStartInfo);
        }
    }
}

So far it looks like this is a solid option for restricting access to your Windows Azure applications to specific IP addresses. If you find any issues or have any suggestions please let me know, and I’ll promise to do the same!