OpenAPI Specification / SwaggerHub – nett gemeint und meistens gut

Fast jeder Entwickler der sich im Web bewegt, wird derzeit früher oder später Kontakt mit der OpenAPI Initiative  machen. Ziel ist es die Spezifikation von (primär REST)-Schnittstellen zu vereinheitlichen und dabei eine Version zu erzeugen die auch maschinenlesbar ist. Damit eröffnen sich viele Möglichkeiten zur Validierung, Dokumentation und wo nötig auch Code-Generierung wenn man entsprechende Frameworks einsetzt und externe Rest-APIs anspricht. Soweit so gut.

Die Spezifikation ist mittlerweile recht umfangreich und beschreibt sehr viele der Use-Cases recht gut. Insbesondere die Methodik zur Beschreibung von Endpunkten ist in meinen Augen wirklich gut gelungen. Nun gilt aber wie immer: Wo Licht ist, ist auch Schatten. Gerade im Umfeld der Schema-Definitionen bin ich auf eine Lästigkeit gestoßen, für die ich noch keine wirklich brauchbare Lösung gefunden habe. Im Wesentlichen geht es dabei um dynamische indizierte Arrays bzw. Objekte. Continue reading

PowerDNS mit Pi-Hole auf RasPi

Eines der etwas länger schon mal angedachten Projekte, welches ich dann aufgrund Zeitmangels und auch etwas Frustration in die Ecke gelegt hatte ist ein eigener, lokaler DNS-Server den man auch schnell aktualisieren kann – wer häufiger in verschiedenen Projekten arbeitet weiß es zu schätzen wenn man den Stall der Entwicklungs-Container nicht per IP-Adresse auswendig lernen muss, sondern ihn per sprechenden Namen ansprechen kann. In diesem Post geht es erst einmal darum die Grundlagen zu schaffen, auch das ist bereits etwas mehr Arbeit als ich gedacht hätte.Wichtige Eckpunkte / Material zum Nachbauen:

  • Raspberry Pi (inklusive Netzteil)
  • als DNS-Server kommt PowerDNS zum Einsatz
  • MySQL/MariaDB als Datenbank für die DNS-Einträge
  • DNS-Dist als Loadbalancer für DNS-Queries
  • PiHole als Resolver mit Blacklist
  • Docker als Virtualisierung für alle Services
  • IPv6 als primäres Netzwerkprotokoll, IPv4 nur als Fallback

Continue reading

MySQL >= 8.x oder MariaDB – RegularExpression Word-Bounderies – Workaround

Ich hatte gerade ein echtes Aha-Erlebnis in Sachen Regular-Expressions. In denen bin ich sonst eigentlich recht fit, aber man lernt ja bekanntlich nie aus.

Folgende Konstrukte sind in einem SQL-Statement fehlgeschlagen, nachdem ich eine aktuelle MariaDB bzw. eine MySQL 8 Datenbank im Einsatz hatte.


[[:<:]]4711[[:>:]]

Selbst mir war diese Syntax in der Form nicht bekannt. Die beiden Pattern beziehen sich auf Word-Bounderies. Mit anderen Worten wir suchen ob in einem String die Folge 4711 enthalten ist, aber nur exakt diese, Folgen wie 47112 sollen nicht gefunden werden. Im konkreten Beispiel ging es um eine kommagetrennte Liste von Zahlen die zu durchsuchen ist. Dabei kann die 4711 aber auch am Anfang oder am Ende stehen. In MySQL 8 und höher ist der Unterstützung für diese Syntax weggefallen, aus welchem Grunde auch immer. Aber es gibt Ersatz:


[^[:word:]]4711[^[:word:]]

Auch hier suchen wir die Ziffernfolge umrandet von etwas was kein Wort ist – das kann ein Zeilenende, Zeilenanfang, oder etwas anderes als alphanumerisch + “_” sein [:word:] == [:alphanum:_].

 

Vorinitialisierter FTP-Server in Docker

Bevor hier jetzt einige gleich aufschreien für was man eine derartig krude Sache überhaupt brauchen kann, es gibt durchaus Fälle in denen etwas derartiges nützlich ist. Mir ist durchaus bewusst, dass FTP (ohne TLS) kein wirklich aktueller Standard mehr ist und auch das Protokoll mit der “out-of-band”-Regelung mit getrennten Ports etwas altbacken wirkt. Besser wäre es natürlich hier auf SFTP (welches als Basis SSH nutzt und somit direkt Verschlüsselung anbietet) zu nutzen. Zudem wurde ja auch schon mehrfach angekündigt, dass immer weniger Browser FTP nativ unterstützen wollen. Das Protokoll hat sich schlichtweg etwas überlebt, aber es ist leider in ganz vielen Bereichen immer noch im Einsatz – und genau dafür benötigte ich diese Konstellation: Für ein Projekt bei dem ich im Rahmen der Tests sicherstellen muss, dass FTP-Verbindungen bzw. die Verarbeitung von per FTP bereit gestellten Dateien wie erwartet funktionieren. Bisher habe ich mir dafür immer eine virtuelle Maschine in der Hinterhand gehalten und diese dann entsprechend hochgefahren bzw. konfiguriert. Das geht auch auf einem CI/CD-System wie Jenkins, aber schicker wäre es doch wenn man einfach ein Image starten kann (vorzugsweise in einem gekappselten  Netzwerk) und die Konfiguration und ggf. sogar schon die notwendigen Testdaten liegen fix und fertig bereit.

Insgesamt muss ich leider sagen: Diese Nuss hat mich mehr Nerven gekostet als gedacht.

<pre>FROM fauria/vsftpd

COPY ftp-custom-dir-list /tmp/
COPY ftp-user-list /etc/vsftpd/user-list
COPY init_users_and_directories.sh /tmp/
COPY vsftpd.conf /etc/vsftpd/vsftpd.conf

RUN chmod +x /tmp/init_users_and_directories.sh && mkdir -p /var/log/vsftpd/
RUN /tmp/init_users_and_directories.sh

ENTRYPOINT ["/usr/sbin/vsftpd"]
CMD ["/etc/vsftpd/vsftpd.conf"]

Hier sind einige Kleinigkeiten versteckt die man beachten muss: Das aufgerufene Skript kümmert sich letztlich während des Bauens des Images darum, dass die notwendigen Verzeichnisse angelegt werden und die Benutzerliste in eine Berkeley-DB überführt wird, damit sie später zur Authentifizierung genutzt werden kann. Das Format gilt es hierbei zu beachten: Immer abwechselnd Benutzername und Passwort in einer Zeile.

<pre>db_load -T -t hash -f /etc/vsftpd/userlist /etc/vsftpd/virtual_users.db</pre>

Es gibt noch einige Kleinigkeiten, die einem ggf. das Leben schwer machen. Zum einen ist VSFTP nicht wirklich ein gesprächiges Programm, ich habe auch mit sehr viel Suchen keine Möglichkeit gefunden in irgendeinem Log mehr als die FTP-Verbindungen zu finden, geschweige denn irgendwelche Aussagen warum es initial mit der Authentifizierung nicht so ganz klappen wollte.

Authentifizierung ist ein gutes Stichwort: Wie man hier sieht habe ich auf die Verschlüsselung der Passwörter verzichtet. Für das angestrebte Ziel einen Testserver zu haben auf dem man Dateien ablegen zum Testen ablegen kann und den man hinterher wieder wegwirft ist das gerade noch zu verschmerzen, für die Produktion ist ein deratiges Vorgehen definitiv nicht geeignet! Hier darf man sich dann aber mit den Wirren von PAM (plugable authentification module) herum schlagen, auch dieses Tool ist zumindest im Docker-Umfeld eher schweigsam und verrät sicherheitsgerichtet nicht wirklich was gerade schiefläuft.

Wer sich das von mir verwendete Basis-Image anschaut wird feststellen, dass es eigentlich auch noch diverse Parameter gibt, die man dem Container mitgeben könnte. Das habe ich allerdings lahmgelegt, denn für meinen Anwendungsfall benötige ich ja stets ein einheitliches Set an Benutzern und Verzeichnissen. Selbst wenn ich den Container beim Testen mit docker-compose hochfahren würde, bekäme ich so nicht die Kohäsion die ich mir speziell für diesen Fall wünschen würde: Einmal bauen und alles ist für den spezifischen Anwendungsfall schon vorbereitet.

Damit es mit verschlüsselten Passwörtern klappt, muss man folgende Punkte beachten:

Passwörter kann man sich mittels folgendem Befehl in Hashes umwandeln lassen:


openssl passwd -1 meintollesPasswort

Wenn man es stärker automatisieren möchte kann man sich das natürlich auch in ein Skript packen welches die Datei der User durchackert und das automatisch macht. Ganz wichtig ist aber dann auch die Anpassung der PAM-Konfiguration

<pre>#%PAM-1.0
auth   required   pam_userdb.so  db=/etc/vsftpd/virtual_users crypt=crypt
account    required   pam_userdb.so  db=/etc/vsftpd/virtual_users crypt=crypt
session    required   pam_loginuid.so

Man muss PAM hier mitteilen, dass die Passwörter in verschlüsselter Form vorliegen. Nicht wundern, dass ich oben nicht mit crypt als Methode im Zusammenhang mit openssl gearbeitet habe: Crypt ist dort die Urform des Hash-Algorithmus für Passwörter. Aus historischen Gründen dürfen Passwörter dort nur 8 Zeichen lang werden. Hier sei nochmals darauf hingewiesen, dies betrifft nur die Art und Weise wie die Passwörter auf dem Server gespeichert werden, die Übertragung bei FTP erfolgt weiterhin ohne Veschlüsselung – hier muss man ggf. mit VPN oder anderen Mechanismen nachhelfen (oder eben gleich SFTP einsetzen).

Wer genau aufgepasst hat, wird feststellen, dass in der PAM-Konfiguration scheinbar ein Fehler ist (und dieser hat mich lange beschäftigt). Es ist aber richtig: die Datei muss auf .db enden, die Referenz für pam_userdb.so muss aber ohne dieses Suffix erfolgen. Wer auch immer sich das ausgedacht hat – wirklich gut dokumentiert ist es auf keinen Fall.

 

IPv6 – Wer hat Angst vor langen IP-Adressen?

Nachdem ich ja in einigen Posts nunmehr beschrieben habe wie man sich einen eigenen DNS-Server unter Docker mit samt IPv6 hinstellen kann (inkl. einer Rückwärtskompatibilität für IPv4) habe ich mich gefragt was denn noch so problematisch ist am Umstieg auf IPv6. Mein derzeitigen, subjektiven Ergebnisse fasse ich hier einmal zusammen. Vorsicht dieser Post kann ggf. Spuren von Zynismus, Sarkasmus und auch einen Schuß Frustration meinerseits enthalten.

Natürlich ist derartige Kritik leicht daher geredet, daher werde ich auch einige Lösungen bzw. Lösungsansätze vorstellen.

Continue reading

DNS mit Docker Teil III – dnsdist als Router / Loadbalancer

Wer meine bisherigen Posts zu Docker und DNS verfolgt hat, weiß: Da steht noch was aus (ich komme nur momentan nicht immer ganz zeitnah dazu meine Erfahrungen zusammen zu schreiben.

Was bisher geschah:

Was jetzt noch aussteht ist eine Sache die mit der Microservice-Architektur von PowerDNS im Vergleich zu “klassischen” Nameservern wie BIND zusammen hängt: BIND ist monolitisch und kümmert sich um jegliche Anfrage die gestellt wird. In PowerDNS hat man diese Dinge wie oben bereits angedeutet aufgetrennt: Einen Spezialisten zum Auflösen von externen Einträgen (Recursor) und einen weiteren Prozess der sich um die Sachen kümmert für die man selbst verantwortlich ist (authorative). Continue reading

Anwendungsfall für MSSQL in Docker unter Linux

Wie in einem meiner letzten Beiträge beschrieben, kann man mittlerweile einen MSSQL-Server auch unter Linux in Docker laufen lassen. Nun gilt es noch die berechtigte Frage zu klären: Weshalb sollte man sowas tun? Gibt es für dieses Konstrukt überhaupt sinnvolle Anwendungsfälle? Für alle die es eilig haben: Ja die gibt es. Wer es im Detail wissen will sollte weiter lesen.

Der Anwendungsfall für mich hängt indirekt auch mit der Jahreszeit zusammen, wie in jedem Jahr fällt mir als Schriftführer unseres Sportvereins die Aufgabe zu, mich um den Versand der Jahreszeitschrift zu kümmern. Man könnte es auch eine Alternative zu Weihnachtskarten nennen. Für die Verwaltung des Vereins haben wir eine fertige Software die leider nicht open source ist, geschweige denn dass sie nativ unter Linux laufen würde. Aus Sicht eines Anwendungsentwicklers ist die Software nicht gerade optimal, aber sie ist nunmal da und die anderen Mitglieder des Vorstands sind damit vertraut. Mal eben etwas zu ändern ist an dieser Stelle nicht drin.

Continue reading

MSSQL-Server mit Docker unter Linux

Der Titel hört sich im ersten Moment etwas schräg an – insbesondere wenn man zu den etwas älteren Semestern gehört, die noch die Anfeindungen Microsofts in Richtung Linux kennen gelernt haben. Unter anderem mit Werbesprüchen wie “ein freies Betriebssystem hat nicht nur Vorteile” – verbunden mit einem Bild welches Piguine als Repräsentanten von Linux zeigte mit nicht ganz passenden Köpfen. Das war 2001 und geworben wurde für Windows 2000. Die Zeiten haben sich geändert und mittlerweile gibt es sogar offizieller Weise ein “Windows Subsystem for Linux” (WSL) und auch erfreuliche Entwicklungen hin zu einer deutlich brauchbareren (Power-)Shell als es die altgediente command.com bzw. cmd.exe je waren. Das Ganze kommt nicht ganz von ungefähr – mit der Microsoft Azure Cloud mussten Werkzeuge her mit denen man Systeme vernünftig remote administrieren kann – da ist es durchaus legitim einmal zu schauen was bei anderen Betriebssystemen für den Erfolg mit verantwortlich ist.

Continue reading

DNS mit Docker Teil II – Recusor

Im letzten Post hatte ich den Anfang des Setups eines vollständigen DNS-Servers mit Docker und PowerDNS beschrieben. Nun folgen die nächsten Schritte. Eigentlich wollte ich diesen Post schon geraume Zeit fertig machen aber besser spät als nie.

PowerDNS-Recursor

Wie beschrieben teilt sich PowerDNS in drei Teile auf, im klassischen BIND-Server ist es ein monolitscher Block (bei dem man per Konfiguration ggf. einzelne Teile stillegen kann).
Der Recursor von PowerDNS übernimmt die Aufgabe der Namensauflösung für Anfragen die der Server nicht selbst auflösen kann. Diese Funktion braucht man im ersten Augenblick ür einen reinenNameserver nicht, aber in der Regel laufen auf dem Server ja auch noch andere Prozesse oder der DNS-Server dient für Clients im Netzwerk als Schnittstelle zum Netz. Dabei legt er praktischer Weise auch noch einen Cache an. Etwas ganz ähnliches macht ganz häufig Router für einen mit, wobei dieser die Anfragen initial dann auch nur an den nächst größeren Nameserver (z.B. beim Provider) abfragt. Ein solcher Cache ist auch für andere Serverprozesse sinnvoll, die viele DNS-Abfragen durchführen. Ein klassicher Fall hierfür ist der Mailserver, wenn er Echtzteit-Blacklists verwendet – diese teilen ihre Einschätzung anhand bestimmter DNS-Antworten mit (das ist schneller und effizienter als http(s), da tcp sondern nur eine upd-Verbindung aufgebaut wird.

Der Recursor ist auch recht schnell eingerichtet, das Dockerfile enthält auch hier keine großen Überraschungen bereit


FROM pschiffe/pdns-recursor:alpine
COPY recursor.conf /etc/pdns/recursor.conf
ENTRYPOINT [ "/usr/sbin/pdns_recursor" ]

Wobei das Config-File wie folgt aussieht:


allow-from=127.0.0.0/8,::1/128,2001:db8:ffff:ffff::/64,192.168.1.0/16
any-to-tcp=yes
#api-key=xxxx
#api-readonly=yes
daemon=no
local-address=::,0.0.0.0
local-port=53
lowercase-outgoing=yes
setgid=recursor
setuid=recursor

das einzig spannende an dieser Stelle ist die oberste Zeile “allow-from” – sie legt fest von welchen Adressen aus überhaupt auf den Server zugegriffen werden kann. Hier führt man natürlich die Localhost-Adressen an, genauso wie die eigene IPv6-Range (ich erlaube es dem gesamten Bereich der mir zugewiesen ist, damit auch andere auf dem Server laufende Prozesse den DNS-Service nutzen können). Zudem erlaube ich dem eigenen Docker-Netzwerk auf IPv4-Basis den Zugriff, das ist aber eher eine Fallback-Möglichkeit. Man sollte sich überlegen einen Recursor generell für jedermann zu öffnen. Der Recursor kann das durchaus ab, aber es besteht leider die Gefahr, dass er missbraucht wird und dann andere Systeme per Denial-of-Service angreift. Hier kann man ggf. mit dem Load-Balancer vornedran die Menge der Requests von extern limitieren, aber selbst dann bekommt man recht bald wahrscheinlich eine Mail vom BSI die auf die Problematik hinweist – ich hatte es beim Aufbauen mit einem Load-Limit auf 10 externe Anfragen pro Minute versucht, aber der Bot der das automatisiert prüft ist etwas stumpfsinnig und prüft nur ob man generell Abfragen für externe Domains an den Port 53 schicken darf.  Wie sinnvoll derartige Prüfungen sind, darf jeder für sich selbst entscheiden.
Auch diese Definition baut man zu einem Image:


docker build -t powerdns-recursor .

Danach startet man den Server ebenfalls im eingerichteten Docker-Netzwerk für die DNS-Services, natürlich auch hier wieder mit einer festen IPv6-Adresse da man auch hier sonst recht schnell ein Henne-Ei-Problem bekommt was die Namensauflösung betrifft.


docker run --ip6 2001:db8:ffff:ffff:ffff:ffff:0053:4 --network dns --name power_dns_recursor -d powerdns-recursor

Es kann natürlich auch als Dokumentation die vergebenen IP-Adressen mit Namen in der eingenen Datenbank in der eingenen DNS-Zone zu hinterlegen z.B. in einem eigenen DNS-Abschnitt wie dns.my.example.com mit sprechenden Namen: primary,secondary,recursor – denn so richtig einprägsam waren schon IPv4-Adressen nur bedingt und mit der zusätzlichen Länge wird das bei Ipv6 nicht besser.


INSERT INTO `records` VALUES (4, 1, 'authorative.my.example.com', 'AAAA', '2001:db8:ffff:ffff:ffff:ffff:0053:3', 3600, 0, NULL, 0, NULL, 1);

INSERT INTO `records` VALUES (4, 1, 'recursor.my.example.com', 'AAAA', '2001:db8:ffff:ffff:ffff:ffff:0053:4', 3600, 0, NULL, 0, NULL, 1);

Den Recursor kann man dann natürlich auch testen und nach beliebigen nicht eigenen Adressen wie www.google.com, www.amazon.de, www.meine-website.org
{{Beispiel mit dig}}


			

Boot-Trick für Linux-Systeme wenn USB per BIOS nicht geht

Da wollte ich nur einen etwas älteren Laptop wieder soweit flott bekommen. Also die eigenen Daten sichern und eine Neuinstallation erzwingen. Leichter gesagt als getan. Backup dank ein wenig Ordnung im System und der Tatsache, dass es ein Linux-System ist war recht flott erledigt. Einfach die betreffenden Home-Verzeichnisse auf ein Backup-Medium (Netzwerk, externe Platte oder Stick) packen und schon ist man so gut wie fertig. Die Einstellungen des Systems waren mir in dem Fall nicht weiter wichtig – die waren ohnehin etwas “verbastelt”, da kam die Neuinstallation ja gerade recht.

Kniffelig wurde es dann aber diesmal beim Neuinstallieren des Betriebssystems: Von wegen einfach USB-Stick einstöpseln – Reboot und gut – leider nicht! Denn das alte Gerät verweigert sich jeglichem Boot-Versucht von einem USB-Stick und auch die SD-Card findet das System nicht beachtenswert wenn es ums Booten geht.

Nächster Versuch: Klassisch eine Boot-CD brennen – scheitert leider daran, dass die meisten Images mittlerweile größer als eine CD mit ihren 700MB sind. Dabei wollte ich schon aus gutem Grund ein ressourcenschonendes Lubuntu installieren (war auch vorher bereits drauf). Älter CD-Images waren auch nicht so ohne weiteres aufzutreiben. DVDs kann das alte optische Laufwerk nach einigen Versuchen nicht lesen (es steht leider auch nichts auf der Laufwerksklappe). Falls jetzt jemand fragt was man mit derart alten Systemen noch anfangen kann: Sie sind als Notbehelf in der ein oder anderen Situation doch ganz nützlich – vor allem wenn die Gefahr besteht, dass man das Gerät im Zweifel nicht wieder sieht oder es irreparabel beschädigt wird (das man auf derartigen Systemen auch keine privaten Daten lagert sollte jedem klar sein).

Jetzt war ich schon fast davor einen PXE-Server zu installieren und dann das System hoffentlich per Netzwerkboot auf die Spur zu bekommen, aber das ist ja doch etwas Umstand. Daher war das der absolut letzte Notnagel den ich gehen wollte. In der Zwischenzeit hatte ich auch irgendwo im Netz noch eine uralte Lubuntu-ISO-Datei gefunden (14.x – steinalt auf gut deutsch). Doch der Download zog sich etwas in die Länge. Mehr als genügend Zeit sich noch mit anderen Optionen zu beschäftigen.

Eigentlich ist die Lösung recht simpel: Was das BIOS nicht kann, kann das OS bzw. der Bootloader vielleicht dann doch. Da auf dem System ja bereits ein Linux samt Grub als Bootloader vorhanden war geht es dann wie folgt, nachdem man aus dem Bootmenü mit “c” in den Consolen-Modus gewechselt ist.

ls
set root=(hd1)
chainloader +1
boot

Wobei ls einem die möglichen vorhandenen Partitionen listet und man dann bei set root=…. natürlich die passende Partition angeben muss – hier hilft im Zweifel ein wenig ausprobieren. Mehr als nicht booten oder das alte OS booten kann ja nicht schiefgehen. Danke an die hilfreichen Kommentare hier.