Add extended info to status output using Cyrmu

pull/924/head
Lee Clemens 2015-01-23 15:12:04 -05:00
parent 17b3df004e
commit 625bcef8a1
12 changed files with 227 additions and 10 deletions

View File

@ -12,6 +12,8 @@ before_install:
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi
install:
- 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 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 ..; pip install -q coveralls; cd -; fi
script:

View File

@ -41,7 +41,11 @@ ver. 0.9.2 (2014/XX/XXX) - wanna-be-released
- Monit config for fail2ban in /files/monit
- New actions:
- 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:
* Enable multiport for firewallcmd-new action. Closes gh-834
* files/debian-initd migrated from the debian branch and should be

View File

@ -55,6 +55,7 @@ protocol = [
["start <JAIL>", "starts the jail <JAIL>"],
["stop <JAIL>", "stops the jail <JAIL>. The jail is removed"],
["status <JAIL>", "gets the current status of <JAIL>"],
["status <JAIL> extended", "gets the current status of <JAIL> with extended info"],
['', "JAIL CONFIGURATION", ""],
["set <JAIL> idle on|off", "sets the idle state of <JAIL>"],
["set <JAIL> addignoreip <IP>", "adds <IP> to the ignore list of <JAIL>"],

View File

@ -372,9 +372,20 @@ class Actions(JailThread, Mapping):
@property
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()),
("Total banned", self.__banManager.getBanTotal()),
("Banned IP list", self.__banManager.getBanList())]
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

View File

@ -26,6 +26,9 @@ __license__ = "GPL"
from threading import Lock
import dns.exception
import dns.resolver
from .ticket import BanTicket
from .mytime import MyTime
from ..helpers import getLogger
@ -118,6 +121,115 @@ class BanManager:
finally:
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.
#

View File

@ -183,6 +183,15 @@ class Jail:
("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):
"""Add a fail ticket to the jail.

View File

@ -72,6 +72,12 @@ class JailThread(Thread):
"""
pass
@abstractproperty
def statusExtended(self): # pragma: no cover - abstract
"""Abstract - Should provide extended status information.
"""
pass
def start(self):
"""Sets active flag and starts thread.
"""

View File

@ -322,7 +322,10 @@ class Server:
def statusJail(self, name):
return self.__jails[name].status
def statusJailExtended(self, name):
return self.__jails[name].statusExtended
# Logging
##

View File

@ -333,5 +333,10 @@ class Transmitter:
elif len(command) == 1:
name = command[0]
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)")

View File

@ -30,7 +30,6 @@ from ..server.banmanager import BanManager
from ..server.ticket import BanTicket
class AddFailure(unittest.TestCase):
def setUp(self):
"""Call before every test case."""
self.__ticket = BanTicket('193.168.0.128', 1167605999.0)
@ -39,19 +38,58 @@ class AddFailure(unittest.TestCase):
def tearDown(self):
"""Call after every test case."""
def testAdd(self):
self.assertEqual(self.__banManager.size(), 1)
def testAddDuplicate(self):
self.assertFalse(self.__banManager.addBanTicket(self.__ticket))
self.assertEqual(self.__banManager.size(), 1)
def testInListOK(self):
ticket = BanTicket('193.168.0.128', 1167605999.0)
self.assertTrue(self.__banManager._inBanList(ticket))
def testInListNOK(self):
ticket = BanTicket('111.111.1.111', 1167605999.0)
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])

View File

@ -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):
action = "TestCaseAction"
cmdList = [
@ -601,6 +622,10 @@ class Transmitter(TransmitterBase):
self.assertEqual(
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):
if not filtersystemd: # pragma: no cover
if sys.version_info >= (2, 7):

View File

@ -107,6 +107,7 @@ def gatherTests(regexps=None, no_network=False):
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
# BanManager
tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure))
tests.addTest(unittest.makeSuite(banmanagertestcase.StatusExtendedCymruInfo))
# ClientReaders
tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest))
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))