mirror of
				https://github.com/searxng/searxng.git
				synced 2025-11-04 03:27:06 -05:00 
			
		
		
		
	Replaces `x_for` functionality with `trusted_proxies`. This allows defining which IP / ranges to trust extracting the client IP address from X-Forwarded-For and X-Real-IP headers. We don't know if the proxy chain will give us the proper client address (REMOTE_ADDR in the WSGI environment), so we rely on reading the headers of the proxy before SearXNG (if there is one, in that case it must be added to trusted_proxies) hoping it has done the proper checks. In case a proxy in the chain does not check the client address correctly, integrity is compromised and this should be fixed by whoever manages the proxy, not us. Closes: - https://github.com/searxng/searxng/issues/4940 - https://github.com/searxng/searxng/issues/4939 - https://github.com/searxng/searxng/issues/4907 - https://github.com/searxng/searxng/issues/3632 - https://github.com/searxng/searxng/issues/3191 - https://github.com/searxng/searxng/issues/1237 Related: - https://github.com/searxng/searxng-docker/issues/386 - https://github.com/inetol-infrastructure/searxng-container/issues/81
		
			
				
	
	
		
			85 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			85 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# SPDX-License-Identifier: AGPL-3.0-or-later
 | 
						|
""".. _botdetection.ip_lists:
 | 
						|
 | 
						|
Method ``ip_lists``
 | 
						|
-------------------
 | 
						|
 | 
						|
The ``ip_lists`` method implements :py:obj:`block-list <block_ip>` and
 | 
						|
:py:obj:`pass-list <pass_ip>`.
 | 
						|
 | 
						|
.. code:: toml
 | 
						|
 | 
						|
   [botdetection.ip_lists]
 | 
						|
 | 
						|
   pass_ip = [
 | 
						|
     '167.235.158.251', # IPv4 of check.searx.space
 | 
						|
     '192.168.0.0/16',  # IPv4 private network
 | 
						|
     'fe80::/10',       # IPv6 linklocal
 | 
						|
   ]
 | 
						|
 | 
						|
   block_ip = [
 | 
						|
     '93.184.216.34',   # IPv4 of example.org
 | 
						|
     '257.1.1.1',       # invalid IP --> will be ignored, logged in ERROR class
 | 
						|
   ]
 | 
						|
 | 
						|
"""
 | 
						|
# pylint: disable=unused-argument
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
from typing import Tuple
 | 
						|
from ipaddress import (
 | 
						|
    ip_network,
 | 
						|
    IPv4Address,
 | 
						|
    IPv6Address,
 | 
						|
)
 | 
						|
 | 
						|
from . import config
 | 
						|
from ._helpers import logger
 | 
						|
 | 
						|
logger = logger.getChild('ip_limit')
 | 
						|
 | 
						|
SEARXNG_ORG = [
 | 
						|
    # https://github.com/searxng/searxng/pull/2484#issuecomment-1576639195
 | 
						|
    '167.235.158.251',  # IPv4 check.searx.space
 | 
						|
    '2a01:04f8:1c1c:8fc2::/64',  # IPv6 check.searx.space
 | 
						|
]
 | 
						|
"""Passlist of IPs from the SearXNG organization, e.g. `check.searx.space`."""
 | 
						|
 | 
						|
 | 
						|
def pass_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]:
 | 
						|
    """Checks if the IP on the subnet is in one of the members of the
 | 
						|
    ``botdetection.ip_lists.pass_ip`` list.
 | 
						|
    """
 | 
						|
 | 
						|
    if cfg.get('botdetection.ip_lists.pass_searxng_org', default=True):
 | 
						|
        for net in SEARXNG_ORG:
 | 
						|
            net = ip_network(net, strict=False)
 | 
						|
            if real_ip.version == net.version and real_ip in net:
 | 
						|
                return True, f"IP matches {net.compressed} in SEARXNG_ORG list."
 | 
						|
    return ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.pass_ip', cfg)
 | 
						|
 | 
						|
 | 
						|
def block_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]:
 | 
						|
    """Checks if the IP on the subnet is in one of the members of the
 | 
						|
    ``botdetection.ip_lists.block_ip`` list.
 | 
						|
    """
 | 
						|
 | 
						|
    block, msg = ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.block_ip', cfg)
 | 
						|
    if block:
 | 
						|
        msg += " To remove IP from list, please contact the maintainer of the service."
 | 
						|
    return block, msg
 | 
						|
 | 
						|
 | 
						|
def ip_is_subnet_of_member_in_list(
 | 
						|
    real_ip: IPv4Address | IPv6Address, list_name: str, cfg: config.Config
 | 
						|
) -> Tuple[bool, str]:
 | 
						|
    for net in cfg.get(list_name, default=[]):
 | 
						|
        try:
 | 
						|
            net = ip_network(net, strict=False)
 | 
						|
        except ValueError:
 | 
						|
            logger.error("invalid IP %s in %s", net, list_name)
 | 
						|
            continue
 | 
						|
        if real_ip.version == net.version and real_ip in net:
 | 
						|
            return True, f"IP matches {net.compressed} in {list_name}."
 | 
						|
    return False, f"IP is not a member of an item in the f{list_name} list"
 |