Ich hatte ja schon beschrieben wie man OpenDKIM konfiguriert und auch, dass ich dieses auf weitere Domains ausgeweitet habe und dafür die Konfiguration angepasst und verbessert habe.
Was noch offen war: Bislang waren die Schlüssel und Signatur-Regeln in Dateien im Dateisystem hinterlegt und der Abgleich der Daten mit dem DNS erfolgte manuell. Das war mir dann doch etwas lästig auch wenn das Anlegen mit Sicherheit nicht zu meiner Tagesroutine werden wird. Daher war ich sehr erfreut, dass openDKIM mit Support für ein SQL-Backend daher kommt. Im folgenden beschreibe ich die notwendigen Schritte um ein derartiges Setup ans Laufen zu bekommen. Hilfreich ist weiterhin die Seite von kofler.info und die Readme zu SQL von OpenDKIM.
Schritt 1: Datenbank vorbereiten
DKIM ist eine zweigesichtige Sache – einerseits gehört es zum Mail-Server, aber es hat eine starke Anbindung an das DNS. Für mich habe ich entschieden dass es näher am DNS liegt und daher landen die Einträge im gleichen Datenbank-Schema wie auch der Rest meiner DNS Konfiguration, welche auf PowerDNS basiert.
Analog zu den zwei Dateien zur Steuerung der zu verwendenden Schlüssel legt man sich zwei Tabellen an:
CREATE TABLE `dkim_keys` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `domain_name` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_general_ci', `selector` VARCHAR(63) NOT NULL COLLATE 'utf8mb3_general_ci', `private_key` MEDIUMTEXT NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', `public_key` MEDIUMTEXT NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `domain_name_selector` (`domain_name`, `selector`) USING BTREE, CONSTRAINT `FK_dkim_domains` FOREIGN KEY (`domain_name`) REFERENCES `domains` (`name`) ON UPDATE CASCADE ON DELETE RESTRICT ) COLLATE='utf8mb3_general_ci' ENGINE=InnoDB;
CREATE TABLE `dkim_signing` ( `domain_name` VARCHAR(255) NOT NULL COLLATE 'utf8mb3_general_ci', `key_id` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`domain_name`) USING BTREE, INDEX `FK_dkim_signing_dkim_keys` (`key_id`) USING BTREE, CONSTRAINT `FK_dkim_signing_dkim_keys` FOREIGN KEY (`key_id`) REFERENCES `dkim_keys` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT `FK_dkim_signing_domains` FOREIGN KEY (`domain_name`) REFERENCES `domains` (`name`) ON UPDATE CASCADE ON DELETE RESTRICT ) COLLATE='utf8mb3_general_ci' ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
Wie man sehr schön sieht habe ich die Tabelle auch gleich mit Fremdschlüsseln ausgestattet, das gibt zusätzliche Sicherheit, dass man nicht Keys für nicht angelegt Domains verwaltet. Selbst wenn man nicht PowerDNS nutzt empfehle ich die Verwendung einer Tabelle mit den vorhandenen Domains.
Ob man einen dedizierten Benutzer mit eingeschränkten Rechten für die Tabellen verwendet muss man selbst entscheiden – ich halte dies mittlerweile recht strikt und jeder Service bekommt einen eigenen User mit minimalen Zugriffsrechten. Wer nur erst einmal experimentieren möchte, kann hier natürlich auch einen bestehenden Benutzer verwenden – z.B. den aus PowerDNS.
Schritt 2: Datenbank befüllen
Die Tabellen sind im Wesentlichen ja selbsterklärend, man muss nunmehr diese entsprechend der bisherigen Konfiguration befüllen: Also anhand des DKIM Selektors jeweils den privaten und den öffentlichen Schlüssel heraussuchen und in die ‚dkim_keys‘ eintragen.
In der ‚dkim_signing‘ trägt man dann die Regeln ein nach denen ein bestimmter Schlüssel ausgewählt werden soll – da ich aktuell immer nur einen Schlüssel pro Domain bzw. Subdomain verwende kommen bei mir hier auch nur diese Namen in Frage, daher habe ich auch hier einen Fremdschlüssel gesetzt – wichtig ist dabei: Wenn man Subdomains einsetzt muss man diese in PowerDNS auch als solche ganz offiziell deklarieren – es ginge auch ohne. Zudem kann es natürlich sein, dass man weitere Wünsche hat. Wie wir gleich sehen werden, ist das Schema und das Query aus OpenDKIM an dieser Stelle recht flexibel.
Schritt 3: OpenDBX installieren
OpenDKIM verwendet als Datenbank-Abstraktion opendbx. Dies wird nicht per default als dependency mit installiert. Unter Debian und Ubuntu erledigt man dies mit
apt-get install libopendbx1-mysql
Vergisst man dies, merkt man es spätestens wenn man OpenDKIM mit der geänderten Konfiguration starten möchte – die Fehlermeldungen zeigen in diesem Fall aber dankenswerter Weise gleich auf die richtige Stelle.
Schritt 4: OpenDKIM konfigurieren
In der Datei ‚/etc/opendkim.conf‘ trägt man nun noch ein, wie OpenDKIM die Schlüssel ermitteln soll:
SigningTable dsn:mysql://dkimuser:verysecretpassword@localhost/dns/table=dkim_signing?keycol=domain_name?datacol=key_id KeyTable dsn:mysql://dkimuser:verysecretpassword@localhost/dns/table=dkim_keys?keycol=id?datacol=domain_name,selector,private_key
Wie man sehr schön sieht kann man hier einen nahezu beliebigen Host ansprechend (was potentiell interessant ist, wenn man daran denkt OpenDKIM als Mikroservice z.B. in einem Docker-Container zu betreiben. Ebenso sieht man sehr schön, dass man die Queries entsprechend der eigenen Namenskonventionen hinsichtlich der Tabellennamen und Spalten anpassen kann.
Nach der Anpassung muss OpenDKIM neu gestartet werden:
systemctl restart opendkim.service
Schritt 5: Ausprobieren / Testen
Wie immer sollte man im Anschluss einige Tests durchführen – also einige e-mails schreiben. Da OpenDKIM als Milter arbeitet bekommt man in der Regel schon direkt mit, wenn man in der Konfiguration etwas nicht richtig gemacht hat, sobald man eine e-mail verschicken möchte. Externe Prüftools wie https://www.mail-tester.com helfen zusätzlich bei der Prüfung.
Schritt 6: Noch etwas mehr automatisieren per Trigger
Bei der initialen Einrichtung von OpenDKIM bzw. beim Schlüsseltausch muss man auch immer die DNS-Einträge aktualisieren. OpenDKIM erzeugt leider nur einen Schnippsel für BIND, aber kein Insert-Statement für PowerDNS. Da man aber gerne vergisst, das man auch die Domain-Records entsprechend zu aktualisieren, habe ich mir noch zwei Trigger in der Datenbank angelegt die dies automatisieren.
Zum einen sorgt man dafür, dass beim Eintragen eines neuen Schlüsselpaares der öffentliche Schlüssel auch in der DNS-Record Tabelle verewigt wird – inklusive der notwendigen Syntax für DKIM (auch die muss man sich somit nicht mehr merken oder nachschlagen:
CREATE DEFINER=`murphy`@`localhost` TRIGGER `dkim_keys_publish_new_key` AFTER INSERT ON `dkim_keys` FOR EACH ROW BEGIN
INSERT INTO `records` (`domain_id`, `name`, `type`, `content`)
SELECT id,CONCAT(NEW.selector,'._domainkey.',domains.name), 'TXT', CONCAT('v=DKIM1; h=sha256; k=rsa; s=email; p=', NEW.public_key)
FROM domains
WHERE domains.name = NEW.domain_name;
END
Wenn man die Einträge verändert, soll dies auch in der Record-Tabelle geschehen – hierbei muss man aufpassen, denn PowerDNS erfordert es, dass man die Domain-Ids (Fremdschlüssel) korrekt setzt.
CREATE DEFINER=`murphy`@`localhost` TRIGGER `dkim_keys_update_published_key` AFTER UPDATE ON `dkim_keys` FOR EACH ROW BEGIN
UPDATE domains
INNER JOIN records ON 1=1
SET records.domain_id = domains.id,
records.content= CONCAT('v=DKIM1; h=sha256; k=rsa; s=email; p=', NEW.public_key),
records.name = CONCAT(NEW.selector,'._domainkey.', NEW.domain_name)
where
records.name = CONCAT(OLD.selector,'._domainkey.', OLD.domain_name)
AND domains.name = NEW.domain_name
AND records.`type`='TXT';
END