Compare commits
	
		
			25 Commits
		
	
	
		
			main
			...
			e0d115790e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e0d115790e | |||
| 58ac46126a | |||
| a7970c4f6b | |||
| 3dacbe7a5d | |||
| 798a72cb73 | |||
| 59cc645b98 | |||
| feb8c93737 | |||
| 27bc683f8f | |||
| 3f94410716 | |||
| e3a71a851d | |||
| 542718b956 | |||
| 34f871ae75 | |||
| f3f8bd5dc6 | |||
| 5971c27a8f | |||
| 47e8208ce2 | |||
| af1fef189c | |||
| c33e63978e | |||
| 963f7e5702 | |||
| 450792b2d2 | |||
| ffe144f6b5 | |||
| a20145b447 | |||
| b5f16f161c | |||
| 7c7734c996 | |||
| bd12b2a18a | |||
| 64523ae8b5 | 
| @ -1,17 +0,0 @@ | ||||
| --- | ||||
| name: Flake8 | ||||
| on: [push] | ||||
|  | ||||
|  | ||||
| # XXX need to do stuff with uv | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: freebsd | ||||
|     strategy: | ||||
|       matrix: | ||||
|         python-version: ["3.11"] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Analyse code with Flake8 | ||||
|         run: | | ||||
|           flake8 $(git ls-files '*.py') | ||||
| @ -1,17 +0,0 @@ | ||||
| --- | ||||
| name: Mypy | ||||
| on: [push] | ||||
|  | ||||
|  | ||||
| # XXX need to do stuff with uv | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: freebsd | ||||
|     strategy: | ||||
|       matrix: | ||||
|         python-version: ["3.11"] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Analyse code with Mypy | ||||
|         run: | | ||||
|           mypy --install-types --non-interactive $(git ls-files '*.py') | ||||
| @ -1,17 +0,0 @@ | ||||
| --- | ||||
| name: Pylint | ||||
| on: [push] | ||||
|  | ||||
|  | ||||
| # XXX need to do stuff with uv | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: freebsd | ||||
|     strategy: | ||||
|       matrix: | ||||
|         python-version: ["3.11"] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Analyse code with Pylint | ||||
|         run: | | ||||
|           pylint $(git ls-files '*.py') | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -12,6 +12,3 @@ htmlcov/ | ||||
| dist/ | ||||
| build/ | ||||
| *.egg-info/ | ||||
|  | ||||
| coverage.xml | ||||
| report.xml | ||||
|  | ||||
| @ -1,34 +1,6 @@ | ||||
| --- | ||||
| run pylint: | ||||
|   stage: test | ||||
|   image: python:3.11 | ||||
|   script: | ||||
|     - pip install --upgrade pylint | ||||
|     - pylint $(git ls-files '*.py') | ||||
|   tags: | ||||
|     - docker | ||||
|  | ||||
| run flake8: | ||||
|   stage: test | ||||
|   image: python:3.11 | ||||
|   script: | ||||
|     - pip install --upgrade flake8 | ||||
|     - flake8 $(git ls-files '*.py') | ||||
|   tags: | ||||
|     - docker | ||||
|  | ||||
| run mypy: | ||||
|   stage: test | ||||
|   image: python:3.11 | ||||
|   script: | ||||
|     - pip install --upgrade mypy | ||||
|     - mypy --install-types --non-interactive $(git ls-files '*.py') | ||||
|   tags: | ||||
|     - docker | ||||
|  | ||||
| run tests: | ||||
|   stage: test | ||||
|   image: python:3.11 | ||||
|   image: python:3.8 | ||||
|   script: | ||||
|     - pip install pytest pytest-cov pytest-mock pytest-flask | ||||
|     - pip install Flask-HTTPAuth | ||||
| @ -39,9 +11,7 @@ run tests: | ||||
|   artifacts: | ||||
|     when: always | ||||
|     reports: | ||||
|       coverage_report: | ||||
|         coverage_format: cobertura | ||||
|         path: coverage.xml | ||||
|       cobertura: coverage.xml | ||||
|       junit: report.xml | ||||
|   tags: | ||||
|     - docker | ||||
|  | ||||
							
								
								
									
										128
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								README.md
									
									
									
									
									
								
							| @ -1,131 +1,3 @@ | ||||
| [](https://gitlab.niet.verweg.com/ruben/jail2ban-pf/-/commits/main) | ||||
| [](https://gitlab.niet.verweg.com/ruben/jail2ban-pf/-/commits/main) | ||||
|  | ||||
| An API to remotely control a pf based fail2ban | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
|  | ||||
| * Install uwsgi  | ||||
|  | ||||
| ``` | ||||
| sudo pkg install www/uwsgi | ||||
| ``` | ||||
|  | ||||
| * Clone this repository | ||||
|  | ||||
| ## Configuration | ||||
|  | ||||
| ### rc.conf | ||||
|  | ||||
| * Use the following for configuring uwsgi in rc.conf | ||||
|  | ||||
| ``` | ||||
| sudo sysrc uwsgi\_enable="YES" | ||||
| sudo sysrc uwsgi\_profiles="jail2ban\_pf" | ||||
| sudo sysrc uwsgi\_jail2ban\_pf\_flags="-L -M --uid \_jail2ban --python-path /opt/jail2ban-pf --wsgi-file /opt/jail2ban-pf/wsgi.py --stats 127.0.0.1:9191 --socket 127.0.0.1:3031 --chdir /var/empty --callable app --manage-script-name" | ||||
| ``` | ||||
|  | ||||
| ### jail2ban | ||||
|  | ||||
| * Configure <installation root>/instance/config.py | ||||
|  | ||||
| ``` | ||||
| SECRET\_KEY = os.urandom(32).hex() | ||||
| AUTHFILE = '/usr/local/etc/jail2ban-pf-users.txt' | ||||
| ``` | ||||
|  | ||||
| ### nginx | ||||
|  | ||||
| * Configure a nginx upstream and vhost | ||||
|  | ||||
| _Of course you can listen on ipv4/ipv6 but you want to protect these addresses from inadvertent or malicious probes_ | ||||
|  | ||||
|     upstream uwsgi_pf_jail2ban { | ||||
|         server 127.0.0.1:3031; | ||||
|     } | ||||
|  | ||||
|     server { | ||||
|         listen       unix:/path/to/jail_1/var/run/pf2ban/pf_jail2ban.sock; | ||||
|         listen       unix:/path/to/jail_2/var/run/pf2ban/pf_jail2ban.sock; | ||||
|         listen       unix:/path/to/jail_3/var/run/pf2ban/pf_jail2ban.sock; | ||||
|         server_name  _; | ||||
|  | ||||
|         location / { | ||||
|             index     index.html index.htm index.php; | ||||
|             allow all; | ||||
|             include /usr/local/etc/nginx/uwsgi_params-dist; | ||||
|             uwsgi_pass uwsgi_pf_jail2ban; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| ### /etc/pf.conf | ||||
|  | ||||
| * Place anchors in pf for jail2ban to use. You probably want to place the early in your existing pf configuration | ||||
|  | ||||
| ``` | ||||
| anchor "f2b/*" | ||||
| anchor f2b-jail { | ||||
|     anchor "jail1_fqdn" to { <addr_jail1>, <addr_extra_jail1>, <addr_extra6_jail1> } | ||||
|     anchor "jail2_fqdn" to { <addr_jail2>, <addr_extra_jail2>, <addr_extra6_jail2> } | ||||
|     anchor "jail3_fqdn" to { <addr_jail3>, <addr_extra_jail3>, <addr_extra6_jail3> } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Having seperate anchors per jail makes it possible to have fine grained | ||||
| blocking: Something that is harmful to jail2 might be perfectly legit for jail2. | ||||
|  | ||||
| #### Checking rules/tables made with fail2ban/jail2ban | ||||
| Fail2ban will (re)create the per anchor rules on startup, and populate the designated address tables with offenders, e.g.: | ||||
|  | ||||
|     sudo pfctl -a f2b-jail/jail1\_fqdn -T show -t f2b-recidive | ||||
|     192.0.2.66 | ||||
|     2001:db8:abad:cafe:0bad:f00d | ||||
|  | ||||
| And the rules referencing these tables | ||||
|  | ||||
|     sudo pfctl -a 'f2b-jail/jail1\_fqdn' -s rules | ||||
|     block drop quick proto tcp from <f2b-dovecot> to any port = pop3 | ||||
|     block drop quick proto tcp from <f2b-dovecot> to any port = pop3s | ||||
|     block drop quick proto tcp from <f2b-dovecot> to any port = imap | ||||
|     block drop quick proto tcp from <f2b-dovecot> to any port = imaps | ||||
|     block drop quick proto tcp from <f2b-dovecot> to any port = submission | ||||
|     block drop quick proto tcp from <f2b-dovecot> to any port = smtps | ||||
|     block drop quick proto tcp from <f2b-dovecot> to any port = sieve | ||||
|     block drop quick proto tcp from <f2b-sendmail-auth> to any port = submission | ||||
|     block drop quick proto tcp from <f2b-sendmail-auth> to any port = smtps | ||||
|     block drop quick proto tcp from <f2b-sendmail-auth> to any port = smtp | ||||
|     block drop quick proto tcp from <f2b-sshd> to any port = ssh | ||||
|     block drop quick proto tcp from <f2b-recidive> to any | ||||
|      | ||||
| ### fail2ban | ||||
|  | ||||
| * Create the following action plugin for fail2ban on the jail desiring to use fail2ban/jail2ban | ||||
|  | ||||
| ``` | ||||
| cat <<'EOT' | tee /usr/local/etc/fail2ban/action.d/jail2ban-pf.conf > /dev/null | ||||
| Definition] | ||||
| actionstart = curl --unix-socket <jail2ban_sock> --basic -u '<jail2ban_user>:<jail2ban_pass>' -XPUT -H 'Content-Type: application/json' -d '{"port":"<actiontype>","name":"<name>","protocol":"<protocol>"}' http://localhost/register | ||||
| actionstart_on_demand = false | ||||
| actionstop = curl --unix-socket <jail2ban_sock> --basic -u '<jail2ban_user>:<jail2ban_pass>' -XDELETE -H 'Content-Type: application/json' -d '{"port":"<actiontype>","name":"<name>","protocol":"<protocol>"}' http://localhost/register | ||||
| actionflush = curl --unix-socket <jail2ban_sock> --basic -u '<jail2ban_user>:<jail2ban_pass>' -X GET http://localhost/flush/<name> | ||||
| actioncheck =  | ||||
| actionban = curl --unix-socket <jail2ban_sock> --basic -u '<jail2ban_user>:<jail2ban_pass>' -X PUT -H 'Content-Type: application/json' -d '{"name":"<name>","ip":"<ip>"}' http://localhost/ban | ||||
| actionunban = curl --unix-socket <jail2ban_sock> --basic -u '<jail2ban_user>:<jail2ban_pass>' -X DELETE -H 'Content-Type: application/json' -d '{"name":"<name>","ip":"<ip>"}' http://localhost/ban | ||||
| [Init] | ||||
| protocol = tcp | ||||
| actiontype = <multiport> | ||||
| allports = any | ||||
| multiport = any port {<port>} | ||||
| jail2ban_sock = /var/run/pf2ban/jail2ban.sock | ||||
| jail2ban_user = login as set in password file for jail2ban | ||||
| jail2ban_pass = password as set in password file for jail2ban | ||||
| ``` | ||||
|  | ||||
| * Configure jail.local | ||||
|  | ||||
| ``` | ||||
| cat <<'EOT' | tee /usr/local/etc/fail2ban/jail.local > /dev/null | ||||
| [DEFAULT] | ||||
| banaction = jail2ban-pf | ||||
| ``` | ||||
|  | ||||
| @ -1,17 +1,11 @@ | ||||
| ''' | ||||
| An API to remotely control a pf based fail2ban | ||||
| ''' | ||||
|  | ||||
| import re | ||||
| from ipaddress import ip_address | ||||
| from subprocess import CalledProcessError | ||||
|  | ||||
| from flask import Flask, current_app, jsonify, request | ||||
| from flask_httpauth import HTTPBasicAuth  # type: ignore | ||||
| from flask import Flask, request, jsonify, current_app | ||||
| from flask_httpauth import HTTPBasicAuth | ||||
| from werkzeug.security import check_password_hash | ||||
|  | ||||
| from ipaddress import ip_address | ||||
| import re | ||||
| from jail2ban.pfctl import pfctl_table_op, pfctl_cfg_read, pfctl_cfg_write | ||||
| from jail2ban.auth import get_users | ||||
| from jail2ban.pfctl import pfctl_cfg_read, pfctl_cfg_write, pfctl_table_op | ||||
| from subprocess import CalledProcessError | ||||
|  | ||||
|  | ||||
| auth = HTTPBasicAuth() | ||||
| @ -22,52 +16,39 @@ PAT_PROT = r'^(?:tcp|udp)$' | ||||
| PAT_NAME = r'^[\w\-]+$' | ||||
|  | ||||
|  | ||||
| def untaint(pattern: str, string: str) -> str: | ||||
| def untaint(pattern, string): | ||||
|     ''' | ||||
|     untaint string (as perl does) | ||||
|     ''' | ||||
|     match = re.match(pattern, string) | ||||
|     if match: | ||||
|         return match.string | ||||
|     raise ValueError(f'"{string}" is tainted') | ||||
|     else: | ||||
|         raise ValueError(f'"{string}" is tainted') | ||||
|  | ||||
|  | ||||
| def create_app(): | ||||
|     ''' | ||||
|     Create the flask application | ||||
|     ''' | ||||
|     app = Flask(__name__, instance_relative_config=True) | ||||
|  | ||||
|     # load the instance config, if it exists, when not testing | ||||
|     app.config.from_pyfile('config.py', silent=True) | ||||
|  | ||||
|     @auth.verify_password | ||||
|     def verify_password(username: str, password: str) -> str | None: | ||||
|     def verify_password(username, password): | ||||
|         users = get_users() | ||||
|         current_app.logger.debug(users) | ||||
|         current_app.logger.debug('Checking password of %s', username) | ||||
|         if username in users and \ | ||||
|                 check_password_hash(users.get(username), password): | ||||
|             return username | ||||
|         return None | ||||
|  | ||||
|     @app.route("/ping", methods=['GET']) | ||||
|     @auth.login_required | ||||
|     def ping(): | ||||
|         remote_user = auth.username() | ||||
|         app.logger.info('Received ping for' | ||||
|                         ' anchor f2b-jail/%s', remote_user) | ||||
|         return jsonify({'anchor': f'f2b-jail/{remote_user}', | ||||
|                         'operation': 'ping', | ||||
|                         'result': 'pong'}) | ||||
|  | ||||
|     @app.route("/flush/<name>", methods=['GET']) | ||||
|     @auth.login_required | ||||
|     def flush(name): | ||||
|         remote_user = auth.username() | ||||
|         name = untaint(PAT_NAME, name) | ||||
|         app.logger.info('Flushing table f2b-%s' | ||||
|                         ' in anchor f2b-jail/%s', name, remote_user) | ||||
|         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') | ||||
| @ -107,7 +88,7 @@ def create_app(): | ||||
|             pfctl_table_op(f'f2b-jail/{remote_user}', | ||||
|                            table=f'f2b-{name}', | ||||
|                            operation='kill') | ||||
|         app.logger.info('pfctl -a f2b-jail/%s -f-', remote_user) | ||||
|         app.logger.info(f'pfctl -a f2b-jail/{remote_user} -f-') | ||||
|         return jsonify({'anchor': f'f2b-jail/{remote_user}', | ||||
|                         'table': f'f2b-{name}', | ||||
|                         'action': 'start' if request.method == 'PUT' | ||||
| @ -123,15 +104,15 @@ def create_app(): | ||||
|         name = untaint(PAT_NAME, data['name']) | ||||
|         ip = ip_address(data['ip']) | ||||
|         if request.method == 'PUT': | ||||
|             app.logger.info('Add %s to f2b-%s' | ||||
|                             ' in anchor f2b-jail/%s', ip, name, remote_user) | ||||
|             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)) | ||||
|         else:  # 'DELETE': | ||||
|             app.logger.info('Remove %s from f2b-%s' | ||||
|                             ' in anchor f2b-jail/%s', ip, name, remote_user) | ||||
|             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', | ||||
|  | ||||
| @ -1,13 +1,7 @@ | ||||
| ''' | ||||
| Authentication backend | ||||
| ''' | ||||
| from flask import current_app | ||||
|  | ||||
|  | ||||
| def get_users(): | ||||
|     ''' | ||||
|     Load users from password file (AUTHFILE) | ||||
|     ''' | ||||
|     users = {} | ||||
|     authfile = current_app.config['AUTHFILE'] | ||||
|  | ||||
|  | ||||
| @ -1,6 +1,3 @@ | ||||
| ''' | ||||
| pf table/anchor operations | ||||
| ''' | ||||
| import logging | ||||
| from subprocess import run | ||||
|  | ||||
| @ -9,9 +6,6 @@ _PFCTL = '/sbin/pfctl' | ||||
|  | ||||
|  | ||||
| def pfctl_cfg_read(anchor): | ||||
|     ''' | ||||
|     Read from pf anchor | ||||
|     ''' | ||||
|     cmd = [_SUDO, _PFCTL, '-a', anchor, '-sr'] | ||||
|     logging.info('Running %s', cmd) | ||||
|  | ||||
| @ -22,9 +16,6 @@ def pfctl_cfg_read(anchor): | ||||
|  | ||||
|  | ||||
| def pfctl_cfg_write(anchor, cfg): | ||||
|     ''' | ||||
|     Apply configuration to pf anchor | ||||
|     ''' | ||||
|     cmd = [_SUDO, _PFCTL, '-a', anchor, '-f-'] | ||||
|     logging.info('Running %s', cmd) | ||||
|     logging.info('Config %s', cfg) | ||||
| @ -39,13 +30,6 @@ def pfctl_cfg_write(anchor, cfg): | ||||
|  | ||||
|  | ||||
| def pfctl_table_op(anchor, **kwargs): | ||||
|     ''' | ||||
|     pf table operation | ||||
|     Parameters: | ||||
|     * table: which table to work on | ||||
|     * operation: operation used on the table | ||||
|     * value (optional): value used for the operation | ||||
|     ''' | ||||
|     table = kwargs['table'] | ||||
|     operation = kwargs['operation'] | ||||
|     value = kwargs['value'] if 'value' in kwargs else None | ||||
|  | ||||
| @ -1,10 +1,5 @@ | ||||
| ''' | ||||
| Test fixtures | ||||
| ''' | ||||
| import base64 | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| import base64 | ||||
| from jail2ban import create_app | ||||
|  | ||||
|  | ||||
| @ -26,23 +21,14 @@ def app(): | ||||
|  | ||||
| @pytest.fixture() | ||||
| def client(app): | ||||
|     ''' | ||||
|     Create a synthetic client | ||||
|     ''' | ||||
|     return app.test_client() | ||||
|  | ||||
|  | ||||
| @pytest.fixture() | ||||
| def runner(app): | ||||
|     ''' | ||||
|     Create a synthetic runner | ||||
|     ''' | ||||
|     return app.test_cli_runner() | ||||
|  | ||||
|  | ||||
| @pytest.fixture() | ||||
| def valid_credentials(): | ||||
|     ''' | ||||
|     Mock authentication for the test | ||||
|     ''' | ||||
|     return base64.b64encode(b"test.example.com:testpassword").decode("utf-8") | ||||
|  | ||||
| @ -1,13 +1,7 @@ | ||||
| ''' | ||||
| Test banning | ||||
| ''' | ||||
| from types import SimpleNamespace | ||||
|  | ||||
|  | ||||
| def test_ban_ipv6(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test ban an IPv6 address | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = SimpleNamespace() | ||||
| @ -28,9 +22,6 @@ def test_ban_ipv6(client, mocker, valid_credentials): | ||||
|  | ||||
|  | ||||
| def test_ban_ipv4(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test ban an IPv4 address | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = SimpleNamespace() | ||||
| @ -51,9 +42,6 @@ def test_ban_ipv4(client, mocker, valid_credentials): | ||||
|  | ||||
|  | ||||
| def test_ban_invalid(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test ban an invalid address | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = SimpleNamespace() | ||||
| @ -75,9 +63,6 @@ def test_ban_invalid(client, mocker, valid_credentials): | ||||
|  | ||||
|  | ||||
| def test_unban_ipv6(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test unbanning an IPv6 address | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = SimpleNamespace() | ||||
| @ -98,9 +83,6 @@ def test_unban_ipv6(client, mocker, valid_credentials): | ||||
|  | ||||
|  | ||||
| def test_unban_ipv4(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test unbanning an IPv4 address | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = SimpleNamespace() | ||||
|  | ||||
| @ -1,14 +1,8 @@ | ||||
| ''' | ||||
| Test flushing pf tables | ||||
| ''' | ||||
| from types import SimpleNamespace | ||||
| from subprocess import CalledProcessError | ||||
|  | ||||
|  | ||||
| def test_flush(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test flushing existing entry | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = SimpleNamespace() | ||||
| @ -28,9 +22,6 @@ def test_flush(client, mocker, valid_credentials): | ||||
|  | ||||
|  | ||||
| def test_flush_nonexistent(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test flushing non existing entry | ||||
|     ''' | ||||
|  | ||||
|     cmd = ['/usr/local/bin/sudo', | ||||
|            '/sbin/pfctl', '-a', 'some/anchor', | ||||
| @ -51,9 +42,6 @@ def test_flush_nonexistent(client, mocker, valid_credentials): | ||||
|  | ||||
|  | ||||
| def test_wrong_method(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test invalid method | ||||
|     ''' | ||||
|  | ||||
|     cmd = ['/usr/local/bin/sudo', | ||||
|            '/sbin/pfctl', '-a', 'some/anchor', | ||||
| @ -73,10 +61,7 @@ def test_wrong_method(client, mocker, valid_credentials): | ||||
|     assert response.status_code == 405 | ||||
|  | ||||
|  | ||||
| def test_filenotfound(app, valid_credentials): | ||||
|     ''' | ||||
|     Test for when AUTHFILE cannot be found | ||||
|     ''' | ||||
| def test_filenotfound(app, mocker, valid_credentials): | ||||
|  | ||||
|     app.config.update({ | ||||
|         "AUTHFILE": '../tests/nonexistent-users-test.txt' | ||||
|  | ||||
| @ -1,15 +0,0 @@ | ||||
| ''' | ||||
| Test application health check | ||||
| ''' | ||||
|  | ||||
|  | ||||
| def test_ping(client, valid_credentials): | ||||
|     ''' | ||||
|     Test application health check | ||||
|     ''' | ||||
|  | ||||
|     response = client.get("/ping", | ||||
|                           headers={"Authorization": | ||||
|                                    "Basic " + valid_credentials}) | ||||
|  | ||||
|     assert response.json['operation'] == 'ping' | ||||
| @ -1,9 +1,6 @@ | ||||
| ''' | ||||
| Test various registration scenarios | ||||
| ''' | ||||
| from subprocess import CompletedProcess | ||||
|  | ||||
| PFCTL_STDOUT_LINES = b''' | ||||
| pfctl_stdout_lines = b''' | ||||
| block drop quick proto tcp from <f2b-sendmail-auth> to any port = submission | ||||
| block drop quick proto tcp from <f2b-sendmail-auth> to any port = smtps | ||||
| block drop quick proto tcp from <f2b-sendmail-auth> to any port = smtp | ||||
| @ -11,16 +8,13 @@ block drop quick proto tcp from <f2b-sshd> to any port = ssh | ||||
| block drop quick proto tcp from <f2b-recidive> to any | ||||
| '''.strip() + b'\n' | ||||
|  | ||||
| PFCTL_STDOUT_LINES_SCRATCH = b'table <f2b-dovecot> persist counters\n' \ | ||||
| pfctl_stdout_lines_scratch = b'table <f2b-dovecot> persist counters\n' \ | ||||
|                              b'block quick proto tcp from <f2b-dovecot>' \ | ||||
|                              b' to any port ' \ | ||||
|                              b'{pop3,pop3s,imap,imaps,submission,465,sieve}\n' | ||||
|  | ||||
|  | ||||
| def test_register_unauth(client): | ||||
|     ''' | ||||
|     Test a registration without being authorized | ||||
|     ''' | ||||
|     json_payload = {"port": | ||||
|                     "any port {pop3,pop3s,imap,imaps,submission,465,sieve}", | ||||
|                     "name": "dovecot", "protocol": "tcp"} | ||||
| @ -30,13 +24,10 @@ def test_register_unauth(client): | ||||
|  | ||||
|  | ||||
| def test_unregister_valid(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test unregistration | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = CompletedProcess(args=['true'], returncode=0) | ||||
|     run_res.stdout = PFCTL_STDOUT_LINES | ||||
|     run_res.stdout = pfctl_stdout_lines | ||||
|     run_res.check_returncode = noop | ||||
|  | ||||
|     mocker.patch('jail2ban.pfctl.run', return_value=run_res) | ||||
| @ -54,13 +45,10 @@ def test_unregister_valid(client, mocker, valid_credentials): | ||||
|  | ||||
|  | ||||
| def test_register_valid(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test a registration of a rule | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = CompletedProcess(args=['true'], returncode=0) | ||||
|     run_res.stdout = PFCTL_STDOUT_LINES | ||||
|     run_res.stdout = pfctl_stdout_lines | ||||
|     run_res.check_returncode = noop | ||||
|  | ||||
|     pfctl_run = mocker.patch('jail2ban.pfctl.run', return_value=run_res) | ||||
| @ -75,16 +63,13 @@ def test_register_valid(client, mocker, valid_credentials): | ||||
|                                    "Basic " + valid_credentials}) | ||||
|  | ||||
|     pfctl_run_input_arg = pfctl_run.call_args_list[1][1]['input'] | ||||
|     for existing_line in PFCTL_STDOUT_LINES.splitlines(): | ||||
|     for existing_line in pfctl_stdout_lines.splitlines(): | ||||
|         assert existing_line in pfctl_run_input_arg.splitlines() | ||||
|  | ||||
|     assert response.json['action'] == 'start' | ||||
|  | ||||
|  | ||||
| def test_register_valid_from_scratch(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test from scratch point of view | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = CompletedProcess(args=['true'], returncode=0) | ||||
| @ -103,18 +88,15 @@ def test_register_valid_from_scratch(client, mocker, valid_credentials): | ||||
|                                    "Basic " + valid_credentials}) | ||||
|  | ||||
|     pfctl_run_input_arg = pfctl_run.call_args_list[1][1]['input'] | ||||
|     assert pfctl_run_input_arg == PFCTL_STDOUT_LINES_SCRATCH | ||||
|     assert pfctl_run_input_arg == pfctl_stdout_lines_scratch | ||||
|     assert response.json['action'] == 'start' | ||||
|  | ||||
|  | ||||
| def test_register_invalid(client, mocker, valid_credentials): | ||||
|     ''' | ||||
|     Test a bogus pf command | ||||
|     ''' | ||||
|     def noop(): | ||||
|         pass | ||||
|     run_res = CompletedProcess(args=['true'], returncode=0) | ||||
|     run_res.stdout = PFCTL_STDOUT_LINES | ||||
|     run_res.stdout = pfctl_stdout_lines | ||||
|     run_res.check_returncode = noop | ||||
|  | ||||
|     mocker.patch('jail2ban.pfctl.run', return_value=run_res) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user