I have my firewall, pfsense, using haproxy to do ssl termination and then connect to my home-assistant on my lan. I wanted to make things so that if an ip is banned it’s blocked at my firewall rather than being able to hit my server.
I have 2 scripts I load onto my pfsense that will modify an Alias that I can use to block the connections.
/etc/phpshellsessions/banip
//<?
global $argv, $command_split;
if (is_array($command_split)) {
$args = array_slice($command_split, 2);
} else {
$args = array_slice($argv, 3);
}
$ip = $args[0];
$jail = $args[1];
date_default_timezone_set('America/New_York');
$date = date('l jS \of F Y h:i:s A');
parse_config(true);
$alias_name = "Fail2Ban";
create_modify_alias($alias_name, $ip, $jail, $date);
/* save our changes */
save_config("Fail2Ban banned [${ip}] from [${jail}]");
/* Kill the state for that ip */
/* Use of the ! allows for shell execution per: https://openschoolsolutions.org/automate-pfsense-with-pfssh-php/ */
! pfctl -k $ip
/* ========== FUNCTIONS ============= */
function create_modify_alias($name, $ip, $jail, $date) {
$index = find_alias($name);
/* we couldn't find the alias so create an empty one */
if ($index == -1) {
$index = create_empty_alias($name, $date);
}
if ($index >= 0) {
modify_alias($index, $ip, "${jail} - ${date}");
}
}
function find_alias($name) {
global $config;
$index = -1;
for ($i = 0; $i <= count($config['aliases']['alias']); $i++) {
if ($config['aliases']['alias'][$i]['name'] == $name) {
$index = $i;
break;
}
}
return $index;
}
function create_empty_alias($name, $date) {
$index = add_alias_ip(
$name,
'host',
"Auto Created: ${date}",
'127.0.0.1',
"DO NOT DELETE, REQUIRED SO THE ALIAS IS NEVER EMPTY"
);
return $index;
}
function modify_alias($index, $address, $detail) {
global $config;
/* Get current */
$aliases = explode(' ',$config['aliases']['alias'][$index]['address']);
$details = explode('||',$config['aliases']['alias'][$index]['detail']);
if (!in_array($address, $aliases)) {
/* add the new stuff */
array_push($aliases, $address);
array_push($details, $detail);
/* write it back */
$config['aliases']['alias'][$index]['address'] = implode(' ',$aliases);
$config['aliases']['alias'][$index]['detail'] = implode('||',$details);
}
}
function add_alias_ip($name, $type, $descr, $address, $detail) {
global $config;
$new_alias = array(
'name' => $name,
'type' => $type,
'descr' => $descr,
'address' => $address,
'detail' => $detail
);
$index = array_push($config['aliases']['alias'], $new_alias) - 1;
return $index;
}
function save_config($desc) {
write_config($desc);
log_error("write_config: ${desc}");
}
/etc/phpshellsession/unbanip
//<?
global $argv, $command_split;
if (is_array($command_split)) {
$args = array_slice($command_split, 2);
} else {
$args = array_slice($argv, 3);
}
$ip = $args[0];
$jail = $args[1];
parse_config(true);
$alias_name = "Fail2Ban";
$index = find_alias($alias_name);
if ($index >= 0) {
$aliases_left = remove_alias($index, $ip);
}
save_config("Fail2Ban unbanned [${ip}] from [${jail}]");
/* ================ FUNCTIONS =============== */
function find_alias($name) {
global $config;
$index = -1;
for ($i = 0; $i <= count($config['aliases']['alias']); $i++) {
if ($config['aliases']['alias'][$i]['name'] == $name) {
$index = $i;
break;
}
}
return $index;
}
function remove_alias($index, $address) {
global $config;
/* Get current */
$aliases = explode(' ',$config['aliases']['alias'][$index]['address']);
$details = explode('||',$config['aliases']['alias'][$index]['detail']);
/* add the new stuff */
while ($key = array_search($address, $aliases)) {
unset($aliases[$key]);
unset($details[$key]);
}
/* write it back */
$config['aliases']['alias'][$index]['address'] = implode(' ',$aliases);
$config['aliases']['alias'][$index]['detail'] = implode('||',$details);
return count($aliases);
}
function save_config($desc) {
write_config($desc);
log_error("write_config: ${desc}");
}
/etc/fail2ban/actions.d/pfsense.conf
# Fail2Ban configuration file
#
# Author: Mike
#
[Definition]
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = sudo -u pfsense ssh root@firewall_ip "/usr/local/sbin/pfSsh.php playback banip <ip> <name>"
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban = sudo -u pfsense ssh root@firewall_ip "/usr/local/sbin/pfSsh.php playback unbanip <ip> <name>"
I created a user named pfsense
on my home-assistant system and generated ssh keys. Then on my pfsense box I allow the pfsense ssh key to login as root.
Then following the fail2ban docs: https://www.home-assistant.io/components/fail2ban/
Just change the action in jail.local
Before:
action = iptables-allports[name=HASS]
After:
action = pfsense[name=HASS]
You can test that the alias is updated with:
sudo fail2ban-client set hass banip 555.555.555.555
then remove it
sudo fail2ban-client set hass unbanip 555.555.555.555
I also disabled ip_ban_enabled in home-assistant so that fail2ban will do all of it. Now when fail2ban detects 5 failed logins to home-assistant it will ssh to my firewall and run the banip script to add an entry to the Fail2Ban alias.
Dummy ban of invalid ip 555.555.555.555
Firewall rule to block only access to HTTPS port
Edit:
@tkohhh had some good changes to immediately kill the state for the ip being banned: https://openschoolsolutions.org/automate-pfsense-with-pfssh-php/