Add GeoIP support for banning IPs

Fixes #3790

This should be a good fix for issue #3790, ip-api is a pretty nice API but I haven't had a ton of time to play with it so I might be missing something. I would greatly appreciate any review on this if someone wanted to help.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/fail2ban/fail2ban/issues/3790?shareId=XXXX-XXXX-XXXX-XXXX).
This commit is contained in:
Carter McKay 2024-10-29 16:20:36 -05:00
parent 0bf1106d72
commit c3f3aeb851
4 changed files with 92 additions and 0 deletions

View file

@ -166,6 +166,11 @@ mode = normal
#
filter = %(__name__)s[mode=%(mode)s]
# "ignoregeo" specifies the countries to ignore based on geolocation.
#ignoregeo =
# "geoipdb" specifies the path to the GeoIP database.
#geoipdb =
#
# ACTIONS

View file

@ -743,3 +743,56 @@ class Actions(JailThread, Mapping):
("Banned Country list", self.banManager.geBanListExtendedCountry(cymru_info)),
("Banned RIR list", self.banManager.geBanListExtendedRIR(cymru_info))]
return ret
def perform_geolocation_lookup(self, ip):
"""Perform geolocation lookup for the given IP address.
Parameters
----------
ip : str
The IP address to perform geolocation lookup for.
Returns
-------
str
The country code of the IP address.
"""
# Implement the geolocation lookup logic here
# For example, you can use an external service like ip-api.com
import requests
response = requests.get(f"http://ip-api.com/json/{ip}")
data = response.json()
return data.get("countryCode")
def ban(self, aInfo):
"""Executes the given command ("actionban" or "actionreban").
Replaces the tags in the action command with actions properties
and ban information, and executes the resulting command.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
the ban.
"""
# Perform geolocation lookup
ip = aInfo["ip"]
country_code = self.perform_geolocation_lookup(ip)
aInfo["country_code"] = country_code
# Check if the IP should be ignored based on geolocation
ignoregeo = self._jail.filter.getOptions().get("ignoregeo", "").split()
if country_code in ignoregeo:
logSys.info("Ignoring IP %s from country %s based on geolocation", ip, country_code)
return
# if we should start the action on demand (conditional by family):
family = aInfo.get('family', '')
if self._startOnDemand:
if not self.__started.get(family):
self._start(family, forceStart=True)
# ban:
if not self._processCmd('<actionban>', aInfo):
raise RuntimeError("Error banning %(ip)s" % aInfo)
self.__started[family] = self.__started.get(family, 0) | 3; # started and contains items

View file

@ -384,3 +384,23 @@ class BanManager:
except KeyError:
pass
return None # if none found
##
# Handle geolocation data for banned IPs.
#
# @param ticket the ticket
# @return True if the IP address is not in the ban list
def handleGeolocationData(self, ticket):
# Perform geolocation lookup
ip = ticket.getID()
country_code = self.perform_geolocation_lookup(ip)
ticket.setData("country_code", country_code)
# Check if the IP should be ignored based on geolocation
ignoregeo = self._jail.filter.getOptions().get("ignoregeo", "").split()
if country_code in ignoregeo:
logSys.info("Ignoring IP %s from country %s based on geolocation", ip, country_code)
return False
return True

View file

@ -85,6 +85,8 @@ class Jail(object):
if backend is not None:
self._realBackend = self._setBackend(backend)
self.backend = backend
self.__ignoregeo = set()
self.__geoipdb = None
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
@ -207,6 +209,16 @@ class Jail(object):
Used by filter to add a failure for banning.
"""
# Perform geolocation lookup
ip = ticket.getID()
country_code = self.actions.perform_geolocation_lookup(ip)
ticket.setData("country_code", country_code)
# Check if the IP should be ignored based on geolocation
if country_code in self.__ignoregeo:
logSys.info("Ignoring IP %s from country %s based on geolocation", ip, country_code)
return
self.__queue.put(ticket)
# add ban to database moved to observer (should previously check not already banned
# and increase ticket time if "bantime.increment" set)
@ -324,6 +336,8 @@ class Jail(object):
self.filter.start()
self.actions.start()
self.restoreCurrentBans()
self.__ignoregeo = set(self.filter.getOptions().get("ignoregeo", "").split())
self.__geoipdb = self.filter.getOptions().get("geoipdb", None)
logSys.info("Jail %r started", self.name)
def stop(self, stop=True, join=True):