diff options
| author | Jan Tuomi <jan@jantuomi.fi> | 2025-12-29 23:16:22 +0200 |
|---|---|---|
| committer | Jan Tuomi <jan@jantuomi.fi> | 2025-12-29 23:16:22 +0200 |
| commit | 533bc95c751337d5f2952252b0378848cf078357 (patch) | |
| tree | 2bf4ccadc31baf02d2a586fe7357af99787265dc | |
| parent | 39feba1c79c1a0c2743932af35e157ba29d089d1 (diff) | |
Add crawler tarpit
| -rw-r--r-- | tasks/jail_ingress.yml | 42 | ||||
| -rw-r--r-- | templates/ingress/etc_pf.conf.j2 | 5 | ||||
| -rw-r--r-- | templates/ingress/nginx_snippet_ban.inc | 17 | ||||
| -rw-r--r-- | templates/ingress/pf-ban-socket.py | 72 | ||||
| -rw-r--r-- | templates/ingress/usr_local_etc_nginx_nginx.conf.j2 | 4 | ||||
| -rw-r--r-- | templates/ingress/usr_local_etc_rc.d_pf_ban_socket | 43 |
6 files changed, 182 insertions, 1 deletions
diff --git a/tasks/jail_ingress.yml b/tasks/jail_ingress.yml index 2844c12..1848177 100644 --- a/tasks/jail_ingress.yml +++ b/tasks/jail_ingress.yml @@ -39,6 +39,24 @@ shell: jexec ingress sysctl net.inet.ip.forwarding=1 when: ingress_ip_forwarding_enabled.rc != 0 +- name: Install pf-ban-socket.py + copy: + src: templates/ingress/pf-ban-socket.py + dest: /usr/local/jails/containers/ingress/usr/local/bin/pf-ban-socket.py + owner: root + group: wheel + mode: "0755" + register: ingress_pf_ban_socket_py + +- name: Install pf-ban-socket service + copy: + src: templates/ingress/usr_local_etc_rc.d_pf_ban_socket + dest: /usr/local/jails/containers/ingress/usr/local/etc/rc.d/pf_ban_socket + owner: root + group: wheel + mode: "0755" + register: ingress_pf_ban_socket_service + - name: Check if pf enabled shell: jexec ingress sysrc pf_enable | grep -q "YES" register: ingress_pf_enabled @@ -58,6 +76,12 @@ shell: service -j ingress pf start when: ingress_pf_status.rc != 0 +- name: Enable pf-ban-socket service + shell: | + service -j ingress pf_ban_socket enable + service -j ingress pf_ban_socket restart + when: ingress_pf_ban_socket_service.changed or ingress_pf_ban_socket_py.changed + - name: Copy acme-dns-auth.py copy: src: templates/ingress/acme-dns-auth.py @@ -90,6 +114,22 @@ mode: "0644" register: nginx_conf +- name: Create nginx snippets directory + file: + path: "/usr/local/jails/containers/ingress/usr/local/etc/nginx/snippets" + state: directory + owner: root + group: wheel + mode: "0755" + +- name: Include ban.inc + template: + src: ingress/nginx_snippet_ban.inc + dest: /usr/local/jails/containers/ingress/usr/local/etc/nginx/snippets/ban.inc + owner: root + group: wheel + mode: "0644" + - name: Create static directories loop: "{{ ingress_routes | selectattr('static', 'defined') | map(attribute='static') | unique | list }}" file: @@ -109,7 +149,7 @@ - name: Enable and start nginx shell: | service -j ingress nginx enable - service -j ingress nginx onestart + service -j ingress nginx restart when: ingress_nginx_enabled.rc != 0 or nginx_conf.changed - name: Install hetzner_ddns.sh diff --git a/templates/ingress/etc_pf.conf.j2 b/templates/ingress/etc_pf.conf.j2 index feb35d5..c0528e1 100644 --- a/templates/ingress/etc_pf.conf.j2 +++ b/templates/ingress/etc_pf.conf.j2 @@ -5,6 +5,8 @@ lan = "epl{{ jail.num }}b" wan = "epw{{ jail.num }}b" lan_net = "{{ lan_ipv4_network }}" +table <blocked> persist + # Keep PF out of loopback, drop by default if you add blocks later set skip on lo0 set block-policy drop @@ -12,6 +14,9 @@ set block-policy drop # NAT: translate LAN traffic to the WAN interface address nat on $wan from $lan_net to any -> ($wan) +# Block traffic from IPs in the blocked table +block in quick from <blocked> to any + # Allow all outbound traffic from the jail and LAN via both interfaces # NAT will be applied automatically when source is in $lan_net and going out $wan pass out on $wan all keep state diff --git a/templates/ingress/nginx_snippet_ban.inc b/templates/ingress/nginx_snippet_ban.inc new file mode 100644 index 0000000..029b965 --- /dev/null +++ b/templates/ingress/nginx_snippet_ban.inc @@ -0,0 +1,17 @@ +# Trap route +location ^~ /wp-admin/ { + proxy_set_header X-IP $remote_addr; + proxy_method POST; + proxy_pass http://unix:/var/run/pfban/ban.sock:/ban; + + proxy_connect_timeout 50ms; + proxy_send_timeout 50ms; + proxy_read_timeout 50ms; + + # If the socket isn't up yet, still return something + error_page 500 502 503 504 = @ban_fallback; +} + +location @ban_fallback { + return 204; +} diff --git a/templates/ingress/pf-ban-socket.py b/templates/ingress/pf-ban-socket.py new file mode 100644 index 0000000..cbcd80e --- /dev/null +++ b/templates/ingress/pf-ban-socket.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import http.server +import os +import re +import socketserver +import subprocess + +SOCK_PATH = "/var/run/pfban/ban.sock" +PF_TABLE = "blocked" + +# Simple, conservative filter to avoid junk / injection +IP_RE = re.compile(r"^[0-9A-Fa-f:.]{3,}$") + + +def ensure_socket_dir(path: str) -> None: + os.makedirs(os.path.dirname(path), mode=0o755, exist_ok=True) + + +class BanHandler(http.server.BaseHTTPRequestHandler): + # Silence default logging + def log_message(self, format, *args): + return + + def do_POST(self): + if self.path != "/ban": + self.send_response(404) + self.end_headers() + return + + ip = self.headers.get("X-IP", "").strip() + + if ip and IP_RE.match(ip): + subprocess.run( + ["/sbin/pfctl", "-t", PF_TABLE, "-T", "add", ip], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + + self.send_response(204) + self.end_headers() + + def do_GET(self): + self.send_response(405) + self.end_headers() + + +class ThreadingUnixHTTPServer( + socketserver.ThreadingMixIn, + socketserver.UnixStreamServer, +): + daemon_threads = True + + +def main() -> None: + ensure_socket_dir(SOCK_PATH) + + # Remove stale socket if present + try: + os.unlink(SOCK_PATH) + except FileNotFoundError: + pass + + with ThreadingUnixHTTPServer(SOCK_PATH, BanHandler) as httpd: + # Allow nginx workers (www) to connect + os.chmod(SOCK_PATH, 0o660) + + httpd.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/templates/ingress/usr_local_etc_nginx_nginx.conf.j2 b/templates/ingress/usr_local_etc_nginx_nginx.conf.j2 index 456a607..086d7f9 100644 --- a/templates/ingress/usr_local_etc_nginx_nginx.conf.j2 +++ b/templates/ingress/usr_local_etc_nginx_nginx.conf.j2 @@ -22,6 +22,8 @@ http { listen 80 default_server; server_name _; + include /usr/local/etc/nginx/snippets/ban.inc; + location / { return 404; } @@ -32,6 +34,8 @@ http { server_name {{ route.host }}; http2 on; + include /usr/local/etc/nginx/snippets/ban.inc; + listen 443 ssl; listen [::]:443 ssl; diff --git a/templates/ingress/usr_local_etc_rc.d_pf_ban_socket b/templates/ingress/usr_local_etc_rc.d_pf_ban_socket new file mode 100644 index 0000000..d21bc2d --- /dev/null +++ b/templates/ingress/usr_local_etc_rc.d_pf_ban_socket @@ -0,0 +1,43 @@ +#!/bin/sh +# +# PROVIDE: pf_ban_socket +# REQUIRE: NETWORKING pf +# KEYWORD: shutdown +# +# Enable in /etc/rc.conf: +# pf_ban_socket_enable="YES" +# +. /etc/rc.subr + +name="pf_ban_socket" +rcvar=pf_ban_socket_enable + +load_rc_config $name + +: ${pf_ban_socket_command:=/usr/local/bin/pf-ban-socket.py} +: ${pf_ban_socket_log:=/var/log/${name}.log} + +start_cmd="${name}_start" +stop_cmd="${name}_stop" +status_cmd="${name}_status" + +extra_commands="status" + +pf_ban_socket_start() { + /usr/local/bin/logto ${pf_ban_socket_log} ${pf_ban_socket_command} & +} + +pf_ban_socket_status() { + if pgrep -f "pf-ban-socket.py"; then + echo "pf_ban_socket is running" + else + echo "pf_ban_socket is not running" + exit 1 + fi +} + +pf_ban_socket_stop() { + pkill "pf-ban-socket.py" +} + +run_rc_command "$1" |
