Browse Source

filter_inNetworks: check if IP is in one of the given networks

master
Y 3 years ago
parent
commit
c5135e847a
5 changed files with 154 additions and 17 deletions
  1. +16
    -1
      doc/builtinfilters.md
  2. +47
    -15
      extra/examples/full_pyruse.json
  3. +47
    -0
      pyruse/filters/filter_inNetworks.py
  4. +42
    -0
      tests/filter_inNetworks.py
  5. +2
    -1
      tests/main.py

+ 16
- 1
doc/builtinfilters.md View File

@ -4,7 +4,7 @@ Pyruse comes with a few very simple filters.
## `=`, `≤`, `≥`, `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.
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.
## 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)
Filter `filter_pcre` should only be used on character strings.


+ 47
- 15
extra/examples/full_pyruse.json View File

@ -86,14 +86,22 @@
{
"action": "action_dailyReport",
"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": [
{
"action": "action_dailyReport",
"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": [
@ -251,7 +259,7 @@
{
"action": "action_dailyReport",
"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": [
@ -261,10 +269,18 @@
"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>[^,]+),"
] },
"then": "… Detect repeated HTTPS failures",
"then": "… Do not ban local HTTP users",
"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": [
{
"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: Too many invalid commands.*, rip=(?P<thatIP>[^,]+),"
] },
"then": "… Detect repeated mail failures",
"then": "… Do not ban local mail users",
"else": "… Detect failed IMAP logins"
}
],
@ -388,14 +404,22 @@
{
"action": "action_dailyReport",
"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": [
{
"action": "action_dailyReport",
"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": [
@ -514,7 +538,7 @@
"^.{19} rejected [EH]{2}LO from (?:\\([^)]*\\) )?\\[(?P<thatIP>[^]]+)\\]: syntactically invalid",
"\\[(?P<thatIP>[^ ]+)\\] dropped: too many nonmail commands"
] },
"then": "… Detect repeated mail failures",
"then": "… Do not ban local mail users",
"else": "… NOOP if PRIORITY 5+"
}
],
@ -715,9 +739,9 @@
{
"filter": "filter_pcreAny",
"args": { "field": "MESSAGE", "re": [
"^Failed password for (?P<thatUser>.*) from (?P<thatIP>(?!192\\.168\\.1\\.201 )[^ ]*) port",
"^Invalid user (?P<thatUser>.*) from (?P<thatIP>(?!192\\.168\\.1\\.201 )[^ ]*) port",
"^User (?P<thatUser>.*) from (?P<thatIP>(?!192\\.168\\.1\\.201 )[^ ]*) not allowed because not listed in AllowUsers$"
"^Failed password for (?P<thatUser>.*) from (?P<thatIP>[^ ]*) port",
"^Invalid user (?P<thatUser>.*) from (?P<thatIP>[^ ]*) port",
"^User (?P<thatUser>.*) from (?P<thatIP>[^ ]*) not allowed because not listed in AllowUsers$"
] },
"else": "… Forbid antiquated clients"
},
@ -733,24 +757,32 @@
{
"action": "action_dailyReport",
"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": [
{
"action": "action_dailyReport",
"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": [
{
"filter": "filter_pcre",
"args": { "field": "MESSAGE", "re": "^Unable to negotiate with ((?!192\\.168\\.1\\.201 )[^ ]*) port", "save": [ "thatIP" ] },
"then": "… Detect repeated SSH login failures",
"args": { "field": "MESSAGE", "re": "^Unable to negotiate with ([^ ]*) port", "save": [ "thatIP" ] },
"then": "… Do not ban local SSH users",
"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": [
{
"action": "action_counterRaise",


+ 47
- 0
pyruse/filters/filter_inNetworks.py View File

@ -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))

+ 42
- 0
tests/filter_inNetworks.py View File

@ -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()

+ 2
- 1
tests/main.py View File

@ -19,12 +19,13 @@ def main():
conf = config.Config(os.curdir)
# 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
filter_equals.unitTests()
filter_greaterOrEquals.unitTests()
filter_in.unitTests()
filter_inNetworks.unitTests()
filter_lowerOrEquals.unitTests()
filter_pcre.unitTests()
filter_pcreAny.unitTests()


Loading…
Cancel
Save