From f3763d73ad8cf93ea32d7e12713662f7963d950f Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Sat, 3 Jun 2023 13:43:34 +0200 Subject: [mod] limiter: blocklist and passlist (ip_lists) A blocklist and a passlist can be configured in /etc/searxng/limiter.toml:: [botdetection.ip_lists] pass_ip = [ '51.15.252.168', # IPv4 of check.searx.space ] block_ip = [ '93.184.216.34', # IPv4 of example.org ] Closes: https://github.com/searxng/searxng/issues/2127 Closes: https://github.com/searxng/searxng/pull/2129 Signed-off-by: Markus Heiser --- searx/botdetection/limiter.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) (limited to 'searx/botdetection/limiter.py') diff --git a/searx/botdetection/limiter.py b/searx/botdetection/limiter.py index 18ffc8407..366665854 100644 --- a/searx/botdetection/limiter.py +++ b/searx/botdetection/limiter.py @@ -40,6 +40,7 @@ and set the redis-url connection. Check the value, it depends on your redis DB from __future__ import annotations from pathlib import Path +from ipaddress import ip_address import flask import werkzeug @@ -53,6 +54,7 @@ from . import ( http_connection, http_user_agent, ip_limit, + ip_lists, ) from ._helpers import ( @@ -84,16 +86,41 @@ def get_cfg() -> config.Config: def filter_request(request: flask.Request) -> werkzeug.Response | None: + # pylint: disable=too-many-return-statements cfg = get_cfg() - real_ip = get_real_ip(request) + real_ip = ip_address(get_real_ip(request)) network = get_network(real_ip, cfg) + + if request.path == '/healthz': + return None + + # link-local + if network.is_link_local: return None - if request.path == '/healthz': + # block- & pass- lists + # + # 1. The IP of the request is first checked against the pass-list; if the IP + # matches an entry in the list, the request is not blocked. + # 2. If no matching entry is found in the pass-list, then a check is made against + # the block list; if the IP matches an entry in the list, the request is + # blocked. + # 3. If the IP is not in either list, the request is not blocked. + + match, msg = ip_lists.pass_ip(real_ip, cfg) + if match: + logger.warning("PASS %s: matched PASSLIST - %s", network.compressed, msg) return None + match, msg = ip_lists.block_ip(real_ip, cfg) + if match: + logger.error("BLOCK %s: matched BLOCKLIST - %s", network.compressed, msg) + return flask.make_response(('IP is on BLOCKLIST - %s' % msg, 429)) + + # methods applied on / + for func in [ http_user_agent, ]: @@ -101,6 +128,8 @@ def filter_request(request: flask.Request) -> werkzeug.Response | None: if val is not None: return val + # methods applied on /search + if request.path == '/search': for func in [ -- cgit v1.2.3