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
Hopefully this has been useful :)