summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Heiser <markus.heiser@darmarit.de>2023-10-02 16:36:07 +0200
committerMarkus Heiser <markus.heiser@darmarIT.de>2023-11-01 06:44:56 +0100
commitfd814aac863673047c46a9d80682415dae180969 (patch)
tree52ea45e433ebfb8cb4e82f69a01413cc4b0c13d3
parentb05a15540e1dc2dfb8e4e25aa537b2a68e713844 (diff)
[mod] isolation of botdetection from the limiter
This patch was inspired by the discussion around PR-2882 [2]. The goals of this patch are: 1. Convert plugin searx.plugin.limiter to normal code [1] 2. isolation of botdetection from the limiter [2] 3. searx/{tools => botdetection}/config.py and drop searx.tools 4. in URL /config, 'limiter.enabled' is true only if the limiter is really enabled (Redis is available). This patch moves all the code that belongs to botdetection into namespace searx.botdetection and code that belongs to limiter is placed in namespace searx.limiter. Tthe limiter used to be a plugin at some point botdetection was added, it was not a plugin. The modularization of these two components was long overdue. With the clear modularization, the documentation could then also be organized according to the architecture. [1] https://github.com/searxng/searxng/pull/2882 [2] https://github.com/searxng/searxng/pull/2882#issuecomment-1741716891 To test: - check the app works without the limiter, check `/config` - check the app works with the limiter and with the token, check `/config` - make docs.live .. and read - http://0.0.0.0:8000/admin/searx.limiter.html - http://0.0.0.0:8000/src/searx.botdetection.html#botdetection Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
-rw-r--r--docs/admin/index.rst2
-rw-r--r--docs/admin/searx.limiter.rst17
-rw-r--r--docs/admin/settings/settings_server.rst4
-rw-r--r--docs/conf.py5
-rw-r--r--docs/src/searx.botdetection.rst (renamed from docs/admin/searx.botdetection.rst)14
-rw-r--r--searx/botdetection/__init__.py47
-rw-r--r--searx/botdetection/_helpers.py6
-rw-r--r--searx/botdetection/config.py (renamed from searx/tools/config.py)0
-rw-r--r--searx/botdetection/http_accept.py2
-rw-r--r--searx/botdetection/http_accept_encoding.py2
-rw-r--r--searx/botdetection/http_accept_language.py2
-rw-r--r--searx/botdetection/http_connection.py2
-rw-r--r--searx/botdetection/http_user_agent.py2
-rw-r--r--searx/botdetection/ip_limit.py5
-rw-r--r--searx/botdetection/ip_lists.py2
-rw-r--r--searx/botdetection/link_token.py4
-rw-r--r--searx/limiter.py (renamed from searx/botdetection/limiter.py)126
-rw-r--r--searx/limiter.toml (renamed from searx/botdetection/limiter.toml)0
-rw-r--r--searx/plugins/limiter.py38
-rw-r--r--searx/tools/__init__.py8
-rwxr-xr-xsearx/webapp.py8
-rw-r--r--tests/unit/test_plugins.py9
22 files changed, 180 insertions, 125 deletions
diff --git a/docs/admin/index.rst b/docs/admin/index.rst
index f335f163..606b51c2 100644
--- a/docs/admin/index.rst
+++ b/docs/admin/index.rst
@@ -15,7 +15,7 @@ Administrator documentation
installation-apache
update-searxng
answer-captcha
- searx.botdetection
+ searx.limiter
api
architecture
plugins
diff --git a/docs/admin/searx.limiter.rst b/docs/admin/searx.limiter.rst
new file mode 100644
index 00000000..c2363557
--- /dev/null
+++ b/docs/admin/searx.limiter.rst
@@ -0,0 +1,17 @@
+.. _limiter:
+
+=======
+Limiter
+=======
+
+.. sidebar:: info
+
+ The limiter requires a :ref:`Redis <settings redis>` database.
+
+.. contents::
+ :depth: 2
+ :local:
+ :backlinks: entry
+
+.. automodule:: searx.limiter
+ :members:
diff --git a/docs/admin/settings/settings_server.rst b/docs/admin/settings/settings_server.rst
index e2b4cb67..ba0c9484 100644
--- a/docs/admin/settings/settings_server.rst
+++ b/docs/admin/settings/settings_server.rst
@@ -36,11 +36,9 @@
``secret_key`` : ``$SEARXNG_SECRET``
Used for cryptography purpose.
-.. _limiter:
-
``limiter`` :
Rate limit the number of request on the instance, block some bots. The
- :ref:`limiter src` requires a :ref:`settings redis` database.
+ :ref:`limiter` requires a :ref:`settings redis` database.
.. _image_proxy:
diff --git a/docs/conf.py b/docs/conf.py
index aa4905ef..2ed85a80 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
import sys, os
+from pathlib import Path
from pallets_sphinx_themes import ProjectLink
from searx import get_setting
@@ -13,7 +14,6 @@ project = 'SearXNG'
copyright = 'SearXNG team'
author = 'SearXNG team'
release, version = VERSION_STRING, VERSION_STRING
-
SEARXNG_URL = get_setting('server.base_url') or 'https://example.org/searxng'
ISSUE_URL = get_setting('brand.issue_url')
DOCS_URL = get_setting('brand.docs_url')
@@ -22,6 +22,9 @@ PRIVACYPOLICY_URL = get_setting('general.privacypolicy_url')
CONTACT_URL = get_setting('general.contact_url')
WIKI_URL = get_setting('brand.wiki_url')
+SOURCEDIR = Path(__file__).parent.parent / "searx"
+os.environ['SOURCEDIR'] = str(SOURCEDIR)
+
# hint: sphinx.ext.viewcode won't highlight when 'highlight_language' [1] is set
# to string 'none' [2]
#
diff --git a/docs/admin/searx.botdetection.rst b/docs/src/searx.botdetection.rst
index c89f1cd9..04cb81df 100644
--- a/docs/admin/searx.botdetection.rst
+++ b/docs/src/searx.botdetection.rst
@@ -12,8 +12,10 @@ Bot Detection
.. automodule:: searx.botdetection
:members:
-.. automodule:: searx.botdetection.limiter
- :members:
+.. _botdetection ip_lists:
+
+IP lists
+========
.. automodule:: searx.botdetection.ip_lists
:members:
@@ -50,3 +52,11 @@ Probe HTTP headers
.. automodule:: searx.botdetection.http_user_agent
:members:
+
+.. _botdetection config:
+
+Config
+======
+
+.. automodule:: searx.botdetection.config
+ :members:
diff --git a/searx/botdetection/__init__.py b/searx/botdetection/__init__.py
index 74f6c426..d5716bcc 100644
--- a/searx/botdetection/__init__.py
+++ b/searx/botdetection/__init__.py
@@ -2,43 +2,22 @@
# lint: pylint
""".. _botdetection src:
-The :ref:`limiter <limiter src>` implements several methods to block bots:
-
-a. Analysis of the HTTP header in the request / can be easily bypassed.
-
-b. Block and pass lists in which IPs are listed / difficult to maintain, since
- the IPs of bots are not all known and change over the time.
-
-c. Detection of bots based on the behavior of the requests and blocking and, if
- necessary, unblocking of the IPs via a dynamically changeable IP block list.
-
-For dynamically changeable IP lists a Redis database is needed and for any kind
-of IP list the determination of the IP of the client is essential. The IP of
-the client is determined via the X-Forwarded-For_ HTTP header
-
-.. _X-Forwarded-For:
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
-
-X-Forwarded-For
-===============
-
-.. attention::
-
- A correct setup of the HTTP request headers ``X-Forwarded-For`` and
- ``X-Real-IP`` is essential to be able to assign a request to an IP correctly:
-
- - `NGINX RequestHeader`_
- - `Apache RequestHeader`_
-
-.. _NGINX RequestHeader:
- https://docs.searxng.org/admin/installation-nginx.html#nginx-s-searxng-site
-.. _Apache RequestHeader:
- https://docs.searxng.org/admin/installation-apache.html#apache-s-searxng-site
-
-.. autofunction:: searx.botdetection.get_real_ip
+Implementations used for bot detection.
"""
from ._helpers import dump_request
from ._helpers import get_real_ip
+from ._helpers import get_network
from ._helpers import too_many_requests
+
+__all__ = ['dump_request', 'get_network', 'get_real_ip', 'too_many_requests']
+
+redis_client = None
+cfg = None
+
+
+def init(_cfg, _redis_client):
+ global redis_client, cfg # pylint: disable=global-statement
+ redis_client = _redis_client
+ cfg = _cfg
diff --git a/searx/botdetection/_helpers.py b/searx/botdetection/_helpers.py
index f50250e8..365067c2 100644
--- a/searx/botdetection/_helpers.py
+++ b/searx/botdetection/_helpers.py
@@ -13,8 +13,8 @@ from ipaddress import (
import flask
import werkzeug
-from searx.tools import config
from searx import logger
+from . import config
logger = logger.getChild('botdetection')
@@ -104,10 +104,10 @@ def get_real_ip(request: flask.Request) -> str:
if not forwarded_for:
_log_error_only_once("X-Forwarded-For header is not set!")
else:
- from .limiter import get_cfg # pylint: disable=import-outside-toplevel, cyclic-import
+ from . import cfg # pylint: disable=import-outside-toplevel, cyclic-import
forwarded_for = [x.strip() for x in forwarded_for.split(',')]
- x_for: int = get_cfg()['real_ip.x_for'] # type: ignore
+ x_for: int = cfg['real_ip.x_for'] # type: ignore
forwarded_for = forwarded_for[-min(len(forwarded_for), x_for)]
if not real_ip:
diff --git a/searx/tools/config.py b/searx/botdetection/config.py
index d2710456..d2710456 100644
--- a/searx/tools/config.py
+++ b/searx/botdetection/config.py
diff --git a/searx/botdetection/http_accept.py b/searx/botdetection/http_accept.py
index b78a8627..b1f52459 100644
--- a/searx/botdetection/http_accept.py
+++ b/searx/botdetection/http_accept.py
@@ -24,7 +24,7 @@ from ipaddress import (
import flask
import werkzeug
-from searx.tools import config
+from . import config
from ._helpers import too_many_requests
diff --git a/searx/botdetection/http_accept_encoding.py b/searx/botdetection/http_accept_encoding.py
index 60718a4c..e0c03cc7 100644
--- a/searx/botdetection/http_accept_encoding.py
+++ b/searx/botdetection/http_accept_encoding.py
@@ -25,7 +25,7 @@ from ipaddress import (
import flask
import werkzeug
-from searx.tools import config
+from . import config
from ._helpers import too_many_requests
diff --git a/searx/botdetection/http_accept_language.py b/searx/botdetection/http_accept_language.py
index 395d28bf..aaef81cc 100644
--- a/searx/botdetection/http_accept_language.py
+++ b/searx/botdetection/http_accept_language.py
@@ -21,7 +21,7 @@ from ipaddress import (
import flask
import werkzeug
-from searx.tools import config
+from . import config
from ._helpers import too_many_requests
diff --git a/searx/botdetection/http_connection.py b/searx/botdetection/http_connection.py
index ee0d80a2..a3287715 100644
--- a/searx/botdetection/http_connection.py
+++ b/searx/botdetection/http_connection.py
@@ -22,7 +22,7 @@ from ipaddress import (
import flask
import werkzeug
-from searx.tools import config
+from . import config
from ._helpers import too_many_requests
diff --git a/searx/botdetection/http_user_agent.py b/searx/botdetection/http_user_agent.py
index 17025f68..e2e02a9b 100644
--- a/searx/botdetection/http_user_agent.py
+++ b/searx/botdetection/http_user_agent.py
@@ -24,7 +24,7 @@ from ipaddress import (
import flask
import werkzeug
-from searx.tools import config
+from . import config
from ._helpers import too_many_requests
diff --git a/searx/botdetection/ip_limit.py b/searx/botdetection/ip_limit.py
index 5ff3c87c..071978a3 100644
--- a/searx/botdetection/ip_limit.py
+++ b/searx/botdetection/ip_limit.py
@@ -13,8 +13,7 @@ and at least for a maximum of 10 minutes.
The :py:obj:`.link_token` method can be used to investigate whether a request is
*suspicious*. To activate the :py:obj:`.link_token` method in the
-:py:obj:`.ip_limit` method add the following to your
-``/etc/searxng/limiter.toml``:
+:py:obj:`.ip_limit` method add the following configuration:
.. code:: toml
@@ -46,13 +45,13 @@ from ipaddress import (
import flask
import werkzeug
-from searx.tools import config
from searx import settings
from searx import redisdb
from searx.redislib import incr_sliding_window, drop_counter
from . import link_token
+from . import config
from ._helpers import (
too_many_requests,
logger,
diff --git a/searx/botdetection/ip_lists.py b/searx/botdetection/ip_lists.py
index 456ef436..5c904f4a 100644
--- a/searx/botdetection/ip_lists.py
+++ b/searx/botdetection/ip_lists.py
@@ -33,7 +33,7 @@ from ipaddress import (
IPv6Address,
)
-from searx.tools import config
+from . import config
from ._helpers import logger
logger = logger.getChild('ip_limit')
diff --git a/searx/botdetection/link_token.py b/searx/botdetection/link_token.py
index 7ea15f5c..dcfee33d 100644
--- a/searx/botdetection/link_token.py
+++ b/searx/botdetection/link_token.py
@@ -99,15 +99,13 @@ def ping(request: flask.Request, token: str):
The expire time of this ping-key is :py:obj:`PING_LIVE_TIME`.
"""
- from . import limiter # pylint: disable=import-outside-toplevel, cyclic-import
+ from . import redis_client, cfg # pylint: disable=import-outside-toplevel, cyclic-import
- redis_client = redisdb.client()
if not redis_client:
return
if not token_is_valid(token):
return
- cfg = limiter.get_cfg()
real_ip = ip_address(get_real_ip(request))
network = get_network(real_ip, cfg)
diff --git a/searx/botdetection/limiter.py b/searx/limiter.py
index 9b3532f0..b61292d7 100644
--- a/searx/botdetection/limiter.py
+++ b/searx/limiter.py
@@ -1,15 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# lint: pylint
-""".. _limiter src:
-
-Limiter
-=======
-
-.. sidebar:: info
-
- The limiter requires a :ref:`Redis <settings redis>` database.
-
-Bot protection / IP rate limitation. The intention of rate limitation is to
+"""Bot protection / IP rate limitation. The intention of rate limitation is to
limit suspicious requests from an IP. The motivation behind this is the fact
that SearXNG passes through requests from bots and is thus classified as a bot
itself. As a result, the SearXNG engine then receives a CAPTCHA or is blocked
@@ -17,7 +8,40 @@ by the search engine (the origin) in some other way.
To avoid blocking, the requests from bots to SearXNG must also be blocked, this
is the task of the limiter. To perform this task, the limiter uses the methods
-from the :py:obj:`searx.botdetection`.
+from the :ref:`botdetection`:
+
+- Analysis of the HTTP header in the request / :ref:`botdetection probe headers`
+ can be easily bypassed.
+
+- Block and pass lists in which IPs are listed / :ref:`botdetection ip_lists`
+ are hard to maintain, since the IPs of bots are not all known and change over
+ the time.
+
+- Detection & dynamically :ref:`botdetection rate limit` of bots based on the
+ behavior of the requests. For dynamically changeable IP lists a Redis
+ database is needed.
+
+The prerequisite for IP based methods is the correct determination of the IP of
+the client. The IP of the client is determined via the X-Forwarded-For_ HTTP
+header.
+
+.. attention::
+
+ A correct setup of the HTTP request headers ``X-Forwarded-For`` and
+ ``X-Real-IP`` is essential to be able to assign a request to an IP correctly:
+
+ - `NGINX RequestHeader`_
+ - `Apache RequestHeader`_
+
+.. _X-Forwarded-For:
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
+.. _NGINX RequestHeader:
+ https://docs.searxng.org/admin/installation-nginx.html#nginx-s-searxng-site
+.. _Apache RequestHeader:
+ https://docs.searxng.org/admin/installation-apache.html#apache-s-searxng-site
+
+Enable Limiter
+==============
To enable the limiter activate:
@@ -35,36 +59,72 @@ and set the redis-url connection. Check the value, it depends on your redis DB
redis:
url: unix:///usr/local/searxng-redis/run/redis.sock?db=0
+
+Configure Limiter
+=================
+
+The methods of :ref:`botdetection` the limiter uses are configured in a local
+file ``/etc/searxng/limiter.toml``. The defaults are shown in limiter.toml_ /
+Don't copy all values to your local configuration, just enable what you need by
+overwriting the defaults. For instance to activate the ``link_token`` method in
+the :ref:`botdetection.ip_limit` you only need to set this option to ``true``:
+
+.. code:: toml
+
+ [botdetection.ip_limit]
+ link_token = true
+
+.. _limiter.toml:
+
+``limiter.toml``
+================
+
+In this file the limiter finds the configuration of the :ref:`botdetection`:
+
+- :ref:`botdetection ip_lists`
+- :ref:`botdetection rate limit`
+- :ref:`botdetection probe headers`
+
+.. kernel-include:: $SOURCEDIR/limiter.toml
+ :code: toml
+
+Implementation
+==============
+
"""
from __future__ import annotations
+import sys
from pathlib import Path
from ipaddress import ip_address
import flask
import werkzeug
-from searx.tools import config
-from searx import logger
-
-from . import (
+from searx import (
+ logger,
+ redisdb,
+)
+from searx import botdetection
+from searx.botdetection import (
+ config,
http_accept,
http_accept_encoding,
http_accept_language,
http_user_agent,
ip_limit,
ip_lists,
-)
-
-from ._helpers import (
get_network,
get_real_ip,
dump_request,
)
-logger = logger.getChild('botdetection.limiter')
+# the configuration are limiter.toml and "limiter" in settings.yml so, for
+# coherency, the logger is "limiter"
+logger = logger.getChild('limiter')
CFG: config.Config = None # type: ignore
+_INSTALLED = False
LIMITER_CFG_SCHEMA = Path(__file__).parent / "limiter.toml"
"""Base configuration (schema) of the botdetection."""
@@ -143,3 +203,31 @@ def filter_request(request: flask.Request) -> werkzeug.Response | None:
return val
logger.debug(f"OK {network}: %s", dump_request(flask.request))
return None
+
+
+def pre_request():
+ """See :py:obj:`flask.Flask.before_request`"""
+ return filter_request(flask.request)
+
+
+def is_installed():
+ return _INSTALLED
+
+
+def initialize(app: flask.Flask, settings):
+ """Instal the botlimiter aka limiter"""
+ global _INSTALLED # pylint: disable=global-statement
+ if not settings['server']['limiter'] and not settings['server']['public_instance']:
+ return
+ redis_client = redisdb.client()
+ if not redis_client:
+ logger.error(
+ "The limiter requires Redis, please consult the documentation: "
+ + "https://docs.searxng.org/admin/searx.botdetection.html#limiter"
+ )
+ if settings['server']['public_instance']:
+ sys.exit(1)
+ return
+ botdetection.init(get_cfg(), redis_client)
+ app.before_request(pre_request)
+ _INSTALLED = True
diff --git a/searx/botdetection/limiter.toml b/searx/limiter.toml
index 9560ec8f..9560ec8f 100644
--- a/searx/botdetection/limiter.toml
+++ b/searx/limiter.toml
diff --git a/searx/plugins/limiter.py b/searx/plugins/limiter.py
deleted file mode 100644
index 24b14f3b..00000000
--- a/searx/plugins/limiter.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# SPDX-License-Identifier: AGPL-3.0-or-later
-# lint: pylint
-# pyright: basic
-"""see :ref:`limiter src`"""
-
-import sys
-import flask
-
-from searx import redisdb
-from searx.plugins import logger
-from searx.botdetection import limiter
-
-name = "Request limiter"
-description = "Limit the number of request"
-default_on = False
-preference_section = 'service'
-
-logger = logger.getChild('limiter')
-
-
-def pre_request():
- """See :ref:`flask.Flask.before_request`"""
- return limiter.filter_request(flask.request)
-
-
-def init(app: flask.Flask, settings) -> bool:
- if not settings['server']['limiter'] and not settings['server']['public_instance']:
- return False
- if not redisdb.client():
- logger.error(
- "The limiter requires Redis, please consult the documentation: "
- + "https://docs.searxng.org/admin/searx.botdetection.html#limiter"
- )
- if settings['server']['public_instance']:
- sys.exit(1)
- return False
- app.before_request(pre_request)
- return True
diff --git a/searx/tools/__init__.py b/searx/tools/__init__.py
deleted file mode 100644
index 08e6d982..00000000
--- a/searx/tools/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# SPDX-License-Identifier: AGPL-3.0-or-later
-# lint: pylint
-""".. _tools src:
-
-A collection of *utilities* used by SearXNG, but without SearXNG specific
-peculiarities.
-
-"""
diff --git a/searx/webapp.py b/searx/webapp.py
index 13a31d3e..53ca9678 100755
--- a/searx/webapp.py
+++ b/searx/webapp.py
@@ -57,7 +57,9 @@ from searx import (
)
from searx import infopage
-from searx.botdetection import limiter
+from searx import limiter
+from searx.botdetection import link_token
+
from searx.data import ENGINE_DESCRIPTIONS
from searx.results import Timing
from searx.settings_defaults import OUTPUT_FORMATS
@@ -94,7 +96,6 @@ from searx.utils import (
from searx.version import VERSION_STRING, GIT_URL, GIT_BRANCH
from searx.query import RawTextQuery
from searx.plugins import Plugin, plugins, initialize as plugin_initialize
-from searx.botdetection import link_token
from searx.plugins.oa_doi_rewrite import get_doi_resolver
from searx.preferences import (
Preferences,
@@ -1288,7 +1289,7 @@ def config():
'DOCS_URL': get_setting('brand.docs_url'),
},
'limiter': {
- 'enabled': settings['server']['limiter'],
+ 'enabled': limiter.is_installed(),
'botdetection.ip_limit.link_token': _limiter_cfg.get('botdetection.ip_limit.link_token'),
'botdetection.ip_lists.pass_searxng_org': _limiter_cfg.get('botdetection.ip_lists.pass_searxng_org'),
},
@@ -1322,6 +1323,7 @@ if not werkzeug_reloader or (werkzeug_reloader and os.environ.get("WERKZEUG_RUN_
redis_initialize()
plugin_initialize(app)
search_initialize(enable_checker=True, check_network=True, enable_metrics=settings['general']['enable_metrics'])
+ limiter.initialize(app, settings)
def run():
diff --git a/tests/unit/test_plugins.py b/tests/unit/test_plugins.py
index 0d555fdc..90436266 100644
--- a/tests/unit/test_plugins.py
+++ b/tests/unit/test_plugins.py
@@ -1,6 +1,11 @@
# -*- coding: utf-8 -*-
-from searx import plugins
+from searx import (
+ plugins,
+ limiter,
+ botdetection,
+)
+
from mock import Mock
from tests import SearxTestCase
@@ -46,6 +51,8 @@ class SelfIPTest(SearxTestCase):
plugin = plugins.load_and_initialize_plugin('searx.plugins.self_info', False, (None, {}))
store = plugins.PluginStore()
store.register(plugin)
+ cfg = limiter.get_cfg()
+ botdetection.init(cfg, None)
self.assertTrue(len(store.plugins) == 1)