SASL DIGEST-MD5 mit slapd bzw. OpenLDAP

SASL DIGEST-MD5 mit slapd bzw. OpenLDAP

Eine kurze Anleitung wie man einem slapd von OpenLDAP dazu bringt Benutzer via SASL mit DIGEST-MD5 anmelden zu lassen. Selten in einem Titel bereits so viele Akronyme gehabt. Deswegen der Reihe nach:

Aber das alles ist unter Umständen ja bereits bekannt - warum sonst solltest Du Dich auf diese Seite verirrt haben? Was mich immer dran geärgert hat ist, dass aus der OpenLDAP Dokumentation zwar hervor geht wie alles funktioniert, ein praktisches Einrichtungsbeispiel aber schlichtweg fehlt. Dies möchte ich an dieser Stelle nach holen.

Und als Beispiel soll an dieser Stelle DIGEST-MD5 auch völlig ausreichen - auf die anderen Möglichkeiten will ich hier in diesem Posting gar nicht weiter eingehen.

Für die ungeduldigen Leser: Ganz unten ist mein Dockerfile angefügt sowie die ldif Datei mit der man cn=config anpassen kann sobald der slapd läuft. Docker bzw. ldapadd usw. ist noch nicht ganz Deins? In der Dockerfile stehen oben in den Kommentaren passende Beispiele wie das laufen könnte.

Docker

Um solche Sachen zu testen nutze ich ganz gern einen Docker Container. Ich mag es einfach, dass ich Dinge testen kann ohne groß irgendwas an Software installieren zu müssen. Und wenn man fertig ist kann man den Container bzw. das Image einfach so löschen und alles ist wieder spurlos verschwunden als ob es nie dagewesen wäre.

Natürlich geht das ganze auch ohne Docker. Dazu einfach die Schritte aus der Dockerfile sinngemäß auf einem beliebigen slapd übernehmen.

Umsetzung

Um rasch einen OpenLDAP Docker Container zu bekommen verwende ich ganz gerne dieses Image: https://hub.docker.com/r/osixia/openldap/. Das ist Debian basiert, klein, schlank und richtet einen grundlegend vorkonfigurierten slapd Server ein. Wenn es schnell gehen muss oder man sich einfach die Arbeit sparen will selbst ein Image anzulegen: Meiner Meinung nach ein gutes Ausgangsimage!

Und was mache ich im Dockerfile? Wie gehabt, diese Schritte kann man ohne Docker sinngemäß auf dem Linux bzw. der Linux VM durchführen.

  1. Ich installiere das Debian-Paket sasl2-bin. Durch dieses Paket werden diverse Hilfstools rund um SASL installiert.
  2. Als Beispiel lege ich einen SASL Benutzer an sasladmin@slpad.ldap.
  3. Der Benutzer unter welchem Debian slapd laufen lässt wird in die sasl Gruppe mit aufgenommen. Eben damit er die sasldb auch tatsächlich lesen kann 😉
  4. Die SASL Konfiguration für OpenLDAP /etc/ldap/sasl2/slapd.conf wird angelegt und entsprechend mit den notwendigen Dateisystemsrechten versehen.
Entgegen dem Eindruck der aus der OpenLDAP Dokumentation entstehen könnte braucht man für DIGEST-MD5 keinen laufenden saslauthd. Das ist auch der Grund warum ich diesen Passus in der SASL Konfiguration übersprungen habe.

slapd.conf bzw. cn=config

Nun fehlen noch Änderungen an der slapd.conf selbst. Wer das alte Konfigschema verwendet muss die entsprechende slapd.conf Datei sinngemäß bearbeiten, für alle mit neuem cn=config Schema liegt die passende ldif Datei mit dabei. Das von mir verwendete Image verwendet bereits cn=config und da das Dockerfile den slapd-Server erst noch konfiguriert läuft dieser nunmal auch noch nicht. Somit ist die ldif über die üblchen Wege im LDAP-Server mit aufzunehmen nach dem der Container am laufen ist.

Und das steht im config.ldif drin:

  1. Zum einen setze ich eine Umschreibe-Regeln von dem SASL Benutzer auf einem LDAP internen Benutzer
  2. Diesen Schritt kann man weglassen, funktioniert auch ohne: Ich habe die Verwendung von sasldb explizit gesetzt.
#
# slapd Dockerfile inkl. SASL DIGEST-MD5
#

# Starten mit:
# docker build -t gmn-ldap --no-cache .
# docker run -p 127.0.0.1:389:389 --rm --name gmn-ldap gmn-ldap

# SASL Konfig in cn=config übernehmen
# ldapadd  -x -Dcn=admin,cn=config -wchangeme -f config.ldif
# ldapsearch -ZZ -Hldap://localhost -YDIGEST-MD5 -U sasladmin@slapd.ldap

# Suche mit:
# ldapsearch -ZZ -Hldap://localhost -x -Dcn=admin,dc=gmn,dc=ldap -wchangeme


FROM osixia/openldap

ENV \
   LDAP_ORGANISATION=gmn \
   LDAP_DOMAIN=gmn.ldap \
   LDAP_ADMIN_PASSWORD=changeme \
   LDAP_CONFIG_PASSWORD=changeme \
   LDAP_TLS_VERIFY_CLIENT=never


# Notwendige Tools installieren
RUN apt-get update && apt-get install -y sasl2-bin && apt-get clean

# sasldb anlegen sowie notwendige Datei-/Gruppenrechte setzen
RUN echo changeme | /usr/sbin/saslpasswd2 -c -u slapd.ldap sasladmin
RUN usermod -a -G sasl openldap
RUN echo "mech_list: EXTERNAL DIGEST-MD5 CRAM-MD5 PLAIN LOGIN" > /etc/ldap/sasl2/slapd.conf
RUN chown openldap.sasl /etc/ldap/sasl2/slapd.conf && chmod 0660 /etc/ldap/sasl2/slapd.conf

 

dn: cn=config
changeType: modify
add: olcAuthzRegexp
olcAuthzRegexp: uid=sasladmin@slapd.ldap,cn=digest-md5,cn=auth cn=admin,dc=gmn,dc=ldap

dn: cn=config
changeType: modify
add: olcSaslAuxprops
olcSaslAuxprops: sasldb

 

Test

Und so sieht es aus wenn man das jetzt testet, an einem einfachen ldapwhoami gezeigt:

$ ldapwhoami -ZZ -Hldap://localhost -x -Dcn=admin,dc=gmn,dc=ldap -W
Enter LDAP Password: 
dn:cn=admin,dc=gmn,dc=ldap

$ ldapwhoami -ZZ -Hldap://localhost -YDIGEST-MD5 -U sasladmin@slapd.ldap
SASL/DIGEST-MD5 authentication started
Please enter your password: 
SASL username: sasladmin@slapd.ldap
SASL SSF: 128
SASL data security layer installed.
dn:cn=admin,dc=gmn,dc=ldap

Im ersten Aufruf ganz normal via simple bind direkt an den LDAP-Server, im zweiten via SASL/DIGEST-MD5. Gut zu sehen ist der Benutzernamen sasladmin@slapd.ldap welcher dank der Umschreibung als cn=admin,dc=gmn,dc=ldap zurück geliefert wird.

stunnel: noch lange nicht ausgepopt

stunnel: noch lange nicht ausgepopt

Wer kennt das nicht? Irgendeine alte Software die betriebsnotwendig ist und schon seit geraumer Zeit keine Updates mehr bekommen hat. Und die dümpelt halt so vor sich hin. So lange alle Clients sich noch verbinden bzw. die Verbindungen zu anderen Servern aufgebaut werden können scheint ja alles wunderbar zu sein. Es scheint so. Bis irgendeiner meint seine TLS-Einstellungen zu aktualisieren...Und das ist gar nicht so unwahrscheinlich wenn man zum Beispiel den Empfehlungen des BSI folgen mag.Von SSL ist da schon lange keine Rede mehr und sogar TLSv1.1 fehlt neuerdings. Weitere Infos dazu siehe hier:

Besonders prekär ist es, wenn die Anwendung noch gegen openssl 0.9.x verlinkt ist. Damit geht maximal TLSv1.0. Ihr seht schon, das Unheil nimmt seinen Lauf sobald diese alte Software versucht verschlüsselte Verbindungen aufzubauen bzw. über verschlüsselte Verbindungen erreicht werden soll. Die Verbindung startet zwar, wird aber gleich wieder beendet. Meist sind die Fehlermeldungen im Log dazu wenig aussagekräftig.

Interesse daran wie man mittels tcpdump und openssl solche Verbindungsprobleme erkennen kann? Schreibt mir einfach (Mail oder Kommentar), dann reiche ich einen passenden Blogeintrag nach.

Und wie kann man das lösen? Ein Update der Software wäre möglich. Nur gibt es leider viele Gründe warum dies noch nicht passiert ist bzw. die nächsten Tage ebenfalls nicht passieren wird. Sind wir ehrlich zueinander: Das Verständnis dafür mag sich als Admin in Grenzen halten. Dennoch kennen wir alle die Situation. Die gibt es oft. Zu oft. Mit zu vielen Gründen.

Also gut, das geht so nicht. Also was tun wenn guter Rat teuer ist? Es gibt da so ein altes Sprichwort: "Wenn der Prophet nicht zum Berg kommt, dann kommt halt der Berg zum Propheten" (frei nach Francis Bacon 1625). Ein Update der Software ist nicht möglich, die Sicherheitseinstellungen der gegenüber liegenden Seite wird man wohl kaum reduzieren wollen. Bleibt also der Weg durch die goldene Mitte: Ein Proxy muss her! Der macht aus Klartext aktuelle Verschlüsselung, aus zu alter Verschlüsselung aktuelle Verschlüsselung oder, und das soll es ebenfalls geben: Aus alter Verschlüsselung gar keine Verschlüsselung. Auch hierfür gibt es Fälle wo das völlig ausreichend ist.

stunnel

Für solche Dinge nehme ich gerne stunnel (https://www.stunnel.org/). Der ist klein und schlank und läuft unter aller gängigen Unix/Linux Systemen als auch unter Windows. Das Tool gibt es bereits seit über 20 Jahren und wird immer noch aktiv entwickelt. Hut ab!

Ok, als ich das erste Mal mit stunnel zu tun hatte waren meine Beweggründe eher anders herum: Viele Webfrontends damals kannten noch gar keine Verschlüsselung. Also fix stunnel davor geschaltet und schon war die Seite via https anstatt http zu erreichen. Die Richtung ist aber genau genommen völlig egal - es gibt ein Grundprinzip welches man bei stunnel immer im Hinterkopf behalten muss: Auf der einen Seite ist verschlüsselt, auf der anderen Seite ist Klartext. Wer aus alter Verschlüsselung neue machen will braucht somit zwei stunnel: Einen der übers Netz alte Verschlüsselung an nimmt und Klartext nach localhost leitet. Auf localhost geht ein stunnel ran der Klartext nach aktueller Verschlüsselung umwandelt. Reicht eine der beiden Richtungen reicht logischerweise auch nur ein stunnel.

alpine

Kein Platz stunnel irgendwo mit drauf zu packen? stunnel kann zwar drauf funktioniert aber nicht wie erhofft da am Installationsort eine völlig veraltete openSSL Bibliothek installiert ist? Für sowas nehme ich ganz gerne alpine Linux. Klein, sehr, sehr schlank und bringt stunnel in den zusätzlichen Paketen mit. Egal ob als Mini-VM oder als Container: läuft 🙂

Infos zu alpine? Gibt es hier: https://alpinelinux.org/

Beispiel 1: pop3 mit STARTTLS

Genug der großen Reden, ein paar Beispiele müssen her! Die Konfig ist simpel: Zuerst ein paar globale Einstellungen und im Anschluss daran die jeweiligen Proxys. Das Format sollte jedem bekannt sein: Ini lässt grüßen.

;debug = info
;foreground = yes
;options = -NO_SSLv3
sslVersion = all
ciphers = ALL

[stls2pop3]
protocol = pop3
client = no
accept = 10110
connect = pop.gehirn-mag.net:110
cert = /etc/stunnel/stunnel.pem

Im globalen Abschnitt kann z.B. stunnel dazu gebracht werden im Vordergrund zu starten. Perfekt für Diagnose. Ansonsten habe ich hier nur sichergestellt, dass alles was "alte" Verschlüsselung ist verbinden darf.

Zum Proxy stls2pop3 selbst: Mir ist einmal eine Software begegnet die konnte POP3 und POP3S. Ok, POP3S ist raus. Nur wurde bei POP3, sobald angeobten, STLS (STARTTLS) verwendet. Logisch, ohne Schalter zum abstellen. Versteht sich. Oder auch nicht.

client auf no gestellt, es sollen eingehende Verbindungen verschlüsselt angenommen werden. Das soll für den Moment reichen, mehr Wissen braucht es zum client Schalter im Moment noch gar nicht. Mit accept wird der Port auf 10110 gestellt. Und via connect wird angegeben wohin stunnel sich bei eingehenden Anfragen letztendlich verbinden soll. In dem Beispiel steht ein ganz normaler Server welcher via POP3 angesprochen werden soll. Nur Moment, damit das funktioniert muss man beachten, dass bei POP3 für STARTTLS nur die Kurzform STLS verwendet werden muss. Das sollte man wissen. Und somit sollte auch stunnel darüber Bescheid wissen. Deswegen noch fix protocol auf pop3 gestellt. In der manpage zu stunnel.conf sind die Protokolle aufgelistet welche stunnel versteht: Eine ordentliche Menge. Die manpage kann hier nachgelesen werden: https://www.stunnel.org/static/stunnel.html#SERVICE-LEVEL-OPTIONS. Im Bereich "Service Level Options" einfach mal nach "protocol" suchen. Infos zum POP3 Protokoll uns STLS: https://de.wikipedia.org/wiki/Post_Office_Protocol#Verschl%C3%BCsselung.

Zu guter Letzt: Das Zertifikat via cert: Bei vielen Distributionen wird beim installieren bereits ein passendes angelegt. Das Zertifikat fehlt oder soll durch ein anderes ersetzt werden? Siehe stunnel HowTo im Abschnitt "Generating the stunnel certificate and private key (pem)": https://www.stunnel.org/howto.html.

Jetzt noch in der Anwendung rasch angegeben, dass der zu erreichende POP3 Server unter der IP-Adresse xxx.xxx.xxx.xxx bzw. folgendem DNS-Namen ... auf Port 10110 erreichbar ist. Und sehe wie die Anwendung über das hoffnungslos veraltete POP3 STARTTLS wieder verbindet. Ok, im Klartext auf das Ziel. Mag Situationen geben wo das reicht. Falls nicht lies einfach im nächsten Abschnitt weiter. In dem wird durch einen zweiten Proxy die Verschlüsselung auf neu getrimmt.

Beispiel 2: Aus https alt mach neu

So, ein Proxy reicht also nicht. Dann halt mit zwei: Aus https alt mach http Klartext welches nur über localhost sich verbindet. Und zwar auf einen zweiten Proxy welcher aus Klartext https neu macht.

;debug = info
;foreground = yes
;options = -NO_SSLv3
sslVersion = all
ciphers = ALL

[https_in]
client = no
accept = 10443
connect = localhost:54321
cert = /etc/stunnel/stunnel.pem

[https_out]
client = yes
accept = localhost:54321
connect = www.gehirn-mag.net:443
cert = /etc/stunnel/stunnel.pem

Das Prinzip Konfig wie gehabt. Zuerst der Kopf und dann der erste Proxy. Hier mit dem Namen https_in. Wie in Lösung 1 beschrieben macht der aus https Klartext http. Und verbindet sich als Ziel auf localhost:54321. An der Adresse lauscht der zweite Proxy https_out. Ein Unterschied gibt es: Hier steht client auf yes. Bei yes geht stunnel der Annahme, dass das Verbindungsziel Verschlüsselung benötigt. Somit ist der eingehende Teil die Klartextseite. Bei no ist das gerade anders herum (siehe manpage). Der Rest, oh Wunder, ist wie gehabt und bereits bekannt.

Noch fix das Ziel in der Anwendung geändert auf die Adresse des Proxyservers mit Port 10443 und schon wird aus https ganz arg alt https ganz arg neu gemacht.

Fazit

Und wieder mal die Welt gerettet. Dank einem kleinen, schlanken Tool. Das trotz seines Alters alles andere als ins Alter gekommen ist. Es ist kein Fehler in einem ruhigen Moment mal die Webseite und die Beispiele etwas genauer zu betrachten. So viele Dinge die man darüber hinaus mit stunnel noch machen kann. Es lohnt sich.

keepalived manual failover

keepalived manual failover

keepalived ist eine einfache Lösung einen HA-Cluster z.B. unter Linux zu betreiben (https://www.keepalived.org/). Jedoch kennt dieser von Haus keinen Weg für einen manuellen failover. Es folgt eine Auflistung verschiedener Möglichkeiten dennoch das Ziel zu erreichen.

1. Dienst anhalten

Wenn man in der keepalived Konfiguration einen Dienst überwacht kann man diesen schlichtweg stoppen. Der Cluster schwenkt logischerweise sinngemäß. Aber halt, das ist nicht ganz ideal wie folgende Beispiele zeigen:

  • Wenn man schwenkt weil man Updates einspielen will hat man ein Problem wenn man den vermeintlich überwachten Dienst für die finalen Tests starten möchte. Das geht zwar, jedoch schwenkt dann keepalived zurück. Der "Test" ist somit die laufende Produktion. So lange alles gut geht: Toi, toi, toi 😉 Falls nicht: Ooops.
  • Ich habe einige Dienste am Laufen die einiges an Zeit brauchen um sich zu beenden. Zuletzt läuft zwar noch der Netzwerkport und der Dienst steht logischerweise auch noch in der Prozesstabelle - aber Anfragen werden nicht mehr beantwortet. In genau dieser Zeitspanne hat man dann also eine Produktionsausfall. Anmerkung dazu: Die meisten keepalived Skripte die ich kenne prüfen schlichtweg auf das vorhanden sein des Programms in der Prozesstabelle und/oder ob am zugehörigen Port jemand lauscht. Ob vernünftige Antworten auf Anfragen kommen wird meistens nicht überwacht. Das ist zugegebener Maßen aber auch nicht wirklich einfach zu überwachen: Was ist wenn der Dienst, warum auch immer, gerade einfach mal etwas länger braucht um die Antwort zu liefern wie sonst? Wie lange soll die Prüfung warten und ab wann schwenken? In der Zeitspanne die hier gewartet wird könnte der Dienst bereits wieder weg sein. Kurzum: Prozess- und Portüberwachung macht Sinn, "sinnvolle" Antworten nur bedingt. Das sollte genau abgewogen sein ob und in wie weit das sinnvoll ist. Das wäre aber ein anderes Thema. Zurück zum Ausgangspunkt: Auch diese Variante ist alles andere als toll.

Zusammengefasst: Als Test mag Dienst stoppen ausreichend sein, wirklich praktikabel für Updates und der gleichen ist das allerdings nicht.

2. keepalived Priorität

Ein anderer Weg ist in der keepalived Konfiguration die Priorität eines Nodes anzupassen. Man muss letztendlich nur dafür Sorge tragen, dass der Node welcher die Rolle übernehmen soll die höchste Prio hat. Aber mal ehrlich: Will man das so? Im keepalived was ändern und diesen neu starten für einen manuellen failover? Nein, das will man sicherlich nicht!

3. Eigenes Skript

Zu guter Letzt könne man ein Skript erstellen welches den weight anpasst. Also Skript starten und z.B. falls die Datei /etc/keepalived.myweight existiert die Wichtung um den in der Datei angegebenen Wert ändern. Ja, das geht. Nur müsste man so ein Skript noch schreiben. Und ob der Aufwand so wirklich lohnt... 😉

dummy-Netzwerkkarte

Es geht aber auch ganz anders ohne großartig irgendwas an Software schreiben zu müssen. Der Kernel bringt hierfür bereits alles passende mit: Die Netzwerkkarte "dummy". Details z.B. per "modinfo dummy" bzw. in der zugehörigen Kerneldokumentation. Sobald das Modul geladen ist kann die Netzwerkkarte dummy0 entsprechend mit einer beliebigen und nirgends sonst wo verwendeten IP belegt werden. Ich habe hier einen SLES am laufen, dort kann das via Yast2 entsprechend erledigt werden. Aber auch bei Debian/Ubuntu kann man dummy-Karten mit den üblichen Werkzeugen anlegen. Einfach mal in die Doku rein schauen, das sollte sich finden lassen.

Zu Letzt muss noch die keepalived.conf angepasst werden:

track_interface {
em1
dummy0
}

Die Liste meiner track_interface Karten wurde einfach um dummy0 erweitert. Jetzt reicht ein einfaches ifdown dummy0 oder ip link set dummy0 down und keepalive schwenkt auf den anderen Node. Ein ifup dummy0 bzw. ip link set dummy0 up und schon kommt er wieder zurück. Ganz ohne irgendwelche zusätzlichen Skripten.

 

Wildes Let’s Encrypt

Wildes Let’s Encrypt

Let’s Encrypt Zertifikate sind inzwischen fürs Webhosting weit verbreitet: Die Akzeptanz ist hervorragend, die Erstellung sowie regelmäßigen Verlängerungen sind gut automatisierbar. Außer bei den inzwischen angebotenen Wildcard-Zertifikaten. Hier ist Handarbeit angesagt. Oder doch nicht? (mehr …)

fail2ban und dynamische IP-Adressen

fail2ban und dynamische IP-Adressen

fail2ban ist ein sehr, sehr praktisches Werkzeug (http://www.fail2ban.org). Und jeder, der fail2ban regelmäßig nutzt hat sich selbst schon einmal ausgesperrt. Gut dem, der eine statische IP hat und diese via ignoreip auf die Whitelist gesetzt hat (http://www.fail2ban.org/wiki/index.php/Whitelist). Pech hingegen für mich, der bei der deutschen Tel... unter Vertrag ist, privat einen DSL Anschluss hat und nicht bereit ist die extrem hohen Kosten für einen Vertrag mit statischer IP zu zahlen.

Dynamische IP-Adresse

Das Problem dabei ist, dass dynamische IP-Adressen sich so dann und wann ändern. Nur was trägt man in einer Witelist ein die statischer Natur ist? So wie von fail2ban verwendet?

Was ich hingegen habe ist einen passenden Eintrag ins DNS der auf meine wechselnde IP zeigt (DynDNS oder wie auch immer ihr das Kind nennen wollt). Also fix ein Skript erstellt welches im tmp-Verzeichnis die dazugehörige IP puffert. Sobald sich diese ändert wird eine passende fail2ban local Config erstellt und die komplette Config neu eingelesen. Da ich ebenfalls auf eine Leitung mit statischer IP-Adresse zugriff habe wird ignoreip eben mit diesem statischen Teil befüllt und anschließend um den dynamischen Teil erweitert.

#!/bin/bash

#
# Whitelisted eine dynamische IP in Fail2ban
#


IGNALWAYS="127.0.0.1/8 ::1 193.nn.mm.0/24"
IGNDYN="dyn.gehirn-mag.net"
IGNFILE="/etc/fail2ban/jail.d/ignoreip.local"
TMPFILE="/tmp/fail2ban-dynip"


# Suche nach aktueller IP sowie gepufferter IP
CURIP=$($(which dig) +short $IGNDYN)
[ -f $TMPFILE ] && OLDIP=$(cat $TMPFILE) || OLDIP="--unknown--"
echo "aktuelle IP: $CURIP gepufferte IP: $OLDIP"

# Falls IPs verschieden ignoreip neu schreiben
if [ $CURIP != $OLDIP ]; then
    echo -n "Ändere ignoreip: "
    echo $CURIP > $TMPFILE
    echo -e "# written by $0 ad $(date)\\n\\n[DEFAULT]\\nignoreip = $IGNALWAYS $CURIP\\n" > $IGNFILE
    $(which fail2ban-client) reload
    # Debug
    $(which fail2ban-client) --dp | mail -s "$(hostname) fail2ban-dynip $CURIP" ...@gehirn-mag.net
fi

 

Falls Du weißt welchen DNS-Server Du fragen musst kannst Du Zeile 15 entsprechend ergänzen. Sowie sich die IP ändert lasse ich mir via Email die aktuelle fail2ban Konfig senden (Zeile 26). Wer das nicht will nimmt diese Zeile einfach raus. Was jetzt noch fehlt ist der regelmäßige Aufruf. Ich selbst verwende dazu folgenden cron Eintrag welcher alle 15 Minuten läuft:

# Fail2ban: die private IP auf die Whitelist setzen
*/15 * * * * /usr/local/bin/fail2ban-ignoreip.sh 2>&1 | /usr/bin/logger -t fail2ban-dynip

Fertig, das war es 🙂