Merge branch 'master' of github.com:fail2ban/fail2ban

* 'master' of github.com:fail2ban/fail2ban:
  ENH: Match non "Bye Bye" for sshd locked accounts failregex
  Even stricter monit regex, now covers entire line
  Tidy up filter.d/monit.conf, make regex more complete. Add ChangeLog / THANKS entry. Add test cases.
  ENH: Move traceback formatter to from tests.utils to helpers
  Block brute-force attempts against the Monit gui
pull/661/merge
Yaroslav Halchenko 2014-05-10 20:02:47 -04:00
commit c619202d6f
12 changed files with 123 additions and 85 deletions

View File

@ -27,11 +27,13 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
* Database now returns persistent bans on restart (bantime < 0)
- New features:
- Added monit filter thanks Jason H Martin.
- Enhancements
* Fail2ban-regex - add print-all-matched option. Closes gh-652
* Suppress fail2ban-client warnings for non-critical config options
* Match non "Bye Bye" disconnect messages for sshd locked account regex
ver. 0.9.0 (2014/03/14) - beta
----------

1
THANKS
View File

@ -48,6 +48,7 @@ Ivo Truxa
John Thoe
Jacques Lav!gnotte
Ioan Indreias
Jason H Martin
Jonathan Kamens
Jonathan Lanning
Jonathan Underwood

View File

@ -45,7 +45,7 @@ from fail2ban.client.filterreader import FilterReader
from fail2ban.server.filter import Filter
from fail2ban.server.failregex import RegexException
from fail2ban.tests.utils import FormatterWithTraceBack
from fail2ban.helpers import FormatterWithTraceBack
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")

View File

@ -34,7 +34,8 @@ if os.path.exists("fail2ban/__init__.py"):
sys.path.insert(0, ".")
from fail2ban.version import version
from fail2ban.tests.utils import FormatterWithTraceBack, gatherTests
from fail2ban.tests.utils import gatherTests
from fail2ban.helpers import FormatterWithTraceBack
from fail2ban.server.mytime import MyTime
from optparse import OptionParser, Option

View File

@ -0,0 +1,9 @@
# Fail2Ban filter for monit.conf, looks for failed access attempts
#
#
[Definition]
failregex = ^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied unknown user '\w+' accessing monit httpd$
^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied wrong password for user '\w+' accessing monit httpd$

View File

@ -30,7 +30,7 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|erro
^%(__prefix_line)sReceived disconnect from <HOST>: 3: \S+: Auth fail$
^%(__prefix_line)sUser .+ from <HOST> not allowed because a group is listed in DenyGroups\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked<SKIPLINES>(?P=__prefix)(?:error: )?Received disconnect from <HOST>: 11: Bye Bye \[preauth\]$
^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked<SKIPLINES>(?P=__prefix)(?:error: )?Received disconnect from <HOST>: 11: .+ \[preauth\]$
^(?P<__prefix>%(__prefix_line)s)Disconnecting: Too many authentication failures for .+? \[preauth\]<SKIPLINES>(?P=__prefix)(?:error: )?Connection closed by <HOST> \[preauth\]$
^(?P<__prefix>%(__prefix_line)s)Connection from <HOST> port \d+<SKIPLINES>(?P=__prefix)Disconnecting: Too many authentication failures for .+? \[preauth\]$

View File

@ -366,6 +366,12 @@ maxretry = 5
port = http,https
logpath = /var/log/tomcat*/catalina.out
[monit]
#Ban clients brute-forcing the monit gui login
filter = monit
port = 2812
logpath = /var/log/monit
[webmin-auth]

View File

@ -20,9 +20,90 @@
__author__ = "Cyril Jaquier, Arturo 'Buanzo' Busleiman, Yaroslav Halchenko"
__license__ = "GPL"
import sys
import os
import traceback
import re
import logging
def formatExceptionInfo():
""" Consistently format exception information """
import sys
cla, exc = sys.exc_info()[:2]
return (cla.__name__, str(exc))
#
# Following "traceback" functions are adopted from PyMVPA distributed
# under MIT/Expat and copyright by PyMVPA developers (i.e. me and
# Michael). Hereby I re-license derivative work on these pieces under GPL
# to stay in line with the main Fail2Ban license
#
def mbasename(s):
"""Custom function to include directory name if filename is too common
Also strip .py at the end
"""
base = os.path.basename(s)
if base.endswith('.py'):
base = base[:-3]
if base in set(['base', '__init__']):
base = os.path.basename(os.path.dirname(s)) + '.' + base
return base
class TraceBack(object):
"""Customized traceback to be included in debug messages
"""
def __init__(self, compress=False):
"""Initialize TrackBack metric
Parameters
----------
compress : bool
if True then prefix common with previous invocation gets
replaced with ...
"""
self.__prev = ""
self.__compress = compress
def __call__(self):
ftb = traceback.extract_stack(limit=100)[:-2]
entries = [
[mbasename(x[0]), os.path.dirname(x[0]), str(x[1])] for x in ftb]
entries = [ [e[0], e[2]] for e in entries
if not (e[0] in ['unittest', 'logging.__init__']
or e[1].endswith('/unittest'))]
# lets make it more concise
entries_out = [entries[0]]
for entry in entries[1:]:
if entry[0] == entries_out[-1][0]:
entries_out[-1][1] += ',%s' % entry[1]
else:
entries_out.append(entry)
sftb = '>'.join(['%s:%s' % (mbasename(x[0]),
x[1]) for x in entries_out])
if self.__compress:
# lets remove part which is common with previous invocation
prev_next = sftb
common_prefix = os.path.commonprefix((self.__prev, sftb))
common_prefix2 = re.sub('>[^>]*$', '', common_prefix)
if common_prefix2 != "":
sftb = '...' + sftb[len(common_prefix2):]
self.__prev = prev_next
return sftb
class FormatterWithTraceBack(logging.Formatter):
"""Custom formatter which expands %(tb) and %(tbc) with tracebacks
TODO: might need locking in case of compressed tracebacks
"""
def __init__(self, fmt, *args, **kwargs):
logging.Formatter.__init__(self, fmt=fmt, *args, **kwargs)
compress = '%(tbc)s' in fmt
self._tb = TraceBack(compress=compress)
def format(self, record):
record.tbc = record.tb = self._tb()
return logging.Formatter.format(self, record)

View File

@ -0,0 +1,6 @@
# failJSON: { "time": "2005-04-16T21:05:29", "match": true , "host": "69.93.127.111" }
[PDT Apr 16 21:05:29] error : Warning: Client '69.93.127.111' supplied unknown user 'foo' accessing monit httpd
# failJSON: { "time": "2005-04-16T20:59:33", "match": true , "host": "97.113.189.111" }
[PDT Apr 16 20:59:33] error : Warning: Client '97.113.189.111' supplied wrong password for user 'admin' accessing monit httpd

View File

@ -136,3 +136,10 @@ Jul 13 18:44:28 mdop sshd[4931]: Received disconnect from 89.24.13.192: 3: com.j
Feb 12 04:09:18 localhost sshd[26713]: Connection from 115.249.163.77 port 51353
# failJSON: { "time": "2005-02-12T04:09:21", "match": true , "host": "115.249.163.77", "desc": "from gh-457" }
Feb 12 04:09:21 localhost sshd[26713]: Disconnecting: Too many authentication failures for root [preauth]
# failJSON: { "match": false }
Apr 27 13:02:04 host sshd[29116]: User root not allowed because account is locked
# failJSON: { "match": false }
Apr 27 13:02:04 host sshd[29116]: input_userauth_request: invalid user root [preauth]
# failJSON: { "time": "2005-04-27T13:02:04", "match": true , "host": "1.2.3.4", "desc": "No Bye-Bye" }
Apr 27 13:02:04 host sshd[29116]: Received disconnect from 1.2.3.4: 11: Normal Shutdown, Thank you for playing [preauth]

View File

@ -32,8 +32,7 @@ import datetime
from glob import glob
from StringIO import StringIO
from .utils import mbasename, TraceBack, FormatterWithTraceBack
from ..helpers import formatExceptionInfo
from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack
from ..server.datetemplate import DatePatternRegex

View File

@ -22,90 +22,17 @@ __author__ = "Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
__license__ = "GPL"
import logging, os, re, traceback, time, unittest
from os.path import basename, dirname
import logging
import os
import re
import time
import unittest
from StringIO import StringIO
from ..server.mytime import MyTime
logSys = logging.getLogger(__name__)
#
# Following "traceback" functions are adopted from PyMVPA distributed
# under MIT/Expat and copyright by PyMVPA developers (i.e. me and
# Michael). Hereby I re-license derivative work on these pieces under GPL
# to stay in line with the main Fail2Ban license
#
def mbasename(s):
"""Custom function to include directory name if filename is too common
Also strip .py at the end
"""
base = basename(s)
if base.endswith('.py'):
base = base[:-3]
if base in set(['base', '__init__']):
base = basename(dirname(s)) + '.' + base
return base
class TraceBack(object):
"""Customized traceback to be included in debug messages
"""
def __init__(self, compress=False):
"""Initialize TrackBack metric
Parameters
----------
compress : bool
if True then prefix common with previous invocation gets
replaced with ...
"""
self.__prev = ""
self.__compress = compress
def __call__(self):
ftb = traceback.extract_stack(limit=100)[:-2]
entries = [[mbasename(x[0]), dirname(x[0]), str(x[1])] for x in ftb]
entries = [ [e[0], e[2]] for e in entries
if not (e[0] in ['unittest', 'logging.__init__']
or e[1].endswith('/unittest'))]
# lets make it more concise
entries_out = [entries[0]]
for entry in entries[1:]:
if entry[0] == entries_out[-1][0]:
entries_out[-1][1] += ',%s' % entry[1]
else:
entries_out.append(entry)
sftb = '>'.join(['%s:%s' % (mbasename(x[0]),
x[1]) for x in entries_out])
if self.__compress:
# lets remove part which is common with previous invocation
prev_next = sftb
common_prefix = os.path.commonprefix((self.__prev, sftb))
common_prefix2 = re.sub('>[^>]*$', '', common_prefix)
if common_prefix2 != "":
sftb = '...' + sftb[len(common_prefix2):]
self.__prev = prev_next
return sftb
class FormatterWithTraceBack(logging.Formatter):
"""Custom formatter which expands %(tb) and %(tbc) with tracebacks
TODO: might need locking in case of compressed tracebacks
"""
def __init__(self, fmt, *args, **kwargs):
logging.Formatter.__init__(self, fmt=fmt, *args, **kwargs)
compress = '%(tbc)s' in fmt
self._tb = TraceBack(compress=compress)
def format(self, record):
record.tbc = record.tb = self._tb()
return logging.Formatter.format(self, record)
def mtimesleep():
# no sleep now should be necessary since polling tracks now not only
# mtime but also ino and size
@ -146,7 +73,6 @@ def gatherTests(regexps=None, no_network=False):
if not regexps: # pragma: no cover
tests = unittest.TestSuite()
else: # pragma: no cover
import re
class FilteredTestSuite(unittest.TestSuite):
_regexps = [re.compile(r) for r in regexps]
def addTest(self, suite):