diff --git a/.gitignore b/.gitignore index e72499f..7e71edb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ htmlcov/ dist/ build/ *.egg-info/ + +coverage.xml +report.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9adadbd..5b27f88 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ run tests: stage: test - image: python:3.8 + image: python:3.9 script: - pip install pytest pytest-cov pytest-mock pytest-flask - pip install Flask-HTTPAuth @@ -11,7 +11,9 @@ run tests: artifacts: when: always reports: - cobertura: coverage.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml junit: report.xml tags: - docker diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..789156c --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,3 @@ +- 2023.1 + +* Implement #3, a /ping health check endpoint diff --git a/README.md b/README.md index 9866aab..cea12bc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,130 @@ [![pipeline status](https://gitlab.niet.verweg.com/ruben/jail2ban-pf/badges/main/pipeline.svg)](https://gitlab.niet.verweg.com/ruben/jail2ban-pf/-/commits/main) [![coverage report](https://gitlab.niet.verweg.com/ruben/jail2ban-pf/badges/main/coverage.svg)](https://gitlab.niet.verweg.com/ruben/jail2ban-pf/-/commits/main) + +## 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 /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 { , , } + anchor "jail2_fqdn" to { , , } + anchor "jail3_fqdn" to { , , } +} +``` + +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 to any port = pop3 + block drop quick proto tcp from to any port = pop3s + block drop quick proto tcp from to any port = imap + block drop quick proto tcp from to any port = imaps + block drop quick proto tcp from to any port = submission + block drop quick proto tcp from to any port = smtps + block drop quick proto tcp from to any port = sieve + block drop quick proto tcp from to any port = submission + block drop quick proto tcp from to any port = smtps + block drop quick proto tcp from to any port = smtp + block drop quick proto tcp from to any port = ssh + block drop quick proto tcp from 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 --basic -u ':' -XPUT -H 'Content-Type: application/json' -d '{"port":"","name":"","protocol":""}' http://localhost/register +actionstart_on_demand = false +actionstop = curl --unix-socket --basic -u ':' -XDELETE -H 'Content-Type: application/json' -d '{"port":"","name":"","protocol":""}' http://localhost/register +actionflush = curl --unix-socket --basic -u ':' -X GET http://localhost/flush/ +actioncheck = +actionban = curl --unix-socket --basic -u ':' -X PUT -H 'Content-Type: application/json' -d '{"name":"","ip":""}' http://localhost/ban +actionunban = curl --unix-socket --basic -u ':' -X DELETE -H 'Content-Type: application/json' -d '{"name":"","ip":""}' http://localhost/ban +[Init] +protocol = tcp +actiontype = +allports = any +multiport = any 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 +``` diff --git a/jail2ban/__init__.py b/jail2ban/__init__.py index 889963d..da969d1 100644 --- a/jail2ban/__init__.py +++ b/jail2ban/__init__.py @@ -42,6 +42,15 @@ def create_app(): check_password_hash(users.get(username), password): return username + @app.route("/ping", methods=['GET']) + @auth.login_required + def ping(): + remote_user = auth.username() + app.logger.info('Received ping for' + f' anchor f2b-jail/{remote_user}') + return jsonify({'anchor': f'f2b-jail/{remote_user}', + 'operation': 'ping', + 'result': 'pong'}) @app.route("/flush/", methods=['GET']) @auth.login_required def flush(name): diff --git a/tests/test_ping.py b/tests/test_ping.py new file mode 100644 index 0000000..8384d9b --- /dev/null +++ b/tests/test_ping.py @@ -0,0 +1,10 @@ +def test_ping(client, mocker, valid_credentials): + ''' + Test application health check + ''' + + response = client.get("/ping", + headers={"Authorization": + "Basic " + valid_credentials}) + + assert response.json['operation'] == 'ping' diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..9600cb9 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,3 @@ +from jail2ban import create_app + +app = create_app()