diff --git a/.travis.yml b/.travis.yml index c0c1250..598ee7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,6 @@ matrix: include: - python: 3.7 env: TOXENV=lint - - python: 2.7 - env: TOXENV=py27 - - python: 3.4 - env: TOXENV=py34 - - python: 3.5 - env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 diff --git a/README.rst b/README.rst index d57bed7..d414bab 100644 --- a/README.rst +++ b/README.rst @@ -146,6 +146,22 @@ Url patterns Url matching match_type can be 'string' or 'regex'. String is direct match. Regex is a regex pattern. +Block networks / cidr +--------------------- + +Use the `block_cidr(network)` method to block a range of addresses or whole regions. + +Example: + +.. code:: python + + ip_ban = IpBan() + app = Flask(__name__) + ip_ban.init_app(app) + # block a network in Aruba + ip_ban.block_cidr('190.220.142.104/29') + + Nuisance file ------------- @@ -159,7 +175,7 @@ that use nuisance url patterns they won't result in a block. Load them by calling ip_ban.load_nuisances() -You can add your own nuisance yaml file by calling with the parameter file_name=. +You can add your own nuisance yaml file by calling with the parameter `file_name`. See the nuisance.yaml file in the source for formatting and details. @@ -179,6 +195,10 @@ This folder and secret key is also used by the persistence feature. Only ip records using the `block`, `add` and `remove` methods or by 404; are persisted or shared. Any whitelisting or pattern bans are not persisted/shared and must be done for each instance of your application. +The bit that shares ipc records between processes only updates during the `before_request` handler +of the Flask app. It only updates every 5 seconds at the most. If the app does no +request handling between bans then that ban record won't be shared between processes. + IP Header --------- When running a flask app in a docker hosted environment (or similar) the ip address will be the virtual @@ -216,6 +236,7 @@ Release History --------------- 1.0.13 - Remove reason= which did nothing. Add url to report table. Add more nuisances. Add release history. +1.1.0 - Add more nuisances. Add ability to block regions by using `block_cidr()`. Remove support for obsolete Python releases (2.7,3.4,3.5). Licensing --------- diff --git a/flask_ipban/ip_ban.py b/flask_ipban/ip_ban.py index 2a294f4..b2daa15 100644 --- a/flask_ipban/ip_ban.py +++ b/flask_ipban/ip_ban.py @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import - +import ipaddress import os import re from datetime import datetime @@ -20,8 +19,8 @@ import yaml from flask import request, abort -from flask_ipban.ip_record import IpRecord from flask_ipban.abuse_ipdb import AbuseIPDB +from flask_ipban.ip_record import IpRecord class IpBan: @@ -37,7 +36,7 @@ class IpBan: """ - VERSION = '1.0.13' + VERSION = '1.1.0' def __init__(self, app=None, ban_count=20, ban_seconds=3600 * 24, persist=False, record_dir=None, ipc=False, secret_key=None, ip_header=None, abuse_IPDB_config=None): @@ -60,6 +59,7 @@ def __init__(self, app=None, ban_count=20, ban_seconds=3600 * 24, persist=False, self._ip_whitelist = {'127.0.0.1': True} # self._ip_whitelist = {} self._ip_ban_list = {} + self._cidr_entries = {} # initialise with well known search bot links self._url_whitelist_patterns = { '^/.well-known/': dict(pattern=re.compile('^/.well-known'), match_type='regex'), @@ -122,7 +122,7 @@ def _after_request(self, response): def block(self, ip_list, permanent=False, no_write=False, timestamp=None, url='block'): """ - add a list of ip address to the block list + add a list of ip addresses to the block list :param ip_list: list of ip addresses to block :param permanent: (optional) True=do not allow entries to expire @@ -170,11 +170,11 @@ def get_ip(self): ip = request.headers.get(self.ip_header) return ip or request.environ.get('REMOTE_ADDR') - def test_pattern_blocklist(self, url, ip=None): + def test_pattern_blocklist(self, url='', ip=None): """ return true if the url or ip pattern matches an existing block - :param url: the url to check + :param url: (optional) the url to check :param ip: (optional) an ip to check :return: """ @@ -191,6 +191,13 @@ def test_pattern_blocklist(self, url, ip=None): self._logger.warning('ip block match {}'.format(ip)) return True + if ip: + ip_address = ipaddress.ip_address(ip) + for c, c_ip in self._cidr_entries.items(): + if ip_address in c_ip: + self._logger.warning(f'ip block match {ip}. CIDR: {c}') + return True + return False def _before_request_check(self): @@ -227,6 +234,9 @@ def _before_request_check(self): self._logger.debug('IP expired from ban list {}. Url: {}'.format(ip, url)) entry['count'] = 0 + def block_cidr(self, cidr): + self._cidr_entries[cidr] = ipaddress.ip_network(cidr) + def ip_whitelist_add(self, ip): """ add the ip to the list of ips to whitelist @@ -325,9 +335,9 @@ def display(self, option='html'): s += '
\n' for k, r in self._ip_ban_list.items(): s += '