Keeping your firewall rules updated can be a tedious chore when doing it manually - especially when there is so much malicious traffic going on from multiple sources.

My home network currently uses the Unifi Security Gateway and it has a really nice user interface (via the Unifi Controller software) - however, nice interfaces are only good for manual interactions.

Luckily, the Unifi Controller has its own API and thankfully it has been documented by curious and helpful Internet-people on it's own [Ubiquiti Community Wiki] (ubntwiki.com)].

For our purpose, we like this endpoint at first glance:

rest/firewallrule	GET/PUT	User defined firewall rules.

However, maintaining an ever-growing list of rules can be messy and hard to keep track of.

Instead, we will create a Firewall Group and use that for our rule.

From here, all we need to do is use that group to add/remove IP's.

We then set that group as the rejection rule source in the firewall:

We now have a neat little rule to block any IP from the firewall group in front of everything else:

Next, we can make use of the following endpoint to update the firewall group instead:

rest/firewallgroup	GET/PUT	User defined firewall groups.

Note: Before any endpoints can be called, we first need to call /api/login with a dictionary of {"username":"dummy","password":"dummy"} to retrieve a cookie for our session - this cookie called unifises needs to be sent with all subsequent calls.

curl https://<url>:8443/api/login -d '{"username":"<user>", "password":"<password>"}' -v
...snippet..
< Set-Cookie: unifises=abcdefagasdf1234; Path=/; Secure; HttpOnly 
< Set-Cookie: csrf_token=abcdefghj; Path=/; Secure  
...snippet..

The way the firewall group updates work is that we first need to perform a GET to retrieve all groups (which includes the IPs already in there) and then update the object with changes to the IP list and PUT the firewall group object to the rest/firewallgroup/<group_id> endpoint.

curl https://<url>:8443/api/s/default/rest/firewallgroup -H "Cookie: unifises=abcdefhgdsf1234;"
{
    "meta": {
        "rc": "ok"
    },
    "data": [
        {
            "_id": "12345",
            "name": "Block IPs",
            "group_type": "address-group",
            "group_members": [],
            "site_id": "abcdefg"
        }
    ]
}

Grap the object with the name of the group you wish to update and append IP strings to the group-members array.

curl -XPUT https://<url>:8443/api/s/default/rest/firewallgroup -H "Cookie: unifises=abcdefhgdsf1234;" -d '{"meta":{"rc":"ok"},"data":[{"_id":"12345","name":"Block IPs","group_type":"address-group","group_members":["256.256.256.256"],"site_id":"abcdefg"}]}'

Our group is now updated with 256.256.256.256 and any visits from there will be blocked (I'd be a bit surprised if that IP showed up).

This is a bit manual, so read on for a Python variant with some candy sprinkled in.

Getting IPs from Azure Queue

You need to tell which IPs to block, obviously, and there are many ways you could do that - now you have the tools to perform the block.

In my setup, I use Azure Sentinel (more on this here) to alert when malicious or suspicious IPs are trying to connect to my home network - it then triggers a Sentinel Playbook that determines if the IP should be blocked and places a message on an Azure Queue.

The reason for using Azure Queue and not, say, a web hook or similar is because I don't want to expose more target vectors to the internet.

By using a queue, I can initiate communication from within my network and pull the data I want in a more secure manner.

Of course, this is not real time protection, but should keep most out of your network before any real damage can be done.

To do this, I have a Docker container running on my NAS - this container has a simple script that runs every five minutes and does the following:

  • Read new messages from the queue
  • If they are flagged as malicious, append them to the firewall group we created earlier
  • Create an Azure Table entry for the IP to track when we last saw it
  • Retrieve all entries from the Azure Table that is older than 7 days and evict them from the firewall group

The Python script and Docker file can be found here sentinel-log/usg-scripts at main · FrodeHus/sentinel-log (github.com)

It needs a couple of things:

  • Username/password for a admin user on your Unifi Controller
  • The address of the Unifi Controller
  • Connection string for your Azure Storage Account where the queue and table resides

The script reads these from environment variables and if you run it as a Docker container, this should do the trick:

# build the image
docker build -t firewall-updater:latest .

# run the image
docker run -d -e UNIFI_USERNAME=<username> -e UNIFI_PASSWORD=<password> -e UNIFI_CONTROLLER=<my.controller.local> -e AZURE_STORAGE_CONNECTION_STRING=`az storage account show-connection-string -n <account name> -g <resource group> | jq ".connectionString"` firewall-updater:latest
Busy blocking IPs

Hopefully this has been useful :)