IT security, FreeBSD, Linux, mail server hardening, post-quantum crypto, DNS, retro computing & hands-on hardware hacks. Privater Tech-Blog seit 2003.

Schlagwort: Sysadmin (Seite 3 von 12)

Rspamd: Automatisches Spam/Ham-Lernen mit Dovecot und IMAPSieve

Rspamd hat ein Webinterface. Da kann man E-Mails reinkopieren und als Spam oder Ham markieren. Klingt erstmal praktisch. Ist es aber nicht. Niemand kopiert ernsthaft den Quellcode jeder fehlklassifizierten Mail in ein Webformular. Das macht man einmal zum Testen und dann nie wieder.

Automatisches Spam-Training mit Rspamd über Dovecot IMAPSieve – Mail wird zwischen Inbox und Junk verschoben

Was man eigentlich will: Wenn ein Benutzer eine Mail in den Junk-Ordner verschiebt, soll rspamd das automatisch als Spam lernen. Und wenn eine Mail aus dem Junk-Ordner rausgeholt wird, soll rspamd sie als Ham lernen. Kein Webinterface, kein manueller Eingriff. Der Benutzer sortiert einfach seine Mails — und rspamd lernt mit.

Genau das geht mit Dovecot und IMAPSieve. Hier beschreibe ich, wie ich das bei mir eingerichtet habe. Die Konfiguration läuft seit Mai 2020 unverändert — über sechs Jahre, ohne eine einzige Anpassung. Das darf man ruhig als stabil bezeichnen.

Was passiert da eigentlich

Der Datenfluss ist simpel:

  • Benutzer verschiebt eine Mail in den Ordner „Junk“
  • Dovecot erkennt die Verschiebung per IMAPSieve
  • IMAPSieve startet ein Sieve-Script
  • Das Sieve-Script ruft ein Shell-Script auf
  • Das Shell-Script übergibt die Mail per rspamc an rspamd
  • Rspamd lernt die Mail als Spam (Bayes-Klassifikator)

In die andere Richtung genauso: Mail raus aus Junk, Dovecot erkennt es, rspamd lernt Ham. Egal ob der Benutzer über Thunderbird, Roundcube, ein Smartphone oder was auch immer sortiert — solange es IMAP ist, greift das.

Voraussetzungen

  • Dovecot mit Sieve-Support (dovecot-pigeonhole unter FreeBSD, dovecot-sieve unter Debian/Ubuntu)
  • Rspamd mit laufendem Controller-Worker
  • rspamc CLI-Tool (kommt mit rspamd mit)

Mein Setup läuft auf FreeBSD. Die Pfade beginnen daher mit /usr/local/. Unter Linux ist es /etc/dovecot/ statt /usr/local/etc/dovecot/ und /usr/lib/dovecot/ statt /usr/local/libexec/dovecot/. Ansonsten ist alles identisch.

Mein rspamd läuft in einer eigenen Jail und lauscht auf 127.0.0.3:11334. Wer rspamd lokal auf dem gleichen System hat, nimmt stattdessen 127.0.0.1:11334 oder den Unix-Socket.

Dovecot konfigurieren

Zuerst muss das Sieve-Plugin für IMAP aktiviert werden.

20-imap.conf:

protocol imap {
  mail_plugins = $mail_plugins sieve
}

Dann die IMAPSieve-Konfiguration. Hier wird festgelegt, welche Ordner-Aktionen welches Sieve-Script auslösen.

90-plugin.conf:

plugin {
  sieve_plugins = sieve_imapsieve sieve_extprograms

  # Wenn eine Mail in den Junk-Ordner kopiert oder dort ein Flag geaendert wird
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY FLAG
  imapsieve_mailbox1_before = file:/usr/local/etc/dovecot/sieve/report-spam.sieve

  # Wenn eine Mail AUS dem Junk-Ordner woanders hin verschoben wird
  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/usr/local/etc/dovecot/sieve/report-ham.sieve

  sieve_pipe_bin_dir = /usr/local/libexec/dovecot

  sieve_global_extensions = +vnd.dovecot.pipe
}

Zwei Trigger: Einer für „Mail landet im Junk“ (→ Spam lernen), einer für „Mail verlässt Junk“ (→ Ham lernen). COPY deckt Verschieben ab, FLAG fängt den Fall ab, dass ein Mail-Client den Junk-Status per Flag statt per Verschieben setzt.

Sieve-Scripts

Jetzt die beiden Sieve-Scripts, die von IMAPSieve aufgerufen werden.

report-spam.sieve — wird ausgelöst, wenn eine Mail im Junk-Ordner landet:

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "imap4flags"];

if environment :is "imap.cause" "COPY" {
    pipe :copy "sa-learn-spam.sh";
}

# Beantworteten oder weitergeleiteten Spam ebenfalls lernen
elsif anyof (allof (hasflag "\\Answered",
                    environment :contains "imap.changedflags" "\\Answered"),
             allof (hasflag "$Forwarded",
                    environment :contains "imap.changedflags" "$Forwarded")) {
    pipe :copy "sa-learn-spam.sh";
}

Der erste Block fängt das normale Verschieben ab. Der zweite Block ist für einen Sonderfall: Wenn jemand auf eine Mail im Junk-Ordner antwortet oder sie weiterleitet, ändert sich das Flag — und auch das sollte als Spam gelernt werden.

report-ham.sieve — wird ausgelöst, wenn eine Mail den Junk-Ordner verlässt:

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}

if string "${mailbox}" [ "Trash", "train_ham", "train_prob", "train_spam" ] {
  stop;
}

pipe :copy "sa-learn-ham.sh";

Hier passiert etwas Wichtiges: Bevor die Mail als Ham gelernt wird, prüfen wir wohin sie verschoben wurde. Wenn sie im Papierkorb landet, war das vermutlich kein „Das ist kein Spam“ sondern ein „Ich lösche den Spam“. Deshalb: stop; für Trash und die Trainingsordner. Nur wenn die Mail in einen echten Ordner verschoben wird, ist es ein Ham-Signal.

Beide Scripts müssen kompiliert werden:

sievec /usr/local/etc/dovecot/sieve/report-spam.sieve
sievec /usr/local/etc/dovecot/sieve/report-ham.sieve

Shell-Scripts für rspamc

Die Sieve-Scripts rufen Shell-Scripts auf, die die Mail per rspamc an rspamd übergeben. Simpel — jeweils ein Einzeiler.

/usr/local/libexec/dovecot/sa-learn-spam.sh:

#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_spam

/usr/local/libexec/dovecot/sa-learn-ham.sh:

#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_ham

Die Dateinamen sa-learn-* kommen historisch von SpamAssassin. Verwirrend, wenn man rspamd nutzt. Man könnte sie auch rspamd-learn-spam.sh nennen — funktional ist es egal. Ich habe sie so gelassen, weil man funktionierende Dinge nicht anfasst.

Beide ausführbar machen:

chmod +x /usr/local/libexec/dovecot/sa-learn-spam.sh /usr/local/libexec/dovecot/sa-learn-ham.sh

Wer rspamd lokal laufen hat, ersetzt 127.0.0.3 durch 127.0.0.1 oder nutzt den Unix-Socket (-h /var/run/rspamd/rspamd.sock). Unter Linux liegen die Scripts in /usr/lib/dovecot/ statt /usr/local/libexec/dovecot/. Der Pfad in sieve_pipe_bin_dir muss natürlich dazu passen.

Wichtig: Damit rspamc ohne Passwort trainieren darf, muss die IP im rspamd Controller-Worker als vertrauenswürdig eingetragen sein. In /usr/local/etc/rspamd/local.d/worker-controller.inc (FreeBSD) bzw. /etc/rspamd/local.d/worker-controller.inc (Linux):

secure_ip = "127.0.0.0/8";
secure_ip = "::1";

Ohne das schlägt rspamc learn_spam mit einem Authentifizierungsfehler fehl. Bei Jail-Setups wie meinem muss die Jail-IP (127.0.0.3) in der Liste stehen.

Testen

Dovecot neu laden:

service dovecot reload

Dann eine beliebige Mail in den Junk-Ordner verschieben und im rspamd-Log nachschauen:

rspamd_controller_learn_fin_task: <127.0.0.3> learned message as spam: MESSAGE-ID

Mail wieder raus aus Junk in den Posteingang:

rspamd_controller_learn_fin_task: <127.0.0.3> learned message as ham: MESSAGE-ID

Wenn das im Log steht, funktioniert alles. Kein Neustart nötig, kein Cache-Flush, kein Warten.

Wie viel Training braucht rspamd

Rspamd nutzt einen Bayes-Klassifikator. Der braucht eine Mindestmenge an gelernten Nachrichten, bevor er aktiv wird. Die Standardeinstellung ist 200 — also mindestens 200 Spam-Mails und 200 Ham-Mails. Vorher ignoriert rspamd die Bayes-Ergebnisse komplett.

Das klingt nach viel, geht aber schneller als man denkt. Wer ein paar Dutzend Benutzer auf dem Server hat, kommt da in wenigen Wochen hin. Und danach wird rspamd mit jeder sortierten Mail ein bisschen besser.

Den aktuellen Stand kann man jederzeit prüfen:

rspamc stat

Unter Statfile sieht man wie viele Nachrichten rspamd bereits gelernt hat.

Rspamd trainiert standardmäßig einen globalen Bayes-Klassifikator — alle Benutzer lernen in denselben Pool. Wer das pro Benutzer trennen will, setzt in der classifier-bayes.conf:

per_user = true;

Für die meisten Setups mit einer Handvoll Domains ist der globale Pool sinnvoller — mehr Trainingsdaten, schneller gute Ergebnisse.

Hinweise

Die Konfiguration ist stabil — Dovecot-Updates, rspamd-Updates, FreeBSD-Upgrades, alles durchgelaufen ohne Anpassung.

Wer rspamd danach noch eine Stufe weiter bringen will: Ich habe einen eigenen Beitrag geschrieben, wie man GPT-basierte Spam-Erkennung in rspamd integriert. Das läuft zusätzlich zum Bayes-Klassifikator und fängt die Mails ab, die durch das statistische Netz rutschen.

Siehe auch: Postfix und AMaViS: content_filter oder smtpd_proxy_filter?, DMARC-Prüfung in Postfix: OpenDMARC und rspamd im Vergleich

Fragen? Schreib mir über die Kontaktseite.

TLS-ECDHE mit AES-256-GCM-SHA384 einfach erklärt

Verschlüsselung-cipher

Wer sich mit TLS beschäftigt, stolpert früher oder später über Zeichenketten wie TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 oder TLS_AES_256_GCM_SHA384. Was da genau drinsteht, ist auf den ersten Blick nicht offensichtlich. Dabei folgt die Benennung einem klaren Schema.

Die Bestandteile einer Cipher Suite

Jede Cipher Suite beschreibt vier Dinge:

  • Key Exchange — wie sich Client und Server auf einen gemeinsamen Sitzungsschlüssel einigen.
  • Certificate Verification — wie das Serverzertifikat geprüft wird (Signaturverfahren).
  • Bulk Encryption — die symmetrische Verschlüsselung der eigentlichen Daten.
  • Hashing — die Prüfsummen, die Integrität und Authentizität sicherstellen.

TLS 1.2 vs. TLS 1.3

In TLS 1.2 stehen alle vier Bestandteile im Namen der Cipher Suite. Nehmen wir TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 auseinander:

  • TLS — das Protokoll (Transport Layer Security)
  • ECDHE — Key Exchange (Elliptic Curve Diffie-Hellman Ephemeral)
  • ECDSA — Certificate Verification (Elliptic Curve Digital Signature Algorithm)
  • AES_256_GCM — Bulk Encryption (AES mit 256 Bit im Galois/Counter Mode)
  • SHA384 — Hashing (SHA-2 mit 384 Bit)

TLS 1.3 hat das Namensschema verkürzt. Key Exchange und Certificate Verification sind nicht mehr Teil des Cipher-Suite-Namens, weil sie separat verhandelt werden. Darum sieht TLS_AES_256_GCM_SHA384 so kompakt aus: nur Protokoll, Verschlüsselung und Hash.

Key Exchange

Der Schlüsselaustausch legt fest, wie Client und Server einen temporären Sitzungsschlüssel aushandeln. Man will hier Ephemeral-Verfahren, also temporäre Schlüssel. Warum? Selbst wenn jemand den Traffic mitschneidet und später an den privaten Schlüssel des Servers kommt, kann er die aufgezeichneten Verbindungen nicht entschlüsseln. Der Sitzungsschlüssel existiert nur für die Dauer der Verbindung. Das nennt sich Perfect Forward Secrecy.

DHE (Diffie-Hellman Ephemeral) funktioniert, sollte aber mindestens 2048 Bit nutzen. Besser ist ECDHE (Elliptic Curve DHE), weil es bei gleicher Sicherheit deutlich kleiner und schneller ist. Idealerweise bietet der Server nur ECDHE an. Alles ohne das E am Ende (also statisches DH) hat kein Forward Secrecy und gehört abgeschaltet.

In Zukunft kommt hier noch Post-Quantum dazu. Mit X25519MLKEM768 lassen sich hybride Verfahren nutzen, die auch gegen Quantencomputer absichern. Wer das auf Nginx einrichten will, findet bei mir eine Anleitung: Post-Quantum TLS.

Certificate Verification

Verschlüsselung allein hilft nicht, wenn man mit dem falschen Server spricht. Das Serverzertifikat beweist die Identität. Es wird von einer CA signiert, kann per DANE/TLSA im DNSSEC-geschützten DNS verankert sein und sollte nicht trivial fälschbar sein.

RSA-Zertifikate sollten mindestens 2048 Bit haben, besser 4096 Bit. Allerdings werden RSA-Schlüssel mit steigender Sicherheit immer größer und langsamer. ECDSA-Zertifikate lösen das elegant: Ein ECDSA-Schlüssel mit 256 Bit bietet vergleichbare Sicherheit wie RSA mit 3072 Bit, ist aber deutlich kleiner und schneller zu verifizieren. Als Kurve sollte es mindestens secp256r1 (P-256) sein. secp384r1 geht auch, bringt aber aktuell keinen praktischen Vorteil.

Bulk Encryption

Das ist die eigentliche Datenverschlüsselung. Brauchbare Kombinationen sind:

  • AES-128-GCM oder AES-256-GCM — Standard, schnell, hardware-beschleunigt auf den meisten CPUs
  • ChaCha20-Poly1305 — gute Alternative, besonders auf Geräten ohne AES-NI

AES mit CBC ist noch akzeptabel, aber GCM ist vorzuziehen. Von 3DES sollte man die Finger lassen. Wenn irgendwo RC4 oder DES auftaucht: abschalten.

Hashing

Der Hash sichert die Integrität der übertragenen Daten. Minimum ist SHA-256, ein guter Mittelweg ist SHA-384. SHA-1 sollte man nicht mehr einsetzen. Taucht MD5 auf, stimmt etwas grundlegend nicht.

Fragen, Korrekturen oder Ergänzungen? Einfach melden.

Automatische E-Mail-Archivierung mit cleanup-maildir und Dovecot

IMAP-Postfächer wachsen mit der Zeit. Irgendwann hat man tausende Mails im Posteingang und in Sent, die Suche wird langsam und die Übersicht geht verloren. cleanup-maildir ist ein Python-Script das Mails nach Alter automatisch in Archiv-Ordner sortiert. Es läuft als Cronjob und arbeitet direkt auf dem Maildir-Verzeichnis.

Installation

# FreeBSD
pkg install cleanup-maildir

# Debian/Ubuntu
pip install cleanup-maildir

Grundlegender Aufruf

Alle Mails die älter als 365 Tage sind aus der Inbox ins Archiv verschieben:

# Inbox archivieren
sudo -u vmail cleanup-maildir --age=365 \
  --archive-folder='Archive.Inbox' \
  --maildir-root='/var/mail/vhosts/example.com/user' \
  archive ''

# Gesendete archivieren
sudo -u vmail cleanup-maildir --age=365 \
  --archive-folder='Archive.Sent' \
  --maildir-root='/var/mail/vhosts/example.com/user' \
  archive 'Sent/'

--age=365 fasst nur Mails an die älter als ein Jahr sind. --archive-folder gibt den Zielordner im IMAP-Postfach an. archive ist der Modus (alternativ delete). Der leere String '' steht für die Inbox, 'Sent/' für die gesendeten Mails.

Das Script legt automatisch Unterordner nach Jahr und Monat an. Die Struktur im Postfach sieht dann so aus:

Archive/
├── Inbox/
│   ├── 2024-01/
│   ├── 2024-02/
│   └── ...
└── Sent/
    ├── 2024-01/
    ├── 2024-02/
    └── ...

Cronjob

Als Cronjob einmal pro Nacht laufen lassen. Wichtig ist dass der Cronjob als der Benutzer läuft der Zugriff auf die Maildir-Verzeichnisse hat:

# /etc/crontab
30 3 * * * vmail cleanup-maildir --age=365 --archive-folder='Archive.Inbox' --maildir-root='/var/mail/vhosts/example.com/user' archive ''

Mehrere Postfächer mit LDAP

Bei mehreren Postfächern will man nicht für jeden Benutzer einen eigenen Cronjob-Eintrag pflegen. Ein Wrapper-Script kann die Benutzer, Maildir-Pfade und die Option ob archiviert werden soll aus dem LDAP holen. Das LDAP-Attribut (z.B. autoarchive=1) steuert pro Benutzer ob die Archivierung aktiv ist. So lässt sich die Archivierung zentral verwalten ohne auf jedem Mailserver Cronjobs anzupassen.

Python 3.11+ und kaputte Header

Ab Python 3.11 nutzt cleanup-maildir den strikten RFC-Header-Parser aus email.policy.default. Das führt zu einem Problem: Mails mit fehlerhaften Headern (z.B. Microsoft Exchange Message-IDs wie <[b378dfc5...]@microsoft.com>) lassen das Script mit einem IndexError abstürzen. Alle Mails nach der fehlerhaften werden nicht mehr verarbeitet.

Den Fix dafür habe ich als Pull Request eingereicht. Ein _safe_header()-Wrapper fängt Parse-Fehler ab und überspringt kaputte Header, statt das ganze Script abzubrechen. Bei mir hat das Script vorher bei Mail #8 aufgehört, danach liefen alle 2.986 Mails sauber durch.

Siehe auch: Dovecot Quota einrichten

Fragen? Einfach melden.

Bluetooth-Audio unter FreeBSD und GhostBSD: Workaround mit Creative BT-W2

Bluetooth-Audio und FreeBSD vertragen sich nicht. Der Bluetooth-Stack wird nicht mehr gepflegt, in OpenBSD wurde er komplett entfernt. Einen Bluetooth-Dongle oder eine eingebaute Bluetooth-Karte dazu zu bringen, sich mit einem Audio-Gerät zu verbinden, funktioniert unter FreeBSD praktisch nicht.

Der Workaround: Creative BT-W2

Die Lösung ist ein USB-Dongle, der sich selbst um Bluetooth kümmert: Der Creative BT-W2. Das Gerät meldet sich am Betriebssystem als normale USB-Soundkarte. Das Pairing mit dem Bluetooth-Kopfhörer oder -Lautsprecher macht der Dongle selbständig per Knopfdruck. FreeBSD sieht nur eine Soundkarte, kein Bluetooth.

Das Kernelmodul snd_uaudio kümmert sich um die Erkennung. In der /etc/rc.conf:

kld_list="snd_uaudio"

Nach dem Laden des Moduls erscheint das Gerät im dmesg:

uaudio0:  on usbus0
uaudio0: Play: 48000 Hz, 2 ch, 16-bit S-LE PCM format, 2x8ms buffer.
uaudio0: Record: 48000 Hz, 1 ch, 16-bit S-LE PCM format, 2x8ms buffer.
pcm5:  on uaudio0
uaudio0: HID volume keys found.

Pairing und Nutzung

Kurz den Knopf am Dongle drücken, er blinkt und verbindet sich mit dem nächsten Bluetooth-Audio-Gerät in Reichweite. Kopfhörer, Headsets, Lautsprecher und Mikrofone funktionieren. Die Qualität reicht auch für Telefonie, Latenz und Codec sind in Ordnung.

Das Audio-Device ist je nach Systemkonfiguration /dev/dsp5 oder ein anderer Index. Mit cat /dev/sndstat lässt sich prüfen welches Device der BT-W2 bekommen hat.

Nicht die eleganteste Lösung, aber sie funktioniert zuverlässig. Solange der Bluetooth-Stack auf FreeBSD nicht wiederbelebt wird, ist ein Dongle wie der BT-W2 der pragmatischste Weg.

Siehe auch: FreeBSD auf dem Desktop, GhostBSD und FreeBSD: GNOME-Keyring automatisch entsperren, GhostBSD 19.09 Ports benutzen

Fragen? Einfach melden.

GhostBSD 19.09 Ports benutzen

Veraltet: GhostBSD 19.09 ist stark veraltet. Aktuelle GhostBSD-Versionen nutzen pkg statt Ports. Siehe ghostbsd.org für die aktuelle Version.

GhostBSD basierte früher direkt auf FreeBSD. Inzwischen ist es aber auf TrueOS gewechselt. So sieht es ebenfalls mit den Ports aus. Man kann also nicht wie unter FreeBSD gewohnt mit portsnap arbeiten sondern muss einen gewissen „Umweg“ nehmen.

Die zu GhostBSD gehörenden Ports bekommt man nun so ins System:

sudo git clone https://github.com/ghostbsd/ghostbsd-ports.git /usr/ports

In GhostBSD Version 19.09 ist etwas Ordnung geschaffen worden und viele vermeintlich unnötige Pakete mussten weichen. Zum arbeiten mit den Ports benötigt man daher noch folgendes:

pkg install src os-generic-userland-devtools

Ab jetzt kann man wie gewohnt mit den Ports arbeiten!

Siehe auch: GhostBSD und FreeBSD: GNOME-Keyring automatisch entsperren, Bluetooth-Audio unter FreeBSD und GhostBSD: Workaround mit Creative BT-W2

Fragen? Einfach melden.

FreeBSD: Native ZFS Encryption einrichten und nutzen

Seit FreeBSD 13 steht native ZFS Encryption zur Verfügung. Datasets lassen sich mit AES-256-GCM verschlüsseln, ohne dass der gesamte Pool verschlüsselt sein muss. Die Verschlüsselung greift pro Dataset und vererbt sich auf Kind-Datasets.

Verschlüsseltes Dataset anlegen

Ein neues Dataset mit Passphrase-Abfrage:

zfs create -o encryption=aes-256-gcm -o keyformat=passphrase usbpool/test01
Enter passphrase:
Re-enter passphrase:

Das Dataset wird sofort gemountet und ist einsatzbereit. Alles was hineingeschrieben wird, liegt verschlüsselt auf der Platte:

zfs list usbpool/test01
NAME             USED  AVAIL     REFER  MOUNTPOINT
usbpool/test01    99K   899G       99K  /usbpool/test01

zfs get encryption usbpool/test01
NAME            PROPERTY    VALUE        SOURCE
usbpool/test01  encryption  aes-256-gcm  -

Nach einem Reboot

Bei einem Passphrase-geschützten Dataset hat ZFS nach einem Reboot den Schlüssel nicht mehr. Das Dataset existiert, ist aber nicht gemountet:

zfs get mounted usbpool/test01
NAME            PROPERTY  VALUE    SOURCE
usbpool/test01  mounted   no       -

Mit zfs mount -l wird der Schlüssel geladen und das Dataset eingehängt:

zfs mount -l usbpool/test01
Enter passphrase for 'usbpool/test01':

zfs get mounted usbpool/test01
NAME            PROPERTY  VALUE    SOURCE
usbpool/test01  mounted   yes      -

Keyfile statt Passphrase

Statt einer Passphrase-Abfrage kann der Schlüssel auch in einer Datei liegen. Praktisch für Server die ohne Interaktion booten sollen:

zfs create -o encryption=aes-256-gcm \
  -o keyformat=passphrase \
  -o keylocation=file:///root/keys/pool.key \
  zroot/encrypted-data

Die Key-Datei enthält das Passphrase als Text. Wichtig: Die Datei muss beim Boot erreichbar sein, also auf einem unverschlüsselten Dataset liegen. Berechtigungen auf 0400 setzen.

Bestehende Datasets verschlüsseln

Verschlüsselung lässt sich nicht nachträglich auf ein bestehendes Dataset aktivieren. Man muss die Daten per zfs send | zfs receive in ein neues, verschlüsseltes Dataset migrieren. Die komplette Anleitung dafür steht im Beitrag ZFS-Dataset nachträglich verschlüsseln.

Eine Übersicht über alle ZFS-Funktionen gibt es im ZFS-Überblick. Wer sich für ZFS Encryption unter Solaris/OpenIndiana interessiert, findet die Anleitung unter ZFS Encryption unter Solaris. Fragen? Einfach melden.

FreeBSD als IPsec/L2TP-Client für Microsoft Windows Routing und RAS VPN

FreeBSD IPsec L2TP Client to Microsoft Windows Routing RAS Server Diagramm.

Einen FreeBSD-Desktop an einen Microsoft Windows Routing und RAS VPN-Server anbinden, per IPsec/L2TP. Klingt nach Qual, ist aber erstaunlich einfach. Ich nutze strongSwan für den IPsec-Tunnel und mpd5 für L2TP.

Ausgangslage

Der FreeBSD-Desktop hat die IP 192.168.10.57. Der Windows RRAS-Server steht unter vpnserver.domain.tld (88.88.88.88). Tunneltyp ist IPsec/L2TP mit Pre-Shared Key für IPsec und Active Directory-Anmeldung über L2TP. Die Firmennetze 172.16.0.0/12 und 10.0.0.0/8 sollen über den Tunnel erreichbar sein.

strongSwan: IPsec-Tunnel

/usr/local/etc/ipsec.conf:

config setup

conn %default
        ikelifetime=60m
        keylife=20m
        rekeymargin=3m
        keyingtries=1
        keyexchange=ikev1
        authby=psk

conn vpnname
        type=transport
        leftfirewall=yes
        right=vpnserver.domain.tld
        rightid=%any
        rightsubnet=0.0.0.0/0
        auto=add
        left=%defaultroute
        leftprotoport=17/%any
        rightprotoport=17/1701
        ike=3des-sha1-modp1024!
        esp=3des-sha1
        modeconfig=push

Der Pre-Shared Key in /usr/local/etc/ipsec.secrets:

vpnserver.domain.tld %any : PSK "abcdefg1234567"

Tunnel aufbauen:

root@errortest:~ # ipsec up vpnname
initiating Main Mode IKE_SA vpnname[20] to 88.88.88.88
[...]
IKE_SA vpnname[20] established between 192.168.10.57[192.168.10.57]...88.88.88.88[88.88.88.88]
CHILD_SA vpnname{38} established with SPIs c387d93f_i 4720cab6_o
  and TS 192.168.10.57/32[udp] === 88.88.88.88/32[udp/l2f]
connection 'vpnname' established successfully

Status prüfen mit ipsec statusall. Wichtig ist die Zeile ESTABLISHED und dass die SPIs gesetzt sind.

mpd5: L2TP-Verbindung

/usr/local/etc/mpd5/mpd.conf:

startup:
    log +ALL +EVENTS -FRAME -ECHO

default:
    load L2TP_client

L2TP_client:
    create bundle static B1
    set iface up-script /home/kernel/vpnname-up.sh
    set iface down-script /home/kernel/vpnname-down.sh
    set bundle enable crypt-reqd
    set bundle enable compression
    set bundle enable ipv6cp
    set ccp yes mppc
    set mppc no e40 e56
    set mppc yes e128 stateless
    set ipcp ranges 0.0.0.0/0 0.0.0.0/0
    set ipcp enable req-pri-dns
    set ipcp enable req-sec-dns
    set iface route 172.16.0.0/12
    set iface route 10.0.0.0/8
    set iface enable tcpmssfix

    create link static L1 l2tp
    set link action bundle B1
    set auth authname "AD-USERNAME"
    set auth password "AD-PASSWORD"
    set link max-redial 0
    set link mtu 1400
    set link keep-alive 20 75
    set link accept chap-msv2
    set link no pap eap

    set l2tp peer vpnserver.domain.tld
    open

Starten mit mpd5. Wenn alles klappt, erscheint ein ng0 Interface:

ng0: flags=88d1<UP,POINTOPOINT,RUNNING,NOARP,SIMPLEX,MULTICAST>
    inet 10.16.100.34 --> 10.16.100.13 netmask 0xffffffff

Hinweise zur mpd5-Konfiguration

set ccp yes mppc aktiviert MPPC-Komprimierung und MPPE-Verschlüsselung. set mppc yes e128 stateless ist Pflicht für die Zusammenarbeit mit MS-CHAPv2 auf der Windows-Seite. Andere MPPE-Varianten (e40, e56) funktionieren mit MS-CHAPv2 nicht.

Der Windows VPN-Server übermittelt zwar Routen und DNS-Server, mpd5 übernimmt davon aber nicht alles automatisch. Deshalb die manuellen Routen mit set iface route. Die DNS-Server werden per IPCP abgefragt und an die Up/Down-Scripte übergeben. Da ich die DNS-Konfiguration kenne, kopiere ich in den Scripten einfach die passende /etc/resolv.conf.

Ich starte IPsec und dann mpd5 von Hand, wenn ich die Verbindung brauche. Man kann beides auch als Dienst konfigurieren.

Wer seinen Windows RRAS-Server mit sicheren Cipher Suites absichern will: In dem Beitrag geht es um die TLS-Seite der gleichen Infrastruktur.

Siehe auch: RRAS L2TP/IPsec VPN Cipher Suites

Fragen? Einfach melden.

IPv6 ULA (fd00::/8), fc00::/7 und warum die Priorität oft anders ist als erwartet

Pv6 Unique Local Address fd00::/8 vs IPv4 – Priorität, Prefix Policy und Default Address Selection

Unique Local IPv6 Addresses sind eines dieser Themen, über die man meist erst stolpert, wenn man IPv6 ernsthaft benutzt. Nicht beim ersten „IPv6 ist an“-Häkchen, sondern dann, wenn man anfängt, Netze sauber zu trennen, VPNs aufzubauen, interne Services umzuziehen oder einfach keine Lust mehr auf NAT und IPv4-Private hat. Wer die IPv6-Grundlagen auffrischen will, findet dort den Einstieg.

ULA sollen genau das sein: lokal, eindeutig genug, nicht global routbar. Im Prinzip der IPv6-Nachfolger von 10/8 & Co. Klingt simpel. Ist es auch – bis man merkt, dass Betriebssysteme mit ULA manchmal Dinge tun, die man nicht intuitiv erwartet.

Fangen wir vorne an.

Der reservierte Adressraum für ULA ist fc00::/7. Das liest man oft so, und formal ist das auch korrekt. Praktisch relevant ist davon aber nur fd00::/8. Das sogenannte L-Bit (local) muss gesetzt sein. Der andere Teil, also fc00::/8, ist bis heute nicht weiter definiert und sollte in realen Netzen schlicht nicht verwendet werden. Wenn man ULA nutzt, dann immer fd….

Eine typische ULA sieht dann so aus:

fdXX:XXXX:XXXX::/48

Aufgeschlüsselt:

| 8 Bit | 40 Bit    | 16 Bit | 64 Bit        |
| fd    | Global ID | Subnet | Interface ID |
  • fd → Local-Bit gesetzt
  • Global ID → pseudozufällig, soll Kollisionen vermeiden
  • Subnet → klassische Subnetzstruktur
  • Interface ID → wie bei anderen IPv6-Unicast-Adressen

Die Global ID ist nicht „zentral vergeben“, sondern wird lokal generiert. Ziel ist nicht Sicherheit, sondern praktische Eindeutigkeit, falls Netze später zusammengeführt werden. In der Praxis funktioniert das erstaunlich gut.

Bis hierhin ist alles noch harmlos. Die eigentliche Verwirrung beginnt in dem Moment, in dem ein Host mehrere mögliche Wege zum Ziel hat.

Dual-Stack ist heute der Normalfall. IPv4 und IPv6 gleichzeitig. Und plötzlich steht ein System vor der Frage:
Nehme ich IPv4? Nehme ich IPv6? Und wenn IPv6 – welche Adresse eigentlich?

Die Antwort darauf regelt RFC 6724. Dort ist die Default Address Selection definiert. Vereinfacht gesagt: eine Prioritätenliste für Adresspräfixe. Jedes Präfix bekommt eine Präzedenz. Höher gewinnt.

Und genau hier liegt der Punkt, der viele überrascht:
IPv6 ULA haben nach RFC 6724 eine niedrigere Priorität als IPv4.

Das heißt ganz konkret:
Ist ein Ziel sowohl über IPv4 als auch über IPv6-ULA erreichbar, wird IPv4 bevorzugt.

Das fühlt sich erstmal kontraintuitiv an. IPv6 ist doch „das Neue“. Aber aus Sicht des Standards ist die Logik klar: ULA sind bewusst lokal begrenzt. IPv4 ist – trotz aller Altlasten – global eindeutig. Also gewinnt IPv4.

In der Praxis sieht man dieses Verhalten regelmäßig, vor allem auf Linux- und FreeBSD-Systemen, die sich sehr nah am RFC orientieren. Windows und Apple-Systeme mischen zusätzlich noch Happy-Eyeballs-Mechanismen hinein, was das Verhalten manchmal schwerer nachvollziehbar macht, am Grundprinzip aber nichts ändert.

Wenn man verstehen will, was ein System tatsächlich tut, hilft ein Blick in die jeweilige Prefix-Policy.

Diagnose: Welche Prioritäten nutzt mein System?

Linux:

ip -6 addr show
ip -6 route show
ip -f inet6 addrlabel show

Interessant ist vor allem die Ausgabe der Address-Labels. Dort sieht man, mit welcher Präzedenz fd00::/8, IPv4-Mapped-Adressen und andere Präfixe bewertet werden.

Windows:

netsh interface ipv6 show prefixpolicies

Hier sieht man sehr direkt, welche Präzedenz Windows den einzelnen Präfixen zuordnet. In der Default-Konfiguration liegt ULA unter IPv4.

FreeBSD:

ip6addrctl

Auch hier ist die RFC-6724-Policy gut sichtbar.

Spätestens an dieser Stelle wird klar, warum ein interner Dienst trotz sauber konfigurierter IPv6-ULA plötzlich doch über IPv4 angesprochen wird. Das System macht exakt das, was der Standard vorsieht.

Nun kann man sagen: „Okay, verstanden.“
Oder man kann sagen: „Das ist nicht das Verhalten, das ich will.“

Beides ist legitim.

Anpassung: ULA bewusst höher priorisieren

Wenn ULA für interne Kommunikation wichtiger sind als IPv4 – etwa in reinen IPv6-Infrastrukturen mit IPv4 nur als Fallback – kann man die Präzedenz anpassen.

Linux (/etc/gai.conf):

# IPv6 ULA höher priorisieren als IPv4
precedence fd00::/8  45

Nach einem Reload des Stacks oder Neustart gilt die neue Reihenfolge.

Windows:

netsh interface ipv6 set prefixpolicy fd00::/8 precedence=45 label=1

Damit liegt ULA über IPv4. Windows speichert diese Einstellung persistent.

FreeBSD:

Je nach Version über ip6addrctl oder entsprechende rc-Settings.

Wichtig: Das ist keine rein kosmetische Änderung. Man greift hier bewusst in die Adressauswahl ein. Das sollte man nur tun, wenn man das Netzdesign verstanden hat und weiß, warum man es will.

ULA sind kein Ersatz für Global Unicast Addresses. Sie sind auch kein Allheilmittel. Sie sind ein Werkzeug. Ein gutes – aber eben eines mit klar definiertem Scope.

Spannend ist, dass es inzwischen Entwürfe gibt, die das Verhalten von RFC 6724 weiterentwickeln. Ziel ist unter anderem, ULA-zu-ULA-Kommunikation besser zu priorisieren und bestimmte unerwünschte IPv4-Fallbacks zu vermeiden (ähnlich dem Problem mit Carrier Grade NAT und IPv6). Stand heute ist das aber noch nicht flächendeckend umgesetzt. Man sollte sich also nicht darauf verlassen, sondern das Verhalten der eigenen Systeme prüfen.

Am Ende bleibt:

ULA funktionieren. Sie sind sauber spezifiziert. Aber ihre Priorität ist kein Zufall, sondern eine bewusste Designentscheidung. Wer sie einsetzt, sollte wissen, warum IPv4 manchmal „gewinnt“ – und dann entscheiden, ob das so bleiben soll oder nicht.

Wie so oft bei IPv6 liegt das eigentliche Problem nicht im Protokoll, sondern in den Erwartungen, die man aus der IPv4-Welt mitbringt.

Siehe auch: IPv6 Grundlagen

Fragen? Einfach melden.

FreeBSD Kernel Quellen installieren | How to install FreeBSD kernel sources

Wie immer wenn mich eine Frage oft erreicht, gibt es hier dazu eine kurze Erklärung. Dieser Beitrag wird wirklich extrem kurz, denn um die Kernel Quellen für sein FreeBSD zu installieren nutze ich selbst immer folgenden Einzeiler:

# sudo svn checkout https://svn.freebsd.org/base/releng/`uname -r | cut -d'-' -f1,1` /usr/src

Tja, ich sag doch… Einfach und kurz. Viel Spaß

root@errortest:/etc/X11 # cd /usr/ports/graphics/drm-current-kmod/ && make install clean
===>  drm-current-kmod-4.16.g20190430 requires kernel source files in /usr/src.
*** Error code 1

Stop.
make: stopped in /usr/ports/graphics/drm-current-kmod

Fragen? Einfach melden.

FreeBSD Jail Upgrade: Wenn freebsd-update die Version nicht erkennt

FreeBSD-Jails lassen sich mit freebsd-update genauso upgraden wie das Host-System. Der Parameter -b gibt den Pfad zur Jail an:

# Normales Jail-Upgrade
freebsd-update -r 14.2-RELEASE upgrade -b /zroot/jails/myjail
freebsd-update install -b /zroot/jails/myjail
service jail restart myjail
freebsd-update install -b /zroot/jails/myjail
# Pakete aktualisieren
jexec myjail pkg upgrade
freebsd-update install -b /zroot/jails/myjail

Das Problem: Falsche Versionserkennung

Manchmal ist freebsd-update davon überzeugt, dass die Jail bereits auf der Zielversion läuft, obwohl sie es nicht ist. Prüft man manuell, steht da noch die alte Version:

jexec myjail freebsd-version
13.2-RELEASE-p9

Das passiert typischerweise wenn die Jail schon Patches bekommen hat oder wenn der Host auf einer anderen Version läuft als die Jail. freebsd-update liest die Version aus Dateien im Jail-Dateisystem und kommt durcheinander.

Die Lösung: –currently-running

Mit --currently-running gibt man freebsd-update die aktuelle Version explizit vor:

freebsd-update -b /zroot/jails/myjail --currently-running 13.2-RELEASE-p9 -r 14.2-RELEASE upgrade

Danach läuft das Upgrade normal durch. Die Version, die man bei --currently-running angibt, muss exakt der Ausgabe von freebsd-version in der Jail entsprechen, inklusive Patchlevel.

Tipp: Vor dem Upgrade einen ZFS-Snapshot der Jail anlegen. Falls etwas schiefgeht, ist ein Rollback in Sekunden erledigt.

Fragen? Einfach melden.

« Ältere Beiträge Neuere Beiträge »

© 2026 -=Kernel-Error=-RSS

Theme von Anders NorénHoch ↑