GeoIP2 Modul zu Nginx-Proxy-Manager hinzufügen
NGINX-Proxy-Manager wird immer populärer, auch die Funktionen erweitern sich stetig.
Die Software ermöglicht die komplexe Software nginx super einfach per Webinterface zu konfigurieren.
Doch leider fehlt dort das ein oder andere Modul, welches auch ich gerne nutze. Heute soll es um geoip2 gehen.
Das Modul kann genutzt werden, um Länder rückwärts zu IP-Adressen zu ermitteln oder bestimmte Regionen IP-Basiert einzuschränken. Gerade im Bereich Cybersicherheit ist das eine gute Möglichkeit um zumindest mal eine weitere Hürde zu setzen. (Von VPNs mal abgesehen).
Da sich NPM (Nginx-Proxy-Manager) nach Standard-Installation in Docker-Compose befindet, wird dieses Tutorial auch darauf aufbauen.
Vorbereitung
Um die Datenbank für die Rückwärtssuche herunterzuladen und sogar automatisch zu aktualisieren, muss zunächst ein Geoip-Dienst her. Ein gutes Beispiel hierfür ist maxmind.
Dort kann kostenfrei ein Account beantragt werden, und im Anschluss eine Lizenz für die GeoIP-Lite Datenbank generiert werden. Diese brauchen wir im Anschluss. Zu Maxmind geht es hier.
Docker-Compose Anpassungen
Um die Datenbank nun in unser Projekt zu integrieren, müssen wir die docker-compose.yml etwas anpassen.
version: "3" services: npm: image: 'jc21/nginx-proxy-manager:latest' entrypoint: "/data/entrypoint.sh" restart: unless-stopped ports: # These ports are in format <host-port>:<container-port> - '80:80' # Public HTTP Port - '443:443' # Public HTTPS Port - '81:81' # Admin Web Port healthcheck: test: ["CMD", "/bin/check-health"] interval: 30s timeout: 25s environment: DB_MYSQL_HOST: "npm-db" DB_MYSQL_PORT: 3306 DB_MYSQL_USER: "root" DB_MYSQL_PASSWORD: "npm" DB_MYSQL_NAME: "npm" #X_FRAME_OPTIONS: "sameorigin" # Uncomment this if IPv6 is not enabled on your host DISABLE_IPV6: 'true' volumes: - ./modules:/etc/nginx/modules - ./data:/data - ./letsencrypt:/etc/letsencrypt depends_on: - npm-db npm-db: image: 'mariadb' restart: unless-stopped environment: MARIADB_ROOT_PASSWORD: 'npm' MARIADB_DATABASE: 'npm' MARIADB_USER: 'npm' MARIADB_PASSWORD: 'npm' volumes: - ./mysql:/var/lib/mysql geoip-maxmind: image: maxmindinc/geoipupdate:latest restart: unless-stopped volumes: - ./data/geoip2:/usr/share/GeoIP environment: TZ: "Europe/BERLIN" GEOIPUPDATE_ACCOUNT_ID: "xxx" GEOIPUPDATE_LICENSE_KEY: "xxxx" GEOIPUPDATE_EDITION_IDS: "GeoLite2-City GeoLite2-Country GeoLite2-ASN" GEOIPUPDATE_FREQUENCY: 12 GEOIPUPDATE_PRESERVE_FILE_TIMES: 1
Wie man sieht, wurde ein neuer Service in dem compose-file hinzugefügt. Außerdem wurde ein anderer Entrypoint für den npm eingestellt.
Die Environment Variablen müssen wir nun nur noch mit den richtigen Werten anpassen. Danach sollte die Datei immer aktuell gehalten werden.
Nun haben wir die Datei, aber es fehlt noch das Modul in Nginx sowie die Nginx Einstellungen.
GeoIP Compiler Skript
Um nun das Modul zu kompilieren, gibt es zwei Möglichkeiten. Entweder ein eigenes Image nutzen, oder das Image beim Start selbst anpassen.
Dieses Tutorial behandelt das Anpassen des Images vor Start mit manipulation des entrypoints.
Um nun also das Image zu kompilieren muss eine neue Datei z.B. namens „geoip2-compile.sh“ im ./data Verzeichnis angelegt werden. Der ./data Ordner sollte korrekt eingehangen sein, wie oben aus der Compose Datei zu entnehmen ist.
In die Datei kommt folgendes:
#!/bin/bash apt-get install -y wget libpcre3 libpcre3-dev libssl-dev zlib1g-dev ngxversion=openresty-$(/etc/nginx/bin/openresty -v 2>&1|cut -d "/" -f2) mkdir /tmp/compile && cd /tmp/compile wget https://openresty.org/download/$ngxversion.tar.gz tar xvf $ngxversion.tar.gz mkdir /tmp/compile/$ngxversion/modules cd /tmp/compile/$ngxversion/modules git clone https://github.com/leev/ngx_http_geoip2_module.git cd ../bundle/nginx-$(/etc/nginx/bin/openresty -v 2>&1|cut -d "/" -f2|grep -oP '^\d*\.\d*\.\d*') export LUAJIT_LIB="/etc/nginx/luajit/lib/" export LUAJIT_INC="../LuaJIT-*/src/" COMPILEOPTIONS=$(/etc/nginx/bin/openresty -V 2>&1|grep -i "arguments"|cut -d ":" -f2-) eval ./configure $COMPILEOPTIONS --add-dynamic-module=../../modules/ngx_http_geoip2_module make cp -f objs/ngx_stream_geoip2_module.so /etc/nginx/modules/ cp -f objs/ngx_http_geoip2_module.so /etc/nginx/modules/ rm -f /etc/nginx/modules/ngx_geoip2_* touch /etc/nginx/modules/ngx_geoip2_$ngxversion rm -rf /tmp/compile
In dem Skript wird zunächst die Nginx-Version ermittelt sowie einige Pakete zum kompilieren geladen.
Danach wird openrestry sowie das GeoIp2 Modul heruntergeladen und kompiliert.
Danach muss lediglich die Datei ausführbar gemacht werden, mittels
chmod +x ./data/geoip2-compile.sh
Manipulation des Entry-Points
Wir erstellen nun den Entrypoint den wir vorher in dem docker-compose File festgelegt haben.
Also wird die Datei „entrypoint.sh“ unter /data erstellt.
#!/bin/bash apt-get update apt-get install -y libmaxminddb0 libmaxminddb-dev echo "=>Check for GeoIP modules files and version flag..." set -- /etc/nginx/modules/ngx_geoip2_* if [[ -f /etc/nginx/modules/ngx_http_geoip2_module.so && -f /etc/nginx/modules/ngx_stream_geoip2_module.so && -f "$1" ]]; then moduleversion=$(echo $1|cut -d "-" -f2|grep -oP '^\d*\.\d*\.\d*') ngxversion=$(/etc/nginx/bin/openresty -v 2>&1|cut -d "/" -f2|grep -oP '^\d*\.\d*\.\d*') if [ "$moduleversion" != "$ngxversion" ]; then echo "!=>GeoIP modules ($moduleversion) and nginx ($ngxversion) version mismatch !" echo "!=>Starting compilation !" /data/geoip2-compile.sh else echo "=>GeoIP modules found and version match nginx !" fi else echo "!=>No GeoIP module found !" echo "!=>Starting compilation !" /data/geoip2-compile.sh fi apt-get clean rm -rf /var/lib/apt/lists/* set -- /etc/nginx/modules/ngx_geoip2_* moduleversion=$(echo $1|cut -d "-" -f2|grep -oP '^\d*\.\d*\.\d*') ngxversion=$(/etc/nginx/bin/openresty -v 2>&1|cut -d "/" -f2|grep -oP '^\d*\.\d*\.\d*') echo "### GeoIP Modules $moduleversion - Nginx $ngxversion ###" echo "### Starting NPM orignal entrypoint ###" exec /init
Das Skript installiert zunächst die benötigten Bibliotheken für die Prüfung und Installation des Moduls.
Danach wird geprüft ob die Versionen für das Geoip2 Modul und Nginx voneinander abweichen, oder ob Geoip2 sogar nie installiert war.
Falls eines der beiden Fehlschlägt, wird das Modul über die eben erstellte Datei heruntergeladen und nginx damit kompiliert.
Zum Schluss wird der normale init-Prozess von NPM fortgesetzt.
Natürlich machen wir die Datei mittels
chmod +x ./data/entrypoint.sh
wieder ausführbar.
NGINX Konfiguration
Für Nginx erstellen wir unter ./modules ein neues Modul. Sollte der Ordner noch nicht vorhanden sein, kann er ohne Gefahr angelegt werden. Dazu mkdir ./modules ausführen.
Für das Modul legen wir die Datei ‚geoip2.conf‘ an und fügen folgendes an:
load_module /etc/nginx/modules/ngx_http_geoip2_module.so; load_module /etc/nginx/modules/ngx_stream_geoip2_module.so;
Hier werden die geoip2 Module für Streams und HTTP Kontexte geladen.
Für die Serverkonfiguration muss noch eine Direktive abgelegt werden, damit die jeweiligen Länder blockiert werden. Dieser Schritt ist teils optional, sofern man geoip2 anders nutzen möchte.
Unter ./data/nginx/custom legen wir eine neue datei http_top.conf an.
charset utf-8; geoip2 /data/geoip2/GeoLite2-City.mmdb { auto_reload 6h; $geoip2_metadata_country_build metadata build_epoch; $geoip2_data_country_code default=XX source=$remote_addr country iso_code; $geoip2_data_country_name default=- country names de; $geoip2_data_city_name default=- city names de; $geoip2_data_region_name default=- subdivisions 0 names de; } geo $allowed_ip { default no; # on default everything is blocked 192.168.0.0/16 yes; # allowed local ip range 10.0.0.0/8 yes; # allowed local vpn range } map $geoip2_data_country_code $allowed_country { default $allowed_ip; # Führt die direktive darüber aus DE yes; # Nur deutsche Adressen z.B. } log_format proxy_geo escape=json '[$time_local] [Client $remote_addr] [$allowed_country $geoip2_data_country_code $geoip2_data_country_name $geoip2_data_region_name $geoip2_data_city_name] "$http_user_agent" ' '$upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_referer"';
Natürlich kann die Konfiguration beliebig angepasst werden. Im Normalfall ist auch nicht nur deutschland erlaubt, sondern eher nur einige Länder blockiert.
Das lässt sich ändern indem man unter $allowed_ip default auf yes ändert und DE yes in der uunteren Direktive entfernt. Zum blockieren kann dann der Ländercode + no genutzt werden. Beispiel an Indien IN no;
Zu guter letzt muss noch das Standardverhalten für den Filter definiert werden. Das kann auch ausgelassen werden und für jeden Proxy Host im Webinterface unter „Custom“ eingefügt werden. Ich zeige es aber mal global.
Dazu wird eine neue Datei „server_proxy.conf“ in ./data/custom/ angelegt.
Hier definieren wir nun noch die Regel, auf die Anfrage mit einer entsprechenden Antwort zu reagieren.
if ($allowed_country = no) { return 403 "Access denied"; } access_log /data/logs/proxy-host-1_access-geo.log proxy_geo;
Natürlich kann hier das Verhalten auch auf eine HTML Datei verweisen. Das geht beispielsweise mit
if ($allowed_country = no) { root /data/; try_files /geoblock.html /geoblock.html; return 403 "Access denied"; }
Dazu natürlich noch die Datei geoblock.html unter /data anlegen.
Up & Go!
Nun ist alles eingerichtet. Also docker-compose up -d eingeben und logs prüfen ob alles startet. Die Anleitung gilt für die aktuelle Version von npm, nicht für ältere.
Vielen Dank für’s Lesen!
Quellen
- https://domopi.eu/ajouter-la-geoip-a-nginx-proxy-manager/
- https://www.reddit.com/r/nginxproxymanager/comments/sjf1hw/tutorial_nginx_proxy_manager_together_with_geoip2/
- https://shashanksrivastava.medium.com/block-a-website-in-specific-countries-using-nginx-20a651288795
- https://docs.nginx.com/nginx/admin-guide/dynamic-modules/geoip2/
- https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-by-geoip/
- https://stackoverflow.com/questions/30532538/nginx-setting-the-error-page-to-a-static-file