filter_inNetworks: check if IP is in one of the given networks
parent
fb6a69c40e
commit
c5135e847a
|
@ -4,7 +4,7 @@ Pyruse comes with a few very simple filters.
|
||||||
|
|
||||||
## `=`, `≤`, `≥`, `in`
|
## `=`, `≤`, `≥`, `in`
|
||||||
|
|
||||||
The filters `filter_equals`, `filter_lowerOrEquals`, and `filter_greaterOrEquals` simply check equality on inequality between a given field, given by the parameter `field`, and a constant value, given py the parameter `value`.
|
The filters `filter_equals`, `filter_lowerOrEquals`, and `filter_greaterOrEquals` simply check equality or inequality between a given field, given by the parameter `field`, and a constant value, given py the parameter `value`.
|
||||||
Both parameters are mandatory.
|
Both parameters are mandatory.
|
||||||
Here are two examples:
|
Here are two examples:
|
||||||
|
|
||||||
|
@ -32,6 +32,21 @@ Here is an example:
|
||||||
|
|
||||||
For any of these filters, the constant values must be of the same type as the typical contents of the chosen field.
|
For any of these filters, the constant values must be of the same type as the typical contents of the chosen field.
|
||||||
|
|
||||||
|
## Test if an IP address is part of given networks
|
||||||
|
|
||||||
|
Filter `filter_inNetworks` reads an IP address in a field given by the `field` parameter, and a list of networks in the `nets` parameter; each net is written as an IP address, then “`/`”, then an integer network mask.
|
||||||
|
|
||||||
|
The filter is passing if the IP address that was read is part of one of the networks configured for the filter.
|
||||||
|
|
||||||
|
Here is an example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"filter": "filter_inNetworks",
|
||||||
|
"args": { "field": "IP", "nets": [ "fd00::/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ] }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Perl-compatible regular expressions (pcre)
|
## Perl-compatible regular expressions (pcre)
|
||||||
|
|
||||||
Filter `filter_pcre` should only be used on character strings.
|
Filter `filter_pcre` should only be used on character strings.
|
||||||
|
|
|
@ -86,14 +86,22 @@
|
||||||
{
|
{
|
||||||
"action": "action_dailyReport",
|
"action": "action_dailyReport",
|
||||||
"args": { "level": "WARN", "message": "Failed login as {thatUser}@{_HOSTNAME} on Nextcloud" },
|
"args": { "level": "WARN", "message": "Failed login as {thatUser}@{_HOSTNAME} on Nextcloud" },
|
||||||
"then": "… Detect repeated Nextcloud login failures"
|
"then": "… Do not ban local Nextcloud users"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"… Report inexisting Nextcloud user": [
|
"… Report inexisting Nextcloud user": [
|
||||||
{
|
{
|
||||||
"action": "action_dailyReport",
|
"action": "action_dailyReport",
|
||||||
"args": { "level": "INFO", "message": "Failed login as {thatUser}@{_HOSTNAME} on Nextcloud" },
|
"args": { "level": "INFO", "message": "Failed login as {thatUser}@{_HOSTNAME} on Nextcloud" },
|
||||||
"then": "… Detect repeated Nextcloud login failures"
|
"then": "… Do not ban local Nextcloud users"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"… Do not ban local Nextcloud users": [
|
||||||
|
{
|
||||||
|
"action": "filter_inNetworks",
|
||||||
|
"args": { "field": "thatIP", "nets": [ "192.168.1.0/24", "fd00::/8" ] },
|
||||||
|
"then": "… NOOP",
|
||||||
|
"else": "… Detect repeated Nextcloud login failures"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"… Detect repeated Nextcloud login failures": [
|
"… Detect repeated Nextcloud login failures": [
|
||||||
|
@ -251,7 +259,7 @@
|
||||||
{
|
{
|
||||||
"action": "action_dailyReport",
|
"action": "action_dailyReport",
|
||||||
"args": { "level": "INFO", "message": "Failed login on {_HOSTNAME} by HTTPS" },
|
"args": { "level": "INFO", "message": "Failed login on {_HOSTNAME} by HTTPS" },
|
||||||
"then": "… Detect repeated HTTPS failures"
|
"then": "… Do not ban local HTTP users"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"… Detect abnormal HTTP 404 errors": [
|
"… Detect abnormal HTTP 404 errors": [
|
||||||
|
@ -261,10 +269,18 @@
|
||||||
"open\\(\\) \"[^\"]*\\.(?:cgi|php|pl|py|sh)\" failed \\(2: No such file or directory\\), client: (?P<thatIP>[^,]+),",
|
"open\\(\\) \"[^\"]*\\.(?:cgi|php|pl|py|sh)\" failed \\(2: No such file or directory\\), client: (?P<thatIP>[^,]+),",
|
||||||
"Unable to open primary script: .*\\.(?:cgi|php|pl|py|sh) \\(No such file or directory[^,]+, client: (?P<thatIP>[^,]+),"
|
"Unable to open primary script: .*\\.(?:cgi|php|pl|py|sh) \\(No such file or directory[^,]+, client: (?P<thatIP>[^,]+),"
|
||||||
] },
|
] },
|
||||||
"then": "… Detect repeated HTTPS failures",
|
"then": "… Do not ban local HTTP users",
|
||||||
"else": "… Immediate warning for connectivity errors"
|
"else": "… Immediate warning for connectivity errors"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"… Do not ban local HTTP users": [
|
||||||
|
{
|
||||||
|
"action": "filter_inNetworks",
|
||||||
|
"args": { "field": "thatIP", "nets": [ "192.168.1.0/24", "fd00::/8" ] },
|
||||||
|
"then": "… NOOP",
|
||||||
|
"else": "… Detect repeated HTTPS failures"
|
||||||
|
}
|
||||||
|
],
|
||||||
"… Detect repeated HTTPS failures": [
|
"… Detect repeated HTTPS failures": [
|
||||||
{
|
{
|
||||||
"action": "action_counterRaise",
|
"action": "action_counterRaise",
|
||||||
|
@ -366,7 +382,7 @@
|
||||||
"^imap-login: Disconnected \\(no auth attempts in [0-9]{2,} secs\\): user=<>, rip=(?P<thatIP>[^,]+),",
|
"^imap-login: Disconnected \\(no auth attempts in [0-9]{2,} secs\\): user=<>, rip=(?P<thatIP>[^,]+),",
|
||||||
"^imap-login: Disconnected: Too many invalid commands.*, rip=(?P<thatIP>[^,]+),"
|
"^imap-login: Disconnected: Too many invalid commands.*, rip=(?P<thatIP>[^,]+),"
|
||||||
] },
|
] },
|
||||||
"then": "… Detect repeated mail failures",
|
"then": "… Do not ban local mail users",
|
||||||
"else": "… Detect failed IMAP logins"
|
"else": "… Detect failed IMAP logins"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -388,14 +404,22 @@
|
||||||
{
|
{
|
||||||
"action": "action_dailyReport",
|
"action": "action_dailyReport",
|
||||||
"args": { "level": "WARN", "message": "Failed login as {thatUser}@{_HOSTNAME} by IMAP" },
|
"args": { "level": "WARN", "message": "Failed login as {thatUser}@{_HOSTNAME} by IMAP" },
|
||||||
"then": "… Detect repeated mail failures"
|
"then": "… Do not ban local mail users"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"… Report inexisting IMAP user": [
|
"… Report inexisting IMAP user": [
|
||||||
{
|
{
|
||||||
"action": "action_dailyReport",
|
"action": "action_dailyReport",
|
||||||
"args": { "level": "INFO", "message": "Failed login as {thatUser}@{_HOSTNAME} by IMAP" },
|
"args": { "level": "INFO", "message": "Failed login as {thatUser}@{_HOSTNAME} by IMAP" },
|
||||||
"then": "… Detect repeated mail failures"
|
"then": "… Do not ban local mail users"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"… Do not ban local mail users": [
|
||||||
|
{
|
||||||
|
"action": "filter_inNetworks",
|
||||||
|
"args": { "field": "thatIP", "nets": [ "192.168.1.0/24", "fd00::/8" ] },
|
||||||
|
"then": "… NOOP",
|
||||||
|
"else": "… Detect repeated mail failures"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"… Detect repeated mail failures": [
|
"… Detect repeated mail failures": [
|
||||||
|
@ -514,7 +538,7 @@
|
||||||
"^.{19} rejected [EH]{2}LO from (?:\\([^)]*\\) )?\\[(?P<thatIP>[^]]+)\\]: syntactically invalid",
|
"^.{19} rejected [EH]{2}LO from (?:\\([^)]*\\) )?\\[(?P<thatIP>[^]]+)\\]: syntactically invalid",
|
||||||
"\\[(?P<thatIP>[^ ]+)\\] dropped: too many nonmail commands"
|
"\\[(?P<thatIP>[^ ]+)\\] dropped: too many nonmail commands"
|
||||||
] },
|
] },
|
||||||
"then": "… Detect repeated mail failures",
|
"then": "… Do not ban local mail users",
|
||||||
"else": "… NOOP if PRIORITY 5+"
|
"else": "… NOOP if PRIORITY 5+"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -715,9 +739,9 @@
|
||||||
{
|
{
|
||||||
"filter": "filter_pcreAny",
|
"filter": "filter_pcreAny",
|
||||||
"args": { "field": "MESSAGE", "re": [
|
"args": { "field": "MESSAGE", "re": [
|
||||||
"^Failed password for (?P<thatUser>.*) from (?P<thatIP>(?!192\\.168\\.1\\.201 )[^ ]*) port",
|
"^Failed password for (?P<thatUser>.*) from (?P<thatIP>[^ ]*) port",
|
||||||
"^Invalid user (?P<thatUser>.*) from (?P<thatIP>(?!192\\.168\\.1\\.201 )[^ ]*) port",
|
"^Invalid user (?P<thatUser>.*) from (?P<thatIP>[^ ]*) port",
|
||||||
"^User (?P<thatUser>.*) from (?P<thatIP>(?!192\\.168\\.1\\.201 )[^ ]*) not allowed because not listed in AllowUsers$"
|
"^User (?P<thatUser>.*) from (?P<thatIP>[^ ]*) not allowed because not listed in AllowUsers$"
|
||||||
] },
|
] },
|
||||||
"else": "… Forbid antiquated clients"
|
"else": "… Forbid antiquated clients"
|
||||||
},
|
},
|
||||||
|
@ -733,24 +757,32 @@
|
||||||
{
|
{
|
||||||
"action": "action_dailyReport",
|
"action": "action_dailyReport",
|
||||||
"args": { "level": "WARN", "message": "Failed login as {thatUser}@{_HOSTNAME} by SSH" },
|
"args": { "level": "WARN", "message": "Failed login as {thatUser}@{_HOSTNAME} by SSH" },
|
||||||
"then": "… Detect repeated SSH login failures"
|
"then": "… Do not ban local SSH users"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"… Report inexisting SSH user": [
|
"… Report inexisting SSH user": [
|
||||||
{
|
{
|
||||||
"action": "action_dailyReport",
|
"action": "action_dailyReport",
|
||||||
"args": { "level": "INFO", "message": "Failed login as {thatUser}@{_HOSTNAME} by SSH" },
|
"args": { "level": "INFO", "message": "Failed login as {thatUser}@{_HOSTNAME} by SSH" },
|
||||||
"then": "… Detect repeated SSH login failures"
|
"then": "… Do not ban local SSH users"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"… Forbid antiquated clients": [
|
"… Forbid antiquated clients": [
|
||||||
{
|
{
|
||||||
"filter": "filter_pcre",
|
"filter": "filter_pcre",
|
||||||
"args": { "field": "MESSAGE", "re": "^Unable to negotiate with ((?!192\\.168\\.1\\.201 )[^ ]*) port", "save": [ "thatIP" ] },
|
"args": { "field": "MESSAGE", "re": "^Unable to negotiate with ([^ ]*) port", "save": [ "thatIP" ] },
|
||||||
"then": "… Detect repeated SSH login failures",
|
"then": "… Do not ban local SSH users",
|
||||||
"else": "… NOOP if PRIORITY 6+"
|
"else": "… NOOP if PRIORITY 6+"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"… Do not ban local SSH users": [
|
||||||
|
{
|
||||||
|
"action": "filter_inNetworks",
|
||||||
|
"args": { "field": "thatIP", "nets": [ "192.168.1.0/24", "fd00::/8" ] },
|
||||||
|
"then": "… NOOP",
|
||||||
|
"else": "… Detect repeated SSH login failures"
|
||||||
|
}
|
||||||
|
],
|
||||||
"… Detect repeated SSH login failures": [
|
"… Detect repeated SSH login failures": [
|
||||||
{
|
{
|
||||||
"action": "action_counterRaise",
|
"action": "action_counterRaise",
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
# pyruse is intended as a replacement to both fail2ban and epylog
|
||||||
|
# Copyright © 2017–2018 Y. Gablin
|
||||||
|
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
||||||
|
import socket
|
||||||
|
from functools import reduce
|
||||||
|
from pyruse import base
|
||||||
|
|
||||||
|
class Filter(base.Filter):
|
||||||
|
ipReducer = lambda bits, byte: bits<<8 | byte
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
super().__init__()
|
||||||
|
self.field = args["field"]
|
||||||
|
ip4Nets = []
|
||||||
|
ip6Nets = []
|
||||||
|
for net in args["nets"]:
|
||||||
|
if ":" in net:
|
||||||
|
ip6Nets.append(self._toNetAndMask(socket.AF_INET6, 128, net))
|
||||||
|
else:
|
||||||
|
ip4Nets.append(self._toNetAndMask(socket.AF_INET, 32, net))
|
||||||
|
self.ip4Nets = ip4Nets
|
||||||
|
self.ip6Nets = ip6Nets
|
||||||
|
|
||||||
|
def filter(self, entry):
|
||||||
|
if self.field not in entry:
|
||||||
|
return False
|
||||||
|
ip = entry[self.field]
|
||||||
|
if ":" in ip:
|
||||||
|
return self._filter(socket.AF_INET6, ip, self.ip6Nets)
|
||||||
|
else:
|
||||||
|
return self._filter(socket.AF_INET, ip, self.ip4Nets)
|
||||||
|
|
||||||
|
def _filter(self, family, ip, nets):
|
||||||
|
for (net, mask) in nets:
|
||||||
|
numericIP = self._numericIP(family, ip)
|
||||||
|
if numericIP & mask == net:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _toNetAndMask(self, family, bits, net):
|
||||||
|
ip, mask = net.split("/")
|
||||||
|
numericMask = ((1<<int(mask))-1)<<(bits-int(mask))
|
||||||
|
numericIP = self._numericIP(family, ip)
|
||||||
|
return numericIP & numericMask, numericMask
|
||||||
|
|
||||||
|
def _numericIP(self, family, ipString):
|
||||||
|
return reduce(Filter.ipReducer, socket.inet_pton(family, ipString))
|
|
@ -0,0 +1,42 @@
|
||||||
|
# pyruse is intended as a replacement to both fail2ban and epylog
|
||||||
|
# Copyright © 2017–2018 Y. Gablin
|
||||||
|
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
||||||
|
from pyruse.filters.filter_inNetworks import Filter
|
||||||
|
|
||||||
|
def whenIp4InNet4ThenTrue():
|
||||||
|
assert Filter({"field": "ip", "nets": ["34.56.78.90/12"]}).filter({"ip": "34.48.0.1"})
|
||||||
|
|
||||||
|
def whenIp4NotInNet4ThenFalse():
|
||||||
|
assert not Filter({"field": "ip", "nets": ["34.56.78.90/12"]}).filter({"ip": "34.47.255.254"})
|
||||||
|
|
||||||
|
def whenIp6InNet6ThenTrue():
|
||||||
|
assert Filter({"field": "ip", "nets": ["2001:db8:1:1a0::/59"]}).filter({"ip": "2001:db8:1:1a0::1"})
|
||||||
|
|
||||||
|
def whenIp6NotInNet6ThenFalse():
|
||||||
|
assert not Filter({"field": "ip", "nets": ["2001:db8:1:1a0::/59"]}).filter({"ip": "2001:db8:1:19f:ffff:ffff:ffff:fffe"})
|
||||||
|
|
||||||
|
def whenNumericIp6InNet4ThenFalse():
|
||||||
|
assert not Filter({"field": "ip", "nets": ["34.56.78.90/12"]}).filter({"ip": "::2230:1"})
|
||||||
|
|
||||||
|
def whenNumericIp4InNet6ThenFalse():
|
||||||
|
assert not Filter({"field": "ip", "nets": ["::2230:1/108"]}).filter({"ip": "34.48.0.1"})
|
||||||
|
|
||||||
|
def whenIpInOneNetworkThenTrue():
|
||||||
|
assert Filter({"field": "ip", "nets": ["::2230:1/108", "10.0.0.0/8", "34.56.78.90/12", "2001:db8:1:1a0::/59"]}).filter({"ip": "34.48.0.1"})
|
||||||
|
|
||||||
|
def whenNoIpThenFalse():
|
||||||
|
assert not Filter({"field": "ip", "nets": ["::2230:1/108", "10.0.0.0/8"]}).filter({"no_ip": "Hi!"})
|
||||||
|
|
||||||
|
def whenNoNetworkThenFalse():
|
||||||
|
assert not Filter({"field": "ip", "nets": []}).filter({"ip": "34.48.0.1"})
|
||||||
|
|
||||||
|
def unitTests():
|
||||||
|
whenIp4InNet4ThenTrue()
|
||||||
|
whenIp4NotInNet4ThenFalse()
|
||||||
|
whenIp6InNet6ThenTrue()
|
||||||
|
whenIp6NotInNet6ThenFalse()
|
||||||
|
whenNumericIp6InNet4ThenFalse()
|
||||||
|
whenNumericIp4InNet6ThenFalse()
|
||||||
|
whenIpInOneNetworkThenTrue()
|
||||||
|
whenNoIpThenFalse()
|
||||||
|
whenNoNetworkThenFalse()
|
|
@ -19,12 +19,13 @@ def main():
|
||||||
conf = config.Config(os.curdir)
|
conf = config.Config(os.curdir)
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
import filter_equals, filter_greaterOrEquals, filter_in, filter_lowerOrEquals, filter_pcre, filter_pcreAny, filter_userExists
|
import filter_equals, filter_greaterOrEquals, filter_in, filter_inNetworks, filter_lowerOrEquals, filter_pcre, filter_pcreAny, filter_userExists
|
||||||
import action_counterRaise, action_counterReset, action_dailyReport, action_email, action_nftBan
|
import action_counterRaise, action_counterReset, action_dailyReport, action_email, action_nftBan
|
||||||
|
|
||||||
filter_equals.unitTests()
|
filter_equals.unitTests()
|
||||||
filter_greaterOrEquals.unitTests()
|
filter_greaterOrEquals.unitTests()
|
||||||
filter_in.unitTests()
|
filter_in.unitTests()
|
||||||
|
filter_inNetworks.unitTests()
|
||||||
filter_lowerOrEquals.unitTests()
|
filter_lowerOrEquals.unitTests()
|
||||||
filter_pcre.unitTests()
|
filter_pcre.unitTests()
|
||||||
filter_pcreAny.unitTests()
|
filter_pcreAny.unitTests()
|
||||||
|
|
Loading…
Reference in New Issue