Merge branch '0.11'

pull/2752/head
sebres 2020-05-25 19:09:29 +02:00
commit 6cff2bb007
34 changed files with 366 additions and 108 deletions

View File

@ -22,10 +22,27 @@ ver. 1.0.1-dev-1 (20??/??/??) - development nightly edition
* python 3.9 compatibility (and Travis CI support)
* restoring a large number (500+ depending on files ulimit) of current bans when using PyPy fixed
* manual ban is written to database, so can be restored by restart (gh-2647)
* `jail.conf`: don't specify `action` directly in jails (use `action_` or `banaction` instead)
* no mails-action added per default anymore (e. g. to allow that `action = %(action_mw)s` should be specified
per jail or in default section in jail.local), closes gh-2357
* ensure we've unique action name per jail (also if parameter `actname` is not set but name deviates from standard name, gh-2686)
* don't use `%(banaction)s` interpolation because it can be complex value (containing `[...]` and/or quotes),
so would bother the action interpolation
* `action.d/*-ipset*.conf`: several ipset actions fixed (no timeout per default anymore), so no discrepancy
between ipset and fail2ban (removal from ipset will be managed by fail2ban only, gh-2703)
* `action.d/cloudflare.conf`: fixed `actionunban` (considering new-line chars and optionally real json-parsing
with `jq`, gh-2140, gh-2656)
* `filter.d/common.conf`: avoid substitute of default values in related `lt_*` section, `__prefix_line`
should be interpolated in definition section (inside the filter-config, gh-2650)
* `filter.d/courier-smtp.conf`: prefregex extended to consider port in log-message (gh-2697)
* `filter.d/traefik-auth.conf`: filter extended with parameter mode (`normal`, `ddos`, `aggressive`) to handle
the match of username differently (gh-2693):
- `normal`: matches 401 with supplied username only
- `ddos`: matches 401 without supplied username only
- `aggressive`: matches 401 and any variant (with and without username)
### New Features
* new filter and jail for GitLab recognizing failed application logins (gh-2689)
### Enhancements
* introduced new prefix `{UNB}` for `datepattern` to disable word boundaries in regex;

View File

@ -5,7 +5,7 @@
#
# Please set jail.local's permission to 640 because it contains your CF API key.
#
# This action depends on curl.
# This action depends on curl (and optionally jq).
# Referenced from http://www.normyee.net/blog/2012/02/02/adding-cloudflare-support-to-fail2ban by NORM YEE
#
# To get your CloudFlare API Key: https://www.cloudflare.com/a/account/my-account
@ -43,9 +43,9 @@ actioncheck =
# API v1
#actionban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=ban' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
# API v4
actionban = curl -s -o /dev/null -X POST -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \
-H 'Content-Type: application/json' -d '{ "mode": "block", "configuration": { "target": "ip", "value": "<ip>" } }' \
https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules
actionban = curl -s -o /dev/null -X POST <_cf_api_prms> \
-d '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Fail2Ban <name>"}' \
<_cf_api_url>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -58,9 +58,14 @@ actionban = curl -s -o /dev/null -X POST -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-
# API v1
#actionunban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=nul' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
# API v4
actionunban = curl -s -o /dev/null -X DELETE -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \
https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$(curl -s -X GET -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \
'https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1' | cut -d'"' -f6)
actionunban = id=$(curl -s -X GET <_cf_api_prms> \
"<_cf_api_url>?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1&notes=Fail2Ban%%20<name>" \
| { jq -r '.result[0].id' 2>/dev/null || tr -d '\n' | sed -nE 's/^.*"result"\s*:\s*\[\s*\{\s*"id"\s*:\s*"([^"]+)".*$/\1/p'; })
if [ -z "$id" ]; then echo "<name>: id for <ip> cannot be found"; exit 0; fi;
curl -s -o /dev/null -X DELETE <_cf_api_prms> "<_cf_api_url>/$id"
_cf_api_url = https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules
_cf_api_prms = -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' -H 'Content-Type: application/json'
[Init]

View File

@ -18,7 +18,7 @@ before = firewallcmd-common.conf
[Definition]
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout><familyopt>
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout> <familyopt>
firewall-cmd --direct --add-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype>
actionflush = ipset flush <ipmset>
@ -27,9 +27,9 @@ actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 <acti
<actionflush>
ipset destroy <ipmset>
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
actionban = ipset add <ipmset> <ip> timeout <timeout> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
actionunban = ipset del <ipmset> <ip> -exist
@ -44,9 +44,17 @@ chain = INPUT_direct
# Option: default-timeout
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-timeout = 0
default-timeout = 600
# Option: timeout
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
timeout = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[timeout='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
# Option: actiontype
# Notes.: defines additions to the blocking rule
@ -71,7 +79,7 @@ familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6
familyopt = family inet6
# DEV NOTES:

View File

@ -26,7 +26,7 @@ before = iptables-common.conf
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
# Values: CMD
#
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout><familyopt>
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout> <familyopt>
<iptables> -I <chain> -m set --match-set <ipmset> src -j <blocktype>
# Option: actionflush
@ -49,9 +49,9 @@ actionstop = <iptables> -D <chain> -m set --match-set <ipmset> src -j <blocktype
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
actionban = ipset add <ipmset> <ip> timeout <timeout> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -65,9 +65,17 @@ actionunban = ipset del <ipmset> <ip> -exist
# Option: default-timeout
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-timeout = 0
default-timeout = 600
# Option: timeout
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
timeout = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[timeout='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
ipmset = f2b-<name>
familyopt =
@ -76,4 +84,4 @@ familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6
familyopt = family inet6

View File

@ -26,7 +26,7 @@ before = iptables-common.conf
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
# Values: CMD
#
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout><familyopt>
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout> <familyopt>
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
# Option: actionflush
@ -49,9 +49,9 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
actionban = ipset add <ipmset> <ip> timeout <timeout> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -65,9 +65,17 @@ actionunban = ipset del <ipmset> <ip> -exist
# Option: default-timeout
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-timeout = 0
default-timeout = 600
# Option: timeout
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
timeout = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[timeout='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
ipmset = f2b-<name>
familyopt =
@ -76,4 +84,4 @@ familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6
familyopt = family inet6

View File

@ -66,9 +66,9 @@ actionstop = ipset flush f2b-<name>
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
actionban = ipset add f2b-<name> <ip> timeout <timeout> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -80,6 +80,14 @@ actionunban = ipset del f2b-<name> <ip> -exist
# Option: default-timeout
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-timeout = 0
default-timeout = 600
# Option: timeout
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
timeout = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[timeout='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)

View File

@ -12,7 +12,7 @@ before = common.conf
_daemon = courieresmtpd
prefregex = ^%(__prefix_line)serror,relay=<HOST>,<F-CONTENT>.+</F-CONTENT>$
prefregex = ^%(__prefix_line)serror,relay=<HOST>,(?:port=\d+,)?<F-CONTENT>.+</F-CONTENT>$
failregex = ^[^:]*: 550 User (<.*> )?unknown\.?$
^msg="535 Authentication failed\.",cmd:( AUTH \S+)?( [0-9a-zA-Z\+/=]+)?(?: \S+)$

View File

@ -0,0 +1,6 @@
# Fail2Ban filter for Gitlab
# Detecting unauthorized access to the Gitlab Web portal
# typically logged in /var/log/gitlab/gitlab-rails/application.log
[Definition]
failregex = ^: Failed Login: username=<F-USER>.+</F-USER> ip=<HOST>$

View File

@ -14,16 +14,15 @@ before = common.conf
_daemon = proftpd
__suffix_failed_login = (User not authorized for login|No such user found|Incorrect password|Password expired|Account disabled|Invalid shell: '\S+'|User in \S+|Limit (access|configuration) denies login|Not a UserAlias|maximum login length exceeded).?
__suffix_failed_login = ([uU]ser not authorized for login|[nN]o such user found|[iI]ncorrect password|[pP]assword expired|[aA]ccount disabled|[iI]nvalid shell: '\S+'|[uU]ser in \S+|[lL]imit (access|configuration) denies login|[nN]ot a UserAlias|[mM]aximum login length exceeded)
prefregex = ^%(__prefix_line)s%(__hostname)s \(\S+\[<HOST>\]\)[: -]+ <F-CONTENT>(?:USER|SECURITY|Maximum).+</F-CONTENT>$
prefregex = ^%(__prefix_line)s%(__hostname)s \(\S+\[<HOST>\]\)[: -]+ <F-CONTENT>(?:USER|SECURITY|Maximum) .+</F-CONTENT>$
failregex = ^USER .*: no such user found from \S+ \[\S+\] to \S+:\S+ *$
^USER .* \(Login failed\): %(__suffix_failed_login)s\s*$
^SECURITY VIOLATION: .* login attempted\. *$
^Maximum login attempts \(\d+\) exceeded *$
failregex = ^USER <F-USER>\S+|.*?</F-USER>(?: \(Login failed\))?: %(__suffix_failed_login)s
^SECURITY VIOLATION: <F-USER>\S+|.*?</F-USER> login attempted
^Maximum login attempts \(\d+\) exceeded
ignoreregex =

View File

@ -73,13 +73,12 @@ mdre-normal-other = ^<F-NOFAIL><F-MLFFORGET>(Connection closed|Disconnected)</F-
mdre-ddos = ^Did not receive identification string from <HOST>
^kex_exchange_identification: client sent invalid protocol identifier
^Bad protocol version identification '.*' from <HOST>
^Connection <F-MLFFORGET>reset</F-MLFFORGET> by <HOST>
^<F-NOFAIL>SSH: Server;Ltype:</F-NOFAIL> (?:Authname|Version|Kex);Remote: <HOST>-\d+;[A-Z]\w+:
^Read from socket failed: Connection <F-MLFFORGET>reset</F-MLFFORGET> by peer
# same as mdre-normal-other, but as failure (without <F-NOFAIL>) and [preauth] only:
mdre-ddos-other = ^<F-MLFFORGET>(Connection closed|Disconnected)</F-MLFFORGET> (?:by|from)%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$
mdre-ddos-other = ^<F-MLFFORGET>(Connection (?:closed|reset)|Disconnected)</F-MLFFORGET> (?:by|from)%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$
mdre-extra = ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*14: No supported authentication methods available
mdre-extra = ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*14: No(?: supported)? authentication methods available
^Unable to negotiate with <HOST>%(__on_port_opt)s: no matching <__alg_match> found.
^Unable to negotiate a <__alg_match>
^no matching <__alg_match> found:

View File

@ -51,6 +51,26 @@
[Definition]
failregex = ^<HOST> \- (?!- )\S+ \[\] \"(GET|POST|HEAD) [^\"]+\" 401\b
# Parameter "method" can be used to specifiy request method
req-method = \S+
# Usage example (for jail.local):
# filter = traefik-auth[req-method="GET|POST|HEAD"]
failregex = ^<HOST> \- <usrre-<mode>> \[\] \"(?:<req-method>) [^\"]+\" 401\b
ignoreregex =
# Parameter "mode": normal (default), ddos or aggressive
# Usage example (for jail.local):
# [traefik-auth]
# mode = aggressive
# # or another jail (rewrite filter parameters of jail):
# [traefik-auth-ddos]
# filter = traefik-auth[mode=ddos]
#
mode = normal
# part of failregex matches user name (must be available in normal mode, must be empty in ddos mode, and both for aggressive mode):
usrre-normal = (?!- )<F-USER>\S+</F-USER>
usrre-ddos = -
usrre-aggressive = <F-USER>\S+</F-USER>

View File

@ -52,7 +52,7 @@ before = paths-debian.conf
# to prevent "clever" botnets calculate exact time IP can be unbanned again:
#bantime.rndtime =
# "bantime.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
# "bantime.maxtime" is the max number of seconds using the ban time can reach (doesn't grow further)
#bantime.maxtime =
# "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
@ -60,7 +60,7 @@ before = paths-debian.conf
# grows by 1, 2, 4, 8, 16 ...
#bantime.factor = 1
# "bantime.formula" used by default to calculate next value of ban time, default value bellow,
# "bantime.formula" used by default to calculate next value of ban time, default value below,
# the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
#bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
#
@ -212,19 +212,19 @@ banaction_allports = iptables-allports
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.
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
action_mw = %(action_)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
# to the destemail.
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
action_mwl = %(action_)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
#
# ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines
# to the destemail.
action_xarf = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
action_xarf = %(action_)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
@ -371,7 +371,7 @@ maxretry = 1
[openhab-auth]
filter = openhab
action = iptables-allports[name=NoAuthFailures]
banaction = %(banaction_allports)s
logpath = /opt/openhab/logs/request.log
@ -744,8 +744,8 @@ logpath = /var/log/named/security.log
[nsd]
port = 53
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/nsd.log
@ -756,9 +756,8 @@ logpath = /var/log/nsd.log
[asterisk]
port = 5060,5061
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/asterisk/messages
maxretry = 10
@ -766,9 +765,8 @@ maxretry = 10
[freeswitch]
port = 5060,5061
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/freeswitch.log
maxretry = 10
@ -856,8 +854,12 @@ logpath = /opt/cstrike/logs/L[0-9]*.log
# Firewall: http://www.cstrike-planet.com/faq/6
tcpport = 27030,27031,27032,27033,27034,27035,27036,27037,27038,27039
udpport = 1200,27000,27001,27002,27003,27004,27005,27006,27007,27008,27009,27010,27011,27012,27013,27014,27015
action = %(banaction)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
action_ = %(default/action_)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp"]
[gitlab]
port = http,https
logpath = /var/log/gitlab/gitlab-rails/application.log
[bitwarden]
port = http,https
@ -909,8 +911,8 @@ findtime = 1
[murmur]
# AKA mumble-server
port = 64738
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol=tcp, chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol=udp, chain="%(chain)s", actname=%(banaction)s-udp]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/mumble-server/mumble-server.log

View File

@ -53,13 +53,17 @@ class ActionReader(DefinitionInitConfigReader):
}
def __init__(self, file_, jailName, initOpts, **kwargs):
# always supply jail name as name parameter if not specified in options:
n = initOpts.get("name")
if n is None:
initOpts["name"] = n = jailName
actname = initOpts.get("actname")
if actname is None:
actname = file_
# ensure we've unique action name per jail:
if n != jailName:
actname += n[len(jailName):] if n.startswith(jailName) else '-' + n
initOpts["actname"] = actname
# always supply jail name as name parameter if not specified in options:
if initOpts.get("name") is None:
initOpts["name"] = jailName
self._name = actname
DefinitionInitConfigReader.__init__(
self, file_, jailName, initOpts, **kwargs)

View File

@ -55,6 +55,8 @@ protocol = [
["stop", "stops all jails and terminate the server"],
["unban --all", "unbans all IP addresses (in all jails and database)"],
["unban <IP> ... <IP>", "unbans <IP> (in all jails and database)"],
["banned", "return jails with banned IPs as dictionary"],
["banned <IP> ... <IP>]", "return list(s) of jails where given IP(s) are banned"],
["status", "gets the current status of the server"],
["ping", "tests if the server is alive"],
["echo", "for internal usage, returns back and outputs a given string"],
@ -120,6 +122,8 @@ protocol = [
["set <JAIL> action <ACT> <PROPERTY> <VALUE>", "sets the <VALUE> of <PROPERTY> for the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]", "calls the <METHOD> with <JSONKWARGS> for the action <ACT> for <JAIL>"],
['', "JAIL INFORMATION", ""],
["get <JAIL> banned", "return banned IPs of <JAIL>"],
["get <JAIL> banned <IP> ... <IP>]", "return 1 if IP is banned in <JAIL> otherwise 0, or a list of 1/0 for multiple IPs"],
["get <JAIL> logpath", "gets the list of the monitored files for <JAIL>"],
["get <JAIL> logencoding", "gets the encoding of the log files for <JAIL>"],
["get <JAIL> journalmatch", "gets the journal filter match for <JAIL>"],

View File

@ -211,6 +211,14 @@ class Actions(JailThread, Mapping):
def getBanTime(self):
return self.__banManager.getBanTime()
def getBanned(self, ids):
lst = self.__banManager.getBanList()
if not ids:
return lst
if len(ids) == 1:
return 1 if ids[0] in lst else 0
return map(lambda ip: 1 if ip in lst else 0, ids)
def getBanList(self, withTime=False):
"""Returns the list of banned IP addresses.

View File

@ -105,7 +105,7 @@ class BanManager:
def getBanList(self, ordered=False, withTime=False):
with self.__lock:
if not ordered:
return self.__banList.keys()
return list(self.__banList.keys())
lst = []
for ticket in self.__banList.itervalues():
eob = ticket.getEndOfBanTime(self.__banTime)
@ -125,8 +125,9 @@ class BanManager:
# @return ban list iterator
def __iter__(self):
# ensure iterator is safe (traverse over the list in snapshot created within lock):
with self.__lock:
return self.__banList.itervalues()
return iter(list(self.__banList.values()))
##
# Returns normalized value

View File

@ -81,6 +81,7 @@ class Filter(JailThread):
## Ignore own IPs flag:
self.__ignoreSelf = True
## The ignore IP list.
self.__ignoreIpSet = set()
self.__ignoreIpList = []
## External command
self.__ignoreCommand = False
@ -490,28 +491,36 @@ class Filter(JailThread):
# Create IP address object
ip = IPAddr(ipstr)
# Avoid exact duplicates
if ip in self.__ignoreIpList:
logSys.warn(" Ignore duplicate %r (%r), already in ignore list", ip, ipstr)
if ip in self.__ignoreIpSet or ip in self.__ignoreIpList:
logSys.log(logging.MSG, " Ignore duplicate %r (%r), already in ignore list", ip, ipstr)
return
# log and append to ignore list
logSys.debug(" Add %r to ignore list (%r)", ip, ipstr)
self.__ignoreIpList.append(ip)
# if single IP (not DNS or a subnet) add to set, otherwise to list:
if ip.isSingle:
self.__ignoreIpSet.add(ip)
else:
self.__ignoreIpList.append(ip)
def delIgnoreIP(self, ip=None):
# clear all:
if ip is None:
self.__ignoreIpSet.clear()
del self.__ignoreIpList[:]
return
# delete by ip:
logSys.debug(" Remove %r from ignore list", ip)
self.__ignoreIpList.remove(ip)
if ip in self.__ignoreIpSet:
self.__ignoreIpSet.remove(ip)
else:
self.__ignoreIpList.remove(ip)
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
if log_ignore:
logSys.info("[%s] Ignore %s by %s", self.jailName, ip, ignore_source)
def getIgnoreIP(self):
return self.__ignoreIpList
return self.__ignoreIpList + list(self.__ignoreIpSet)
##
# Check if IP address/DNS is in the ignore list.
@ -551,8 +560,11 @@ class Filter(JailThread):
if self.__ignoreCache: c.set(key, True)
return True
# check if the IP is covered by ignore IP (in set or in subnet/dns):
if ip in self.__ignoreIpSet:
self.logIgnoreIp(ip, log_ignore, ignore_source="ip")
return True
for net in self.__ignoreIpList:
# check if the IP is covered by ignore IP
if ip.isInNet(net):
self.logIgnoreIp(ip, log_ignore, ignore_source=("ip" if net.isValid else "dns"))
if self.__ignoreCache: c.set(key, True)

View File

@ -379,6 +379,12 @@ class IPAddr(object):
"""
return self._family != socket.AF_UNSPEC
@property
def isSingle(self):
"""Returns whether the object is a single IP address (not DNS and subnet)
"""
return self._plen == {socket.AF_INET: 32, socket.AF_INET6: 128}.get(self._family, -1000)
def __eq__(self, other):
if self._family == IPAddr.CIDR_RAW and not isinstance(other, IPAddr):
return self._raw == other

View File

@ -540,6 +540,32 @@ class Server:
cnt += jail.actions.removeBannedIP(value, ifexists=ifexists)
return cnt
def banned(self, name=None, ids=None):
if name is not None:
# single jail:
jails = [self.__jails[name]]
else:
# in all jails:
jails = self.__jails.values()
# check banned ids:
res = []
if name is None and ids:
for ip in ids:
ret = []
for jail in jails:
if jail.actions.getBanned([ip]):
ret.append(jail.name)
res.append(ret)
else:
for jail in jails:
ret = jail.actions.getBanned(ids)
if name is not None:
return ret
res.append(ret)
else:
res.append({jail.name: ret})
return res
def getBanTime(self, name):
return self.__jails[name].actions.getBanTime()

View File

@ -118,6 +118,9 @@ class Transmitter:
if len(value) == 1 and value[0] == "--all":
return self.__server.setUnbanIP()
return self.__server.setUnbanIP(None, value)
elif name == "banned":
# check IP is banned in all jails:
return self.__server.banned(None, command[1:])
elif name == "echo":
return command[1:]
elif name == "server-status":
@ -430,7 +433,10 @@ class Transmitter:
return None
else:
return db.purgeage
# Filter
# Jail, Filter
elif command[1] == "banned":
# check IP is banned in all jails:
return self.__server.banned(name, command[2:])
elif command[1] == "logpath":
return self.__server.getLogPath(name)
elif command[1] == "logencoding":

View File

@ -57,11 +57,10 @@ mdre-normal =
mdre-ddos = ^%(__prefix_line_sl)sDid not receive identification string from <HOST>
^%(__prefix_line_sl)sBad protocol version identification '.*' from <HOST>
^%(__prefix_line_sl)sConnection closed by%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$
^%(__prefix_line_sl)sConnection reset by <HOST>
^%(__prefix_line_sl)sConnection (?:closed|reset) by%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$
^%(__prefix_line_ml1)sSSH: Server;Ltype: (?:Authname|Version|Kex);Remote: <HOST>-\d+;[A-Z]\w+:.*%(__prefix_line_ml2)sRead from socket failed: Connection reset by peer%(__suff)s$
mdre-extra = ^%(__prefix_line_sl)sReceived disconnect from <HOST>%(__on_port_opt)s:\s*14: No supported authentication methods available
mdre-extra = ^%(__prefix_line_sl)sReceived disconnect from <HOST>%(__on_port_opt)s:\s*14: No(?: supported)? authentication methods available
^%(__prefix_line_sl)sUnable to negotiate with <HOST>%(__on_port_opt)s: no matching <__alg_match> found.
^%(__prefix_line_ml1)sConnection from <HOST>%(__on_port_opt)s%(__prefix_line_ml2)sUnable to negotiate a <__alg_match>
^%(__prefix_line_ml1)sConnection from <HOST>%(__on_port_opt)s%(__prefix_line_ml2)sno matching <__alg_match> found:

View File

@ -37,7 +37,7 @@ from threading import Thread
from ..client import fail2banclient, fail2banserver, fail2bancmdline
from ..client.fail2bancmdline import Fail2banCmdLine
from ..client.fail2banclient import exec_command_line as _exec_client, VisualWait
from ..client.fail2banclient import exec_command_line as _exec_client, CSocket, VisualWait
from ..client.fail2banserver import Fail2banServer, exec_command_line as _exec_server
from .. import protocol
from ..server import server
@ -453,6 +453,14 @@ class Fail2banClientServerBase(LogCaptureTestCase):
self.assertRaises(exitType, self.exec_command_line[0],
(self.exec_command_line[1:] + startparams + args))
def execCmdDirect(self, startparams, *args):
sock = startparams[startparams.index('-s')+1]
s = CSocket(sock)
try:
return s.send(args)
finally:
s.close()
#
# Common tests
#
@ -1041,6 +1049,30 @@ class Fail2banServerTest(Fail2banClientServerBase):
all=True)
# if observer available wait for it becomes idle (write all tickets to db):
_observer_wait_idle()
# test banned command:
self.assertSortedEqual(self.execCmdDirect(startparams,
'banned'), (0, [
{'test-jail1': ['192.0.2.4', '192.0.2.1', '192.0.2.8', '192.0.2.3', '192.0.2.2']},
{'test-jail2': ['192.0.2.4', '192.0.2.9', '192.0.2.8']}
]
))
self.assertSortedEqual(self.execCmdDirect(startparams,
'banned', '192.0.2.1', '192.0.2.4', '192.0.2.222'), (0, [
['test-jail1'], ['test-jail1', 'test-jail2'], []
]
))
self.assertSortedEqual(self.execCmdDirect(startparams,
'get', 'test-jail1', 'banned')[1], [
'192.0.2.4', '192.0.2.1', '192.0.2.8', '192.0.2.3', '192.0.2.2'])
self.assertSortedEqual(self.execCmdDirect(startparams,
'get', 'test-jail2', 'banned')[1], [
'192.0.2.4', '192.0.2.9', '192.0.2.8'])
self.assertEqual(self.execCmdDirect(startparams,
'get', 'test-jail1', 'banned', '192.0.2.3')[1], 1)
self.assertEqual(self.execCmdDirect(startparams,
'get', 'test-jail1', 'banned', '192.0.2.9')[1], 0)
self.assertEqual(self.execCmdDirect(startparams,
'get', 'test-jail1', 'banned', '192.0.2.3', '192.0.2.9')[1], [1, 0])
# rotate logs:
_write_file(test1log, "w+")

View File

@ -12,3 +12,5 @@ Nov 21 23:16:17 server courieresmtpd: error,relay=::ffff:1.2.3.4,from=<>,to=<>:
Aug 14 12:51:04 HOSTNAME courieresmtpd: error,relay=::ffff:1.2.3.4,from=<firozquarl@aclunc.org>,to=<BOGUSUSER@HOSTEDDOMAIN.org>: 550 User unknown.
# failJSON: { "time": "2004-08-14T12:51:04", "match": true , "host": "1.2.3.4" }
Aug 14 12:51:04 mail.server courieresmtpd[26762]: error,relay=::ffff:1.2.3.4,msg="535 Authentication failed.",cmd: AUTH PLAIN AAAAABBBBCCCCWxlZA== admin
# failJSON: { "time": "2004-08-14T12:51:05", "match": true , "host": "192.0.2.3" }
Aug 14 12:51:05 mail.server courieresmtpd[425070]: error,relay=::ffff:192.0.2.3,port=43632,msg="535 Authentication failed.",cmd: AUTH LOGIN PlcmSpIp@example.com

View File

@ -0,0 +1,5 @@
# Access of unauthorized host in /var/log/gitlab/gitlab-rails/application.log
# failJSON: { "time": "2020-04-09T16:04:00", "match": true , "host": "80.10.11.12" }
2020-04-09T14:04:00.667Z: Failed Login: username=admin ip=80.10.11.12
# failJSON: { "time": "2020-04-09T16:15:09", "match": true , "host": "80.10.11.12" }
2020-04-09T14:15:09.344Z: Failed Login: username=user name ip=80.10.11.12

View File

@ -1,6 +1,6 @@
# failJSON: { "time": "2005-01-10T00:00:00", "match": true , "host": "123.123.123.123" }
# failJSON: { "time": "2005-01-10T00:00:00", "match": true , "host": "123.123.123.123", "user": "username" }
Jan 10 00:00:00 myhost proftpd[12345] myhost.domain.com (123.123.123.123[123.123.123.123]): USER username (Login failed): User in /etc/ftpusers
# failJSON: { "time": "2005-02-01T00:00:00", "match": true , "host": "123.123.123.123" }
# failJSON: { "time": "2005-02-01T00:00:00", "match": true , "host": "123.123.123.123", "user": "username" }
Feb 1 00:00:00 myhost proftpd[12345] myhost.domain.com (123.123.123.123[123.123.123.123]): USER username: no such user found from 123.123.123.123 [123.123.123.123] to 234.234.234.234:21
# failJSON: { "time": "2005-06-09T07:30:58", "match": true , "host": "67.227.224.66" }
Jun 09 07:30:58 platypus.ace-hosting.com.au proftpd[11864] platypus.ace-hosting.com.au (mail.bloodymonster.net[::ffff:67.227.224.66]): USER username (Login failed): Incorrect password.
@ -12,7 +12,9 @@ Jun 13 22:07:23 platypus.ace-hosting.com.au proftpd[15719] platypus.ace-hosting.
Jun 14 00:09:59 platypus.ace-hosting.com.au proftpd[17839] platypus.ace-hosting.com.au (::ffff:59.167.242.100[::ffff:59.167.242.100]): USER platypus.ace-hosting.com.au proftpd[17424] platypus.ace-hosting.com.au (hihoinjection[1.2.3.44]): no such user found from ::ffff:59.167.242.100 [::ffff:59.167.242.100] to ::ffff:113.212.99.194:21
# failJSON: { "time": "2005-05-31T10:53:25", "match": true , "host": "1.2.3.4" }
May 31 10:53:25 mail proftpd[15302]: xxxxxxxxxx (::ffff:1.2.3.4[::ffff:1.2.3.4]) - Maximum login attempts (3) exceeded
# failJSON: { "time": "2004-12-05T15:44:32", "match": true , "host": "1.2.3.4" }
# failJSON: { "time": "2004-10-02T15:45:44", "match": true , "host": "192.0.2.13", "user": "Root", "desc": "dot at end is optional (mod_sftp, gh-2246)" }
Oct 2 15:45:44 ftp01 proftpd[5517]: 192.0.2.13 (192.0.2.13[192.0.2.13]) - SECURITY VIOLATION: Root login attempted
# failJSON: { "time": "2004-12-05T15:44:32", "match": true , "host": "1.2.3.4", "user": "jtittle@domain.org" }
Dec 5 15:44:32 serv1 proftpd[70944]: serv1.domain.com (example.com[1.2.3.4]) - USER jtittle@domain.org: no such user found from example.com [1.2.3.4] to 1.2.3.4:21
# failJSON: { "time": "2013-11-16T21:59:30", "match": true , "host": "1.2.3.4", "desc": "proftpd-basic 1.3.5~rc3-2.1 on Debian uses date format with milliseconds if logging under /var/log/proftpd/proftpd.log" }
2013-11-16 21:59:30,121 novo proftpd[25891] localhost (andy[1.2.3.4]): USER kjsad: no such user found from andy [1.2.3.5] to ::ffff:192.168.1.14:21
2013-11-16 21:59:30,121 novo proftpd[25891] localhost (andy[1.2.3.4]): USER kjsad: no such user found from andy [1.2.3.5] to ::ffff:192.168.1.14:21

View File

@ -296,6 +296,9 @@ Nov 24 23:46:43 host sshd[32686]: fatal: Read from socket failed: Connection res
# failJSON: { "time": "2005-03-15T09:20:57", "match": true , "host": "192.0.2.39", "desc": "Singleline for connection reset by" }
Mar 15 09:20:57 host sshd[28972]: Connection reset by 192.0.2.39 port 14282 [preauth]
# failJSON: { "time": "2005-03-16T09:29:50", "match": true , "host": "192.0.2.20", "desc": "connection reset by user (gh-2662)" }
Mar 16 09:29:50 host sshd[19131]: Connection reset by authenticating user root 192.0.2.20 port 1558 [preauth]
# failJSON: { "time": "2005-07-17T23:03:05", "match": true , "host": "192.0.2.10", "user": "root", "desc": "user name additionally, gh-2185" }
Jul 17 23:03:05 srv sshd[1296]: Connection closed by authenticating user root 192.0.2.10 port 46038 [preauth]
# failJSON: { "time": "2005-07-17T23:04:00", "match": true , "host": "192.0.2.11", "user": "test 127.0.0.1", "desc": "check inject on username, gh-2185" }
@ -327,6 +330,8 @@ Nov 25 01:34:12 srv sshd[123]: Received disconnect from 127.0.0.1: 14: No suppor
Nov 25 01:35:13 srv sshd[123]: error: Received disconnect from 127.0.0.1: 14: No supported authentication methods available [preauth]
# failJSON: { "time": "2004-11-25T01:35:14", "match": true , "host": "192.168.2.92", "desc": "Optional space after port" }
Nov 25 01:35:14 srv sshd[3625]: error: Received disconnect from 192.168.2.92 port 1684:14: No supported authentication methods available [preauth]
# failJSON: { "time": "2004-11-25T01:35:15", "match": true , "host": "192.168.2.93", "desc": "No authentication methods available (supported is optional, gh-2682)" }
Nov 25 01:35:15 srv sshd[3626]: error: Received disconnect from 192.168.2.93 port 1883:14: No authentication methods available [preauth]
# gh-1545:
# failJSON: { "time": "2004-11-26T13:03:29", "match": true , "host": "192.0.2.1", "desc": "No matching cipher" }

View File

@ -1,6 +1,23 @@
# filterOptions: [{"mode": "normal"}]
# failJSON: { "match": false }
10.0.0.2 - - [18/Nov/2018:21:34:30 +0000] "GET /dashboard/ HTTP/2.0" 401 17 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" 72 "Auth for frontend-Host-traefik-0" "/dashboard/" 0ms
# filterOptions: [{"mode": "ddos"}]
# failJSON: { "match": false }
10.0.0.2 - username [18/Nov/2018:21:34:30 +0000] "GET /dashboard/ HTTP/2.0" 401 17 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" 72 "Auth for frontend-Host-traefik-0" "/dashboard/" 0ms
# filterOptions: [{"mode": "normal"}, {"mode": "aggressive"}]
# failJSON: { "time": "2018-11-18T22:34:34", "match": true , "host": "10.0.0.2" }
10.0.0.2 - username [18/Nov/2018:21:34:34 +0000] "GET /dashboard/ HTTP/2.0" 401 17 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" 72 "Auth for frontend-Host-traefik-0" "/dashboard/" 0ms
# failJSON: { "time": "2018-11-18T22:34:34", "match": true , "host": "10.0.0.2", "desc": "other request method" }
10.0.0.2 - username [18/Nov/2018:21:34:34 +0000] "TRACE /dashboard/ HTTP/2.0" 401 17 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" 72 "Auth for frontend-Host-traefik-0" "/dashboard/" 0ms
# failJSON: { "match": false }
10.0.0.2 - username [27/Nov/2018:23:33:31 +0000] "GET /dashboard/ HTTP/2.0" 200 716 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" 118 "Host-traefik-0" "/dashboard/" 4ms
# filterOptions: [{"mode": "ddos"}, {"mode": "aggressive"}]
# failJSON: { "time": "2018-11-18T22:34:30", "match": true , "host": "10.0.0.2" }
10.0.0.2 - - [18/Nov/2018:21:34:30 +0000] "GET /dashboard/ HTTP/2.0" 401 17 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" 72 "Auth for frontend-Host-traefik-0" "/dashboard/" 0ms

View File

@ -1899,7 +1899,9 @@ class DNSUtilsNetworkTests(unittest.TestCase):
ip4 = IPAddr('192.0.2.1')
ip6 = IPAddr('2001:DB8::')
self.assertTrue(ip4.isIPv4)
self.assertTrue(ip4.isSingle)
self.assertTrue(ip6.isIPv6)
self.assertTrue(ip6.isSingle)
self.assertTrue(asip('192.0.2.1').isIPv4)
self.assertTrue(id(asip(ip4)) == id(ip4))
@ -1908,6 +1910,7 @@ class DNSUtilsNetworkTests(unittest.TestCase):
r = IPAddr('xxx', IPAddr.CIDR_RAW)
self.assertFalse(r.isIPv4)
self.assertFalse(r.isIPv6)
self.assertFalse(r.isSingle)
self.assertTrue(r.isValid)
self.assertEqual(r, 'xxx')
self.assertEqual('xxx', str(r))
@ -1916,6 +1919,7 @@ class DNSUtilsNetworkTests(unittest.TestCase):
r = IPAddr('1:2', IPAddr.CIDR_RAW)
self.assertFalse(r.isIPv4)
self.assertFalse(r.isIPv6)
self.assertFalse(r.isSingle)
self.assertTrue(r.isValid)
self.assertEqual(r, '1:2')
self.assertEqual('1:2', str(r))
@ -1938,7 +1942,7 @@ class DNSUtilsNetworkTests(unittest.TestCase):
def testUseDns(self):
res = DNSUtils.textToIp('www.example.com', 'no')
self.assertSortedEqual(res, [])
unittest.F2B.SkipIfNoNetwork()
#unittest.F2B.SkipIfNoNetwork()
res = DNSUtils.textToIp('www.example.com', 'warn')
# sort ipaddr, IPv4 is always smaller as IPv6
self.assertSortedEqual(res, ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
@ -1947,7 +1951,7 @@ class DNSUtilsNetworkTests(unittest.TestCase):
self.assertSortedEqual(res, ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
def testTextToIp(self):
unittest.F2B.SkipIfNoNetwork()
#unittest.F2B.SkipIfNoNetwork()
# Test hostnames
hostnames = [
'www.example.com',
@ -1971,7 +1975,7 @@ class DNSUtilsNetworkTests(unittest.TestCase):
self.assertTrue(isinstance(ip, IPAddr))
def testIpToName(self):
unittest.F2B.SkipIfNoNetwork()
#unittest.F2B.SkipIfNoNetwork()
res = DNSUtils.ipToName('8.8.4.4')
self.assertTrue(res.endswith(('.google', '.google.com')))
# same as above, but with IPAddr:
@ -1993,8 +1997,10 @@ class DNSUtilsNetworkTests(unittest.TestCase):
self.assertEqual(res.addr, 167772160L)
res = IPAddr('10.0.0.1', cidr=32L)
self.assertEqual(res.addr, 167772161L)
self.assertTrue(res.isSingle)
res = IPAddr('10.0.0.1', cidr=31L)
self.assertEqual(res.addr, 167772160L)
self.assertFalse(res.isSingle)
self.assertEqual(IPAddr('10.0.0.0').hexdump, '0a000000')
self.assertEqual(IPAddr('1::2').hexdump, '00010000000000000000000000000002')
@ -2019,6 +2025,8 @@ class DNSUtilsNetworkTests(unittest.TestCase):
def testIPAddr_InInet(self):
ip4net = IPAddr('93.184.0.1/24')
ip6net = IPAddr('2606:2800:220:1:248:1893:25c8:0/120')
self.assertFalse(ip4net.isSingle)
self.assertFalse(ip6net.isSingle)
# ip4:
self.assertTrue(IPAddr('93.184.0.1').isInNet(ip4net))
self.assertTrue(IPAddr('93.184.0.255').isInNet(ip4net))
@ -2114,7 +2122,7 @@ class DNSUtilsNetworkTests(unittest.TestCase):
)
def testIPAddr_CompareDNS(self):
unittest.F2B.SkipIfNoNetwork()
#unittest.F2B.SkipIfNoNetwork()
ips = IPAddr('example.com')
self.assertTrue(IPAddr("93.184.216.34").isInNet(ips))
self.assertTrue(IPAddr("2606:2800:220:1:248:1893:25c8:1946").isInNet(ips))

View File

@ -390,7 +390,15 @@ class TestsUtilsTest(LogCaptureTestCase):
self.assertSortedEqual(['Z', {'A': ['B', 'C'], 'B': ['E', 'F']}], [{'B': ['F', 'E'], 'A': ['C', 'B']}, 'Z'],
level=-1)
self.assertRaises(AssertionError, lambda: self.assertSortedEqual(
['Z', {'A': ['B', 'C'], 'B': ['E', 'F']}], [{'B': ['F', 'E'], 'A': ['C', 'B']}, 'Z']))
['Z', {'A': ['B', 'C'], 'B': ['E', 'F']}], [{'B': ['F', 'E'], 'A': ['C', 'B']}, 'Z'],
nestedOnly=True))
self.assertSortedEqual(
(0, [['A1'], ['A2', 'A1'], []]),
(0, [['A1'], ['A1', 'A2'], []]),
)
self.assertSortedEqual(list('ABC'), list('CBA'))
self.assertRaises(AssertionError, self.assertSortedEqual, ['ABC'], ['CBA'])
self.assertRaises(AssertionError, self.assertSortedEqual, [['ABC']], [['CBA']])
self._testAssertionErrorRE(r"\['A'\] != \['C', 'B'\]",
self.assertSortedEqual, ['A'], ['C', 'B'])
self._testAssertionErrorRE(r"\['A', 'B'\] != \['B', 'C'\]",

View File

@ -36,7 +36,6 @@ from ..server.failmanager import FailManager
from ..server.observer import Observers, ObserverThread
from ..server.utils import Utils
from .utils import LogCaptureTestCase
from ..server.filter import Filter
from .dummyjail import DummyJail
from .databasetestcase import getFail2BanDb, Fail2BanDb
@ -224,7 +223,7 @@ class BanTimeIncrDB(LogCaptureTestCase):
jail.actions.setBanTime(10)
jail.setBanTimeExtra('increment', 'true')
jail.setBanTimeExtra('multipliers', '1 2 4 8 16 32 64 128 256 512 1024 2048')
ip = "127.0.0.2"
ip = "192.0.2.1"
# used as start and fromtime (like now but time independence, cause test case can run slow):
stime = int(MyTime.time())
ticket = FailTicket(ip, stime, [])
@ -385,10 +384,12 @@ class BanTimeIncrDB(LogCaptureTestCase):
# two separate jails :
jail1 = DummyJail(backend='polling')
jail1.filter.ignoreSelf = False
jail1.setBanTimeExtra('increment', 'true')
jail1.database = self.db
self.db.addJail(jail1)
jail2 = DummyJail(name='DummyJail-2', backend='polling')
jail2.filter.ignoreSelf = False
jail2.database = self.db
self.db.addJail(jail2)
ticket1 = FailTicket(ip, stime, [])
@ -477,7 +478,7 @@ class BanTimeIncrDB(LogCaptureTestCase):
self.assertEqual(tickets, [])
# add failure:
ip = "127.0.0.2"
ip = "192.0.2.1"
ticket = FailTicket(ip, stime-120, [])
failManager = FailManager()
failManager.setMaxRetry(3)

View File

@ -1574,10 +1574,10 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# iptables-ipset-proto6 --
('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="10m", default-timeout=0, port="http", protocol="tcp", chain="<known/chain>"]', {
('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, port="http", protocol="tcp", chain="<known/chain>"]', {
'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',),
'ip4-start': (
"`ipset create f2b-j-w-iptables-ipset hash:ip timeout 0`",
"`ipset create f2b-j-w-iptables-ipset hash:ip timeout 0 `",
"`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': (
@ -1597,23 +1597,23 @@ class ServerConfigReaderTests(LogCaptureTestCase):
"`ipset destroy f2b-j-w-iptables-ipset6`",
),
'ip4-ban': (
r"`ipset add f2b-j-w-iptables-ipset 192.0.2.1 timeout 600 -exist`",
r"`ipset add f2b-j-w-iptables-ipset 192.0.2.1 timeout 0 -exist`",
),
'ip4-unban': (
r"`ipset del f2b-j-w-iptables-ipset 192.0.2.1 -exist`",
),
'ip6-ban': (
r"`ipset add f2b-j-w-iptables-ipset6 2001:db8:: timeout 600 -exist`",
r"`ipset add f2b-j-w-iptables-ipset6 2001:db8:: timeout 0 -exist`",
),
'ip6-unban': (
r"`ipset del f2b-j-w-iptables-ipset6 2001:db8:: -exist`",
),
}),
# iptables-ipset-proto6-allports --
('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="10m", default-timeout=0, chain="<known/chain>"]', {
('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, chain="<known/chain>"]', {
'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',),
'ip4-start': (
"`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 0`",
"`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 0 `",
"`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
),
'ip6-start': (
@ -1633,13 +1633,13 @@ class ServerConfigReaderTests(LogCaptureTestCase):
"`ipset destroy f2b-j-w-iptables-ipset-ap6`",
),
'ip4-ban': (
r"`ipset add f2b-j-w-iptables-ipset-ap 192.0.2.1 timeout 600 -exist`",
r"`ipset add f2b-j-w-iptables-ipset-ap 192.0.2.1 timeout 0 -exist`",
),
'ip4-unban': (
r"`ipset del f2b-j-w-iptables-ipset-ap 192.0.2.1 -exist`",
),
'ip6-ban': (
r"`ipset add f2b-j-w-iptables-ipset-ap6 2001:db8:: timeout 600 -exist`",
r"`ipset add f2b-j-w-iptables-ipset-ap6 2001:db8:: timeout 0 -exist`",
),
'ip6-unban': (
r"`ipset del f2b-j-w-iptables-ipset-ap6 2001:db8:: -exist`",
@ -1917,10 +1917,10 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# firewallcmd-ipset (multiport) --
('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", default-timeout=0, port="http", protocol="tcp", chain="<known/chain>"]', {
('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, port="http", protocol="tcp", chain="<known/chain>"]', {
'ip4': (' f2b-j-w-fwcmd-ipset ',), 'ip6': (' f2b-j-w-fwcmd-ipset6 ',),
'ip4-start': (
"`ipset create f2b-j-w-fwcmd-ipset hash:ip timeout 0`",
"`ipset create f2b-j-w-fwcmd-ipset hash:ip timeout 0 `",
"`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 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': (
@ -1940,27 +1940,27 @@ class ServerConfigReaderTests(LogCaptureTestCase):
"`ipset destroy f2b-j-w-fwcmd-ipset6`",
),
'ip4-ban': (
r"`ipset add f2b-j-w-fwcmd-ipset 192.0.2.1 timeout 600 -exist`",
r"`ipset add f2b-j-w-fwcmd-ipset 192.0.2.1 timeout 0 -exist`",
),
'ip4-unban': (
r"`ipset del f2b-j-w-fwcmd-ipset 192.0.2.1 -exist`",
),
'ip6-ban': (
r"`ipset add f2b-j-w-fwcmd-ipset6 2001:db8:: timeout 600 -exist`",
r"`ipset add f2b-j-w-fwcmd-ipset6 2001:db8:: timeout 0 -exist`",
),
'ip6-unban': (
r"`ipset del f2b-j-w-fwcmd-ipset6 2001:db8:: -exist`",
),
}),
# firewallcmd-ipset (allports) --
('j-w-fwcmd-ipset-ap', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", actiontype=<allports>, protocol="tcp", chain="<known/chain>"]', {
('j-w-fwcmd-ipset-ap', 'firewallcmd-ipset[name=%(__name__)s, actiontype=<allports>, protocol="tcp", chain="<known/chain>"]', {
'ip4': (' f2b-j-w-fwcmd-ipset-ap ',), 'ip6': (' f2b-j-w-fwcmd-ipset-ap6 ',),
'ip4-start': (
"`ipset create f2b-j-w-fwcmd-ipset-ap hash:ip timeout 600`",
"`ipset create f2b-j-w-fwcmd-ipset-ap hash:ip timeout 0 `",
"`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
),
'ip6-start': (
"`ipset create f2b-j-w-fwcmd-ipset-ap6 hash:ip timeout 600 family inet6`",
"`ipset create f2b-j-w-fwcmd-ipset-ap6 hash:ip timeout 0 family inet6`",
"`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m set --match-set f2b-j-w-fwcmd-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`",
),
'flush': (
@ -1976,13 +1976,13 @@ class ServerConfigReaderTests(LogCaptureTestCase):
"`ipset destroy f2b-j-w-fwcmd-ipset-ap6`",
),
'ip4-ban': (
r"`ipset add f2b-j-w-fwcmd-ipset-ap 192.0.2.1 timeout 600 -exist`",
r"`ipset add f2b-j-w-fwcmd-ipset-ap 192.0.2.1 timeout 0 -exist`",
),
'ip4-unban': (
r"`ipset del f2b-j-w-fwcmd-ipset-ap 192.0.2.1 -exist`",
),
'ip6-ban': (
r"`ipset add f2b-j-w-fwcmd-ipset-ap6 2001:db8:: timeout 600 -exist`",
r"`ipset add f2b-j-w-fwcmd-ipset-ap6 2001:db8:: timeout 0 -exist`",
),
'ip6-unban': (
r"`ipset del f2b-j-w-fwcmd-ipset-ap6 2001:db8:: -exist`",

View File

@ -39,7 +39,7 @@ from cStringIO import StringIO
from functools import wraps
from ..helpers import getLogger, str2LogLevel, getVerbosityFormat, uni_decode
from ..server.ipdns import DNSUtils
from ..server.ipdns import IPAddr, DNSUtils
from ..server.mytime import MyTime
from ..server.utils import Utils
# for action_d.test_smtp :
@ -331,13 +331,21 @@ def initTests(opts):
c.set('2001:db8::ffff', 'test-other')
c.set('87.142.124.10', 'test-host')
if unittest.F2B.no_network: # pragma: no cover
# precache all wrong dns to ip's used in test cases:
# precache all ip to dns used in test cases:
c.set('192.0.2.888', None)
c.set('8.8.4.4', 'dns.google')
c.set('8.8.4.4', 'dns.google')
# precache all dns to ip's used in test cases:
c = DNSUtils.CACHE_nameToIp
for i in (
('999.999.999.999', set()),
('abcdef.abcdef', set()),
('192.168.0.', set()),
('failed.dns.ch', set()),
('doh1.2.3.4.buga.xxxxx.yyy.invalid', set()),
('1.2.3.4.buga.xxxxx.yyy.invalid', set()),
('example.com', set([IPAddr('2606:2800:220:1:248:1893:25c8:1946'), IPAddr('93.184.216.34')])),
('www.example.com', set([IPAddr('2606:2800:220:1:248:1893:25c8:1946'), IPAddr('93.184.216.34')])),
):
c.set(*i)
# if fast - precache all host names as localhost addresses (speed-up getSelfIPs/ignoreself):
@ -552,7 +560,7 @@ if not hasattr(unittest.TestCase, 'assertDictEqual'):
self.fail(msg)
unittest.TestCase.assertDictEqual = assertDictEqual
def assertSortedEqual(self, a, b, level=1, nestedOnly=True, key=repr, msg=None):
def assertSortedEqual(self, a, b, level=1, nestedOnly=False, key=repr, msg=None):
"""Compare complex elements (like dict, list or tuple) in sorted order until
level 0 not reached (initial level = -1 meant all levels),
or if nestedOnly set to True and some of the objects still contains nested lists or dicts.
@ -562,6 +570,13 @@ def assertSortedEqual(self, a, b, level=1, nestedOnly=True, key=repr, msg=None):
if isinstance(v, dict):
return any(isinstance(v, (dict, list, tuple)) for v in v.itervalues())
return any(isinstance(v, (dict, list, tuple)) for v in v)
if nestedOnly:
_nest_sorted = sorted
else:
def _nest_sorted(v, key=key):
if isinstance(v, (set, list, tuple)):
return sorted(list(_nest_sorted(v, key) for v in v), key=key)
return v
# level comparison routine:
def _assertSortedEqual(a, b, level, nestedOnly, key):
# first the lengths:
@ -580,8 +595,8 @@ def assertSortedEqual(self, a, b, level=1, nestedOnly=True, key=repr, msg=None):
elif v1 != v2:
raise ValueError('%r != %r' % (a, b))
else: # list, tuple, something iterable:
a = sorted(a, key=key)
b = sorted(b, key=key)
a = _nest_sorted(a, key=key)
b = _nest_sorted(b, key=key)
for v1, v2 in zip(a, b):
if isinstance(v1, (dict, list, tuple)) and isinstance(v2, (dict, list, tuple)):
_assertSortedEqual(v1, v2, level-1 if level != 0 else 0, nestedOnly, key)

View File

@ -6,6 +6,7 @@ PartOf=iptables.service firewalld.service ip6tables.service ipset.service nftabl
[Service]
Type=simple
Environment="PYTHONNOUSERSITE=1"
ExecStartPre=/bin/mkdir -p /run/fail2ban
ExecStart=@BINDIR@/fail2ban-server -xf start
# if should be logged in systemd journal, use following line or set logtarget to sysout in fail2ban.local

View File

@ -111,6 +111,14 @@ jails and database)
unbans <IP> (in all jails and
database)
.TP
\fBbanned\fR
return jails with banned IPs as
dictionary
.TP
\fBbanned <IP> ... <IP>]\fR
return list(s) of jails where
given IP(s) are banned
.TP
\fBstatus\fR
gets the current status of the
server
@ -356,6 +364,14 @@ for <JAIL>
.IP
JAIL INFORMATION
.TP
\fBget <JAIL> banned\fR
return banned IPs of <JAIL>
.TP
\fBget <JAIL> banned <IP> ... <IP>]\fR
return 1 if IP is banned in <JAIL>
otherwise 0, or a list of 1/0 for
multiple IPs
.TP
\fBget <JAIL> logpath\fR
gets the list of the monitored
files for <JAIL>