From 438f8eaf638e97f2bb0d59bf8b1c309a3ec00657 Mon Sep 17 00:00:00 2001 From: Ruben van Staveren Date: Mon, 7 Mar 2022 15:25:59 +0100 Subject: [PATCH] Move application into package and use factory --- app.py | 137 ---------------------------------- jail2ban/__init__.py | 135 +++++++++++++++++++++++++++++++++ pfctl.py => jail2ban/pfctl.py | 0 3 files changed, 135 insertions(+), 137 deletions(-) delete mode 100644 app.py create mode 100644 jail2ban/__init__.py rename pfctl.py => jail2ban/pfctl.py (100%) diff --git a/app.py b/app.py deleted file mode 100644 index 7f6abcc..0000000 --- a/app.py +++ /dev/null @@ -1,137 +0,0 @@ -from flask import Flask, request, jsonify -from flask_httpauth import HTTPBasicAuth -from werkzeug.security import check_password_hash -from ipaddress import ip_address -import re -from pfctl import pfctl_table_op, pfctl_cfg_read, pfctl_cfg_write - - -app = Flask(__name__) - -auth = HTTPBasicAuth() - -users = { - "erg.verweg.com": 'pbkdf2:sha256:260000$leXVKkMYNu60eQZR$0893397beb241931d33d2c996e66447a375d3b7923aa32fc4af6b80eec716fbe' -} - -PAT_PORT = r'^any(?:\s+port\s+{\w+(?:,\w+)*})?$' -PAT_PROT = r'^(?:tcp|udp)$' -PAT_NAME = r'^[\w\-]+$' - - -def untaint(pattern, string): - ''' - untaint string (as perl does) - ''' - match = re.match(pattern, string) - if match: - return match.string - else: - raise ValueError(f'"{string}" is tainted') - - -@auth.verify_password -def verify_password(username, password): - if username in users and \ - check_password_hash(users.get(username), password): - return username - - -@app.route("/flush/", methods=['GET']) -@auth.login_required -def flush(name): - remote_user = auth.username() - name = untaint(PAT_NAME, name) - app.logger.info(f'Flushing table f2b-{name}' - ' in anchor f2b-jail/{remote_user}') - res = pfctl_table_op('f2b-jail/{remote_user}', - table='f2b-{name}', - operation='flush') - return jsonify({'anchor': f'f2b-jail/{remote_user}', - 'table': f'f2b-{name}', - 'operation': 'flush', - 'result': res}) - - -@app.route("/register", methods=['PUT', 'DELETE']) -@auth.login_required -def register(): - remote_user = auth.username() - # port / name / protocol - data = request.get_json() - name = untaint(PAT_NAME, data['name']) - protocol = untaint(PAT_PROT, data['protocol']) - port = untaint(PAT_PORT, data['port']) - cfg = pfctl_cfg_read(f'f2b-jail/{remote_user}') - if not cfg: - cfg = [] - if request.method == 'PUT': - cfg.extend([ - bytes(f'table persist counters', 'ascii'), - bytes(f'block quick proto {protocol}' - f' from to {port}', 'ascii') - ]) - res = pfctl_cfg_write(f'f2b-jail/{remote_user}', - b'\n'.join(cfg) + b'\n') - elif request.method == 'DELETE': - cfg = [cfg_line for cfg_line in cfg - if cfg_line.find(bytes(f'', 'ascii')) == -1] - res = pfctl_cfg_write(f'f2b-jail/{remote_user}', - b'\n'.join(cfg) + b'\n') - pfctl_table_op(f'f2b-jail/{remote_user}', - table=f'f2b-{name}', - operation='flush') - pfctl_table_op(f'f2b-jail/{remote_user}', - table=f'f2b-{name}', - operation='kill') - app.logger.info(f'pfctl -a f2b-jail/{remote_user} -f-') - return jsonify({'remote_user': remote_user, 'data': data}) - return jsonify({'anchor': f'f2b-jail/{remote_user}', - 'table': f'f2b-{name}', - 'action': 'start' if request.method == 'PUT' - else 'stop', - 'result': res}) - - -@app.route("/ban", methods=['PUT', 'DELETE']) -@auth.login_required -def ban(): - remote_user = auth.username() - data = request.get_json() - # name / ip - name = untaint(PAT_NAME, data['name']) - ip = ip_address(data['ip']) - if request.method == 'PUT': - app.logger.info(f'Add {ip} to f2b-{name}' - f' in anchor f2b-jail/{remote_user}') - res = pfctl_table_op(f'f2b-jail/{remote_user}', - table=f'f2b-{name}', - operation='add', - value=str(ip)) - elif request.method == 'DELETE': - app.logger.info(f'Remove {ip} from f2b-{name}' - f' in anchor f2b-jail/{remote_user}') - res = pfctl_table_op(f'f2b-jail/{remote_user}', - table=f'f2b-{name}', - operation='delete', - value=str(ip)) - return jsonify({'anchor': f'f2b-jail/{remote_user}', - 'table': f'f2b-{name}', - 'operation': 'add' if request.method == 'PUT' - else 'delete', - 'result': res}) - - -@app.errorhandler(ValueError) -def permission_err(error): - ''' - Show a json parsable error if the value is illegal - ''' - app.logger.fatal(error) - return jsonify({'error': str(error)}), 500 - - -@auth.error_handler -def auth_error(): - app.logger.error('Access Denied') - return jsonify({'error': 'Access Denied'}), 401 diff --git a/jail2ban/__init__.py b/jail2ban/__init__.py new file mode 100644 index 0000000..3de0a2f --- /dev/null +++ b/jail2ban/__init__.py @@ -0,0 +1,135 @@ +from flask import Flask, request, jsonify +from flask_httpauth import HTTPBasicAuth +from werkzeug.security import check_password_hash +from ipaddress import ip_address +import re +from pfctl import pfctl_table_op, pfctl_cfg_read, pfctl_cfg_write + + +auth = HTTPBasicAuth() + +users = { + "erg.verweg.com": 'pbkdf2:sha256:260000$leXVKkMYNu60eQZR$0893397beb241931d33d2c996e66447a375d3b7923aa32fc4af6b80eec716fbe' +} + +PAT_PORT = r'^any(?:\s+port\s+{\w+(?:,\w+)*})?$' +PAT_PROT = r'^(?:tcp|udp)$' +PAT_NAME = r'^[\w\-]+$' + + +def untaint(pattern, string): + ''' + untaint string (as perl does) + ''' + match = re.match(pattern, string) + if match: + return match.string + else: + raise ValueError(f'"{string}" is tainted') + + +def create_app(): + app = Flask(__name__, instance_relative_config=True) + + @auth.verify_password + def verify_password(username, password): + if username in users and \ + check_password_hash(users.get(username), password): + return username + + @app.route("/flush/", methods=['GET']) + @auth.login_required + def flush(name): + remote_user = auth.username() + name = untaint(PAT_NAME, name) + app.logger.info(f'Flushing table f2b-{name}' + f' in anchor f2b-jail/{remote_user}') + res = pfctl_table_op('f2b-jail/{remote_user}', + table='f2b-{name}', + operation='flush') + return jsonify({'anchor': f'f2b-jail/{remote_user}', + 'table': f'f2b-{name}', + 'operation': 'flush', + 'result': res}) + + @app.route("/register", methods=['PUT', 'DELETE']) + @auth.login_required + def register(): + remote_user = auth.username() + # port / name / protocol + data = request.get_json() + name = untaint(PAT_NAME, data['name']) + protocol = untaint(PAT_PROT, data['protocol']) + port = untaint(PAT_PORT, data['port']) + cfg = pfctl_cfg_read(f'f2b-jail/{remote_user}') + if not cfg: + cfg = [] + if request.method == 'PUT': + cfg.extend([ + bytes(f'table persist counters', 'ascii'), + bytes(f'block quick proto {protocol}' + f' from to {port}', 'ascii') + ]) + res = pfctl_cfg_write(f'f2b-jail/{remote_user}', + b'\n'.join(cfg) + b'\n') + elif request.method == 'DELETE': + cfg = [cfg_line for cfg_line in cfg + if cfg_line.find(bytes(f'', 'ascii')) == -1] + res = pfctl_cfg_write(f'f2b-jail/{remote_user}', + b'\n'.join(cfg) + b'\n') + pfctl_table_op(f'f2b-jail/{remote_user}', + table=f'f2b-{name}', + operation='flush') + pfctl_table_op(f'f2b-jail/{remote_user}', + table=f'f2b-{name}', + operation='kill') + app.logger.info(f'pfctl -a f2b-jail/{remote_user} -f-') + return jsonify({'remote_user': remote_user, 'data': data}) + return jsonify({'anchor': f'f2b-jail/{remote_user}', + 'table': f'f2b-{name}', + 'action': 'start' if request.method == 'PUT' + else 'stop', + 'result': res}) + + @app.route("/ban", methods=['PUT', 'DELETE']) + @auth.login_required + def ban(): + remote_user = auth.username() + data = request.get_json() + # name / ip + name = untaint(PAT_NAME, data['name']) + ip = ip_address(data['ip']) + if request.method == 'PUT': + app.logger.info(f'Add {ip} to f2b-{name}' + f' in anchor f2b-jail/{remote_user}') + res = pfctl_table_op(f'f2b-jail/{remote_user}', + table=f'f2b-{name}', + operation='add', + value=str(ip)) + elif request.method == 'DELETE': + app.logger.info(f'Remove {ip} from f2b-{name}' + f' in anchor f2b-jail/{remote_user}') + res = pfctl_table_op(f'f2b-jail/{remote_user}', + table=f'f2b-{name}', + operation='delete', + value=str(ip)) + return jsonify({'anchor': f'f2b-jail/{remote_user}', + 'table': f'f2b-{name}', + 'operation': 'add' if request.method == 'PUT' + else 'delete', + 'result': res}) + + @app.errorhandler(ValueError) + def permission_err(error): + ''' + Show a json parsable error if the value is illegal + ''' + app.logger.fatal(error) + return jsonify({'error': str(error)}), 500 + + @auth.error_handler + def auth_error(): + app.logger.error('Access Denied') + return jsonify({'error': 'Access Denied'}), 401 + + return app diff --git a/pfctl.py b/jail2ban/pfctl.py similarity index 100% rename from pfctl.py rename to jail2ban/pfctl.py