Using Powershell to update Windows Firewall rule IP’s

I like to be careful to expose any service to the public Internet. Especially when running a Windows server. On Windows 2012, the firewall can be managed by the “Windows Firewall with Advanced Security” application.

Windows Firewall defaults

The default settings in the Windows Firewall seem strange to me. By default, the server does not answer to ping requests, but has the WMI (remote management) service open to the world. Another questionable defaults are the RPC port that are open. I don’t want to go too deep into the details on how to properly setup the Windows Firewall. But as a rule of thumb I recommend to block all incoming requests, allow all outgoing and then only open up the ports you need for your applications to function. This policy blocks everything by default and forced you to make “allow” rules to open the Windows Firewall for specific applications.

Don’t lock yourself out (important!)

There is one port you always need to keep open: TCP 3389. If you close it you will lose remote (RDP, Terminal Service Client) access to the server. In my case, that would mean I have to go to the data center to connect a screen, keyboard, and mouse.

Updating a Windows firewall rule using PowerShell

Now for the problem and the solution. I am a bit careful with the Windows Firewall rules, since the server has a public IP address. Each application gets its own rule with a corresponding “scope”. The scope determines which IP addresses are allowed to connect. When you have several people that need to maintain the list of IP addresses that are allowed to connect you may want to make a system that administers the entries. This post proposes a system where this list is maintained in a file (with comments) that is updatable by multiple users on the system. Then the “Task Scheduler” will compare the date in the “Description” field of the Windows Firewall rule with the “Last Modified” date of the file. If the file is modified later than the reload, the Task Scheduler will update the Windows Firewall rule (and the date stored in the description field).

Task Scheduler configuration

The system works with four files that I all load from “firewall” directory on the “H:” partition (home directory):

  • firewall.bat: Batch file invoked by the Task Scheduler that calls the PowerShell script
  • firewall.ps1: PowerShell script that actually reconfigures the firewall rule scope
  • firewall.log: Log file that allows you to keep track of proper operation
  • firewall.allow: List of IP addresses and subnets that are allowed to connect

firewall_task_action
The Task Scheduler should be configured to run the “firewall.bat” file in the “Edit Action” screen.

firewall_task_trigger
You could trigger the task every five minutes by setting the following in the “Edit Trigger” screen.

H:\firewall\firewall.bat

powershell -executionpolicy remotesigned -File H:\firewall\firewall.ps1 >> H:\firewall\firewall.log

H:\firewall\firewall.allow

# This is the firewall configuration file. It is scanned every 5 minutes for updates.

# This file may contain comment lines using hash signs (like this one). Other lines
# must consist of the following: IP address or range in CIDR notation

# Localhost
127.0.0.1/8

H:\firewall\firewall.ps1

$name = "World Wide Web Services (HTTP Traffic-In)"
$rule = Get-NetFirewallRule -DisplayName $name
$ruleTime = [datetime]::ParseExact($rule.Description, 'yyyy-MM-dd HH:mm:ss',$null)
$file = 'H:\firewall\firewall.allow'
$fileTime = (Get-Item $file).LastWriteTime
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$tspan = New-TimeSpan $ruleTime $fileTime
$seconds = ($tspan).TotalSeconds
if ($seconds -lt 0) {
  echo "$time Firewall up-to-date, exitting"
  exit
}
echo "$time Loading new firewall rules"
$ips = New-Object Collections.Generic.List[String]
$lines = Get-Content $file
foreach ($line in $lines) {
  if ($line -match "^s*#") {
    continue
  } elseif ($line -match "^s*$") {
    continue
  } elseif ($line -match "^([0-9./]{7,18})s*(#.*)?$") {
    echo $matches[1]
    $ips.Add($line)
  } else {
    echo ("IGNORED: " + $line)
  }
}
# set new firewall rules
try {
  Set-NetFirewallRule -DisplayName $name -RemoteAddress $ips -Description $time
} catch {
  echo "ERROR: Firewall rules could not be loaded"
}

H:\firewall\firewall.log

2014-02-09 13:10:02 Firewall up-to-date, exitting
2014-02-09 13:15:02 Loading new firewall rules
127.0.0.1/8
2014-02-09 13:20:02 Firewall up-to-date, exitting
2014-02-09 13:25:02 Firewall up-to-date, exitting
Share