Wissenshäppchen

Erlerntes zusammengefasst und aufbereitet
Wenn ich mal wieder experimentiert und mein Wissen erweitert habe, muss ich es dringend aufschreiben.
Aus Notizen entstehen dann irgendwann Niederschriften, die ich euch hier nicht vorenthalten möchte.
SSLH - Ausbruch aus den Firewall-Barrieren
19.10.2019
Linux OpenVPN WebServer

Die klassischen Leiden eines Systemadministrators...

Ich bin gerade mit dem Auto unterwegs und bekomme einen Anruf "Das geht nicht, bestimmt ist auf dem Server was kaputt". Also geht es an der nächsten Rastanlage hinaus und mit dem Laptop ab ins kostenfreie WLAN, das glücklicherweise verfügbar ist.
Anmeldung per SSH auf den Server und nach dem Rechten sehen... doch die Verbindung zu Port 22 wird blockiert - war wohl nix!

Also geht der nächste Griff zum Smartphone und dort schnell einen Hotspot eingerichtet... doch auch hier muss ich passen.
Das Netz ist mal wieder nur mäßig und reicht entweder zum telefonieren oder Hotspot etablieren... da klingelt es schon wieder... Hotspot fällt also auch flach.

Wieder zurück zum kostenfreien WLAN: Wie komme ich nun durch die Firewall hindurch zu meinem gewünschten Ziel?


So oder so ähnlich lässt es sich ganz gut darstellen, was einem Admin gern mal den letzten Nerv raubt. Also begann eine Recherche, wie sich das Ganze optimieren lässt.

Analyse

Beim genaueren Untersuchen von verschiedenen freien Netzwerken ließ sich immer wieder beobachten, dass HTTP Anfragen auf Port 80 gerne gefiltert werden. Ob zur Datenerhebung oder einfach nur zum Schutz vor unerwünschtem Inhalt sei jetzt mal dahingestellt.
Doch Fakt ist: Die Daten werden angesehen und unerwünschter Traffic somit auch effektiv unterbunden. Also keine Lösung, um hier was anderes als HTTP zu senden .

Der zweite Port auf meiner Liste ist 443, denn HTTPS muss schließlich auch möglich sein. Hier gibt es den gewünschten Erfolg, denn da die Daten gesichert per SSL/TLS übertragen werden, sehen für ein Analysetool die Daten alle "gleich" aus, nämlich nach irgendwelchen Daten-Paketen .
Wichtig ist der Aspekt, dass TCP als Transport Layer verwendet werden muss, da eine Filterung in der Firewall nach TCP/UDP weiterhin möglich ist.

Blöd war nun lediglich noch der Aspekt, dass auf meinem Server die Webseiten mit HTTPS angeboten werden, sprich dieser Port bereits belegt ist. Also wird ein Tool benötigt, dass diesen Port teilen kann.
Ursprünglich hatte ich gehofft, dass mein NginX dies könne... doch dies war leider nicht möglich1.

OpenVPN als erste Lösung

Wie sich herausstellte, bietet OpenVPN eine Konfigurationsoption an, um Datenpakete, die nicht für OpenVPN bestimmt sind (der Server einfach nicht versteht) weiterzuleiten.

So wurde mein OpenVPN Server kurzer Hand umkonfiguriert:

proto tcp port 443 port-share 127.0.0.1:4433

Alle Datenpakete würden nun also erst durch den OpenVPN Server gehen und anschließend zu meinem Webserver weitergeleitet werden, den ich auf Port 4433 umgestellt hatte.

Soweit, sogut... bis ich eine zweite Domain auf meinem Server hosten wollte, denn dies war nicht mehr möglich, da OpenVPN den angeforderten Domainnamen nicht weiterleitete und alles auf meiner "Default" Webseite landete .

SSLH - Ziel der Suche

Zum Glück gab es einen findigen Entwickler, der ein kleines Tool schrieb, das genau diese Probleme beheben kann!

SSLH hört nun also auf Port 443, kann durch eine Analyse des Datenstroms erkennen, um welches Protokoll es sich handelt und dann korrekt weiterleiten. Ebenso werden HTTPS Pakete nicht verändert, sodass auch eine Namensauflösung möglich ist

Konfiguriert wird es sehr einfach über eine Konfigurationsdatei, die auf Debian-Systemen unter /etc/default/sslh zu finden ist.
DAEMON_OPTS="--user sslh -- listen 0.0.0.0:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4433 --openvpn 127.0.0.1:1194 --timeout 5 --pidfile /var/run/sslh/sslh.pid"

Hier einmal noch die Parameter aufgeschlüsselt:

  • --user: Der System-Benutzer, unter dem sslh ausgeführt wird
  • --listen: IP und Port auf dem gelauscht werden soll
  • --ssh: Das Ziel zu dem SSH Pakete weitergeleitet werden sollen
  • --ssl: Das Ziel auf dem der HTTPS Server läuft (Hatte ich für OpenVPN verstellt, ist somit einfach geblieben)
  • --openvpn: Das Ziel zu dem OpenVPN Pakete weitergeleitet werden (Wieder der originale Port, aber weiterhin TCP)
  • --timeout: Timeout in Sekunden, bevor die Weiterleitung zu den internen Servern abgebrochen wird
  • --pidfile: Pfad, wo die Prozess-ID abgelegt werden soll

1 Seit Version 1.9.0 kann NginX auch als Proxy für Streams agieren und seit Version 1.11.5 kann auch eine Stream-Server Unterscheidung per SNI durchgeführt werden (SSL PreRead).

NginX Basic-Auth mit Datenbank-Backend
22.08.2019
NginX Auth Lua PHP

Vor einigen Jahren bin ich von Apache zu NginX umgestiegen, da ich meine Webseiten in Docker-Container verpackt hatte und der WebServer zum Proxy für den jeweiligen Container wurde.
Hier hat NginX einfach die Nase vorne und glänzt durch eine deutlich einfachere Konfiguration. Zudem lassen sich auch andere Streams durchleiten und nebenbei mittels SSL Zertifikat absichern (z.B. ein TCP-Stream).

Basic-Auth für NginX

Unter Apache ist die Absicherung von Verzeichnissen mittels Basic-Auth sehr einfach zu realisieren, wenn eine .htaccess-Datei verwendet wird. Eine andere Art habe ich damals nie benutzt.
Diese Authentifizierung ist auch unter NginX relativ einfach möglich, benötigt jedoch die Installation der apache2-utils.

Installation htpasswd und erzeugen einer htpasswd Datei:

# apt-get install apache2-utils
# htpasswd /etc/nginx/auth/htpasswd $user

Mit dieser Datei kann nun die Einrichtung stattfinden: z.B. /etc/nginx/sites-enabled/default

server { # ... sonstige Einrichtung des Server-Bereichs location /data { # ... Einrichtung des Ordners # Hier wird Basic-Auth aktiviert (und das Realm gesetzt) auth_basic "Restricted Area"; # Hier wird angegeben, wo die Benutzer und Passwörter gespeichert sind auth_basic_user_file /etc/nginx/auth/htpasswd; location /public { # Auf diesen Ordner dürfen wieder alle zugreifen auth_basic off; } } }

Mehr Flexibilität mit Lua

Zur Erweiterung der Serverlogik mit komplexeren Abfragen steht in NginX ein Lua-Modul zur Verfügung. Dies muss vermutlich noch installiert werden:

# apt-get install libnginx-mod-http-lua lua-nginx-string

Im Anschluss kann die Konfiguration so geändert werden, dass ein Lua-Script die Basic-Auth Prüfung übernimmt.

server { # ... location /restricted { # Hier wird das Realm gesetzt set $lua_realm 'Restricted Area'; # Wir definieren noch eine bestimmte Gruppe für die spätere Abfrage set $lua_group 'restricted group'; # Hier wird die Authentifizierung an das Lua-Script übergeben access_by_lua_file /etc/nginx/auth/auth.lua; # ... restliche Einrichtung } }

Wer nun der Sprache Lua mächtig ist, kann hergehen und mittels Lua eine vollständige Authentifizierung auf Basis von Basic-Auth vornehmen.
Ich für meinen Teil wollte gerne einen Benutzer-Abgleich über MySQL realisieren. Doch da ich Lua jetzt nicht so wirklich beherrsche, habe ich recherchiert, wie man andere Scripte (z.B. Bash oder eben PHP) ausführen kann.

So wird nun in Lua nun die Basic-Auth Implementierung vorgenommen und der Abgleich mit der Datenbank erledigt ein PHP-Script im Hintergrund.
/etc/nginx/auth/auth.lua:

-- Basic-Auth with lua function authenticate() -- does the auth header exist? local header = ngx.req.get_headers()['Authorization'] if header == nil or header:find(' ') == nil then return false end -- check whether its a Basic-Auth local divider = header:find(' ') if header:sub(0, divider-1) ~= 'Basic' then return false end -- try to decode the Basic-Auth local auth = ngx.decode_base64(header:sub(divider+1)) if auth == nil or auth:find(':') == nil then return false end -- parse the auth information divider = auth:find(':') local username = auth:sub(0, divider-1) local password = auth:sub(divider+1) -- now my lua knowledge is done... -- so request something to validate the information local handle = io.popen(string.format('php /etc/nginx/auth/auth.php %s %s %s', ngx.var.lua_group, username, password)) local response = handle:read("*a") handle:close() -- check the script response if response == nil or response ~= 'OK' then return false end return true end local isValid = authenticate() if not isValid then ngx.header.content_type = 'text/plain' ngx.header.www_authenticate = string.format('Basic realm="%s"', ngx.var.lua_realm) ngx.status = ngx.HTTP_UNAUTHORIZED ngx.say(string.format('ERROR 401: Unauthorized\nArea: %s', ngx.var.lua_realm)) end
/etc/nginx/auth/auth.php
<?php // The MySQL database information $db_host = 'localhost'; $db_port = 3306; $db_name = 'nginx-auth'; $db_user = 'auth-user'; $db_pass = 'some-password'; $group = ''; $user = ''; $password = ''; if (!empty($argv[1])) { $group = trim($argv[1]); } if (!empty($argv[2])) { $user = trim($argv[2]); } if (!empty($argv[3])) { $password = trim($argv[3]); } try { $db = new PDO('mysql:host='.$db_host.';port='.$db_port.';dbname='.$db_name.';', $db_user, $db_pass); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTIION); $query = 'SELECT 1 AS OK FROM `nginx-auth-table` WHERE `is_active` = 1 AND `group` = :group AND `username` = :user AND `password` = SHA2(:pass, 256);'; $stmt = $db->prepare($query); $stmt->bindValue(':group', $group); $stmt->bindValue(':user', $user); $stmt->bindValue(':pass', $password); $stmt->execute(); $res = $stmt->fetchAll(PDO::FETCH_OBJ); if (count($res) == 1) { echo 'OK'; exit(0); } } catch (Exception $ex) { echo 'ERROR: '.$ex->getMessage(); exit(1); } echo 'ERROR: Invalid credentials'; exit(1); ?>

Ein kurzes Fazit

Ich persönlich bin immer wieder sehr erstaunt darüber, wie groß die Palette an Optionen ist, mit der man NginX erweitern kann. Dabei muss man sich zwar manchmal ein paar Tricks überlegen, doch letzten Endes gibt es immer eine Lösung.
Die Idee über Lua ein beliebiges anderes Script auszuführen eröffnet viele neue Möglichkeiten, wie man die Authentifizierung lösen könnte (PHP war lediglich meine Wahl).

.NET Core und das Schreiben von Log-Files
09.08.2019
.NET

Mit .NET Core hat in das Logging von Anwendungen das Interface ILogger Einzug erhalten.

Es gibt verschiedene Implementierungen, die durch Microsoft angeboten werden.
So kann man direkt auf die Console schreiben oder auch in das Windows EventLog. Doch das Schreiben der Informationen ganz klassisch in eine Datei wurde nicht bedacht.

Also habe ich mir das Interface nun einmal angesehen und eine eigene kleine Implementierung für das gute alte Schreiben der Informationen in Dateien erstellt.

Soweit, so gut. Doch beim Beenden der Anwendung war das Log-File nicht vollständig geschrieben, da die reine Implementierung des ILogger Interface keine saubere Terminierung vorsieht. So musste noch das IDisposable Interface implementiert werden, das auf das Fertigschreiben der Datei wartet und dann erst beendet.