mirror of https://github.com/fail2ban/fail2ban
Add extended info to status output using Cyrmu
parent
cfaa76a355
commit
60ac0a1a17
|
@ -12,6 +12,8 @@ before_install:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get update -qq; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get update -qq; fi
|
||||||
install:
|
install:
|
||||||
- travis_retry pip install pyinotify
|
- travis_retry pip install pyinotify
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == 2* || $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then travis_retry pip install dnspython; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == 3* || $TRAVIS_PYTHON_VERSION == 'pypy3' ]]; then travis_retry pip install dnspython3; fi
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; travis_retry pip install -q coveralls; cd -; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; travis_retry pip install -q coveralls; cd -; fi
|
||||||
script:
|
script:
|
||||||
|
|
|
@ -41,7 +41,11 @@ ver. 0.9.2 (2014/XX/XXX) - wanna-be-released
|
||||||
- Monit config for fail2ban in /files/monit
|
- Monit config for fail2ban in /files/monit
|
||||||
- New actions:
|
- New actions:
|
||||||
- action.d/firewallcmd-multiport and action.d/firewallcmd-allports Thanks Donald Yandt
|
- action.d/firewallcmd-multiport and action.d/firewallcmd-allports Thanks Donald Yandt
|
||||||
|
- New status commands:
|
||||||
|
- fail2ban-client status <jail> extended
|
||||||
|
- prints Cymru data (ASN, Country RIR) per banned IP
|
||||||
|
- Requires dnspython or dnspython3
|
||||||
|
|
||||||
- Enhancements:
|
- Enhancements:
|
||||||
* Enable multiport for firewallcmd-new action. Closes gh-834
|
* Enable multiport for firewallcmd-new action. Closes gh-834
|
||||||
* files/debian-initd migrated from the debian branch and should be
|
* files/debian-initd migrated from the debian branch and should be
|
||||||
|
|
|
@ -55,6 +55,7 @@ protocol = [
|
||||||
["start <JAIL>", "starts the jail <JAIL>"],
|
["start <JAIL>", "starts the jail <JAIL>"],
|
||||||
["stop <JAIL>", "stops the jail <JAIL>. The jail is removed"],
|
["stop <JAIL>", "stops the jail <JAIL>. The jail is removed"],
|
||||||
["status <JAIL>", "gets the current status of <JAIL>"],
|
["status <JAIL>", "gets the current status of <JAIL>"],
|
||||||
|
["status <JAIL> extended", "gets the current status of <JAIL> with extended info"],
|
||||||
['', "JAIL CONFIGURATION", ""],
|
['', "JAIL CONFIGURATION", ""],
|
||||||
["set <JAIL> idle on|off", "sets the idle state of <JAIL>"],
|
["set <JAIL> idle on|off", "sets the idle state of <JAIL>"],
|
||||||
["set <JAIL> addignoreip <IP>", "adds <IP> to the ignore list of <JAIL>"],
|
["set <JAIL> addignoreip <IP>", "adds <IP> to the ignore list of <JAIL>"],
|
||||||
|
|
|
@ -372,9 +372,20 @@ class Actions(JailThread, Mapping):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self):
|
def status(self):
|
||||||
"""Status of active bans, and total ban counts.
|
"""Status of current and total ban counts and current banned IP list.
|
||||||
"""
|
"""
|
||||||
ret = [("Currently banned", self.__banManager.size()),
|
ret = [("Currently banned", self.__banManager.size()),
|
||||||
("Total banned", self.__banManager.getBanTotal()),
|
("Total banned", self.__banManager.getBanTotal()),
|
||||||
("Banned IP list", self.__banManager.getBanList())]
|
("Banned IP list", self.__banManager.getBanList())]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def statusExtended(self):
|
||||||
|
"""Jail status plus banned IPs' ASN, Country and RIR
|
||||||
|
"""
|
||||||
|
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||||
|
ret = self.status +\
|
||||||
|
[("Banned ASN list", self.__banManager.geBanListExtendedASN(cymru_info)),
|
||||||
|
("Banned Country list", self.__banManager.geBanListExtendedCountry(cymru_info)),
|
||||||
|
("Banned RIR list", self.__banManager.geBanListExtendedRIR(cymru_info))]
|
||||||
|
return ret
|
||||||
|
|
|
@ -26,6 +26,9 @@ __license__ = "GPL"
|
||||||
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
import dns.exception
|
||||||
|
import dns.resolver
|
||||||
|
|
||||||
from .ticket import BanTicket
|
from .ticket import BanTicket
|
||||||
from .mytime import MyTime
|
from .mytime import MyTime
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger
|
||||||
|
@ -118,6 +121,115 @@ class BanManager:
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns normalized value
|
||||||
|
#
|
||||||
|
# @return value or "unknown" if value is None or empty string
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handleBlankResult(value):
|
||||||
|
if value is None or len(value) == 0:
|
||||||
|
return "unknown"
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns Cymru DNS query information
|
||||||
|
#
|
||||||
|
# @return {"asn": [], "country": [], "rir": []} dict for self.__banList IPs
|
||||||
|
|
||||||
|
def getBanListExtendedCymruInfo(self):
|
||||||
|
self.__lock.acquire()
|
||||||
|
return_dict = {"asn": [], "country": [], "rir": []}
|
||||||
|
try:
|
||||||
|
for banData in self.__banList:
|
||||||
|
ip = banData.getIP()
|
||||||
|
# Reference: http://www.team-cymru.org/Services/ip-to-asn.html#dns
|
||||||
|
# TODO: IPv6 compatibility
|
||||||
|
reversed_ip = ".".join(reversed(ip.split(".")))
|
||||||
|
question = "%s.origin.asn.cymru.com" % reversed_ip
|
||||||
|
try:
|
||||||
|
answers = dns.resolver.query(question, "TXT")
|
||||||
|
for rdata in answers:
|
||||||
|
asn, net, country, rir, changed =\
|
||||||
|
[answer.strip("'\" ") for answer in rdata.to_text().split("|")]
|
||||||
|
asn = self.handleBlankResult(asn)
|
||||||
|
country = self.handleBlankResult(country)
|
||||||
|
rir = self.handleBlankResult(rir)
|
||||||
|
return_dict["asn"].append(self.handleBlankResult(asn))
|
||||||
|
return_dict["country"].append(self.handleBlankResult(country))
|
||||||
|
return_dict["rir"].append(self.handleBlankResult(rir))
|
||||||
|
except dns.resolver.NXDOMAIN:
|
||||||
|
return_dict["asn"].append("nxdomain")
|
||||||
|
return_dict["country"].append("nxdomain")
|
||||||
|
return_dict["rir"].append("nxdomain")
|
||||||
|
except dns.exception.DNSException as dnse:
|
||||||
|
logSys.error("Unhandled DNSException querying Cymru for %s TXT" % question)
|
||||||
|
logSys.exception(dnse)
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Unhandled Exception querying Cymru for %s TXT" % question)
|
||||||
|
logSys.exception(e)
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Failure looking up extended Cymru info")
|
||||||
|
logSys.exception(e)
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
return return_dict
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns list of Banned ASNs from Cymru info
|
||||||
|
#
|
||||||
|
# Use getBanListExtendedCymruInfo() to provide cymru_info
|
||||||
|
#
|
||||||
|
# @return list of Banned ASNs
|
||||||
|
|
||||||
|
def geBanListExtendedASN(self, cymru_info):
|
||||||
|
self.__lock.acquire()
|
||||||
|
try:
|
||||||
|
return [asn for asn in cymru_info["asn"]]
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Failed to lookup ASN")
|
||||||
|
logSys.exception(e)
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns list of Banned Countries from Cymru info
|
||||||
|
#
|
||||||
|
# Use getBanListExtendedCymruInfo() to provide cymru_info
|
||||||
|
#
|
||||||
|
# @return list of Banned Countries
|
||||||
|
|
||||||
|
def geBanListExtendedCountry(self, cymru_info):
|
||||||
|
self.__lock.acquire()
|
||||||
|
try:
|
||||||
|
return [country for country in cymru_info["country"]]
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Failed to lookup Country")
|
||||||
|
logSys.exception(e)
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns list of Banned RIRs from Cymru info
|
||||||
|
#
|
||||||
|
# Use getBanListExtendedCymruInfo() to provide cymru_info
|
||||||
|
#
|
||||||
|
# @return list of Banned RIRs
|
||||||
|
|
||||||
|
def geBanListExtendedRIR(self, cymru_info):
|
||||||
|
self.__lock.acquire()
|
||||||
|
try:
|
||||||
|
return [rir for rir in cymru_info["rir"]]
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Failed to lookup RIR")
|
||||||
|
logSys.exception(e)
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
##
|
##
|
||||||
# Create a ban ticket.
|
# Create a ban ticket.
|
||||||
#
|
#
|
||||||
|
|
|
@ -183,6 +183,15 @@ class Jail:
|
||||||
("Actions", self.actions.status),
|
("Actions", self.actions.status),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def statusExtended(self):
|
||||||
|
"""The extended status of the jail.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("Filter", self.filter.status),
|
||||||
|
("Actions", self.actions.statusExtended),
|
||||||
|
]
|
||||||
|
|
||||||
def putFailTicket(self, ticket):
|
def putFailTicket(self, ticket):
|
||||||
"""Add a fail ticket to the jail.
|
"""Add a fail ticket to the jail.
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,12 @@ class JailThread(Thread):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractproperty
|
||||||
|
def statusExtended(self): # pragma: no cover - abstract
|
||||||
|
"""Abstract - Should provide extended status information.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Sets active flag and starts thread.
|
"""Sets active flag and starts thread.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -322,7 +322,10 @@ class Server:
|
||||||
|
|
||||||
def statusJail(self, name):
|
def statusJail(self, name):
|
||||||
return self.__jails[name].status
|
return self.__jails[name].status
|
||||||
|
|
||||||
|
def statusJailExtended(self, name):
|
||||||
|
return self.__jails[name].statusExtended
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -333,5 +333,10 @@ class Transmitter:
|
||||||
elif len(command) == 1:
|
elif len(command) == 1:
|
||||||
name = command[0]
|
name = command[0]
|
||||||
return self.__server.statusJail(name)
|
return self.__server.statusJail(name)
|
||||||
|
elif len(command) == 2:
|
||||||
|
name = command[0]
|
||||||
|
if command[1] == "extended":
|
||||||
|
return self.__server.statusJailExtended(name)
|
||||||
|
else:
|
||||||
|
raise Exception("Invalid command (invalid status extension)")
|
||||||
raise Exception("Invalid command (no status)")
|
raise Exception("Invalid command (no status)")
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ from ..server.banmanager import BanManager
|
||||||
from ..server.ticket import BanTicket
|
from ..server.ticket import BanTicket
|
||||||
|
|
||||||
class AddFailure(unittest.TestCase):
|
class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
self.__ticket = BanTicket('193.168.0.128', 1167605999.0)
|
self.__ticket = BanTicket('193.168.0.128', 1167605999.0)
|
||||||
|
@ -39,19 +38,58 @@ class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
|
|
||||||
def testAdd(self):
|
def testAdd(self):
|
||||||
self.assertEqual(self.__banManager.size(), 1)
|
self.assertEqual(self.__banManager.size(), 1)
|
||||||
|
|
||||||
def testAddDuplicate(self):
|
def testAddDuplicate(self):
|
||||||
self.assertFalse(self.__banManager.addBanTicket(self.__ticket))
|
self.assertFalse(self.__banManager.addBanTicket(self.__ticket))
|
||||||
self.assertEqual(self.__banManager.size(), 1)
|
self.assertEqual(self.__banManager.size(), 1)
|
||||||
|
|
||||||
def testInListOK(self):
|
def testInListOK(self):
|
||||||
ticket = BanTicket('193.168.0.128', 1167605999.0)
|
ticket = BanTicket('193.168.0.128', 1167605999.0)
|
||||||
self.assertTrue(self.__banManager._inBanList(ticket))
|
self.assertTrue(self.__banManager._inBanList(ticket))
|
||||||
|
|
||||||
def testInListNOK(self):
|
def testInListNOK(self):
|
||||||
ticket = BanTicket('111.111.1.111', 1167605999.0)
|
ticket = BanTicket('111.111.1.111', 1167605999.0)
|
||||||
self.assertFalse(self.__banManager._inBanList(ticket))
|
self.assertFalse(self.__banManager._inBanList(ticket))
|
||||||
|
|
||||||
|
|
||||||
|
class StatusExtendedCymruInfo(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
"""Call before every test case."""
|
||||||
|
self.__ban_ip = "93.184.216.34"
|
||||||
|
self.__asn = "15133"
|
||||||
|
self.__country = "EU"
|
||||||
|
self.__rir = "ripencc"
|
||||||
|
self.__ticket = BanTicket(self.__ban_ip, 1167605999.0)
|
||||||
|
self.__banManager = BanManager()
|
||||||
|
self.assertTrue(self.__banManager.addBanTicket(self.__ticket))
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Call after every test case."""
|
||||||
|
|
||||||
|
def testCymruInfo(self):
|
||||||
|
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||||
|
if "assertDictEqual" in dir(self):
|
||||||
|
self.assertDictEqual(cymru_info, {"asn": [self.__asn], "country": [self.__country], "rir": [self.__rir]})
|
||||||
|
else:
|
||||||
|
# Python 2.6 does not support assertDictEqual()
|
||||||
|
self.assertEqual(cymru_info["asn"], [self.__asn])
|
||||||
|
self.assertEqual(cymru_info["country"], [self.__country])
|
||||||
|
self.assertEqual(cymru_info["rir"], [self.__rir])
|
||||||
|
|
||||||
|
def testCymruInfoASN(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.__banManager.geBanListExtendedASN(self.__banManager.getBanListExtendedCymruInfo()),
|
||||||
|
[self.__asn])
|
||||||
|
|
||||||
|
def testCymruInfoCountry(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.__banManager.geBanListExtendedCountry(self.__banManager.getBanListExtendedCymruInfo()),
|
||||||
|
[self.__country])
|
||||||
|
|
||||||
|
def testCymruInfoRIR(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.__banManager.geBanListExtendedRIR(self.__banManager.getBanListExtendedCymruInfo()),
|
||||||
|
[self.__rir])
|
||||||
|
|
|
@ -474,6 +474,27 @@ class Transmitter(TransmitterBase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def testJailStatusExtended(self):
|
||||||
|
self.assertEqual(self.transm.proceed(["status", self.jailName, "extended"]),
|
||||||
|
(0,
|
||||||
|
[
|
||||||
|
('Filter', [
|
||||||
|
('Currently failed', 0),
|
||||||
|
('Total failed', 0),
|
||||||
|
('File list', [])]
|
||||||
|
),
|
||||||
|
('Actions', [
|
||||||
|
('Currently banned', 0),
|
||||||
|
('Total banned', 0),
|
||||||
|
('Banned IP list', []),
|
||||||
|
('Banned ASN list', []),
|
||||||
|
('Banned Country list', []),
|
||||||
|
('Banned RIR list', [])]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def testAction(self):
|
def testAction(self):
|
||||||
action = "TestCaseAction"
|
action = "TestCaseAction"
|
||||||
cmdList = [
|
cmdList = [
|
||||||
|
@ -601,6 +622,10 @@ class Transmitter(TransmitterBase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["status", "INVALID", "COMMAND"])[0],1)
|
self.transm.proceed(["status", "INVALID", "COMMAND"])[0],1)
|
||||||
|
|
||||||
|
def testStatusJailExtendedNOK(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(["status", self.jailName, "INVALID_COMMAND"])[0],1)
|
||||||
|
|
||||||
def testJournalMatch(self):
|
def testJournalMatch(self):
|
||||||
if not filtersystemd: # pragma: no cover
|
if not filtersystemd: # pragma: no cover
|
||||||
if sys.version_info >= (2, 7):
|
if sys.version_info >= (2, 7):
|
||||||
|
|
|
@ -107,6 +107,7 @@ def gatherTests(regexps=None, no_network=False):
|
||||||
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
|
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
|
||||||
# BanManager
|
# BanManager
|
||||||
tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure))
|
tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure))
|
||||||
|
tests.addTest(unittest.makeSuite(banmanagertestcase.StatusExtendedCymruInfo))
|
||||||
# ClientReaders
|
# ClientReaders
|
||||||
tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest))
|
tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest))
|
||||||
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))
|
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))
|
||||||
|
|
Loading…
Reference in New Issue