mirror of https://github.com/fail2ban/fail2ban
Merge branch '0.10-incr-dynamic-bantime' into 0.10-full
commit
8403eab2c4
|
@ -69,9 +69,14 @@ TODO: implementing of options resp. other tasks from PR #1346
|
||||||
- `<fid>` - failure identifier (if raw resp. failures without IP address)
|
- `<fid>` - failure identifier (if raw resp. failures without IP address)
|
||||||
- `<ip-rev>` - PTR reversed representation of IP address
|
- `<ip-rev>` - PTR reversed representation of IP address
|
||||||
- `<ip-host>` - host name of the IP address
|
- `<ip-host>` - host name of the IP address
|
||||||
|
- `<bancount>` - ban count of this offender if known as bad (started by 1 for unknown)
|
||||||
|
- `<bantime>` - current ban-time of the ticket (prolongation can be retarded up to 10 sec.)
|
||||||
- `<F-...>` - interpolates to the corresponding filter group capture `...`
|
- `<F-...>` - interpolates to the corresponding filter group capture `...`
|
||||||
- `<fq-hostname>` - fully-qualified name of host (the same as `$(hostname -f)`)
|
- `<fq-hostname>` - fully-qualified name of host (the same as `$(hostname -f)`)
|
||||||
- `<sh-hostname>` - short hostname (the same as `$(uname -n)`)
|
- `<sh-hostname>` - short hostname (the same as `$(uname -n)`)
|
||||||
|
* Introduced new action command `actionprolong` to prolong ban-time (e. g. set new timeout if expected);
|
||||||
|
Several actions (like ipset, etc.) rewritten using net logic with `actionprolong`.
|
||||||
|
Note: because ban-time is dynamic, it was removed from jail.conf as timeout argument (check jail.local).
|
||||||
* Allow to use filter options by `fail2ban-regex`, example:
|
* Allow to use filter options by `fail2ban-regex`, example:
|
||||||
fail2ban-regex text.log "sshd[mode=aggressive]"
|
fail2ban-regex text.log "sshd[mode=aggressive]"
|
||||||
* Samples test case factory extended with filter options - dict in JSON to control
|
* Samples test case factory extended with filter options - dict in JSON to control
|
||||||
|
|
|
@ -18,7 +18,7 @@ before = firewallcmd-common.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
actionstart = ipset create <ipmset> hash:ip timeout <bantime>
|
actionstart = ipset create <ipmset> hash:ip
|
||||||
firewall-cmd --direct --add-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
|
firewall-cmd --direct --add-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
|
||||||
|
|
||||||
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
|
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
|
||||||
|
@ -27,6 +27,8 @@ actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -p <p
|
||||||
|
|
||||||
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
|
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
|
||||||
|
|
||||||
|
actionprolong = %(actionban)s
|
||||||
|
|
||||||
actionunban = ipset del <ipmset> <ip> -exist
|
actionunban = ipset del <ipmset> <ip> -exist
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
@ -38,12 +40,6 @@ actionunban = ipset del <ipmset> <ip> -exist
|
||||||
#
|
#
|
||||||
chain = INPUT_direct
|
chain = INPUT_direct
|
||||||
|
|
||||||
# Option: bantime
|
|
||||||
# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban)
|
|
||||||
# Values: [ NUM ] Default: 600
|
|
||||||
|
|
||||||
bantime = 600
|
|
||||||
|
|
||||||
ipmset = f2b-<name>
|
ipmset = f2b-<name>
|
||||||
|
|
||||||
[Init?family=inet6]
|
[Init?family=inet6]
|
||||||
|
|
|
@ -26,7 +26,7 @@ before = iptables-common.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = ipset create <ipmset> hash:ip timeout <bantime><familyopt>
|
actionstart = ipset create <ipmset> hash:ip<familyopt>
|
||||||
<iptables> -I <chain> -m set --match-set <ipmset> src -j <blocktype>
|
<iptables> -I <chain> -m set --match-set <ipmset> src -j <blocktype>
|
||||||
|
|
||||||
# Option: actionflush
|
# Option: actionflush
|
||||||
|
@ -51,6 +51,8 @@ actionstop = <iptables> -D <chain> -m set --match-set <ipmset> src -j <blocktype
|
||||||
#
|
#
|
||||||
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
|
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
|
||||||
|
|
||||||
|
actionprolong = %(actionban)s
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
# command is executed with Fail2Ban user rights.
|
# command is executed with Fail2Ban user rights.
|
||||||
|
@ -61,12 +63,6 @@ actionunban = ipset del <ipmset> <ip> -exist
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
# Option: bantime
|
|
||||||
# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban)
|
|
||||||
# Values: [ NUM ] Default: 600
|
|
||||||
#
|
|
||||||
bantime = 600
|
|
||||||
|
|
||||||
ipmset = f2b-<name>
|
ipmset = f2b-<name>
|
||||||
familyopt =
|
familyopt =
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ before = iptables-common.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = ipset create <ipmset> hash:ip timeout <bantime><familyopt>
|
actionstart = ipset create <ipmset> hash:ip<familyopt>
|
||||||
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
|
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
|
||||||
|
|
||||||
# Option: actionflush
|
# Option: actionflush
|
||||||
|
@ -51,6 +51,8 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
|
||||||
#
|
#
|
||||||
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
|
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
|
||||||
|
|
||||||
|
actionprolong = %(actionban)s
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
# command is executed with Fail2Ban user rights.
|
# command is executed with Fail2Ban user rights.
|
||||||
|
@ -61,12 +63,6 @@ actionunban = ipset del <ipmset> <ip> -exist
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
# Option: bantime
|
|
||||||
# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban)
|
|
||||||
# Values: [ NUM ] Default: 600
|
|
||||||
#
|
|
||||||
bantime = 600
|
|
||||||
|
|
||||||
ipmset = f2b-<name>
|
ipmset = f2b-<name>
|
||||||
familyopt =
|
familyopt =
|
||||||
|
|
||||||
|
|
|
@ -12,5 +12,5 @@ actioncheck =
|
||||||
actionban = /usr/libexec/afctl -a <ip> -t <bantime>
|
actionban = /usr/libexec/afctl -a <ip> -t <bantime>
|
||||||
actionunban = /usr/libexec/afctl -r <ip>
|
actionunban = /usr/libexec/afctl -r <ip>
|
||||||
|
|
||||||
[Init]
|
actionprolong = %(actionunban)s && %(actionban)s
|
||||||
bantime = 2880
|
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = if ! ipset -quiet -name list f2b-<name> >/dev/null;
|
actionstart = if ! ipset -quiet -name list f2b-<name> >/dev/null;
|
||||||
then ipset -quiet -exist create f2b-<name> hash:ip timeout <bantime>;
|
then ipset -quiet -exist create f2b-<name> hash:ip;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
|
@ -68,6 +68,8 @@ actionstop = ipset flush f2b-<name>
|
||||||
#
|
#
|
||||||
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||||
|
|
||||||
|
actionprolong = %(actionban)s
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
# command is executed with Fail2Ban user rights.
|
# command is executed with Fail2Ban user rights.
|
||||||
|
@ -76,10 +78,3 @@ actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||||
#
|
#
|
||||||
actionunban = ipset del f2b-<name> <ip> -exist
|
actionunban = ipset del f2b-<name> <ip> -exist
|
||||||
|
|
||||||
[Init]
|
|
||||||
|
|
||||||
# Option: bantime
|
|
||||||
# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban)
|
|
||||||
# Values: [ NUM ] Default: 600
|
|
||||||
#
|
|
||||||
bantime = 600
|
|
||||||
|
|
|
@ -202,22 +202,22 @@ banaction = iptables-multiport
|
||||||
banaction_allports = iptables-allports
|
banaction_allports = iptables-allports
|
||||||
|
|
||||||
# The simplest action to take: ban only
|
# The simplest action to take: ban only
|
||||||
action_ = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||||
|
|
||||||
# ban & send an e-mail with whois report to the destemail.
|
# ban & send an e-mail with whois report to the destemail.
|
||||||
action_mw = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||||
%(mta)s-whois[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
|
%(mta)s-whois[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||||
|
|
||||||
# ban & send an e-mail with whois report and relevant log lines
|
# ban & send an e-mail with whois report and relevant log lines
|
||||||
# to the destemail.
|
# to the destemail.
|
||||||
action_mwl = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||||
%(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
|
%(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
|
||||||
|
|
||||||
# See the IMPORTANT note in action.d/xarf-login-attack for when to use this action
|
# See the IMPORTANT note in action.d/xarf-login-attack for when to use this action
|
||||||
#
|
#
|
||||||
# ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines
|
# ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines
|
||||||
# to the destemail.
|
# to the destemail.
|
||||||
action_xarf = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
action_xarf = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||||
xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath=%(logpath)s, port="%(port)s"]
|
xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath=%(logpath)s, port="%(port)s"]
|
||||||
|
|
||||||
# ban IP on CloudFlare & send an e-mail with whois report and relevant log lines
|
# ban IP on CloudFlare & send an e-mail with whois report and relevant log lines
|
||||||
|
|
|
@ -45,6 +45,7 @@ class ActionReader(DefinitionInitConfigReader):
|
||||||
"actioncheck": ["string", None],
|
"actioncheck": ["string", None],
|
||||||
"actionrepair": ["string", None],
|
"actionrepair": ["string", None],
|
||||||
"actionban": ["string", None],
|
"actionban": ["string", None],
|
||||||
|
"actionprolong": ["string", None],
|
||||||
"actionunban": ["string", None],
|
"actionunban": ["string", None],
|
||||||
"norestored": ["string", None],
|
"norestored": ["string", None],
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ class CallingMap(MutableMapping, object):
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.data)
|
return len(self.data)
|
||||||
|
|
||||||
def copy(self): # pargma: no cover
|
def copy(self): # pragma: no cover
|
||||||
return self.__class__(_merge_copy_dicts(self.data, self.storage))
|
return self.__class__(_merge_copy_dicts(self.data, self.storage))
|
||||||
|
|
||||||
|
|
||||||
|
@ -224,6 +224,10 @@ class ActionBase(object):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _prolongable(self): # pragma: no cover - abstract
|
||||||
|
return False
|
||||||
|
|
||||||
def unban(self, aInfo): # pragma: no cover - abstract
|
def unban(self, aInfo): # pragma: no cover - abstract
|
||||||
"""Executed when a ban expires.
|
"""Executed when a ban expires.
|
||||||
|
|
||||||
|
@ -236,6 +240,11 @@ class ActionBase(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
WRAP_CMD_PARAMS = {
|
||||||
|
'timeout': 'str2seconds',
|
||||||
|
'bantime': 'ignore',
|
||||||
|
}
|
||||||
|
|
||||||
class CommandAction(ActionBase):
|
class CommandAction(ActionBase):
|
||||||
"""A action which executes OS shell commands.
|
"""A action which executes OS shell commands.
|
||||||
|
|
||||||
|
@ -306,7 +315,10 @@ class CommandAction(ActionBase):
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if not name.startswith('_') and not self.__init and not callable(value):
|
if not name.startswith('_') and not self.__init and not callable(value):
|
||||||
# special case for some pasrameters:
|
# special case for some pasrameters:
|
||||||
if name in ('timeout', 'bantime'):
|
wrp = WRAP_CMD_PARAMS.get(name)
|
||||||
|
if wrp == 'ignore': # ignore (filter) dynamic parameters
|
||||||
|
return
|
||||||
|
elif wrp == 'str2seconds':
|
||||||
value = str(MyTime.str2seconds(value))
|
value = str(MyTime.str2seconds(value))
|
||||||
# parameters changed - clear properties and substitution cache:
|
# parameters changed - clear properties and substitution cache:
|
||||||
self.__properties = None
|
self.__properties = None
|
||||||
|
@ -434,6 +446,26 @@ class CommandAction(ActionBase):
|
||||||
if not self._processCmd('<actionban>', aInfo):
|
if not self._processCmd('<actionban>', aInfo):
|
||||||
raise RuntimeError("Error banning %(ip)s" % aInfo)
|
raise RuntimeError("Error banning %(ip)s" % aInfo)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _prolongable(self):
|
||||||
|
return (hasattr(self, 'actionprolong') and self.actionprolong
|
||||||
|
and not str(self.actionprolong).isspace())
|
||||||
|
|
||||||
|
def prolong(self, aInfo):
|
||||||
|
"""Executes the "actionprolong" command.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
if not self._processCmd('<actionprolong>', aInfo):
|
||||||
|
raise RuntimeError("Error prolonging %(ip)s" % aInfo)
|
||||||
|
|
||||||
def unban(self, aInfo):
|
def unban(self, aInfo):
|
||||||
"""Executes the "actionunban" command.
|
"""Executes the "actionunban" command.
|
||||||
|
|
||||||
|
@ -498,8 +530,10 @@ class CommandAction(ActionBase):
|
||||||
"""
|
"""
|
||||||
return self._executeOperation('<actionreload>', 'reloading')
|
return self._executeOperation('<actionreload>', 'reloading')
|
||||||
|
|
||||||
@staticmethod
|
ESCAPE_CRE = re.compile(r"""[\\#&;`|*?~<>^()\[\]{}$'"\n\r]""")
|
||||||
def escapeTag(value):
|
|
||||||
|
@classmethod
|
||||||
|
def escapeTag(cls, value):
|
||||||
"""Escape characters which may be used for command injection.
|
"""Escape characters which may be used for command injection.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
@ -516,12 +550,15 @@ class CommandAction(ActionBase):
|
||||||
-----
|
-----
|
||||||
The following characters are escaped::
|
The following characters are escaped::
|
||||||
|
|
||||||
\\#&;`|*?~<>^()[]{}$'"
|
\\#&;`|*?~<>^()[]{}$'"\n\r
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for c in '\\#&;`|*?~<>^()[]{}$\'"':
|
_map2c = {'\n': 'n', '\r': 'r'}
|
||||||
if c in value:
|
def substChar(m):
|
||||||
value = value.replace(c, '\\' + c)
|
c = m.group()
|
||||||
|
return '\\' + _map2c.get(c, c)
|
||||||
|
|
||||||
|
value = cls.ESCAPE_CRE.sub(substChar, value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -780,7 +817,8 @@ class CommandAction(ActionBase):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If command execution times out.
|
If command execution times out.
|
||||||
"""
|
"""
|
||||||
logSys.debug(realCmd)
|
if logSys.getEffectiveLevel() < logging.DEBUG: # pragma: no cover
|
||||||
|
logSys.log(9, realCmd)
|
||||||
if not realCmd:
|
if not realCmd:
|
||||||
logSys.debug("Nothing to do")
|
logSys.debug("Nothing to do")
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -34,7 +34,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
OrderedDict = dict
|
OrderedDict = dict
|
||||||
|
|
||||||
from .banmanager import BanManager
|
from .banmanager import BanManager, BanTicket
|
||||||
from .ipdns import DNSUtils
|
from .ipdns import DNSUtils
|
||||||
from .jailthread import JailThread
|
from .jailthread import JailThread
|
||||||
from .action import ActionBase, CommandAction, CallingMap
|
from .action import ActionBase, CommandAction, CallingMap
|
||||||
|
@ -298,6 +298,8 @@ class Actions(JailThread, Mapping):
|
||||||
"fid": lambda self: self.__ticket.getID(),
|
"fid": lambda self: self.__ticket.getID(),
|
||||||
"failures": lambda self: self.__ticket.getAttempt(),
|
"failures": lambda self: self.__ticket.getAttempt(),
|
||||||
"time": lambda self: self.__ticket.getTime(),
|
"time": lambda self: self.__ticket.getTime(),
|
||||||
|
"bantime": lambda self: self._getBanTime(),
|
||||||
|
"bancount": lambda self: self.__ticket.getBanCount(),
|
||||||
"matches": lambda self: "\n".join(self.__ticket.getMatches()),
|
"matches": lambda self: "\n".join(self.__ticket.getMatches()),
|
||||||
# to bypass actions, that should not be executed for restored tickets
|
# to bypass actions, that should not be executed for restored tickets
|
||||||
"restored": lambda self: (1 if self.__ticket.restored else 0),
|
"restored": lambda self: (1 if self.__ticket.restored else 0),
|
||||||
|
@ -322,9 +324,14 @@ class Actions(JailThread, Mapping):
|
||||||
self.immutable = immutable
|
self.immutable = immutable
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def copy(self): # pargma: no cover
|
def copy(self): # pragma: no cover
|
||||||
return self.__class__(self.__ticket, self.__jail, self.immutable, self.data.copy())
|
return self.__class__(self.__ticket, self.__jail, self.immutable, self.data.copy())
|
||||||
|
|
||||||
|
def _getBanTime(self):
|
||||||
|
btime = self.__ticket.getBanTime()
|
||||||
|
if btime is None: btime = self.__jail.actions.getBanTime()
|
||||||
|
return btime
|
||||||
|
|
||||||
def _mi4ip(self, overalljails=False):
|
def _mi4ip(self, overalljails=False):
|
||||||
"""Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside.
|
"""Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside.
|
||||||
|
|
||||||
|
@ -390,15 +397,9 @@ class Actions(JailThread, Mapping):
|
||||||
ticket = self._jail.getFailTicket()
|
ticket = self._jail.getFailTicket()
|
||||||
if not ticket:
|
if not ticket:
|
||||||
break
|
break
|
||||||
bTicket = BanManager.createBanTicket(ticket)
|
|
||||||
btime = ticket.getBanTime()
|
bTicket = BanTicket.wrap(ticket)
|
||||||
if btime is not None:
|
btime = ticket.getBanTime(self.__banManager.getBanTime())
|
||||||
bTicket.setBanTime(btime)
|
|
||||||
bTicket.setBanCount(ticket.getBanCount())
|
|
||||||
else:
|
|
||||||
btime = self.__banManager.getBanTime()
|
|
||||||
if ticket.restored:
|
|
||||||
bTicket.restored = True
|
|
||||||
ip = bTicket.getIP()
|
ip = bTicket.getIP()
|
||||||
aInfo = self.__getActionInfo(bTicket)
|
aInfo = self.__getActionInfo(bTicket)
|
||||||
reason = {}
|
reason = {}
|
||||||
|
@ -445,6 +446,29 @@ class Actions(JailThread, Mapping):
|
||||||
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
|
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
def _prolongBan(self, ticket):
|
||||||
|
# prevent to prolong ticket that was removed in-between,
|
||||||
|
# if it in ban list - ban time already prolonged (and it stays there):
|
||||||
|
if not self.__banManager._inBanList(ticket): return
|
||||||
|
# do actions :
|
||||||
|
aInfo = None
|
||||||
|
for name, action in self._actions.iteritems():
|
||||||
|
try:
|
||||||
|
if ticket.restored and getattr(action, 'norestored', False):
|
||||||
|
continue
|
||||||
|
if not action._prolongable:
|
||||||
|
continue
|
||||||
|
if aInfo is None:
|
||||||
|
aInfo = self.__getActionInfo(ticket)
|
||||||
|
if not aInfo.immutable: aInfo.reset()
|
||||||
|
action.prolong(aInfo)
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error(
|
||||||
|
"Failed to execute ban jail '%s' action '%s' "
|
||||||
|
"info '%r': %s",
|
||||||
|
self._jail.name, name, aInfo, e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
|
||||||
def __checkUnBan(self):
|
def __checkUnBan(self):
|
||||||
"""Check for IP address to unban.
|
"""Check for IP address to unban.
|
||||||
|
|
||||||
|
|
|
@ -243,21 +243,6 @@ class BanManager:
|
||||||
logSys.exception(e)
|
logSys.exception(e)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
##
|
|
||||||
# Create a ban ticket.
|
|
||||||
#
|
|
||||||
# Create a BanTicket from a FailTicket. The timestamp of the BanTicket
|
|
||||||
# is the current time. This is a static method.
|
|
||||||
# @param ticket the FailTicket
|
|
||||||
# @return a BanTicket
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def createBanTicket(ticket):
|
|
||||||
# we should always use correct time to calculate correct end time (ban time is variable now,
|
|
||||||
# + possible double banning by restore from database and from log file)
|
|
||||||
# so use as lastTime always time from ticket.
|
|
||||||
return BanTicket(ticket=ticket)
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Add a ban ticket.
|
# Add a ban ticket.
|
||||||
#
|
#
|
||||||
|
@ -291,6 +276,7 @@ class BanManager:
|
||||||
# not yet banned - add new one:
|
# not yet banned - add new one:
|
||||||
self.__banList[fid] = ticket
|
self.__banList[fid] = ticket
|
||||||
self.__banTotal += 1
|
self.__banTotal += 1
|
||||||
|
ticket.incrBanCount()
|
||||||
# correct next unban time:
|
# correct next unban time:
|
||||||
if self.__nextUnbanTime > eob:
|
if self.__nextUnbanTime > eob:
|
||||||
self.__nextUnbanTime = eob
|
self.__nextUnbanTime = eob
|
||||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .ticket import FailTicket
|
from .ticket import FailTicket, BanTicket
|
||||||
from ..helpers import getLogger, BgService
|
from ..helpers import getLogger, BgService
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
@ -103,13 +103,13 @@ class FailManager:
|
||||||
fData.setMatches(matches[-self.maxEntries:])
|
fData.setMatches(matches[-self.maxEntries:])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# not found - already banned - prevent to add failure if comes from observer:
|
# not found - already banned - prevent to add failure if comes from observer:
|
||||||
if observed:
|
if observed or isinstance(ticket, BanTicket):
|
||||||
return
|
return
|
||||||
# if already FailTicket - add it direct, otherwise create (using copy all ticket data):
|
# if already FailTicket - add it direct, otherwise create (using copy all ticket data):
|
||||||
if isinstance(ticket, FailTicket):
|
if isinstance(ticket, FailTicket):
|
||||||
fData = ticket;
|
fData = ticket;
|
||||||
else:
|
else:
|
||||||
fData = FailTicket(ticket=ticket)
|
fData = FailTicket.wrap(ticket)
|
||||||
if count > ticket.getAttempt():
|
if count > ticket.getAttempt():
|
||||||
fData.setRetry(count)
|
fData.setRetry(count)
|
||||||
self.__failList[fid] = fData
|
self.__failList[fid] = fData
|
||||||
|
|
|
@ -226,7 +226,7 @@ class Jail(object):
|
||||||
if opt == 'increment':
|
if opt == 'increment':
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
be[opt] = value.lower() in ("yes", "true", "ok", "1")
|
be[opt] = value.lower() in ("yes", "true", "ok", "1")
|
||||||
if be[opt] and self.database is None:
|
if be.get(opt) and self.database is None:
|
||||||
logSys.warning("ban time increment is not available as long jail database is not set")
|
logSys.warning("ban time increment is not available as long jail database is not set")
|
||||||
if opt in ['maxtime', 'rndtime']:
|
if opt in ['maxtime', 'rndtime']:
|
||||||
if not value is None:
|
if not value is None:
|
||||||
|
|
|
@ -121,9 +121,28 @@ class ObserverThread(JailThread):
|
||||||
def add_timer(self, starttime, *event):
|
def add_timer(self, starttime, *event):
|
||||||
"""Add a timer event to queue will start (and wake) in 'starttime' seconds
|
"""Add a timer event to queue will start (and wake) in 'starttime' seconds
|
||||||
"""
|
"""
|
||||||
|
# in testing we should wait (looping) for the possible time drifts:
|
||||||
|
if MyTime.myTime is not None and starttime:
|
||||||
|
# test time after short sleep:
|
||||||
|
t = threading.Timer(Utils.DEFAULT_SLEEP_INTERVAL, self._delayedEvent,
|
||||||
|
(MyTime.time() + starttime, time.time() + starttime, event)
|
||||||
|
)
|
||||||
|
t.start()
|
||||||
|
return
|
||||||
|
# add timer event:
|
||||||
t = threading.Timer(starttime, self.add, event)
|
t = threading.Timer(starttime, self.add, event)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
def _delayedEvent(self, endMyTime, endTime, event):
|
||||||
|
if MyTime.time() >= endMyTime or time.time() >= endTime:
|
||||||
|
self.add_timer(0, *event)
|
||||||
|
return
|
||||||
|
# repeat after short sleep:
|
||||||
|
t = threading.Timer(Utils.DEFAULT_SLEEP_INTERVAL, self._delayedEvent,
|
||||||
|
(endMyTime, endTime, event)
|
||||||
|
)
|
||||||
|
t.start()
|
||||||
|
|
||||||
def pulse_notify(self):
|
def pulse_notify(self):
|
||||||
"""Notify wakeup (sets /and resets/ notify event)
|
"""Notify wakeup (sets /and resets/ notify event)
|
||||||
"""
|
"""
|
||||||
|
@ -164,8 +183,6 @@ class ObserverThread(JailThread):
|
||||||
self.add_named_timer('DB_PURGE', self.__db_purge_interval, 'db_purge')
|
self.add_named_timer('DB_PURGE', self.__db_purge_interval, 'db_purge')
|
||||||
## Mapping of all possible event types of observer:
|
## Mapping of all possible event types of observer:
|
||||||
__meth = {
|
__meth = {
|
||||||
'failureFound': self.failureFound,
|
|
||||||
'banFound': self.banFound,
|
|
||||||
# universal lambda:
|
# universal lambda:
|
||||||
'call': self.call_lambda,
|
'call': self.call_lambda,
|
||||||
# system and service events:
|
# system and service events:
|
||||||
|
@ -196,7 +213,8 @@ class ObserverThread(JailThread):
|
||||||
if ev is None:
|
if ev is None:
|
||||||
break
|
break
|
||||||
## retrieve method by name
|
## retrieve method by name
|
||||||
meth = __meth[ev[0]]
|
meth = ev[0]
|
||||||
|
if not callable(ev[0]): meth = __meth.get(meth) or getattr(self, meth)
|
||||||
## execute it with rest of event as variable arguments
|
## execute it with rest of event as variable arguments
|
||||||
meth(*ev[1:])
|
meth(*ev[1:])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -359,6 +377,7 @@ class ObserverThread(JailThread):
|
||||||
db = jail.database
|
db = jail.database
|
||||||
if db is not None:
|
if db is not None:
|
||||||
for banCount, timeOfBan, lastBanTime in db.getBan(ip, jail):
|
for banCount, timeOfBan, lastBanTime in db.getBan(ip, jail):
|
||||||
|
banCount = max(banCount, ticket.getBanCount())
|
||||||
retryCount = ((1 << (banCount if banCount < 20 else 20))/2 + 1)
|
retryCount = ((1 << (banCount if banCount < 20 else 20))/2 + 1)
|
||||||
# if lastBanTime == -1 or timeOfBan + lastBanTime * 2 > MyTime.time():
|
# if lastBanTime == -1 or timeOfBan + lastBanTime * 2 > MyTime.time():
|
||||||
# retryCount = maxRetry
|
# retryCount = maxRetry
|
||||||
|
@ -378,8 +397,8 @@ class ObserverThread(JailThread):
|
||||||
(', Ban' if retryCount >= maxRetry else ''))
|
(', Ban' if retryCount >= maxRetry else ''))
|
||||||
# retryCount-1, because a ticket was already once incremented by filter self
|
# retryCount-1, because a ticket was already once incremented by filter self
|
||||||
retryCount = failManager.addFailure(ticket, retryCount - 1, True)
|
retryCount = failManager.addFailure(ticket, retryCount - 1, True)
|
||||||
|
ticket.setBanCount(banCount)
|
||||||
# after observe we have increased count >= maxretry ...
|
# after observe we have increased attempt count, compare it >= maxretry ...
|
||||||
if retryCount >= maxRetry:
|
if retryCount >= maxRetry:
|
||||||
# perform the banning of the IP now (again)
|
# perform the banning of the IP now (again)
|
||||||
# [todo]: this code part will be used multiple times - optimize it later.
|
# [todo]: this code part will be used multiple times - optimize it later.
|
||||||
|
@ -424,12 +443,14 @@ class ObserverThread(JailThread):
|
||||||
for banCount, timeOfBan, lastBanTime in \
|
for banCount, timeOfBan, lastBanTime in \
|
||||||
jail.database.getBan(ip, jail, overalljails=be.get('overalljails', False)) \
|
jail.database.getBan(ip, jail, overalljails=be.get('overalljails', False)) \
|
||||||
:
|
:
|
||||||
|
# increment count in ticket (if still not increased from banmanager, test-cases?):
|
||||||
|
if banCount >= ticket.getBanCount():
|
||||||
|
ticket.setBanCount(banCount+1)
|
||||||
logSys.debug('IP %s was already banned: %s #, %s', ip, banCount, timeOfBan);
|
logSys.debug('IP %s was already banned: %s #, %s', ip, banCount, timeOfBan);
|
||||||
ticket.setBanCount(banCount);
|
|
||||||
# calculate new ban time
|
# calculate new ban time
|
||||||
if banCount > 0:
|
if banCount > 0:
|
||||||
banTime = be['evformula'](self.BanTimeIncr(banTime, banCount))
|
banTime = be['evformula'](self.BanTimeIncr(banTime, banCount))
|
||||||
ticket.setBanTime(banTime);
|
ticket.setBanTime(banTime)
|
||||||
# check current ticket time to prevent increasing for twice read tickets (restored from log file besides database after restart)
|
# check current ticket time to prevent increasing for twice read tickets (restored from log file besides database after restart)
|
||||||
if ticket.getTime() > timeOfBan:
|
if ticket.getTime() > timeOfBan:
|
||||||
logSys.info('[%s] IP %s is bad: %s # last %s - incr %s to %s' % (jail.name, ip, banCount,
|
logSys.info('[%s] IP %s is bad: %s # last %s - incr %s to %s' % (jail.name, ip, banCount,
|
||||||
|
@ -448,12 +469,14 @@ class ObserverThread(JailThread):
|
||||||
Observer will check ip was known (bad) and possibly increase/prolong a ban time
|
Observer will check ip was known (bad) and possibly increase/prolong a ban time
|
||||||
Secondary we will actualize the bans and bips (bad ip) in database
|
Secondary we will actualize the bans and bips (bad ip) in database
|
||||||
"""
|
"""
|
||||||
oldbtime = btime
|
if ticket.restored: # pragma: no cover (normally not resored tickets only)
|
||||||
ip = ticket.getIP()
|
return
|
||||||
logSys.debug("[%s] Observer: ban found %s, %s", jail.name, ip, btime)
|
|
||||||
try:
|
try:
|
||||||
# if not permanent, not restored and ban time was not set - check time should be increased:
|
oldbtime = btime
|
||||||
if btime != -1 and not ticket.restored and ticket.getBanTime() is None:
|
ip = ticket.getIP()
|
||||||
|
logSys.debug("[%s] Observer: ban found %s, %s", jail.name, ip, btime)
|
||||||
|
# if not permanent and ban time was not set - check time should be increased:
|
||||||
|
if btime != -1 and ticket.getBanTime() is None:
|
||||||
btime = self.incrBanTime(jail, btime, ticket)
|
btime = self.incrBanTime(jail, btime, ticket)
|
||||||
# if we should prolong ban time:
|
# if we should prolong ban time:
|
||||||
if btime == -1 or btime > oldbtime:
|
if btime == -1 or btime > oldbtime:
|
||||||
|
@ -469,12 +492,13 @@ class ObserverThread(JailThread):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logtime = ('permanent', 'infinite')
|
logtime = ('permanent', 'infinite')
|
||||||
# increment count:
|
|
||||||
ticket.incrBanCount()
|
|
||||||
# if ban time was prolonged - log again with new ban time:
|
# if ban time was prolonged - log again with new ban time:
|
||||||
if btime != oldbtime:
|
if btime != oldbtime:
|
||||||
logSys.notice("[%s] Increase Ban %s (%d # %s -> %s)", jail.name,
|
logSys.notice("[%s] Increase Ban %s (%d # %s -> %s)", jail.name,
|
||||||
ip, ticket.getBanCount(), *logtime)
|
ip, ticket.getBanCount(), *logtime)
|
||||||
|
# delayed prolonging ticket via actions that expected this (not later than 10 sec):
|
||||||
|
logSys.log(5, "[%s] Observer: prolong %s in %s", jail.name, ip, (btime, oldbtime))
|
||||||
|
self.add_timer(min(10, max(0, btime - oldbtime - 5)), self.prolongBan, ticket, jail)
|
||||||
# add ticket to database, but only if was not restored (not already read from database):
|
# add ticket to database, but only if was not restored (not already read from database):
|
||||||
if jail.database is not None and not ticket.restored:
|
if jail.database is not None and not ticket.restored:
|
||||||
# add to database always only after ban time was calculated an not yet already banned:
|
# add to database always only after ban time was calculated an not yet already banned:
|
||||||
|
@ -482,6 +506,21 @@ class ObserverThread(JailThread):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
|
||||||
|
def prolongBan(self, ticket, jail):
|
||||||
|
""" Notify observer a ban occured for ip
|
||||||
|
|
||||||
|
Observer will check ip was known (bad) and possibly increase/prolong a ban time
|
||||||
|
Secondary we will actualize the bans and bips (bad ip) in database
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
btime = ticket.getBanTime()
|
||||||
|
ip = ticket.getIP()
|
||||||
|
logSys.debug("[%s] Observer: prolong %s, %s", jail.name, ip, btime)
|
||||||
|
# prolong ticket via actions that expected this:
|
||||||
|
jail.actions._prolongBan(ticket)
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
|
||||||
# Global observer initial created in server (could be later rewriten via singleton)
|
# Global observer initial created in server (could be later rewriten via singleton)
|
||||||
class _Observers:
|
class _Observers:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -33,6 +33,7 @@ logSys = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Ticket(object):
|
class Ticket(object):
|
||||||
|
__slots__ = ('_ip', '_flags', '_banCount', '_banTime', '_time', '_data', '_retry', '_lastReset')
|
||||||
|
|
||||||
MAX_TIME = 0X7FFFFFFFFFFF ;# 4461763-th year
|
MAX_TIME = 0X7FFFFFFFFFFF ;# 4461763-th year
|
||||||
|
|
||||||
|
@ -59,36 +60,44 @@ class Ticket(object):
|
||||||
self._data[k] = v
|
self._data[k] = v
|
||||||
if ticket:
|
if ticket:
|
||||||
# ticket available - copy whole information from ticket:
|
# ticket available - copy whole information from ticket:
|
||||||
self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
|
self.update(ticket)
|
||||||
|
#self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: ip=%s time=%s bantime=%s bancount=%s #attempts=%d matches=%r" % \
|
return "%s: ip=%s time=%s bantime=%s bancount=%s #attempts=%d matches=%r" % \
|
||||||
(self.__class__.__name__.split('.')[-1], self.__ip, self._time,
|
(self.__class__.__name__.split('.')[-1], self._ip, self._time,
|
||||||
self._banTime, self._banCount,
|
self._banTime, self._banCount,
|
||||||
self._data['failures'], self._data.get('matches', []))
|
self._data['failures'], self._data.get('matches', []))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
try:
|
try:
|
||||||
return self.__ip == other.__ip and \
|
return self._ip == other._ip and \
|
||||||
round(self._time, 2) == round(other._time, 2) and \
|
round(self._time, 2) == round(other._time, 2) and \
|
||||||
self._data == other._data
|
self._data == other._data
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def update(self, ticket):
|
||||||
|
for n in ticket.__slots__:
|
||||||
|
v = getattr(ticket, n, None)
|
||||||
|
if v is not None:
|
||||||
|
setattr(self, n, v)
|
||||||
|
|
||||||
|
|
||||||
def setIP(self, value):
|
def setIP(self, value):
|
||||||
# guarantee using IPAddr instead of unicode, str for the IP
|
# guarantee using IPAddr instead of unicode, str for the IP
|
||||||
if isinstance(value, basestring):
|
if isinstance(value, basestring):
|
||||||
value = IPAddr(value)
|
value = IPAddr(value)
|
||||||
self.__ip = value
|
self._ip = value
|
||||||
|
|
||||||
def getID(self):
|
def getID(self):
|
||||||
return self._data.get('fid', self.__ip)
|
return self._data.get('fid', self._ip)
|
||||||
|
|
||||||
def getIP(self):
|
def getIP(self):
|
||||||
return self.__ip
|
return self._ip
|
||||||
|
|
||||||
def setTime(self, value):
|
def setTime(self, value):
|
||||||
self._time = value
|
self._time = value
|
||||||
|
@ -97,16 +106,17 @@ class Ticket(object):
|
||||||
return self._time
|
return self._time
|
||||||
|
|
||||||
def setBanTime(self, value):
|
def setBanTime(self, value):
|
||||||
self._banTime = value;
|
self._banTime = value
|
||||||
|
|
||||||
def getBanTime(self, defaultBT=None):
|
def getBanTime(self, defaultBT=None):
|
||||||
return (self._banTime if self._banTime is not None else defaultBT)
|
return (self._banTime if self._banTime is not None else defaultBT)
|
||||||
|
|
||||||
def setBanCount(self, value):
|
def setBanCount(self, value, always=False):
|
||||||
self._banCount = value;
|
if always or value > self._banCount:
|
||||||
|
self._banCount = value
|
||||||
|
|
||||||
def incrBanCount(self, value = 1):
|
def incrBanCount(self, value=1):
|
||||||
self._banCount += value;
|
self._banCount += value
|
||||||
|
|
||||||
def getBanCount(self):
|
def getBanCount(self):
|
||||||
return self._banCount;
|
return self._banCount;
|
||||||
|
@ -204,21 +214,21 @@ class FailTicket(Ticket):
|
||||||
|
|
||||||
def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
|
def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
|
||||||
# this class variables:
|
# this class variables:
|
||||||
self.__retry = 0
|
self._retry = 0
|
||||||
self.__lastReset = None
|
self._lastReset = None
|
||||||
# create/copy using default ticket constructor:
|
# create/copy using default ticket constructor:
|
||||||
Ticket.__init__(self, ip, time, matches, data, ticket)
|
Ticket.__init__(self, ip, time, matches, data, ticket)
|
||||||
# init:
|
# init:
|
||||||
if ticket is None:
|
if ticket is None:
|
||||||
self.__lastReset = time if time is not None else self.getTime()
|
self._lastReset = time if time is not None else self.getTime()
|
||||||
if not self.__retry:
|
if not self._retry:
|
||||||
self.__retry = self._data['failures'];
|
self._retry = self._data['failures'];
|
||||||
|
|
||||||
def setRetry(self, value):
|
def setRetry(self, value):
|
||||||
""" Set artificial retry count, normally equal failures / attempt,
|
""" Set artificial retry count, normally equal failures / attempt,
|
||||||
used in incremental features (BanTimeIncr) to increase retry count for bad IPs
|
used in incremental features (BanTimeIncr) to increase retry count for bad IPs
|
||||||
"""
|
"""
|
||||||
self.__retry = value
|
self._retry = value
|
||||||
if not self._data['failures']:
|
if not self._data['failures']:
|
||||||
self._data['failures'] = 1
|
self._data['failures'] = 1
|
||||||
if not value:
|
if not value:
|
||||||
|
@ -229,10 +239,10 @@ class FailTicket(Ticket):
|
||||||
""" Returns failures / attempt count or
|
""" Returns failures / attempt count or
|
||||||
artificial retry count increased for bad IPs
|
artificial retry count increased for bad IPs
|
||||||
"""
|
"""
|
||||||
return max(self.__retry, self._data['failures'])
|
return max(self._retry, self._data['failures'])
|
||||||
|
|
||||||
def inc(self, matches=None, attempt=1, count=1):
|
def inc(self, matches=None, attempt=1, count=1):
|
||||||
self.__retry += count
|
self._retry += count
|
||||||
self._data['failures'] += attempt
|
self._data['failures'] += attempt
|
||||||
if matches:
|
if matches:
|
||||||
# we should duplicate "matches", because possibly referenced to multiple tickets:
|
# we should duplicate "matches", because possibly referenced to multiple tickets:
|
||||||
|
@ -249,15 +259,24 @@ class FailTicket(Ticket):
|
||||||
return self._time
|
return self._time
|
||||||
|
|
||||||
def getLastReset(self):
|
def getLastReset(self):
|
||||||
return self.__lastReset
|
return self._lastReset
|
||||||
|
|
||||||
def setLastReset(self, value):
|
def setLastReset(self, value):
|
||||||
self.__lastReset = value
|
self._lastReset = value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def wrap(o):
|
||||||
|
o.__class__ = FailTicket
|
||||||
|
return o
|
||||||
|
|
||||||
##
|
##
|
||||||
# Ban Ticket.
|
# Ban Ticket.
|
||||||
#
|
#
|
||||||
# This class extends the Ticket class. It is mainly used by the BanManager.
|
# This class extends the Ticket class. It is mainly used by the BanManager.
|
||||||
|
|
||||||
class BanTicket(Ticket):
|
class BanTicket(FailTicket):
|
||||||
pass
|
|
||||||
|
@staticmethod
|
||||||
|
def wrap(o):
|
||||||
|
o.__class__ = BanTicket
|
||||||
|
return o
|
||||||
|
|
|
@ -102,7 +102,7 @@ class Utils():
|
||||||
def unset(self, k):
|
def unset(self, k):
|
||||||
try:
|
try:
|
||||||
del self._cache[k]
|
del self._cache[k]
|
||||||
except KeyError: # pragme: no cover
|
except KeyError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ class Utils():
|
||||||
return e.errno == errno.EPERM
|
return e.errno == errno.EPERM
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
else: # pragma : no cover (no windows currently supported)
|
else: # pragma: no cover (no windows currently supported)
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pid_exists(pid):
|
def pid_exists(pid):
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
|
@ -206,15 +206,15 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("<matches>",
|
self.__action.replaceTag("<matches>",
|
||||||
{'matches': "some >char< should \< be[ escap}ed&\n"}),
|
{'matches': "some >char< should \< be[ escap}ed&\n"}),
|
||||||
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
|
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\n")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("<ipmatches>",
|
self.__action.replaceTag("<ipmatches>",
|
||||||
{'ipmatches': "some >char< should \< be[ escap}ed&\n"}),
|
{'ipmatches': "some >char< should \< be[ escap}ed&\n"}),
|
||||||
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
|
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\n")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("<ipjailmatches>",
|
self.__action.replaceTag("<ipjailmatches>",
|
||||||
{'ipjailmatches': "some >char< should \< be[ escap}ed&\n"}),
|
{'ipjailmatches': "some >char< should \< be[ escap}ed&\r\n"}),
|
||||||
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
|
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\r\\n")
|
||||||
|
|
||||||
# Recursive
|
# Recursive
|
||||||
aInfo["ABC"] = "<xyz>"
|
aInfo["ABC"] = "<xyz>"
|
||||||
|
|
|
@ -100,21 +100,22 @@ class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
def testBanTimeIncr(self):
|
def testBanTimeIncr(self):
|
||||||
ticket = BanTicket(self.__ticket.getIP(), self.__ticket.getTime())
|
ticket = BanTicket(self.__ticket.getIP(), self.__ticket.getTime())
|
||||||
## increase twice and at end permanent:
|
## increase twice and at end permanent, check time/count increase:
|
||||||
|
c = 0
|
||||||
for i in (1000, 2000, -1):
|
for i in (1000, 2000, -1):
|
||||||
self.__banManager.addBanTicket(self.__ticket)
|
self.__banManager.addBanTicket(self.__ticket); c += 1
|
||||||
ticket.setBanTime(i)
|
ticket.setBanTime(i)
|
||||||
self.assertFalse(self.__banManager.addBanTicket(ticket))
|
self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
|
||||||
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
|
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
|
||||||
"BanTicket: ip=%s time=%s bantime=%s bancount=0 #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), i))
|
"BanTicket: ip=%s time=%s bantime=%s bancount=%s #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), i, c))
|
||||||
## after permanent, it should remain permanent ban time (-1):
|
## after permanent, it should remain permanent ban time (-1):
|
||||||
self.__banManager.addBanTicket(self.__ticket)
|
self.__banManager.addBanTicket(self.__ticket); c += 1
|
||||||
ticket.setBanTime(-1)
|
ticket.setBanTime(-1)
|
||||||
self.assertFalse(self.__banManager.addBanTicket(ticket))
|
self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
|
||||||
ticket.setBanTime(1000)
|
ticket.setBanTime(1000)
|
||||||
self.assertFalse(self.__banManager.addBanTicket(ticket))
|
self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
|
||||||
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
|
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
|
||||||
"BanTicket: ip=%s time=%s bantime=%s bancount=0 #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), -1))
|
"BanTicket: ip=%s time=%s bantime=%s bancount=%s #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), -1, c))
|
||||||
|
|
||||||
def testUnban(self):
|
def testUnban(self):
|
||||||
btime = self.__banManager.getBanTime()
|
btime = self.__banManager.getBanTime()
|
||||||
|
|
|
@ -566,8 +566,6 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
# all must have some actionban defined
|
# all must have some actionban defined
|
||||||
self.assertTrue(actionReader._opts.get('actionban', '').strip(),
|
self.assertTrue(actionReader._opts.get('actionban', '').strip(),
|
||||||
msg="Action file %r is lacking actionban" % actionConfig)
|
msg="Action file %r is lacking actionban" % actionConfig)
|
||||||
self.assertIn('Init', actionReader.sections(),
|
|
||||||
msg="Action file %r is lacking [Init] section" % actionConfig)
|
|
||||||
|
|
||||||
def testReadStockJailConf(self):
|
def testReadStockJailConf(self):
|
||||||
jails = JailsReader(basedir=CONFIG_DIR, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm
|
jails = JailsReader(basedir=CONFIG_DIR, share_config=CONFIG_DIR_SHARE_CFG) # we are running tests from root project dir atm
|
||||||
|
|
|
@ -43,7 +43,8 @@ from .. import protocol
|
||||||
from ..server import server
|
from ..server import server
|
||||||
from ..server.mytime import MyTime
|
from ..server.mytime import MyTime
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils
|
||||||
from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging
|
from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging, \
|
||||||
|
TEST_NOW, tearDownMyTime
|
||||||
|
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger
|
||||||
|
|
||||||
|
@ -80,6 +81,11 @@ fail2banclient.output = \
|
||||||
fail2banserver.output = \
|
fail2banserver.output = \
|
||||||
protocol.output = _test_output
|
protocol.output = _test_output
|
||||||
|
|
||||||
|
def _time_shift(shift):
|
||||||
|
# jump to the future (+shift minutes):
|
||||||
|
logSys.debug("===>>> time shift + %s min", shift)
|
||||||
|
MyTime.setTime(MyTime.time() + shift*60)
|
||||||
|
|
||||||
|
|
||||||
Observers = server.Observers
|
Observers = server.Observers
|
||||||
|
|
||||||
|
@ -89,6 +95,22 @@ def _observer_wait_idle():
|
||||||
Observers.Main.wait_empty(MID_WAITTIME)
|
Observers.Main.wait_empty(MID_WAITTIME)
|
||||||
Observers.Main.wait_idle(MID_WAITTIME / 5)
|
Observers.Main.wait_idle(MID_WAITTIME / 5)
|
||||||
|
|
||||||
|
def _observer_wait_before_incrban(cond, timeout=MID_WAITTIME):
|
||||||
|
"""Helper to block observer before increase bantime until some condition gets true"""
|
||||||
|
if Observers.Main is not None:
|
||||||
|
# switch ban handler:
|
||||||
|
_obs_banFound = Observers.Main.banFound
|
||||||
|
def _banFound(*args, **kwargs):
|
||||||
|
# restore original handler:
|
||||||
|
Observers.Main.banFound = _obs_banFound
|
||||||
|
# wait for:
|
||||||
|
logSys.debug(' [Observer::banFound] *** observer blocked for test')
|
||||||
|
Utils.wait_for(cond, timeout)
|
||||||
|
logSys.debug(' [Observer::banFound] +++ observer runs again')
|
||||||
|
# original banFound:
|
||||||
|
_obs_banFound(*args, **kwargs)
|
||||||
|
Observers.Main.banFound = _banFound
|
||||||
|
|
||||||
#
|
#
|
||||||
# Mocking .exit so we could test its correct operation.
|
# Mocking .exit so we could test its correct operation.
|
||||||
# Two custom exceptions will be assessed to be raised in the tests
|
# Two custom exceptions will be assessed to be raised in the tests
|
||||||
|
@ -317,6 +339,7 @@ def with_foreground_server_thread(startextra={}):
|
||||||
# so don't kill (same process) - if success, just wait for end of worker:
|
# so don't kill (same process) - if success, just wait for end of worker:
|
||||||
if phase.get('end', None):
|
if phase.get('end', None):
|
||||||
th.join()
|
th.join()
|
||||||
|
tearDownMyTime()
|
||||||
return wrapper
|
return wrapper
|
||||||
return _deco_wrapper
|
return _deco_wrapper
|
||||||
|
|
||||||
|
@ -343,6 +366,7 @@ class Fail2banClientServerBase(LogCaptureTestCase):
|
||||||
server.DEF_LOGTARGET = SRV_DEF_LOGTARGET
|
server.DEF_LOGTARGET = SRV_DEF_LOGTARGET
|
||||||
server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL
|
server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL
|
||||||
LogCaptureTestCase.tearDown(self)
|
LogCaptureTestCase.tearDown(self)
|
||||||
|
tearDownMyTime()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _test_exit(code=0):
|
def _test_exit(code=0):
|
||||||
|
@ -1158,3 +1182,106 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
self.assertLogged(
|
self.assertLogged(
|
||||||
"Jail 'test-jail1' stopped",
|
"Jail 'test-jail1' stopped",
|
||||||
"Jail 'test-jail1' started", all=True)
|
"Jail 'test-jail1' started", all=True)
|
||||||
|
|
||||||
|
@with_foreground_server_thread()
|
||||||
|
def testServerObserver(self, tmp, startparams):
|
||||||
|
cfg = pjoin(tmp, "config")
|
||||||
|
test1log = pjoin(tmp, "test1.log")
|
||||||
|
|
||||||
|
os.mkdir(pjoin(cfg, "action.d"))
|
||||||
|
def _write_action_cfg(actname="test-action1", prolong=True):
|
||||||
|
fn = pjoin(cfg, "action.d", "%s.conf" % actname)
|
||||||
|
_write_file(fn, "w",
|
||||||
|
"[DEFAULT]",
|
||||||
|
"",
|
||||||
|
"[Definition]",
|
||||||
|
"actionban = printf %%s \"[%(name)s] %(actname)s: ++ ban <ip> -c <bancount> -t <bantime> : <F-MSG>\"", \
|
||||||
|
"actionprolong = printf %%s \"[%(name)s] %(actname)s: ++ prolong <ip> -c <bancount> -t <bantime> : <F-MSG>\"" \
|
||||||
|
if prolong else "",
|
||||||
|
"actionunban = printf %%b '[%(name)s] %(actname)s: -- unban <ip>'",
|
||||||
|
)
|
||||||
|
if unittest.F2B.log_level <= logging.DEBUG: # pragma: no cover
|
||||||
|
_out_file(fn)
|
||||||
|
|
||||||
|
def _write_jail_cfg(backend="polling"):
|
||||||
|
_write_file(pjoin(cfg, "jail.conf"), "w",
|
||||||
|
"[INCLUDES]", "",
|
||||||
|
"[DEFAULT]", "",
|
||||||
|
"usedns = no",
|
||||||
|
"maxretry = 3",
|
||||||
|
"findtime = 1m",
|
||||||
|
"bantime = 5m",
|
||||||
|
"bantime.increment = true",
|
||||||
|
"datepattern = {^LN-BEG}EPOCH",
|
||||||
|
"",
|
||||||
|
"[test-jail1]", "backend = " + backend, "filter =",
|
||||||
|
"action = test-action1[name='%(__name__)s']",
|
||||||
|
" test-action2[name='%(__name__)s']",
|
||||||
|
"logpath = " + test1log,
|
||||||
|
"failregex = ^\s*failure <F-ERRCODE>401|403</F-ERRCODE> from <HOST>:\s*<F-MSG>.*</F-MSG>$",
|
||||||
|
"enabled = true",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if unittest.F2B.log_level <= logging.DEBUG: # pragma: no cover
|
||||||
|
_out_file(pjoin(cfg, "jail.conf"))
|
||||||
|
|
||||||
|
# create test config:
|
||||||
|
_write_action_cfg(actname="test-action1", prolong=False)
|
||||||
|
_write_action_cfg(actname="test-action2", prolong=True)
|
||||||
|
_write_jail_cfg()
|
||||||
|
|
||||||
|
_write_file(test1log, "w")
|
||||||
|
# initial start:
|
||||||
|
self.pruneLog("[test-phase 0) time-0]")
|
||||||
|
self.execSuccess(startparams, "reload")
|
||||||
|
# generate bad ip:
|
||||||
|
_write_file(test1log, "w+", *(
|
||||||
|
(str(int(MyTime.time())) + " failure 401 from 192.0.2.11: I'm bad \"hacker\" `` $(echo test)",) * 3
|
||||||
|
))
|
||||||
|
# wait for ban:
|
||||||
|
_observer_wait_idle()
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -c 1 -t 300 : ",
|
||||||
|
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -c 1 -t 300 : ",
|
||||||
|
all=True, wait=MID_WAITTIME)
|
||||||
|
# wait for observer idle (write all tickets to db):
|
||||||
|
_observer_wait_idle()
|
||||||
|
|
||||||
|
self.pruneLog("[test-phase 1) time+10m]")
|
||||||
|
# jump to the future (+10 minutes):
|
||||||
|
_time_shift(10)
|
||||||
|
_observer_wait_idle()
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action1: -- unban 192.0.2.11",
|
||||||
|
"stdout: '[test-jail1] test-action2: -- unban 192.0.2.11",
|
||||||
|
"0 ticket(s) in 'test-jail1'",
|
||||||
|
all=True, wait=MID_WAITTIME)
|
||||||
|
_observer_wait_idle()
|
||||||
|
|
||||||
|
self.pruneLog("[test-phase 2) time+10m]")
|
||||||
|
# following tests are time-related - observer can prolong ticket (increase ban-time)
|
||||||
|
# before banning, so block it here before banFound called, prolong case later:
|
||||||
|
wakeObs = False
|
||||||
|
_observer_wait_before_incrban(lambda: wakeObs)
|
||||||
|
# write again (IP already bad):
|
||||||
|
_write_file(test1log, "w+", *(
|
||||||
|
(str(int(MyTime.time())) + " failure 401 from 192.0.2.11: I'm very bad \"hacker\" `` $(echo test)",) * 2
|
||||||
|
))
|
||||||
|
# wait for ban:
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -c 2 -t 300 : ",
|
||||||
|
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -c 2 -t 300 : ",
|
||||||
|
all=True, wait=MID_WAITTIME)
|
||||||
|
# unblock observer here and wait it is done:
|
||||||
|
wakeObs = True
|
||||||
|
_observer_wait_idle()
|
||||||
|
|
||||||
|
self.pruneLog("[test-phase 2) time+11m]")
|
||||||
|
# jump to the future (+1 minute):
|
||||||
|
_time_shift(1)
|
||||||
|
# wait for observer idle (write all tickets to db):
|
||||||
|
_observer_wait_idle()
|
||||||
|
# wait for prolong:
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action2: ++ prolong 192.0.2.11 -c 2 -t 600 : ",
|
||||||
|
all=True, wait=MID_WAITTIME)
|
||||||
|
|
|
@ -31,9 +31,8 @@ import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ..server.mytime import MyTime
|
from ..server.mytime import MyTime
|
||||||
from ..server.ticket import FailTicket
|
from ..server.ticket import FailTicket, BanTicket
|
||||||
from ..server.failmanager import FailManager
|
from ..server.failmanager import FailManager
|
||||||
from ..server.banmanager import BanManager
|
|
||||||
from ..server.observer import Observers, ObserverThread
|
from ..server.observer import Observers, ObserverThread
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils
|
||||||
from .utils import LogCaptureTestCase
|
from .utils import LogCaptureTestCase
|
||||||
|
@ -246,7 +245,6 @@ class BanTimeIncrDB(unittest.TestCase):
|
||||||
# incr time and ban a ticket again :
|
# incr time and ban a ticket again :
|
||||||
ticket.setTime(stime + 15)
|
ticket.setTime(stime + 15)
|
||||||
self.assertEqual(self.incrBanTime(ticket, 10), 20)
|
self.assertEqual(self.incrBanTime(ticket, 10), 20)
|
||||||
ticket.incrBanCount()
|
|
||||||
self.db.addBan(jail, ticket)
|
self.db.addBan(jail, ticket)
|
||||||
# get a ticket already banned in this jail:
|
# get a ticket already banned in this jail:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -292,7 +290,6 @@ class BanTimeIncrDB(unittest.TestCase):
|
||||||
ticket.setTime(stime + lastBanTime + 5)
|
ticket.setTime(stime + lastBanTime + 5)
|
||||||
banTime = self.incrBanTime(ticket, 10)
|
banTime = self.incrBanTime(ticket, 10)
|
||||||
self.assertEqual(banTime, lastBanTime * 2)
|
self.assertEqual(banTime, lastBanTime * 2)
|
||||||
ticket.incrBanCount()
|
|
||||||
self.db.addBan(jail, ticket)
|
self.db.addBan(jail, ticket)
|
||||||
lastBanTime = banTime
|
lastBanTime = banTime
|
||||||
# increase again, but the last multiplier reached (time not increased):
|
# increase again, but the last multiplier reached (time not increased):
|
||||||
|
@ -300,7 +297,6 @@ class BanTimeIncrDB(unittest.TestCase):
|
||||||
banTime = self.incrBanTime(ticket, 10)
|
banTime = self.incrBanTime(ticket, 10)
|
||||||
self.assertNotEqual(banTime, lastBanTime * 2)
|
self.assertNotEqual(banTime, lastBanTime * 2)
|
||||||
self.assertEqual(banTime, lastBanTime)
|
self.assertEqual(banTime, lastBanTime)
|
||||||
ticket.incrBanCount()
|
|
||||||
self.db.addBan(jail, ticket)
|
self.db.addBan(jail, ticket)
|
||||||
lastBanTime = banTime
|
lastBanTime = banTime
|
||||||
# add two tickets from yesterday: one unbanned (bantime already out-dated):
|
# add two tickets from yesterday: one unbanned (bantime already out-dated):
|
||||||
|
@ -500,7 +496,7 @@ class BanTimeIncrDB(unittest.TestCase):
|
||||||
|
|
||||||
# wrap FailTicket to BanTicket:
|
# wrap FailTicket to BanTicket:
|
||||||
failticket2 = ticket2
|
failticket2 = ticket2
|
||||||
ticket2 = BanManager.createBanTicket(failticket2)
|
ticket2 = BanTicket.wrap(failticket2)
|
||||||
self.assertEqual(ticket2, failticket2)
|
self.assertEqual(ticket2, failticket2)
|
||||||
# add this ticket to ban (use observer only without ban manager):
|
# add this ticket to ban (use observer only without ban manager):
|
||||||
obs.add('banFound', ticket2, jail, 10)
|
obs.add('banFound', ticket2, jail, 10)
|
||||||
|
|
|
@ -1065,8 +1065,20 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
logSys.debug(l)
|
logSys.debug(l)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _testActionInfos(self):
|
||||||
|
if not hasattr(self, '__aInfos'):
|
||||||
|
dmyjail = DummyJail()
|
||||||
|
self.__aInfos = {}
|
||||||
|
for t, ip in (('ipv4', '192.0.2.1'), ('ipv6', '2001:DB8::')):
|
||||||
|
ticket = BanTicket(ip)
|
||||||
|
ticket.setBanTime(600)
|
||||||
|
self.__aInfos[t] = _actions.Actions.ActionInfo(ticket, dmyjail)
|
||||||
|
return self.__aInfos
|
||||||
|
|
||||||
def _testExecActions(self, server):
|
def _testExecActions(self, server):
|
||||||
jails = server._Server__jails
|
jails = server._Server__jails
|
||||||
|
|
||||||
|
aInfos = self._testActionInfos()
|
||||||
for jail in jails:
|
for jail in jails:
|
||||||
# print(jail, jails[jail])
|
# print(jail, jails[jail])
|
||||||
for a in jails[jail].actions:
|
for a in jails[jail].actions:
|
||||||
|
@ -1083,16 +1095,16 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
action.start()
|
action.start()
|
||||||
# test ban ip4 :
|
# test ban ip4 :
|
||||||
logSys.debug('# === ban-ipv4 ==='); self.pruneLog()
|
logSys.debug('# === ban-ipv4 ==='); self.pruneLog()
|
||||||
action.ban({'ip': IPAddr('192.0.2.1'), 'family': 'inet4'})
|
action.ban(aInfos['ipv4'])
|
||||||
# test unban ip4 :
|
# test unban ip4 :
|
||||||
logSys.debug('# === unban ipv4 ==='); self.pruneLog()
|
logSys.debug('# === unban ipv4 ==='); self.pruneLog()
|
||||||
action.unban({'ip': IPAddr('192.0.2.1'), 'family': 'inet4'})
|
action.unban(aInfos['ipv4'])
|
||||||
# test ban ip6 :
|
# test ban ip6 :
|
||||||
logSys.debug('# === ban ipv6 ==='); self.pruneLog()
|
logSys.debug('# === ban ipv6 ==='); self.pruneLog()
|
||||||
action.ban({'ip': IPAddr('2001:DB8::'), 'family': 'inet6'})
|
action.ban(aInfos['ipv6'])
|
||||||
# test unban ip6 :
|
# test unban ip6 :
|
||||||
logSys.debug('# === unban ipv6 ==='); self.pruneLog()
|
logSys.debug('# === unban ipv6 ==='); self.pruneLog()
|
||||||
action.unban({'ip': IPAddr('2001:DB8::'), 'family': 'inet6'})
|
action.unban(aInfos['ipv6'])
|
||||||
# test stop :
|
# test stop :
|
||||||
logSys.debug('# === stop ==='); self.pruneLog()
|
logSys.debug('# === stop ==='); self.pruneLog()
|
||||||
action.stop()
|
action.stop()
|
||||||
|
@ -1310,11 +1322,11 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
|
('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
|
||||||
'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',),
|
'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',),
|
||||||
'ip4-start': (
|
'ip4-start': (
|
||||||
"`ipset create f2b-j-w-iptables-ipset hash:ip timeout 600`",
|
"`ipset create f2b-j-w-iptables-ipset hash:ip`",
|
||||||
"`iptables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`",
|
"`iptables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`",
|
||||||
),
|
),
|
||||||
'ip6-start': (
|
'ip6-start': (
|
||||||
"`ipset create f2b-j-w-iptables-ipset6 hash:ip timeout 600 family inet6`",
|
"`ipset create f2b-j-w-iptables-ipset6 hash:ip family inet6`",
|
||||||
"`ip6tables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
"`ip6tables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
||||||
),
|
),
|
||||||
'flush': (
|
'flush': (
|
||||||
|
@ -1348,11 +1360,11 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="10m", chain="INPUT"]', {
|
('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="10m", chain="INPUT"]', {
|
||||||
'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',),
|
'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',),
|
||||||
'ip4-start': (
|
'ip4-start': (
|
||||||
"`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 600`",
|
"`ipset create f2b-j-w-iptables-ipset-ap hash:ip`",
|
||||||
"`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
|
"`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
|
||||||
),
|
),
|
||||||
'ip6-start': (
|
'ip6-start': (
|
||||||
"`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip timeout 600 family inet6`",
|
"`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip family inet6`",
|
||||||
"`ip6tables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
"`ip6tables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
||||||
),
|
),
|
||||||
'flush': (
|
'flush': (
|
||||||
|
@ -1646,11 +1658,11 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
|
('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
|
||||||
'ip4': (' f2b-j-w-fwcmd-ipset ',), 'ip6': (' f2b-j-w-fwcmd-ipset6 ',),
|
'ip4': (' f2b-j-w-fwcmd-ipset ',), 'ip6': (' f2b-j-w-fwcmd-ipset6 ',),
|
||||||
'ip4-start': (
|
'ip4-start': (
|
||||||
"`ipset create f2b-j-w-fwcmd-ipset hash:ip timeout 600`",
|
"`ipset create f2b-j-w-fwcmd-ipset hash:ip`",
|
||||||
"`firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`",
|
"`firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`",
|
||||||
),
|
),
|
||||||
'ip6-start': (
|
'ip6-start': (
|
||||||
"`ipset create f2b-j-w-fwcmd-ipset6 hash:ip timeout 600`",
|
"`ipset create f2b-j-w-fwcmd-ipset6 hash:ip`",
|
||||||
"`firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
"`firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
||||||
),
|
),
|
||||||
'stop': (
|
'stop': (
|
||||||
|
@ -1695,10 +1707,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
|
|
||||||
jails = server._Server__jails
|
jails = server._Server__jails
|
||||||
|
|
||||||
tickets = {
|
aInfos = self._testActionInfos()
|
||||||
'ip4': BanTicket('192.0.2.1'),
|
|
||||||
'ip6': BanTicket('2001:DB8::'),
|
|
||||||
}
|
|
||||||
for jail, act, tests in testJailsActions:
|
for jail, act, tests in testJailsActions:
|
||||||
# print(jail, jails[jail])
|
# print(jail, jails[jail])
|
||||||
for a in jails[jail].actions:
|
for a in jails[jail].actions:
|
||||||
|
@ -1716,32 +1725,28 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
self.assertLogged(*tests['start'], all=True)
|
self.assertLogged(*tests['start'], all=True)
|
||||||
else:
|
else:
|
||||||
self.assertNotLogged(*tests['ip4-start']+tests['ip6-start'], all=True)
|
self.assertNotLogged(*tests['ip4-start']+tests['ip6-start'], all=True)
|
||||||
ainfo = {
|
|
||||||
'ip4': _actions.Actions.ActionInfo(tickets['ip4'], jails[jail]),
|
|
||||||
'ip6': _actions.Actions.ActionInfo(tickets['ip6'], jails[jail]),
|
|
||||||
}
|
|
||||||
# test ban ip4 :
|
# test ban ip4 :
|
||||||
self.pruneLog('# === ban-ipv4 ===')
|
self.pruneLog('# === ban-ipv4 ===')
|
||||||
action.ban(ainfo['ip4'])
|
action.ban(aInfos['ipv4'])
|
||||||
if tests.get('ip4-start'): self.assertLogged(*tests['ip4-start'], all=True)
|
if tests.get('ip4-start'): self.assertLogged(*tests['ip4-start'], all=True)
|
||||||
if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True)
|
if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True)
|
||||||
self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True)
|
self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip6'], all=True)
|
self.assertNotLogged(*tests['ip6'], all=True)
|
||||||
# test unban ip4 :
|
# test unban ip4 :
|
||||||
self.pruneLog('# === unban ipv4 ===')
|
self.pruneLog('# === unban ipv4 ===')
|
||||||
action.unban(ainfo['ip4'])
|
action.unban(aInfos['ipv4'])
|
||||||
self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True)
|
self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip6'], all=True)
|
self.assertNotLogged(*tests['ip6'], all=True)
|
||||||
# test ban ip6 :
|
# test ban ip6 :
|
||||||
self.pruneLog('# === ban ipv6 ===')
|
self.pruneLog('# === ban ipv6 ===')
|
||||||
action.ban(ainfo['ip6'])
|
action.ban(aInfos['ipv6'])
|
||||||
if tests.get('ip6-start'): self.assertLogged(*tests['ip6-start'], all=True)
|
if tests.get('ip6-start'): self.assertLogged(*tests['ip6-start'], all=True)
|
||||||
if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True)
|
if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True)
|
||||||
self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True)
|
self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip4'], all=True)
|
self.assertNotLogged(*tests['ip4'], all=True)
|
||||||
# test unban ip6 :
|
# test unban ip6 :
|
||||||
self.pruneLog('# === unban ipv6 ===')
|
self.pruneLog('# === unban ipv6 ===')
|
||||||
action.unban(ainfo['ip6'])
|
action.unban(aInfos['ipv6'])
|
||||||
self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True)
|
self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip4'], all=True)
|
self.assertNotLogged(*tests['ip4'], all=True)
|
||||||
# test flush for actions should supported this:
|
# test flush for actions should supported this:
|
||||||
|
|
Loading…
Reference in New Issue