Hands-on: de implementatie van SPF, DKIM en DMARC in Exim
Een solide fundament voor een veilig mailsysteem
Een solide fundament voor een veilig mailsysteem
In dit artikel bespreken we de installatie en configuratie van de internetbeveiligingsstandaarden SPF, DKIM en DMARC in de Exim-mailserver. Deze 3 standaarden gaan phishing, spam, virussen en andere malware tegen door de afzender (een mailadres/-domein), de verzender (een mailsysteem) en de authenticiteit (de inhoud) van een mailbericht te beveiligen. Voor onze setup gaan we uit van een CentOS 8.2 Linux-server met daarop een goed werkende standaardinstallatie van Exim, en een recht-toe-recht-aan zonefile voor het met DNSSEC beveiligde domein example.nl. Deze setup breiden we vervolgens uit met:
• de publicatie van SPF-records • de validatie van SPF • de validatie van DKIM • de ondertekening met DKIM • de publicatie van DKIM-records • de publicatie van DMARC-records • de validatie van DMARC
Waar relevant geven we verschillende mogelijkheden (waarbij we ons wel beperken tot de pakketten die kant-en-klaar in de repositories voor CentOS beschikbaar zijn) en bespreken we de afwegingen om tot een keuze te komen. Bovendien zijn de configuraties ingebed in uitleg over de werking van de standaarden. Hoewel de initiële configuratie (op CentOS) nog helemaal geen policies voor deze 3 standaarden bevat, is volwaardige ondersteuning voor SPF, DKIM en DMARC weldegelijk ingebakken in de software. Om hierop een veilig mailsysteem te bouwen, moet je wel eerst de werking en betekenis van de statements in de Exim-configuratie goed doorgronden. Wat je er voor terugkrijgt is een goed dichtgetimmerd systeem dat voldoet aan de laatste internetstandaarden. Bovendien is mailbeveiliging op domein-niveau een praktische voorwaarde bij de transitie van IPv4 naar IPv6, waar je niet langer kunt filteren op IP-adres.
SPF, DKIM, en DMARC gaan phishing, spam, virussen en andere malware tegen door de afzender (een mailadres/domein), de verzender (een mailsysteem) en de authenticiteit (de inhoud) van een mailbericht te beveiligen. Daarvoor moet de verwerking van berichten door de server worden aangepast en moeten diverse nieuwe records in de DNS-informatie worden opgenomen. Hoewel ondertekening met DNSSEC voor deze toepassing niet strikt noodzakelijk is – dat wil zeggen verplicht volgens de standaarden – is dat wel een belangrijke toevoeging. Daarmee is het drietal SPF/DKIM/DMARC een van de eerste concrete toepassingen van de cryptografisch beveiligde infrastructuur die over het afgelopen decennium met DNSSEC is aangelegd. Hoewel deze 3 standaarden meestal gezamenlijk worden ingezet, hoeft dat niet perse. Je kunt ook alleen DKIM of SPF inzetten, of DMARC weglaten. Dat is ook de reden dat we deze standaarden hieronder onafhankelijk van elkaar bespreken en implementeren. De resulterende configuraties leveren ook steeds een zelfstandige oplossing op die prima op zichzelf kan staan.
Basiskennis van het SMTP- en mailprotocol, alsook het UNIX operating system, is wel vereist. Naast het IP-adres waarmee hij verbinding maakt met de ontvangende mailserver, is dit de informatie die de aanbiedende mail server via de SMTP handshake overdraagt:
bij het 'EHLO'/'HELO'-commando: de hostname van de aanbiedende mailserver
bij het 'MAIL FROM'-commando: het afzender/return-adres van het bericht
bij het 'RCPT TO'-commando: de geadresseerde(n) voor wie het bericht bestemd is
in de 'From' header: de afzendernaam en -adres (de display name) zoals uiteindelijk getoond aan de ontvangende gebruiker
Dat brengt ons gelijk bij de kern van de problemen met het basis (E)SMTP-protocol: de aanbiedende server kan bij de commando's 'EHLO'/'HELO' en 'MAIL FROM' (die samen met de informatie uit het 'RCPT TO'-commando de envelope van het mail-bericht vormen) opgeven wat hij maar wil. Dat maakt dat het mailadres van de verzender (daarna in de 'Return-Path' header) en de hostname van de aanbieder (daarna in de 'Received' header) zo makkelijk te spoofen zijn.
Dit is hoe onze 3 beveiligingsstandaarden de informatie in de envelope en de display name bevestigen (valideren):
SPF: de validatie van de 'EHLO'/'HELO' en 'MAIL FROM'-informatie (de mail envelope)
(optioneel) check de 'EHLO'/'HELO' hostname om te zien of het IP-adres daarvan inderdaad resolvet naar het IP-adres van de client, waarmee is bewezen dat de client (de aanbiedende mailserver) inderdaad is wie hij zegt te zijn
(verplicht) check het afzenderdomein van het 'MAIL FROM'-adres om te zien of het IP-adres van de client inderdaad geautoriseerd is om mail van dit domein aan te bieden, waarmee is bewezen dat de client (de aanbiedende mailserver) gerechtigd is om namens de afzender berichten door/uit te sturen
is er geen 'MAIL FROM'-informatie beschikbaar (typisch voor bounce-berichten), dan valt SPF terug van het afzenderdomein naar de 'EHLO'/'HELO' hostname
het (positieve) resultaat is een gevalideerde mail envelope
validatie doe je zo vroeg mogelijk in de mailpijplijn (op de MX-gateway), zodat de software bij een harde faal nog foutmeldingen terug kan geven via de lopende SMTP-verbinding
als de validatie van een bericht/envelope faalt, dan kan een verbinding/bericht gelijk geblokkeerd worden, of het bericht wordt verder doorgegeven met het negatieve resultaat in een 'Received-SPF' en/of 'Authentication-Results' header
DKIM: de ondertekening van uitgaande mailberichten en hun headers, en de validatie van de handtekening onder binnenkomende berichten
check of de digitale handtekening meegestuurd met het bericht (in de 'DKIM-Signature' header) klopt; gebruik daarvoor de publieke sleutel gepubliceerd in het signing domain dat ook in deze header staat aangegeven;
omdat het signing domain niet persé overeen hoeft te komen met het afzenderdomein in de 'MAIL FROM' of de 'From' header, doet DKIM in eerste instantie niet meer dan de verantwoordelijkheid van het signing domain bevestigen voor het in het verkeer brengen van een bericht
de ondertekening van uitgaande berichten doe je zo laat mogelijk in de verzendende mailpijplijn (op de laatste hop van jouw infrastructuur), zodat zo veel mogelijk headers onder de handtekening vallen; de validatie van de handtekening onder binnenkomende berichten doe je zo vroeg mogelijk in de ontvangende pijplijn (op de eerste hop van jouw infrastructuur), zodat nieuw toegevoegde of aangepaste headers het validatieproces zo min mogelijk verstoren
DKIM blokkeert zelf geen berichten; de uitkomst van de validatie wordt aan het bericht toegevoegd in een 'Authentication-Results' header en bijvoorbeeld meegenomen in de score van een spamfilter of in de DMARC-evaluatie
DMARC: de verwerking van de uitkomsten van de SPF- en/of DKIM-validatie op basis van een policy gepubliceerd door het afzenderdomein in de 'From' header
check of het afzenderdomein in de 'From' header ten eerste overeenkomt met het afzenderdomein in de envelope (voor SPF) en ten tweede met het signing domain (voor DKIM); met deze alignment-check wordt een koppeling gemaakt tussen de SPF/DKIM-gevalideerde afzenderdomeinen enerzijds en de informatie in de 'From' header (de display name) anderzijds
gebruik de policy zoals gepubliceerd in het afzenderdomein van de 'From' header om te beslissen wat te doen met binnengekomen mail-berichten waarvan zowel de SPF- als de DKIM-validatie (inclusief de alignment) negatief is: doorlaten, blokkeren of in quarantaine plaatsen
stuur desgevraagd (in de policy) geaggregeerde en failure-rapportages naar de beheerder van het afzenderdomein; deze berichten zijn gestandaardiseerd zodat ze automatisch kunnen worden verwerkt
Hieronder bespreken we achtereenvolgens de implementatie van SPF, DKIM en DMARC. In dit artikel doen we dat voor Exim, de meest gebruikte mailserver. De configuratie van de Postfix server doorlopen we op vergelijkbare wijze in dit artikel. Met de bespreking van deze 2 mailservers dekken we meer dan 90 procent van de huidige mailservermarkt. In dit artikel laten we zien hoe je DANE, een ander protocol dat voortbouwt op de DNSSEC-infrastructuur, gebruikt om de TLS-certificaten van je mail-gateway te borgen. Omdat SPF, DKIM en DMARC enerzijds en DANE voor mail anderzijds volledig onafhankelijk zijn van elkaar, maakt het niet uit welke van deze 2 je eerst implementeert. Wel is het gebruik van DNSSEC verplicht voor DANE, terwijl dit voor SPF, DKIM en DMARC alleen aanbevolen is. Op onze DNSSEC-pagina vind je hands-on beschrijvingen voor de implementatie van DNSSEC-ondertekening en -validatie voor Unbound, Infoblox, PowerDNS en BIND. Heb je deze 5 standaarden geïmplementeerd en draaien je systemen ook op IPv6, dan is dit alles bij elkaar genoeg voor een 100 procent score op Internet.nl. Met de inzet van deze moderne internet(beveiligings)standaarden zijn je mailsystemen helemaal up-to-date en voldoen ze aan de laatste eisen en inzichten zoals die onder andere door Forum Standaardisatie [1, 2] en het Nationaal Cyber Security Centrum (NCSC) [1, 2] opgesteld zijn. De Internet.nl-portal is sowieso een goede tool om te zien waar je je nu bevindt en om tijdens de implementatie van nieuwe internetstandaarden te checken of alles inderdaad werkt zoals bedoeld.
We zijn voor dit artikel uitgegaan van de CentOS Linux-distributie versie 8.2., de community-editie van Red Hat Enterprise Linux (RHEL). Daarop zijn we aan de slag gegaan met deze software-pakketten:
BIND named (in een chroot jail) versie 9.11.13
Exim versie 4.94
OpenSSL versie 1.1.1c
Dovecot versie 2.3.8
Cyrus SASL versie 2.1.27
Dovecot-Pigeonhole versie 2.3.8
Clam AntiVirus (ClamAV) versie 0.102.3
SpamAssassin versie 3.4.2
libspf2 versie 1.2.10
libopendmarc (OpenDMARC) versie 1.3.2
Op moment van schrijven (juni 2020) zijn dit de laatste versies van de betreffende pakketten zoals die beschikbaar zijn in de standaard repositories van CentOS versie 8.2. De ClamAV, libspf2 en OpenDMARC-pakketten zijn afkomstig uit de EPEL repository . Deze Extra Packages for Enterprise Linux worden gecompileerd uit de Fedora-software, de development-distributie voor RHEL. Dat betekent dat je deze set-up op identieke wijze na kunt bouwen op RHEL of Fedora. Voor het enablen en installeren van de vereiste PowerTools en EPEL repositories, gebruik je achtereenvolgens deze commando's:
dnf config-manager --set-enabled PowerTools dnf install epel-release dnf config-manager --set-enabled epel
ClamAV en SpamAssassin draaien in onze setup als zelfstandige daemons, een configuratie beter geschikt voor grotere productie-omgevingen. Sommige van de zelfstandige daemons zou je in een kleinere omgeving ook lokaal op een UNIX domain socket kunnen draaien.
Uitgangspunt voor onze configuratie is om te beginnen een goed werkende autoritatieve DNS-server voor het domein example.nl. Daarvoor gebruiken we deze deze zonefile:
$TTL 1d ; ttl is 1 day @ IN SOA dns1.example.nl. dns.example.nl. ( 2020050800 ; serial (date & version) 8h ; refresh every 8 hours 20m ; retry after 20 minutes 4w ; expire after 4 weeks 20m ; negative caching ttl is 20 minutes ) ; DNS name servers IN NS dns1.example.nl. ; primary name server IN NS dns2.example.nl. ; secondary name server ; SMTP mail gateways IN MX 10 mx.example.nl. ; MX gateway IN MX 100 fallback-mx.example.nl. ; fallback MX gateway ; hosts IN A 192.0.2.162 ; server IN AAAA 2001:db8:1192::2:162 ; server (IPv6) www IN CNAME example.nl. ; WWW server ftp IN CNAME example.nl. ; FTP server mx IN A 192.0.2.106 ; mail gateway mx IN AAAA 2001:db8:1192::2:106 ; mail gateway (IPv6) mail IN A 192.0.2.107 ; mail server mail IN AAAA 2001:db8:1192::2:107 ; mail server (IPv6) imap IN A 192.0.2.108 ; IMAP server imap IN AAAA 2001:db8:1192::2:108 ; IMAP server (IPv6) smtp IN A 192.0.2.109 ; SMTP gateway smtp IN AAAA 2001:db8:1192::2:109 ; SMTP gateway (IPv6) ; exterior hosts dns1 IN A 192.0.2.5 ; primary name server dns1 IN AAAA 2001:db8:1192::2:5 ; primary name server (IPv6) dns2 IN A 198.51.100.6 ; secondary name server dns2 IN AAAA 2001:db8:2198::16:6 ; secondary name server (IPv6) fallback-mx IN A 198.51.100.106 ; fallback mail gateway fallback-mx IN AAAA 2001:db8:2198::32:106 ; fallback mail gateway (IPv6)
Bovendien is deze zone ondertekend met DNSSEC, wat betekent dat elke Resource Record Set (RRset) voor dit domein van een digitale handtekening is voorzien. Daarmee kan de ontvanger van een DNS-antwoord de authenticiteit van die informatie valideren. De records met deze handtekeningen en de andere records voor DNSSEC staan in ons geval niet in de zonefile. BIND named genereert in een moderne configuratie namelijk zelf de ondertekende zone, waarvan de informatie vervolgens via de autoritatieve DNS-servers op Internet wordt gepubliceerd. In dit hands-on artikel beschrijven we de hele configuratie voor de geautomatiseerde ondertekening van je domeinen op BIND. En op onze DNSSEC-pagina vind je vergelijkbare hands-on artikelen voor de ondertekening en validatie op diverse andere DNS-servers en -resolvers. Zoals je in het vervolg zult zien, bestaat de configuratie van SPF en DMARC uit de toevoeging van extra records in de DNS-configuratie, en aanpassingen in de configuratie van je mailserver. Omdat de Exim-software standaard al is voorzien van ondersteuning voor deze 3 protocollen, blijven de ingrepen relatief beperkt. Wat de configuratie van Exim lastig maakt, is dat de verschillende configuratie-opties niet heel intuïtief en consistent zijn, wat maakt dat je voor elke optie eerst moet nagaan wat de precieze betekenis en werking is. Na de algemene configuratie-opties geven we hieronder dan ook eerst een overzicht van de verschillende opties voor de Access Control Lists (ACL's) en hun eigenaardigheden. Een goed begrip daarvan is cruciaal om de details in dit artikel te kunnen volgen en je eigen setup straks naar eigen hand te zetten.
Voordat we in het diepe springen, geven we hier eerst nog wat meer achtergrondinformatie over de setup van onze Exim-installatie. Daarmee ligt er een basis waarop we straks verder bouwen door de instellingen voor SPF, DKIM en DMARC aan onze configuratie toe te voegen. Je zult zien dat alle faciliteiten hiervoor al beschikbaar zijn in de vorm van acties (verbs), tests (conditions), variabelen en switches (controls), en dat de configuratie van de mailserver vooral bestaat uit het instellen van opties en het toevoegen van nieuwe regels aan de ACL's.
Om te beginnen geven we hieronder de opties die we hebben ingesteld/veranderd in de basisconfiguratie van Exim (in de file '/etc/exim/exim.conf'). Voor de precieze betekenis verwijzen we je naar de bijbehorende documentatie.
primary_hostname = mail.example.nl domainlist local_domains = @ : example.nl : localhost : localhost.localdomain domainlist relay_to_domains = hostlist relay_from_hosts = localhost : 192.168.0.0/16 av_scanner = clamd:127.0.0.1 3310 spamd_address = 127.0.0.1 783 tls_certificate = /etc/pki/tls/certs/exim.pem tls_privatekey = /etc/pki/tls/private/exim.pem tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT #daemon_smtp_ports = 25 : 465 : 587 #tls_on_connect_ports = 465 daemon_smtp_ports = 25 : 587 qualify_domain = example.nl auth_advertise_hosts = ${if eq {$tls_cipher}{}{}{*}} dns_dnssec_ok = 1 accept_8bitmime = false begin acl acl_check_mail: # reject messages from senders listed in these DNSBLs deny dnslists = zen.spamhaus.org acl_check_rcpt: accept hosts = : control = dkim_disable_verify control = dmarc_disable_verify accept hosts = +relay_from_hosts control = submission control = dkim_disable_verify control = dmarc_disable_verify accept authenticated = * control = submission control = dkim_disable_verify control = dmarc_disable_verify acl_check_data: deny malware = * message = This message contains a virus ($malware_name). accept condition = ${if >={$message_size}{100000} {1}} add_header = X-Spam-Note: SpamAssassin run bypassed due to message size warn spam = nobody/defer_ok add_header = X-Spam-Flag: YES accept condition = ${if !def:spam_score_int {1}} add_header = X-Spam-Note: SpamAssassin invocation failed warn add_header = X-Spam-Score: $spam_score ($spam_bar)\n\ X-Spam-Report: $spam_report deny condition = ${if >{$spam_score_int}{100} {1}} message = Your message scored $spam_score SpamAssassin point. Report follows:\n\ $spam_report warn condition = ${if >{$spam_score_int}{5} {1}} set acl_m_greylistreasons = Message has $spam_score SpamAssassin points\n$acl_m_greylistreasons .include /etc/exim/exim-greylist.conf.inc begin routers userforward: local_part_suffix = +* : -* local_part_suffix_optional localuser: local_part_suffix = +* : -* local_part_suffix_optional begin transports local_delivery: #file = /var/mail/$local_part directory = $home/Maildir maildir_format
Voordat we verder kunnen, moeten we eerst aandacht besteden aan de opbouw en de configuratie-opties van het Exim-configuratiebestand ('/etc/exim/exim.conf'). Omdat de verschillende opties niet heel intuïtief en consistent zijn, is een goed overzicht van de precieze betekenis, werking en eigenaardigheden cruciaal om de details in dit artikel te kunnen volgen en je eigen setup naar eigen hand te zetten. Naast de zojuist besproken globale configuratie-opties, bevat de Exim-configuratie ook hooks waarmee op verschillende momenten tijdens de SMTP handshake en de verwerking van mailberichten ACL's kunnen worden gedefinieerd. Je kunt een (korte) expressie gelijk achter deze hooks zetten, maar gebruikelijker is om via een naam te verwijzen naar een uitgebreidere (inline) ACL-definitie in de 'acl'-sectie van de configuratie. Dit zijn de hooks en de bijbehorende ACL's die in onze configuratie worden gebruikt:
acl_smtp_mail → acl_check_mail aangeroepen na ontvangst van het 'MAIL FROM'-commando (en het 'EHLO'/'HELO' commando daarvoor)
acl_smtp_rcpt → acl_check_rcpt aangeroepen na ontvangst van het 'RCPT TO'-commando
acl_smtp_dkim → acl_check_dkim aangeroepen na ontvangst van het bericht, voor elke DKIM-handtekening apart (deze hook is niet gedefinieerd in de standaard configuratie en zullen we straks toevoegen)
acl_smtp_mime → acl_check_mime aangeroepen gelijk nadat alle data is ontvangen, waarmee direct een scan kan worden uitgevoerd op alle MIME-onderdelen in het bericht
acl_smtp_data → acl_check_data aangeroepen (als laatste) nadat het volledige bericht (alle data) is ontvangen
De ACL zelf bevat achtereenvolgende acties (verbs), elk daarvan weer voorzien van een lijst van tests (conditions), switches (controls) en (message) modifiers. Een actie wordt uitgevoerd als alle onderdelen in de lijst true zijn. Daarbij wordt de evaluatie afgebroken (en verdere onderdelen dus niet meer uitgevoerd) zodra de uitkomst van een test false is. Of vervolgens met de evaluatie van de volgende actie wordt doorgegaan, verschilt per actie. Hieronder geven we achtereenvolgens een overzicht van de voor ons relevante acties en tests. Dit overzicht en bovenstaande uitleg zijn het makkelijkst te begrijpen als je een ACL uit je eigen Exim-configuratie ernaast legt. Acties (verbs):
'accept': ga door met het evalueren van de volgende actie als alle tests true zijn; als een van de tests false is, doe dan een 'deny' en stop gelijk met de verdere evaluatie van de lijst Staat er na een falende test ergens een 'endpass' statement, dan wordt wel doorgegaan met de evaluatie van de lijst. Oftewel: bij gebruik van een 'endpass' statement kunnen alleen falende tests die daarna komen een 'accept' in een 'deny' veranderen; alle tests erboven worden altijd uitgevoerd, ongeacht de uitkomst. Omdat het nogal een verwarrend statement is, wordt het gebruik van 'endpass' inmiddels afgeraden. We gebruiken dit statement dan ook nergens in onze configuraties.
'discard': als 'accept' (de client krijgt dus ook een positieve response terug), maar dump het bericht
'deny': wijs het bericht (permanent) af als alle tests true zijn; anders gelijk stoppen met de evaluatie en door naar de volgende actie
'drop': als 'accept', maar breek de SMTP-verbinding gelijk af bij een 'deny'
'defer': wijs het bericht (tijdelijk) af als alle tests true zijn; anders gelijk stoppen met de evaluatie en door naar de volgende actie
'require': ga door naar de volgende actie als alle tests true zijn; doe anders een 'deny'
'warn': stuur de 'log_message' naar de log als alle tests true zijn; ga daarna altijd door naar de volgende actie Bevat de lijst geen 'log_message' statement, dan worden alle controls in de lijst tot aan de eerste gefaalde conditie uitgevoerd. Levert een test een defer op, dan wordt wel dit feit maar niet de 'log_message' weggeschreven. Hierna wordt de evaluatie van de huidige lijst afgebroken en doorgegaan naar de volgende actie.
'add_header=': voeg een header toe aan een bericht
'continue=': doet niets; meestal gebruikt vanwege de side effects bij de expansie van de expressie die daarna volgt
'control=': verander de werking van Exim (locaal) door het zetten van switches
'delay=': wacht een bepaalde tijdsduur
'endpass' (alleen voor 'accept' en 'discard'): verwerk alle statements hierboven ongeacht de uitkomt (en doe een 'accept' ook als sommige falen); voor de statements hieronder geldt (gewoon) dat direct een 'deny' volgt zodra een test faalt Omdat dit nogal een verwarrend statement is, wordt het gebruik van 'endpass' inmiddels afgeraden.
'log_message=': stuur een bericht naar de log als een 'deny' plaatsvindt of als de condities bij een 'warn'-actie allemaal true zijn (in dat laatste geval voorafgegaan door 'Warning: '); maar ook bij een geldige 'discard'-actie Let op: het niet opgeven van een 'message' of 'log_message' betekent niet dat er niets gestuurd/gelogd wordt. Als je zo’n modifier leeg laat, dan wordt hiervoor intern beschikbare informatie gebruikt. Deze informatie wordt in sommige gevallen aangevuld met jouw bericht, in andere gevallen door jouw bericht vervangen.
'log_reject_target=': stel hiermee in wanneer er wel en niet een bericht naar de log gestuurd wordt
'logwrite=': schrijf altijd een bericht naar de log (dit in tegenstelling tot de 'log_message' modifier die alleen een bericht wegschrijft bij een 'deny', 'warn' of 'discard')
'message=': stuur een bericht bij de response op een SMTP-commando (alleen bij een 'accept', 'deny' of 'defer', niet bij een 'discard', en alleen als alle tests true zijn) In sommige gevallen vervang je hiermee een ander bericht dat door Exim al in deze modifier was klaargezet.
'queue=': specificeer the spool queue voor dit bericht (kan alleen voordat de DATA is ontvangen)
'remove_header=': verwijder een of meerdere headers
'set <name>=<value>': zet een variabele voor later gebruik
De laatste 3 van deze modifiers zijn voor ons minder relevant, en 'endpass' moet je zoals gezegd niet meer gebruiken. De specifieke control modifiers die voor ons direct van belang zijn:
'control = dkim_disable_verify': schakel DKIM-validatie uit
'control = dmarc_disable_verify': schakel DMARC-validatie uit
'control = submission/<options>': schakel Exim om naar submission mode (kan alleen voordat de DATA is ontvangen)
In het diagram hieronder geven we de workflow zoals die is na deze basisconfiguratie van Exim. Binnenkomende mailberichten worden aangenomen op de publieke (well-known) poort 25 (smtp), en na een check door ClamAV (op poort 3310) en SpamAssassin (op poort 783) intern doorgegeven voor aflevering. Poort 587 (submission) is na authenticatie (via SASL) beschikbaar als SMTP-gateway voor het versturen van berichten door de eigen gebruikers.
Belangrijkste in de configuratie voor de submission-service op poort 587 is dat deze alleen toegankelijk is voor geauthentiseerde gebruikers en dat de toepassing van TLS-versleuteling op deze verbindingen verplicht is. Voor deze poort gebeurt dat overigens altijd via het STARTTLS-commando. Er was door IANA ooit wel een speciale TCP-poort (nummer 465) gereserveerd voor SMTP-over-SSL (SMTPS), maar die is nooit gestandaardiseerd en inmiddels ook weer toegewezen aan een heel andere service. Mailadressen bevatten immers geen aanduiding voor een al-dan-niet-beveiligd transport, en mailsystemen hebben geen mogelijkheid om direct met de afzender te interacteren, anders dan via een bounce. Vandaar dat we via nu DANE/STARTTLS het transport 'zo veel mogelijk' proberen te beveiligen. De configuratie van DANE voor mail bespreken we in dit artikel. Daarin behandelen we ook de mogelijkheden om de veiligheid van Exim te verbeteren via de cryptografische instellingen voor TLS (hardening).
De enige harde informatie die we in dit stadium wel kunnen controleren is het IP-adres waarmee het aanbiedende systeem (de client) contact zoekt met onze server. Hier gebruiken we die informatie dan ook om te checken (in de 'acl_check_mail' ACL) deze aanbieder op de zwarte lijst van de DNSBL-dienst van het Spamhaus Project staat. Hieronder zul je zien dat we straks op datzelfde punt ('acl_check_mail') hetzelfde IP-adres gebruiken om te controleren of het aanbiedende systeem inderdaad (via de SPF-policy) geautoriseerd is om berichten te versturen vanaf het afzenderdomein zoals gebruikt in het 'MAIL FROM'-commando. Hetzelfde geldt voor de validatie van de DKIM-handtekeningen onder binnenkomende berichten: dat wil je zo vroeg mogelijk doen (op de eerste hop van jouw infrastructuur), voordat door je eigen systemen weer allerlei nieuwe headers aan een binnengekomen bericht zijn toegevoegd.
SPF, DKIM en DMARC zijn standaard al onderdeel van de Exim-software (als bij het compileren van de code de betreffende vlaggen zijn gezet). Voor de SPF-validatie wordt gebruik gemaakt van de 'libspf2' library. Vanaf Exim versie 4.93 is de status van dit onderdeel opgewaardeerd van Experimental naar Supported. Voor de DMARC-validatie gebruikt Exim de OpenDMARC library 'libopendmarc'. Beide libraries zijn beschikbaar in de EPEL repository, dus zorg dat deze ook geïnstalleerd worden:
dnf install libspf2 opendmarc
DKIM- en DMARC-validatie staan standaard aan voor alle binnenkomende berichten. Vandaar dat we in de ACL's van de meegeleverde configuratie (alsook hierboven) steeds deze 2 statements zien staan voor berichten afkomstig van lokale aanbieders, relay hosts en geauthentiseerde verbindingen:
control = dkim_disable_verify control = dmarc_disable_verify
Op deze manier worden de DKIM en DMARC checks expliciet uitgeschakeld voor alle berichten behalve die afkomstig van remote, niet-geauthentiseerde aanbieders (op de MX-gateway op poort 25).
In de log file '/var/log/exim/main.log' zien we dat de DMARC-configuratie na de installatie nog niet helemaal compleet is:
2020-06-06 15:41:35 DMARC failure to load tld list '/usr/share/publicsuffix/public_suffix_list.dat': No such file or directory
Deze 'public suffix' lijst is nodig om de hoofddomeinen te bepalen voor de DMARC alignment. Hoofddomeinen zijn in dit verband namelijk niet noodzakelijk gelijk aan de topleveldomeinen (TLD's) – denk maar aan het domein example.co.uk, waar een second-level domein het hoofddomein is. De installatie van deze lijst doen we met behulp van deze 2 opdrachten:
cd /usr/share/publicsuffix/ wget https://publicsuffix.org/list/public_suffix_list.dat
Het path '/usr/share/publicsuffix/public_suffix_list.dat' is de standaard locatie van dit bestand, zoals ingebakken in Exim. Wil je deze aanpassen, dan is daarvoor de configuratie-optie 'dmarc_tld_file' beschikbaar. We hebben deze file nu met de hand toegevoegd, maar het zou goed zijn de installatie/update hiervan te automatiseren middels een cron job of een systemd timer.
Kijken we naar de logs en de headers van een binnenkomend bericht, dan zien we dat SPF, DKIM en DMARC nu inderdaad alledrie worden gevalideerd:
2020-06-09 12:21:17 1jibNd-000300-Qk DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=yes enforcement='Accept' 2020-06-09 12:21:34 1jibNd-000300-Qk <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9261 DKIM=afzenderdomein.nl id=a3a31cf8-4d95-886d-b460-314d41bb5563@afzenderdomein.nl
Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Mon, 08 Jun 2020 19:44:59 +0200 Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jiLpP-0007an-4p for ontvanger@example.nl; Mon, 08 Jun 2020 19:44:59 +0200 Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id BEDE62800204; Mon, 8 Jun 2020 19:44:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:subject:subject:from:from:reply-to :received:received; s=dkim20191228; t=1591638292; x=1592502293; bh=pivdjK0vpM4A51Ei6TSEGj8ztCZ6DKRJxhL2ZwrggJ0=; b=ezQ/7THIjpP6 +EHXUZ8GTv7l4GT4lJodz10kF7ECG4qXrUxwPHxeAJgodC+Xc08BhdOfcP/zOYM6 UOxsad6uZO6kI5M3Kh8F2PwDz/qAoKM3x4lUkoPayO4nZ6yiXX9af/c/Fv8/18zx AXSYIciIu2K+6CYa480QZBndO/UoEvOZ+g7azyPp/DkB5L809dwMD6lylabX1mE8 qidzDjJ3WfNhHH8akaviawpr8hrF7KJPmcw7FNcvrLBY9mB7gYTU6kfha5Wz0QWr Ua+TG4eMRluxk2pGSJ72jOKvc0mmHP4= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id X8CgECyIYi3X; Mon, 8 Jun 2020 19:44:52 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomein.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 025CC2800629; Mon, 8 Jun 2020 19:44:51 +0200 (CEST) Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl From: "Tester" <tester@afzenderdomein.nl> Subject: Test initial Exim config Message-ID: <0aa1daa2-3e7f-2a63-7127-a2fab95d9b7b@afzenderdomein.nl> Date: Mon, 8 Jun 2020 19:44:50 +0200 X-Spam-Score: 1.1 (+) X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
Zoals je ziet zijn SPF en DKIM ook door SpamAssassin (beide positief) gevalideerd, en is een uitgebreide rapportage in de vorm van een 'X-Spam-Report' header toegevoegd. Alleen de constatering dat de verzendende mailserver geen reverse DNS entry heeft, zorgt voor een iets verhoogde spamscore.
Wat er niet deugt aan deze setup, is dat deze nieuwe (trace) headers aan de onderkant zijn toegevoegd en niet aan de bovenkant. Daarmee wordt de DKIM-handtekening in de 'DKIM-Signature' header die nu boven deze 2 headers staat (en ook de headers daaronder meeneemt) mogelijk verpest. Sowieso moeten trace fields – dat wil zeggen: headers tijdens het transport door de ontvangende en verzendende mailservers stapsgewijs aan een bericht worden toegevoegd – altijd aan de bovenkant van de header-lijst worden toegevoegd. Exim blijkt nieuwe headers standaard echter aan de onderkant van de header-lijst toe te voegen. Om dit gedrag te veranderen, voegen we in het configuratiebestand '/etc/exim/exim.conf' een ':at_start:' prefix toe aan alle 'add_header' statements:
accept condition = ${if >={$message_size}{100000} {1}} add_header = :at_start: X-Spam-Note: SpamAssassin run bypassed due to message size ... warn spam = nobody/defer_ok add_header = :at_start: X-Spam-Flag: YES accept condition = ${if !def:spam_score_int {1}} add_header = :at_start: X-Spam-Note: SpamAssassin invocation failed ... warn add_header = :at_start: X-Spam-Score: $spam_score ($spam_bar)\n\ :at_start: X-Spam-Report: $spam_report
Na de restart van Exim zien we dat de 'X-Spam-Score' en 'X-Spam-Report' headers nu inderdaad aan de bovenkant van de header-lijst worden toegevoegd. Behalve dat je hiermee alle headers netjes van onder naar boven kunt lezen, voldoen we hiermee ook beter aan de standaarden.
De SPF- en DKIM-validaties zoals je die terugziet in de 'X-Spam-Score' en 'X-Spam-Report' headers worden uitgevoerd als onderdeel van de spamscan. De validaties van SPF, DKIM en DMARC die je terugziet in de logs zijn onderdeel van Exim zelf. Zoals je in het configuratiebestand kunt zien, wordt er nu niets gedaan met de uitkomsten van deze validaties. Het is aan ons om op deze fundering onze eigen policies te implementeren in de vorm van ACL's.
Het is niet ongebruikelijk om 2 dezelfde checks na elkaar te doen. Een aparte validatie-slag stelt je in staat om al vroeg in de pijplijn niet-validerende berichten te blokkeren. Bovendien kun je met je eigen implementatie ook je eigen regels implementeren. Omdat de resultaten van de DNS queries gecached worden, kost dit ook nauwelijks iets extra. Met deze setup als uitgangspunt zijn we nu klaar voor de daadwerkelijke implementatie van SPF, DKIM en DMARC.
SPF, kort voor Sender Policy Framework, voorkomt dat MX-gateways mailberichten accepteren van ongeautoriseerde systemen. Daartoe wordt door de eigenaar van een maildomein een lijst van geldige adressen gepubliceerd via DNS. Dat zijn typisch de SMTP-gateways die eindgebruikers moeten instellen voor hun uitgaande berichten (via poort 587), maar bijvoorbeeld ook de adressen van een externe dienstverlener die namens een organisatie marketing-mail verstuurd. Ontvangende gateways kunnen deze lijst van adressen gebruiken om de verzender te controleren (valideren) voordat zij een bericht aannemen. Voor de zonefile voor example.nl zoals we die hierboven hebben gespecificeerd betekent dat dus dat we daar (ergens onder de origin '@') het volgende record toevoegen:
IN TXT "v=spf1 mx a ip4:192.0.2.109 ip6:2001:db8:1192::/64" " a:senders.example.nl a:senders.bizbooster.com -all"
Naast dit TXT-record kun je ook nog een specifiek SPF-record tegenkomen (met precies dezelfde inhoud). Dit is een erfenis uit de begintijd van SPF. Volgens de laatste standaard (RFC 7208) moet het SPF-record type echter helemaal niet meer gebruikt worden. Is er geen 'MAIL FROM'-informatie beschikbaar (typisch voor bounce-berichten), dan valt SPF terug van het afzenderdomein naar de 'EHLO'/'HELO' hostname. Die geef je daarom een eigen SPF-record als volgt:
mail IN TXT "v=spf1 a -all" ; SPF record for fallback to EHLO hostname
Kijken we naar de inhoud van het record zelf, dan zien we na de versie-aanduiding 'v=spf1' een typische SPF-policy: eerst een lijstje van systemen die wel geautoriseerd zijn om mail namens dit domein te versturen, gevolgd door een afsluitende '-all', wat betekent dat alle andere systemen dat niet mogen. Alternatief hier is een '~all' (een zogenaamde softfail), wat aangeeft dat berichten van niet-validerende systemen niet geblokkeerd moeten worden maar doorgestuurd voorzien van een vlaggetje (tag). We komen deze straks bij de bespreking van DMARC nog tegen. Waar alleen een 'a' of een 'mx' aangeeft dat de systemen zoals gespecificeerd in de betreffende records van hetzelfde domein geautoriseerd zijn, geeft een policy als 'a:senders.bizbooster.com' je de gelegenheid om de verdere invulling van de specifieke servers naar anderen te delegeren. Op die manier kun je bijvoorbeeld een marketing-dienstverlener autoriseren om namens jou (dat wil zeggen: ogenschijnlijk afkomstig vanaf jouw mail-domein) in bulk mailberichten naar je klanten te sturen. Nog een stap verder gaat de 'include:bizbooster.com' policy, waarbij de policy voor jouw domein uitgebreid wordt met de SPF-policy zoals die bij bizbooster.com gespecificeerd is. Een dergelijke set-up voorkomt dat een dienstverlener elke keer contact op moet nemen voor aanpassingen in je DNS-configuratie als hij de adressen van zijn mailinfrastructuur aanpast. Een overzicht van de beschikbare policy-elementen vind je hier, of bij de bron in RFC 7208. In die RFC kun je overigens ook lezen dat 'a' niet alleen duidt op de IPv4-adressen in de A-records maar ook op de IPv6-adressen in de AAAA-records.
De validatie van SPF moet zo vroeg mogelijk in de workflow plaatsvinden, dat wil zeggen in de 'acl_check_mail' ACL gelijk na het ontvangen van het 'MAIL FROM' commando (op poort 25 van de MX-gateway). Op die manier kunnen binnenkomende verbindingen van ongeautoriseerde systemen direct na de overdracht van de mail envelope worden gedropt, en kan daarbij nog een foutmelding worden teruggegeven in de lopende SMTP-sessie. In het diagram hieronder zie je waar we de SPF check aan de workflow zullen toevoegen.
Hieronder zie je de configuratie die we voor SPF hebben toegevoegd aan de 'acl_check_mail' ACL (in de file '/etc/exim/exim.conf'). Zoals je ziet doen we de SPF-validatie gelijk na de check voor de Spamhaus DNSBL (die weer gelijk komt na de check op de 'EHLO'/'HELO'-opdracht):
acl_check_mail: # Hosts are required to say HELO (or EHLO) before sending mail. # So don't allow them to use the MAIL command if they haven't # done so. deny condition = ${if eq{$sender_helo_name}{} {1}} message = Nice boys say HELO first # reject messages from senders listed in these DNSBLs deny dnslists = zen.spamhaus.org # SPF validation deny spf = fail : softfail message = SPF validation failed: \ $sender_host_address is not allowed to send mail from \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} log_message = SPF validation failed\ ${if eq{$spf_result}{softfail} { (softfail)}{}}: \ $sender_host_address is not allowed to send mail from \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} deny spf = permerror message = SPF validation failed: \ syntax error in SPF record(s) for \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} log_message = SPF validation failed (permerror): \ syntax error in SPF record(s) for \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} defer spf = temperror message = temporary error during SPF validation; \ please try again later log_message = SPF validation failed temporary; deferred # Log SPF none/neutral result warn spf = none : neutral log_message = SPF validation none/neutral # Use the lack of reverse DNS to trigger greylisting. Some people # even reject for it but that would be a little excessive. warn condition = ${if eq{$sender_host_name}{} {1}} set acl_m_greylistreasons = Host $sender_host_address \ lacks reverse DNS\n$acl_m_greylistreasons accept # Add an SPF-Received header to the message add_header = :at_start: $spf_received logwrite = SPF validation passed
We beginnen onze SPF-configuratie met het blokkeren van alle berichten waarvoor de SPF-validatie 'fail', 'softfail', 'permerror' of 'temperror' als resultaat oplevert. Is de uitkomst 'none' of 'neutral', dan sturen we een waarschuwing naar de log, maar laten we het bericht wel doorgaan. En bij de laatste 'accept' voegen we een log-bericht en een SPF-header toe, zodat we weten dat de SPF-validatie inderdaad geslaagd is.
Na de restart van Exim kunnen we controleren of SPF inderdaad gevalideerd wordt door de headers van een binnengekomen mail-bericht (van onder naar boven) langs te lopen. En zie daar inderdaad de 'Received-SPF' header gelijk na het aanpakken van dit testbericht aangeboden door het systeem sender.afzenderdomein.nl. Daaruit blijkt dat dat systeem inderdaad geautoriseerd is (pass) voor afzenderdomein.nl.
systemctl restart exim.service
Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Sat, 13 Jun 2020 23:04:37 +0200 X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Spam-Score: 1.1 (+) Authentication-Results: mail.example.nl; iprev=fail smtp.remote-ip=203.0.113.109; spf=pass smtp.mailfrom=afzenderdomein.nl; dkim=pass header.d=afzenderdomein.nl header.s=dkim20191228 header.a=rsa-sha256; dmarc=pass header.from=afzenderdomein.nl Received-SPF: pass (mail.example.nl: domain of afzenderdomein.nl designates 203.0.113.109 as permitted sender) client-ip=203.0.113.109; envelope-from=tester@afzenderdomein.nl; helo=sender.afzenderdomein.nl; Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jkDKL-0008AH-3e Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id DBAB02800204; Sat, 13 Jun 2020 23:04:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:reply-to:subject:subject:from:from :received:received; s=dkim20191228; t=1592082271; x=1592946272; bh=FFnxOPm2eaxN2BNVUU25NsLX/TgLxXzkRZya9T48Bog=; b=Lh7BFoIbwkAN E7RmFAhpGXRTSj7Jo5hOBvjY6+nYuGbUOOA4hBMIDu9UBrNq2Du0la6as0VuPpqz e3Jl2xEvzuspY8QVIJg+m+tAwhIeZ0a/xpiZl4zcU6YR3D0rEIeaSNrU5ay7jZ7X d2urEMcCrMmN/tbMYgEk8UmMGVxyPaihw8wK+5T7Wfvt+b3ksK4NABgjy1Jzjwwt eK8ti3UHwn+yVbBMRSRib/KG9g0FmKyK4Fd3XwNzUoPGw0boLVJJiflKvOll3/jI F/UWQKgUZFP/+aW3r8JvxkBkXHOTH+8= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id 9wN82CEi70N7; Sat, 13 Jun 2020 23:04:31 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomei.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 5B6E7280063A; Sat, 13 Jun 2020 23:04:31 +0200 (CEST) From: "Tester" <tester@afzenderdomein.nl> Subject: Test SPF validatie Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl Message-ID: <172523f3-8d2b-8a7d-5368-d38806695e59@afzenderdomein.nl> Date: Sat, 13 Jun 2020 23:04:30 +0200
In de logfile zien we deze succesvolle SPF-validatie en aflevering terug als volgt:
2020-06-13 23:10:48 SPF validation passed 2020-06-13 23:10:52 1jkDQO-0008BT-CA => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery 2020-06-13 23:10:52 1jkDQO-0008BT-CA Completed
Proberen we echter een bericht op deze Exim-server af te leveren vanaf een systeem dat daartoe niet (middels het SPF-record) geautoriseerd is, dan levert dat direct een weigering op:
envelope from address tester@afzenderdomein.nl not accepted by the server server message: 550 SPF validation failed: 203.0.113.192 is not allowed to send mail from afzenderdomein.nl
2020-06-13 22:23:13 H=rpi4.afzenderdomein.nl (203.0.113.192) [203.0.113.192] rejected MAIL <tester@afzenderdomein.nl>: SPF validation failed: 203.0.113.192 is not allowed to send mail from afzenderdomein.nl
Om de signalering van niet-validerende mail-berichten te testen kun je deze online tool voor het versturen van valse berichten gebruiken.
Let op: In februari 2024 werden wij geattendeerd op een probleem met de SPF-records van een grote Nederlandse access provider. Via het 'include'-statement wordt een lange lijst IP-adressen binnengehaald. De libspf2 library die door Exim gebruikt wordt, blijkt het lange TXT-record niet in zijn geheel te kunnen verwerken en de lijst IP-adressen/blokken na 64 stuks af te breken. De adressen/blokken die daarna volgen worden niet meer gebruikt bij de validatie. Dat betekent dat mail-berichten afkomstig van systemen in deze adresreeksen ten onrechte niet meer geaccepteerd worden.
Omdat dit soort fouten veel uitzoekwerk kost, willen we dit probleem hier in ieder geval noemen, zodat mailbeheerders niet opnieuw voor een raadsel komen te staan.
Voor DNS-beheerders betekent dit dat zij lange SPF-records het beste kunnen opsplitsen in afzonderlijke TXT-records met maximaal 64 IP-adressen/blokken. De betreffende provider is ook op de hoogte gesteld van de problemen die hun SPF-records opleveren.
DKIM, kort voor DomainKeys Identified Mail, voorziet de body en header van elk uitgaand mailbericht van een digitale handtekening. De publieke sleutel wordt via DNS gepubliceerd, zodat een ontvangende MX-gateway de digitale handtekening van binnenkomende berichten kan verifiëren (valideren). Met het zetten van een DKIM-handtekening neemt de verzender (signing domain) verantwoordelijkheid voor het in het mail-verkeer brengen van een bericht. Heeft de verzender een goede reputatie, dan vergroot hij hiermee de zogenaamde 'deliverability' van het bericht. Oftewel: hij verkleint de kans dat een bericht als spam wordt aangemerkt. DKIM blokkeert zelf meestal dan ook geen berichten. De uitkomst van de validatie wordt meegenomen in de score van een spamfilter als SpamAssassin. DKIM is ook nadrukkelijk niet bedoeld om de authenticiteit, integriteit en vertrouwelijkheid van berichten te waarborgen. Dat moet op gebruikersniveau gebeuren met end-to-end encryptie. DKIM is gestandaardiseerd in RFC 6376, 8301 en 8463.
De DKIM engine van Exim staat standaard aan vanaf versie 4.70, voor zowel validatie als ondertekening. In de initiële configuratie zien we dat de control modifier 'dkim_disable_verify' (net als 'dmarc_disable_verify') gezet wordt voor berichten afkomstig van lokale aanbieders, relay hosts en geauthenticeerde verbindingen. Op deze manier wordt DKIM-validatie expliciet uitgeschakeld voor alle berichten behalve die afkomstig van remote, niet-geauthentiseerde aanbieders (op de MX-gateway op poort 25). Hoewel niet gedefinieerd in de initiële configuratie, heeft Exim een aparte hook 'acl_smtp_dkim' voor DKIM-validatie. Deze wordt apart aangeroepen voor elke DKIM-handtekening in het bericht. De validatie faalt als een of meer van de handtekeningen niet klopt. Wil je dat gedrag omdraaien zodat één geldige handtekening voldoende is voor een geldige validatie, dan kun je de globale optie 'dkim_verify_minimal' zetten (in de file '/etc/exim/exim.conf'). Om nu net als we hiervoor deden voor SPF ook harde regels voor DKIM-validatie in te stellen, moeten we een hele nieuwe ACL toevoegen. Daartoe nemen we boven in de configuratie eerst dit statement (de hook) op:
acl_smtp_dkim = acl_check_dkim
In het diagram hieronder kun je zien waar deze ACL aangrijpt in onze setup:
Vervolgens voegen we in de 'acl' sectie van onze configuratie deze hele nieuwe ACL toe:
acl_check_dkim: deny dkim_status = fail message = DKIM validation failed: $dkim_verify_status log_message = DKIM validation failed: $dkim_verify_status \ (address=$sender_address, domain=$dkim_cur_signer), \ signature is bad defer dkim_status = invalid message = DKIM signature invalid: $dkim_verify_status log_message = DKIM signature invalid: $dkim_verify_status \ (address=$sender_address, domain=$dkim_cur_signer), \ invalid signature # NOTE: dkim_status = none should never happen in this ACL accept # Add an X-DKIM header to the message add_header = :at_start: X-DKIM: DKIM validation passed: \ (address=$sender_address domain=$dkim_cur_signer), \ signature is good logwrite = DKIM validation passed
De structuur van deze ACL is vergelijkbaar met die van de SPF check: DKIM-handtekeningen waarvoor de check een 'fail' of 'invalid' oplevert, worden (respectievelijk tijdelijk en permanent) afgewezen. Bij een 'accept' voegen we een DKIM-header toe aan het bericht, zodat we ook als alles in orde is weten dat de DKIM-validatie uitgevoerd (en geslaagd) is.
Na de restart van Exim kunnen we controleren of DKIM inderdaad gevalideerd wordt door de headers van een binnengekomen mail-bericht (van onder naar boven) langs te lopen. En zie daar inderdaad de 'X-DKIM' header (samen met de 'Received-SPF' header) gelijk na het aanpakken van dit testbericht aangeboden door het systeem sender.afzenderdomein.nl. Daaruit blijkt dat de DKIM-handtekening (in de 'DKIM-Signature' header) inderdaad klopt (passed).
systemctl restart exim.service
Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Mon, 15 Jun 2020 20:28:39 +0200 X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Spam-Score: 1.1 (+) X-DKIM: DKIM validation passed: (address=tester@afzenderdomein.nl domain=afzenderdomein.nl), signature is good Received-SPF: pass (mail.example.nl: domain of afzenderdomein.nl designates 203.0.113.109 as permitted sender) client-ip=203.0.113.109; envelope-from=tester@afzenderdomein.nl; helo=sender.afzenderdomein.nl; Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jktqV-0000im-7l for ontvanger@example.nl; Mon, 15 Jun 2020 20:28:39 +0200 Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id 0AC7F2800204; Mon, 15 Jun 2020 20:28:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:reply-to:subject:subject:from:from :received:received; s=dkim20191228; t=1592245712; x=1593109713; bh=E669Sbf1Sdb6VFp7EfDLmhBitNGMtvmPdXIbuFsVwJU=; b=N5dcAuG3dTP0 p0OqZv0ULrZm8u1tWzc4AiRudWrm0AFedYGpRvtvex+xz8ioNITpIb+vQJ8z9x/1 GdQ+jk3TrX9g07EhY5ClqH34VCPV03ygbMhfmnKptxcD1V/q97k+rAh4mGrQY065 E6arCs6vulNyIoSC4D7QDNEdC7Mw/YcqhoCimj+lCwgLdjLR+OrRwSzF+1D68jIC uMzc3W+F3RZpqokmWzoROQsagj5tTFbKOyGiGVtdW7/bywMYbGiJ3ux0fp7wiy4Z HY64U1v+Bz2Yc607SGtLJsu/LLiXF6c= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id wzhpWR14LlfM; Mon, 15 Jun 2020 20:28:32 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomein.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 93DA12800608; Mon, 15 Jun 2020 20:28:32 +0200 (CEST) From: "Tester" <tester@afzenderdomein.nl> Subject: Test DKIM validatie Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl Message-ID: <1ef59aee-fc13-0e2f-4a46-38d595c30557@afzenderdomein.nl> Date: Mon, 15 Jun 2020 20:28:32 +0200
In de log file zien we deze succesvolle DKIM-validatie en aflevering terug als volgt:
2020-06-15 20:28:35 SPF validation passed 2020-06-15 20:28:35 1jktqV-0000im-7l DKIM validation passed 2020-06-15 20:28:39 1jktqV-0000im-7l <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9696 DKIM=afzenderdomein.nl id=1ef59aee-fc13-0e2f-4a46-38d595c30557@afzenderdomein.nl 2020-06-15 20:28:39 1jktqV-0000im-7l => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery 2020-06-15 20:28:39 1jktqV-0000im-7l Completed
Klopt de DKIM-handtekening niet, dan wordt een bericht direct geweigerd – dit in tegenstelling tot de initiële setup waar een fail op SPF of DKIM alleen (in negatieve zin) bijdroeg aan de spamscore. Hieronder zie je hoe zo'n weigering vanwege een ongeldige DKIM-handtekening er uit ziet:
the server did not accept the mail server message: 550 DKIM validation failed: fail
2020-06-27 12:47:32 SPF validation passed 2020-06-27 12:47:32 1jp8Mu-000O2d-Rx H=(sender.afzendermail.nl) [203.0.113.109] rejected DKIM : DKIM validation failed: fail (address=tester@afzenderdomein.nl, domain=afzenderdomein.nl), signature is bad
Nu we de DKIM-validatie van binnenkomende mail-berichten voor elkaar hebben, is het tijd om ook de uitgaande berichten van onze eigen gebruikers van een DKIM-handtekening te voorzien. Dat begint met het genereren van een nieuw cryptografisch sleutelpaar en het aanpassen van onze Exim-configuratie. De publieke sleutel publiceren we vervolgens in ons DNS. Dan kunnen andere (ontvangende) mailservers die sleutel gebruiken om de DKIM-handtekening onder de door ons verstuurde berichten te valideren, op dezelfde manier zoals wij dat hierboven met de ondertekende berichten van anderen deden. In het diagram hieronder zie je waar de DKIM-ondertekening aangrijpt in de workflow: berichten aangeleverd op poort 587 (submission) door onze eigen (geauthentiseerde) gebruikers worden van een handtekening voorzien voordat ze worden doorgezet voor aflevering. Zoals eerder uitgelegd moet die ondertekening zo laat mogelijk in de pijplijn (op de laatste hop van jouw infrastructuur) plaatsvinden. Op die manier worden zo veel mogelijk headers (ook die toegevoegd door Exim zelf) onder de handtekening meegenomen en vergroten we de kans op een goede overkomst van ons ondertekende bericht.
Het genereren van het DKIM-sleutelpaar doen we als volgt:
mkdir /etc/exim/keys/ cd /etc/exim/keys/ openssl genrsa -out dkim_rsa.private 2048
De nieuwe file 'dkim_rsa.private' bevat de private sleutel, die geheim gehouden moet worden. Vergeet dus niet om te zorgen dat dit sleutelbestand de juiste (veilige) rechten krijgt:
chmod 640 dkim_rsa.private chown root:exim dkim_rsa.private
Hoewel je hier ook voor een grotere sleutellengte zou kunnen kiezen (4096 in plaats van 2048 bits), hebben DKIM-handtekeningen maar een beperkte geldigheidsduur (hieronder: 10 dagen). Ze worden immers alleen maar gebruikt voor de aflevering van berichten, waarvoor in het slechtste geval een paar dagen nodig is. Met een sleutellengte van 2048 bits kan het DNS-verkeer over het efficiënte UDP-protocol blijven lopen, in plaats dat opgeschakeld moet worden naar het zwaardere TCP-protocol. Zorg in ieder geval dat je het onveilige SHA-1 algoritme niet meer gebruikt (zoals uitgelegd in RFC 8301). RFC 8463 definieert het gebruik van ECDSA voor DKIM. De voordelen van dit het moderne cryptografische algoritme hebben we hier uitgelegd voor DNSSEC. We denken echter dat het op dit moment (juni 2020) nog te vroeg is om ECDSA voor DKIM te gebruiken. Onderteken je je mail met een nieuwe standaard die nog niet door alle DKIM-validerende MX-gateways herkend wordt, dan loop je het risico dat je berichten niet afgeleverd worden. Wil je toch gebruikmaken van ECDSA, dan is er ook de mogelijkheid tot dual-signing. Daarbij worden er 2 digitale handtekeningen (DKIM-Signature headers) aan het bericht toegevoegd. In de documentatie vind je hoe je een ECDSA-sleutelpaar genereert, en hoe je 2 sleutels tegelijk aan de Exim-configuratie toevoegt.
Om het sleutelpaar dat we zojuist genereerden ook door Exim te laten gebruiken om uitgaande mail-berichten te ondertekenen, voegen we de volgende configuratie-opties toe aan het 'remote_smtp' transport (in de file '/etc/exim/exim.conf'):
dkim_domain = example.nl dkim_selector = dkim20200615 dkim_private_key = /etc/exim/keys/dkim_rsa.private dkim_hash = sha256 dkim_canon = relaxed dkim_strict = 1 dkim_timestamps = 864000
In dit overzicht vind je de beschrijving van deze en een aantal andere opties voor DKIM-ondertekening:
'dkim_domain': het signing domain Let op dat dit domein voor DKIM op zichzelf niet persé hetzelfde hoeft te zijn als het 'MAIL FROM'-afzenderdomein van het mail-bericht. Het signing domain is verantwoordelijk voor het in het mailverkeer brengen van een bericht, en kan dat dus voor verschillende afzenderdomeinen doen. Maar ga je ook met DMARC aan de slag, dan moet het signing domein wel gelijk zijn aan het afzenderdomein (de zogenaamde alignment).
'dkim_selector': de DKIM-sleutel selector De selector bepaalt welke sleutel de DKIM-validerende mailserver moet gebruiken om een bericht te verifiëren. We zien straks dat elke publieke DKIM-sleutel onder zijn eigen naam (selector) in de DNS opgeslagen wordt. Het makkelijkst is om daarvoor de bestandsnaam te gebruiken.
'dkim_private_key': de locatie waar de private DKIM-sleutel is opgeslagen Voor dual-signing kun je hier en bij de selector een lijst van meerdere waarden opgeven.
'dkim_hash = sha256': de hash-functie gebruikt om de digitale handtekening te zetten Gebruik hiervoor de waarde 'sha256' (ook de default). Zoals hierboven al even genoemd moet je 'sha1' niet meer gebruiken; deze hash-functie is te oud en onveilig. 'sha512' is weer een stap veiliger dan 'sha256', maar wordt misschien nog niet door alle DKIM-validerende mailservers ondersteund.
'dkim_identity': de identiteit van de afzender Deze optie bevat het e-mailadres van de verantwoordelijke verzender in het signing domain (of een subdomein daarvan). Deze informatie wordt opgenomen in de 'i=' tag van de 'DKIM-Signature' header van het bericht. Op deze manier wordt een koppeling gemaakt met een specifieke verzender (waar DKIM zelf uitsluitend op domeinniveau werkt).
'dkim_canon': de canonicalization Sommige mailservers veranderen bij de verwerking van mailberichten de spatiëring/afbreking, waardoor de digitale handtekening niet meer klopt. Door de spatiëring/afbreking van berichten te standaardiseren voordat de handtekening gezet of gecontroleerd wordt (canonicalization), maak je een ondertekend bericht robuuster tegen veranderingen tijdens het transport. Exim ondersteunt maar één instelling voor canonicalization ('simple' of 'relaxed', waarvan die laatste de default is), die voor zowel de header als body gebruikt wordt. Hieronder wijden we een aparte sectie aan de behandeling van canonicalization, waarbij we ook het verschil tussen deze 2 instellingen uitleggen.
'dkim_strict = 1': zorgt voor een (tijdelijke) defer van een bericht als het ondertekenen om een of andere reden nu niet lukt
'dkim_sign_headers': een lijst van headers die meegenomen moeten worden in de digitale handtekening
'dkim_timestamps = 864000': geldigheidsduur van de handtekening in seconden (hier: 10 dagen) Deze instelling zorgt ervoor dat timestamp-informatie (een geldigheidsduur) wordt opgenomen in de 'DKIM-Signature' header. Gebruikelijk is een geldigheidsduur van 10 dagen (10*24*3600 = 864000 seconden), ruim voldoende voor de aflevering van een mail-bericht. In de 'DKIM-Signature' header zie je straks de begin- en eindtijd terug in respectievelijk de 't=' en 'x=' tags.
De 'dkim_canon'-optie ('relaxed'/'simple') specificeert de nauwkeurigheid waarmee respectievelijk de header- en body-secties van een bericht worden behandeld bij het genereren en controleren van de digitale handtekening. Omdat met name de spatiëring/afbreking in berichten onderweg wel eens veranderd wordt, kan dat problemen opleveren bij een strikte controle. Een belangrijke eigenschap van cryptografische hash-functies, essentieel onderdeel bij de ondertekening, is immers dat een enkel gewijzigd bit in het bericht leidt tot een compleet andere hash (een digitale samenvatting), en dus tot een ongeldige handtekening. Je kunt een ondertekend bericht robuuster maken tegen veranderingen tijdens het transport door de handtekening onafhankelijk te maken van de precieze spatiëring/afbreking. Dat doe je door de headers en de body eerst te converteren naar een standaard berichtenformaat (een normaalvorm), en dan pas de digitale handtekening te zetten. Als de ontvanger aan zijn kant precies hetzelfde doet, dan komt daar ook precies dezelfde hash uit. Ter vergelijking: als een programmeur wil checken of 2 woorden 'hetzelfde' zijn, dan converteert hij beide ook eerst naar kleine letters. Vanzelfsprekend kost deze omzetting (canonicalization) wel extra rekenkracht. Vandaar dat er in RFC 6376 2 verschillende conversieniveaus gedefinieerd zijn: 'relaxed' kost meer werk dan 'simple', maar levert vaker een goedgekeurde handtekening op. Vandaar dat we deze gebruiken voor de headers, en de minder arbeidsintensieve 'simple'-conversie voor de body van onze berichten. Het moge duidelijk zijn dat deze instelling met name relevant is voor organisaties die grote hoeveelheden mail verwerken. Om vergelijkbare redenen gebruikten we eerder in de globale configuratie onderstaande optie. Door 8-bits karakters uit te filteren (en ons te beperken tot ASCII) verkleinen we de kans dat berichten onderweg naar de ontvangende mailserver verminkt worden.
accept_8bitmime = false
Wees je er van bewust dat je in bovenstaande configuratie één enkel signing domain gebruikt voor meerdere afzenderdomeinen (namelijk: alle afzenderdomeinen die jouw lokale gebruikers mogen gebruiken). Omdat validatie steeds plaatsvindt op basis van jouw signing domain ('dkim_domain = example.nl'), hoef je het bijbehorende DKIM record ook alleen aan dat domein toe te voegen. Wil je elk afzenderdomein zijn eigen sleutelpaar op zijn eigen signing domain geven (een voorwaarde bij het gebruik van DMARC), dan moet je ook elk domein van zijn eigen DKIM-record voorzien. Dat betekent ook dat je in de Exim-configuratie afhankelijk van het afzenderdomein (dynamisch) verschillende signing domains (en eventueel verschillende selectors en sleutelbestanden) moet selecteren. Daartoe neem je deze macro’s op in je configuratie (helemaal bovenaan in de file '/etc/exim/exim.conf')
# DKIM macros # get the sender domain from the outgoing mail SENDER_DOMAIN = ${if def:h_from:{${lc:${domain:${address:$h_from:}}}}{$qualify_domain}} # the key file name will be based on the domain name in the From header DKIM_KEY_PATH = /etc/exim/keys DKIM_KEY_FILE = SENDER_DOMAIN-20201213.pem
In de eerste macro 'SENDER_DOMAIN' wordt het domein-gedeelte van de 'From' header geselecteerd en naar lower case geconverteerd (middels 'lc:'). Bevat het bericht geen 'From' header, dan wordt de eerder ingestelde variabele $qualify_domain als default gebruikt. In de laatste macro 'DKIM_KEY_FILE' gebruiken we dit afzenderdomein om straks (in combinatie met de macro 'DKIM_KEY_PATH') het volledige path naar het bijbehorende sleutelbestand te construeren, bijvoorbeeld '/etc/exim/keys/example.nl-20201213.pem' of '/etc/exim/keys/anotherexample.nl-20201213.pem'. In onze DKIM-configuratie in het 'remote_smtp' transport gebruiken we deze macros vervolgens om het juiste signing domain in te stellen en de bijbehorende sleutel te selecteren:
dkim_domain = SENDER_DOMAIN dkim_selector = dkim20201213 dkim_private_key = ${lookup {DKIM_KEY_FILE} dsearch,ret=full {DKIM_KEY_PATH}} dkim_hash = sha256 dkim_canon = relaxed dkim_strict = 1 dkim_timestamps = 864000
Die 'lookup dsearch'-opdracht om het juiste sleutelbestand in te lezen, lijkt in eerste instantie een beetje te veel van het goede. In eerdere versies van Exim konden we ook gewoon een compleet path samenstellen door de onderdelen simpelweg samen te pakken: '/etc/exim/keys/{DKIM_SENDER_DOMAIN}-20200615.pem'. Vanaf versie 4.94 kun je echter niet zomaar meer alle variabelen in je configuratie gebruiken. Variabelen die worden gezet op basis van binnenkomende mail-berichten – en dus van buitenaf misbruikt kunnen worden – krijgen nu het label 'besmet' (tainted) mee. Op die manier wordt voorkomen dat onvoorzichtigheden in je configuratiebestand leiden tot veiligheidsproblemen. Meest voor de hand liggende voorbeeld is als je 'tainted data' uit een dergelijke variabele direct gebruikt om een specifieke file te openen, zoals wij in bovenstaand voorbeeldje doen.
Conceptueel is deze problematiek niet anders dan de bescherming tegen SQL-injecties: alle input die door gebruikers gezet of gemanipuleerd kan worden, moet voor gebruik zorgvuldig gescand en gelimiteerd worden. Een uitgebreide beschrijving van alle string-bewerkingsoperaties vind je hier in de Exim-handleiding. Op die pagina staat ook aangegeven welke variabelen zo'n 'tainted'-label met zich meedragen.
Of een configuratie zoals in deze sectie beschreven ook direct een betere deliverability oplevert, durven we zo niet te zeggen. Uit onze bespreking van de oude DKIM records hieronder blijkt wel dat men meerdere pogingen heeft gedaan om een sterkere koppeling te maken tussen afzenderdomein en signing domain. Wil je straks met DMARC ook het 'From'-afzenderdomein beveiligen, dan moet elk afzenderdomein sowieso ook zichzelf als signing domain ingesteld hebben.
Om te zorgen dat DKIM-validerende mailservers onze digitale handtekeningen kunnen verifiëren, moeten we de publieke sleutel van ons eerder gegenereerde DKIM-sleutelpaar publiceren in onze zone file (dat wil zeggen: die van het signing domain). Daartoe genereren we eerst de publieke sleutel uit het DKIM-sleutelbestand:
[root@system keys]# openssl rsa -in dkim_rsa.private -out /dev/stdout -pubout -outform PEM writing RSA key -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND/S4kXMlJ xuoxNMCKGozD/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3 UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA YLnvAzOQRV4zE/eBB/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi /rnXFDe0ORjtDo/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+ 1wIDAQAB -----END PUBLIC KEY-----
Deze publieke sleutel kun je op deze manier direct in een DKIM-record opnemen:
dkim202005615._domainkey.example.nl. 3600 TXT ( "v=DKIM1; p=" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny" "UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND/S4kXMlJ" "xuoxNMCKGozD/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3" "UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA" "YLnvAzOQRV4zE/eBB/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi" "/rnXFDe0ORjtDo/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+" "1wIDAQAB")
Hierbij is het van belang te weten dat 'dkim20200615' aangeeft om welk sleutelpaar het hier gaat (de zogenaamde selector). We zullen zo zien dat deze selector ook onderdeel is van de 'DKIM-Signature' header, zodat de ontvangende mailserver bij validatie precies weet welke publieke sleutel hij moet opvragen in de DNS. Selectors geven je de gelegenheid om meerdere systemen DKIM-ondertekende berichten namens jouw domein te laten versturen, zonder dat je daarvoor onderling bestanden met private sleutels hoeft uit te wisselen. Elk verzendend mailsysteem kan lokaal zijn eigen DKIM-sleutelpaar genereren, als de publieke sleutel daarvan maar met een unieke selector in de DNS wordt gepubliceerd. Pas op dat je geen online tools gebruikt die met het DKIM-record ook een private en een publieke sleutel voor je genereren – en daar zijn er nogal veel van! Daarmee hebben zij immers ook jouw private sleutel in handen, wat betekent dat zij mail kunnen versturen die ‘door jou’ ondertekend is.
Voordat we bovenstaand DKIM-record in onze zone file kunnen opnemen, moeten we eerst verifiëren dat onze uitgaande mailberichten (op de SMTP-gateway binnenkomend via poort 587) nu inderdaad van een geldige handtekening worden voorzien (anders worden onze berichten immers geblokkeerd door validerende MX-gateways). Na de restart van Exim sturen we een mailbericht vanaf ons eigen domein door deze SMTP-gateway om te controleren of er nu inderdaad een digitale handtekening aan de headers wordt toegevoegd. En zoals je hieronder ziet is dat inderdaad het geval:
dnf restart exim.service
Return-Path: <gebruiker@example.nl> X-Original-To: ontvanger@externdomein.nl Delivered-To: ontvanger@mail.exterdomein.nl Received: from localhost (localhost [127.0.0.1]) by mail.externdomein.nl (Postfix) with ESMTP id 3ADA52800204 for <ontvanger@externdomein.nl>; Sun, 21 Jun 2020 22:57:09 +0200 (CEST) X-Virus-Scanned: amavisd-new at externdomein.nl Received: from mail.externdomein.nl ([127.0.0.1]) by localhost (mail.externdomein.nl [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id fQzUbYri8hun for <ontvanger@externdomein.nl>; Sun, 21 Jun 2020 22:57:08 +0200 (CEST) Received-SPF: None (mailfrom) identity=mailfrom; client-ip=192.0.2.107; helo=mail.example.nl; envelope-from=gebruiker@example.nl; receiver=<UNKNOWN> DMARC-Filter: OpenDMARC Filter v1.3.2 mail.externdomein.nl 3DF992800240 Authentication-Results: OpenDMARC; dmarc=none (p=none dis=none) header.from=example.nl Authentication-Results: OpenDMARC; spf=none smtp.mailfrom=gebruiker@example.nl DKIM-Filter: OpenDKIM Filter v2.11.0 mail.externdomein.nl 3DF992800240 Received: from mail.example.nl (mail.example.nl [192.0.2.107]) by mail.externdomein.nl (Postfix) with ESMTPS id 3DF992800240 for <ontvanger@externdomein.nl>; Sun, 21 Jun 2020 22:57:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=example.nl; s=dkim20200615; h=To:Date:Message-ID:Subject:From:Sender:Reply-To:Cc: MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=gzO8d4Zrge5Vc/EpI/kfLPDgD080p2oyA8Azxm/Pqfw=; t=1592773028; x=1593637028; b=F6BJ5g5qb8T06OumiezfgktG03TjaDK32zPPCIGXDhXbqDeYZrM79Ash55m3DxDuflu1zJClyT oLjCBYDQ7KtsSL62ozkNY8WcTUt97Vqof/rJcTCFMLHO7TfR3iEMwcdOUeg8XOBA0ILMrVUedAeJg wqX9ImQLtWSOBcGYbhBF4OmtndK6rhnFTFBLkffUxIWsAhfmIYV/t/WcJmR9qvDCe/uSl95epcOdG E5Y3fuX92tCLllEyGmqjHwaDq07fLxJzQit5SQs5srx2mRN6sEydScHSc7fifGGaqkUJe2SKaEm7s KClKZ0MV88j3U56qyDIFZU2NJvfKNM+KVyPlw==; Received: from gebruiker by mail.example.nl with local (Exim 4.93) (envelope-from <gebruiker@example.nl>) id 1jn6yA-0005RB-8Y for ontvanger@externdomein.nl; Sun, 21 Jun 2020 22:54:58 +0200 From: "Gebruiker" <gebruiker@example.nl> Subject: Test DKIM ondertekening Message-ID: <8a2d394a-789d-9686-7092-b7d5cf2a8619@example.nl> Date: Sun, 21 Jun 2020 22:17:45 +0200 To: ontvanger@externdomein.nl
In de 'DKIM-Signature' header zie je zowel het signing domain ('d=example.nl;') als de selector ('s=dkim20200615;') staan. Tezamen is dat voor de DKIM-engine aan de ontvangende kant (hier: mail.externdomein.nl) voldoende om bij onze DNS-server het bijbehorende DKIM-record op te vragen. We kunnen de aanwezigheid van dat record zelf ook controleren als volgt:
[user@system ~]# dig TXT dkim20200615._domainkey.example.nl. ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el8 <<>> TXT dkim20200615._domainkey.example.nl. ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19884 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 5 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ; COOKIE: e9836ed80afd3232b4a3a8db5ebafe4bd80d43ef7ae55689 (good) ;; QUESTION SECTION: ;dkim20200615._domainkey.example.nl. IN TXT ;; ANSWER SECTION: dkim20200615._domainkey.example.nl. 86400 IN TXT "v=DKIM1; p=" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny" "UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND/S4kXMlJ" "xuoxNMCKGozD/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3" "UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA" "YLnvAzOQRV4zE/eBB/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi" "/rnXFDe0ORjtDo/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+" "1wIDAQAB" ;; AUTHORITY SECTION: example.nl. 86400 IN NS dns1.example.nl. example.nl. 86400 IN NS dns2.example.nl. ;; ADDITIONAL SECTION: dns1.example.nl. 86400 IN A 192.0.2.5 dns2.example.nl. 86400 IN A 198.51.100.6 dns1.example.nl. 86400 IN AAAA 2001:db8:2198::16:6 dns2.example.nl. 86400 IN AAAA 2001:db8:2198::32:106 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Mon Jun 15 21:51:39 CEST 2020 ;; MSG SIZE rcvd: 1054
De publieke sleutel en de overige informatie in dit record worden vervolgens door de ontvangende mailserver gebruikt om de digitale handtekening in de 'DKIM-Signature' header te valideren. Daarmee is het ‘eigendom’ van dit bericht (dat wil zeggen: de inhoud en de herkomst van het gegeven signing domain example.nl) voor de ontvangende server aangetoond. Alleen de eigenaar van dit DKIM-sleutelpaar kan immers de publieke sleutel in het signing domain publiceren. En alleen hij heeft de private sleutel waarmee de handtekening onder dit bericht is gezet.
Het moge duidelijk zijn dat wanneer je je DKIM-sleutelpaar verandert, ook de publieke sleutel in het DKIM record aangepast moet worden. Dat betekent dat je bij de omwisseling een kleine roll-over zult moeten doen: Het nieuwe DKIM record moet (naast het oude record) gepubliceerd worden voordat het sleutelpaar wordt vervangen. Pas na het verlopen van de TTL, als de oude DKIM-informatie overal uit alle DNS caches is verdwenen, mag het nieuwe sleutelpaar in de server geactiveerd worden. Doe je dat eerder, dan loop je het risico dat validerende mail servers op basis van de oude informatie de DKIM-handtekening gebaseerd op het nieuwe sleutelpaar (nog) niet accepteren. Gebruik je voor je nieuwe sleutelpaar ook een nieuwe selector, dan hoef je niet te wachten op het leeglopen van alle DNS caches voordat je het nieuwe sleutelpaar in gebruik kunt nemen. Voor de nieuwe selector is immers nog geen DNS-informatie gecached. Na ingebruikname van het nieuwe sleutelpaar mag je het oude DKIM record niet gelijk verwijderen. Mail-berichten kunnen immers een paar dagen onderweg zijn voordat ze uiteindelijk afgeleverd worden (of bouncen). Ons advies is om het oude DKIM record nog minstens een week te laten staan.
Naast de zojuist besproken DKIM-records kun je zo her en der ook nog een ADSP-record tegenkomen, bijvoorbeeld als volgt:
adsp._domainkey IN TXT "dkim=all"
De Author Domain Signing Practices (gedefinieerd in RFC 5617) waren een uitbreiding op de DKIM-standaard waarmee de eigenaar kon aangeven dat alle mailberichten van zijn domein met DKIM beveiligd zijn. Daarmee werd een harde verbinding gelegd tussen het afzenderdomein in de 'From' header enerzijds en de inhoud en authenticiteit van het bericht anderzijds. Zoals SPF de verzameling van aanbiedende mailservers afsluit voor een specifiek domein in de envelope ('MAIL FROM'), zo sloot ADSP de mailketen dus af voor een specifiek afzenderdomein in de 'From' header. Het ADSP record is inmiddels echter 'historisch' verklaard. Behalve dat deze standaard nauwelijks gebruikt werd, leverde hij ook problemen op bij de aflevering van berichten van mailing lists. Een andere optie die hetzelfde beoogde was een aanvullend DKIM-record als volgt:
_domainkey IN TXT "o=-;"
Dit betekende dat alle mailberichten afkomstig van dit domein ondertekend zijn. Deze mogelijkheid was echter alleen onderdeel van de eerste DKIM (Sender Signing Policy) drafts, en moet nu niet meer gebruikt worden. Bij de bespreking van DMARC hieronder zullen we zien dat die standaard nu gebruikt wordt om een koppeling te leggen tussen het afzenderadres in de 'From' header en de door SPF en DKIM gevalideerde afzenderdomeinen.
DMARC, kort voor Domain-based Message Authentication, Reporting and Conformance, is een aanvulling op de andere 2 beveiligingsstandaarden voor mail, SPF en DKIM. DMARC geeft ontvangende MX-gateways een aanwijzing (de policy) hoe om te gaan met inkomende mail-berichten waarvoor zowel SPF als DKIM geen geldige validatie opleveren. Deze kunnen bijvoorbeeld weggegooid worden of in quarantaine worden gezet. Bovendien controleert DMARC in hoeverre het afzenderdomein in de ‘From’ header overeenkomt met het afzenderdomein in het envelope (SPF) en met het signing domain in de DKIM-header. Daarmee dwingt DMARC een relatie af tussen de gevalideerde SPF- en DKIM-afzenderdomeinen enerzijds en het 'From'-afzenderdomein anderzijds. Dat betekent dus ook dat een bericht dat positief valideert op zowel SPF als DKIM sec, toch nog afgewezen kan worden omdat DMARC negatief valideert op deze alignment. De DMARC-policy wordt via DNS gepubliceerd. Deze kan bovendien e-mailadressen bevatten waarop mailsystemen aangenomen/afgewezen berichten kunnen rapporteren. Zo krijgt de beheerder van het betreffende maildomein inzicht in de aflevering van zowel echte als vervalste berichten. Merk op dat de inzet van DMARC vereist dat SPF en/of DKIM (bij voorkeur beide) volledig zijn geconfigureerd voor alle uitgaande mailstromen. DMARC is gedefinieerd in RFC 7489.
DMARC brengt een nuancering aan in de harde afwijzing van berichten met een negatieve validatie op SPF. Klopt de DKIM-handtekening wel, dan kan zo’n bericht (voorzien van een vlaggetje) toch doorgestuurd worden. Dat is vooral van belang voor de goede werking van mailing lists en mail forwarding, waarbij de afzender meestal niet in het SPF-record is opgenomen. Bij gebruik van DMARC zul je dus meestal ook het SPF-record als volgt aanpassen (let op de tilde ‘~’ aan het eind):
IN TXT "v=spf1 mx a ip4:192.0.2.109 ip6:2001:db8:1192::/64" " a:senders.example.nl a:senders.bizbooster.com ~all"
Daarmee is DMARC (net als DKIM) een check die meestal onderdeel uitmaakt van het spamfilter. In de DMARC-specificatie RFC 7489 kun je lezen hoe deze beveiligingsstandaard zich met name richt op de bescherming van zakelijke mailberichten, waarmee ook CEO-fraude (whaling) wordt tegengegaan. Omdat ook kwaadwillenden een set-up zoals we hier beschrijven kunnen optuigen, is dat echter maar de helft van het verhaal. Als onderdeel van het spamfilter is de uiteindelijke beslissing om een bericht wel of niet door te laten voor een groot deel afhankelijk van de reputatie van de afzender (host en afzenderdomein), nu onderdeel van de spam score. Dat maakt deze mailbeveiliging op domeinniveau een belangrijke enabler bij de transitie van IPv4 naar IPv6, waar je niet langer kunt filteren op IP-adres.
Om DMARC aan te zetten voor ons domein example.nl, voegen we in eerste instantie dit DNS-record aan de zonefile toe:
; DMARC _dmarc IN TXT "v=DMARC1; p=none; sp=none; adkim=s; aspf=s; rua=mailto:dmarc-reports@example.nl; ruf=mailto:dmarc-reports@example.nl; fo=1;"
We lopen hier de details in de policy langs:
'v=DMARC1;' we gebruiken DMARC versie 1 (op dit moment de enige beschikbare versie)
'p=none;' de policy voor het hoofddomein
'sp=none;' de policy voor de subdomeinen (als zij zelf geen eigen DMARC-policy publiceren)
'adkim=s;' de alignment tussen het 'From'-adres en het signing domain van de door DKIM gevalideerde digitale handtekening
'aspf=s;' de alignment tussen het 'From'-adres en het door SPF gevalideerde 'MAIL FROM'-afzenderdomein
'rua=mailto:dmarc-reports@example.nl;' ons mailadres voor geaggregeerde rapportages (default één keer per dag; aan te passen met de 'ri' tag)
'ruf=mailto:dmarc-reports@example.nl;' ons mailadres voor forensische (failure) rapportages (voor individuele mailberichten die geweigerd zijn)
'fo=1;' verzoek aan een ontvangende mailserver om een forensische rapportage te genereren als een aangeboden bericht wordt geweigerd
Zoals je ziet gaan we hier van start met de tags 'p=none;' en 'sp=none;'. Daarbij voeren ontvangende mailservers wel alle checks uit, maar worden berichten niet geblokkeerd (althans niet vanwege het niet valideren van SPF en/of DMARC). Andere waarden voor deze tags zijn 'quarantine' en 'reject'. Je begint dus altijd met de waarde 'none' om te voorkomen dat valide berichten worden tegengehouden, bijvoorbeeld (typisch) omdat je een (externe) mailserver bent vergeten toe te voegen aan de SPF-policy. Op vergelijkbare wijze gebruik je deze setting om alle valide mailservers vervolgens van DKIM-ondertekening te voorzien. Pas nadat alles goed loopt – en om dat te controleren gebruik je de binnenkomende DMARC-rapportages – stap je over naar de waarde 'reject'. Vanaf dat moment worden niet-validerende berichten ook daadwerkelijk geblokkeerd. Over de vraag of je zelf ook failure reports moet uitsturen lopen de meningen uiteen. Naast spam kunnen immers ook legitieme berichten waar iets mis mee is dan resulteren in een bounce-bericht, waarmee je misschien gevoelige informatie rondstuurt. Tegenargument is dat alle informatie in een failure report sowieso op de heenweg al over het netwerk is getransporteerd, en dat daar dus niets nieuws in staat. Wij pleiten er dan ook liever voor om de mail verdergaand te beveiligen met behulp van STARTTLS, DANE en end-to-end encryptie.
De tags 'adkim=s' en 'aspf=s;' specificeren hoe streng de ontvanger moet omgaan met de alignment tussen het 'From'-afzenderdomein van een binnenkomend bericht enerzijds en de gevalideerde domeinnamen behorend bij SPF en DKIM anderzijds. Staat de 'adkim' tag ingesteld op 's' (strict), dan moet het 'From'-domein precies overeenkomen met het signing domain in de 'd=' tag van de DKIM-header. Dat betekent dat je dan geen berichten kunt sturen van een subdomein (bijvoorbeeld alerts@news.example.nl) voorzien van een digitale handtekening afkomstig van je hoofddomein [1, 2] (example.nl). Gebruik je de tag 'adkim=r;' (relaxed, de default), dan mag dit wel. Merk op dat het gebruik van DKIM impliceert dat het signing domain gelijk is aan je hoofddomein. En let op dat je deze tag niet verward met de canonicalization tag ('c=relaxed/simple;') in de DKIM-header. Voor de 'aspf' tag kun je op dezelfde manier kiezen tussen 'strict' en 'relaxed'. Alleen gaat het hier om de alignment tussen het 'From'-afzenderdomein van het bericht en het 'MAIL FROM'-afzenderdomein van de envelope. Waar SPF en DKIM je dus de mogelijkheid geven om het 'MAIL FROM'-afzenderdomein en de verzendende mailservers te beveiligen, maakt DMARC nog eens de koppeling met de 'From' header. Die bevat immers de afzenderinformatie (de display name) die uiteindelijk aan de eindgebruiker wordt getoond in zijn mail client. De 'adkim=s' en 'aspf=s;' tags geven je daarbij de mogelijkheid om de alignment-eisen van de DMARC-validatie te finetunen.
Het zal geen verrassing zijn dat je in de 'rua=' en 'ruf=' tags geen willekeurige e-mailadressen kunt gebruiken. Wil je de DMARC-rapportages laten sturen naar een e-mailadres op een ander dan je eigen domein – typisch dat van een DMARC-dienstverlener – dan moet de ontvangende partij daarvoor toestemming geven. Dat doet hij door een DMARC authorization-record in zijn zonefile op te nemen:
example.nl._report._dmarc IN TXT "v=DMARC1;"
Hiermee laat de eigenaar van dit domein aan ontvangende mailservers weten dat die hun DMARC-rapportages voor het domein example.nl inderdaad naar een adres op dit domein mogen sturen. Voor alle details en andere beschikbare tags verwijzen we je naar sectie 6.3 van RFC 7489.
Let op dat je de laatste puntkomma ';' aan het eind van het DMARC authorization record niet vergeet. Ondanks dat deze puntkomma verplicht is, was deze weggelaten in eerdere versies van RFC 7489.
Je zal zien dat je na de publicatie van je DMARC-record vanzelf rapportage-berichten van de grote mailverwerkers gaat ontvangen. Wij krijgen ze hier bijvoorbeeld van Google, Yahoo, Microsoft, Amazon, Fastmail en een aantal kleinere partijen. Maar ook Nederlandse overheidsorganisaties en XS4ALL sturen ons regelmatig DMARC-rapportages. De rapportageberichten bevatten een (gezipt) XML-bestand met daarin informatie over alle binnengekomen mailberichten. Je kunt die laten verwerken tot mooie grafieken door commerciële partijen als DMARC Advisor en DMARC Analyzer. Daarvoor open je een account aldaar en geef je het bijbehorende mailadres (van hun dus) op in je DMARC-record. Zoals hierboven beschreven zullen zij daarvoor een authorization-record aan hun zonefile moeten toevoegen. Wat zij vervolgens leveren is een mooie (online) analyse en visualisatie van de binnengekomen rapportageberichten.
Maar er zijn inmiddels ook diverse (low-level) open-source tools voor de verwerking en analyse van DMARC-rapportages beschikbaar:
Deze kunnen wellicht als uitgangspunt dienen voor je eigen analyse en dashboard [1, 2, 3, 4].
De DMARC-engine van Exim staat standaard aan. In de initiële configuratie zien we dat de control modifier 'dmarc_disable_verify' (net als 'dkim_disable_verify') gezet wordt voor berichten afkomstig van lokale aanbieders, relay hosts en geauthentiseerde verbindingen. Op deze manier wordt DMARC-validatie expliciet uitgeschakeld voor alle berichten behalve die afkomstig van remote, niet-geauthentiseerde aanbieders (op poort 25 van de MX-gateway). Vanwege de afhankelijkheid van de DKIM-checks zijn ook de uitkomsten van de DMARC-checks pas bekend nadat alle data is ontvangen. Dat betekent dat de 'acl_check_data' ACL (gelijk na het ontvangen van de complete message body) pas de eerste gelegenheid is om onze DMARC-configuratie toe te voegen (op poort 25 van de MX-gateway). Op die manier kunnen binnenkomende berichten die negatief valideren direct na de overdracht worden gedropt, en kan daarbij nog een foutmelding worden teruggegeven in de lopende SMTP-sessie. In het diagram hieronder zie je waar we de DMARC-checks aan de workflow toevoegen:
Hieronder zie je eerst de globale configuratie-opties die we voor DMARC hebben toegevoegd aan de file '/etc/exim/exim.conf':
# DMARC dmarc_tld_file=/usr/share/publicsuffix/public_suffix_list.dat dmarc_history_file=/var/spool/exim/opendmarc/history.dat dmarc_forensic_sender=postmaster@example.nl
De eerste optie ('dmarc_tld_file') bespraken we al bij de eerste DMARC-configuratie aan het begin van dit artikel. De optie 'dmarc_history_file' geeft aan waar de data wordt weggeschreven om zelf geaggregeerde rapportages uit te kunnen sturen. Deze locatie bestaat echter nog niet, vandaar dat we die eerst met de hand aanmaken:
mkdir /var/spool/exim/opendmarc/ chmod 750 /var/spool/exim/opendmarc/ chown exim:exim /var/spool/exim/opendmarc/
En in de laatste optie ('dmarc_forensic_sender') stellen we het afzenderadres in voor de failure reports. Deze worden echter pas verstuurd als we in de ACL ook de control modifier 'dmarc_enable_forensic' zetten (zoals hieronder).
Na deze globale configuratie-opties volgen hier de tests die we aan de 'acl_check_data' ACL hebben toegevoegd (tussen de header checks en de virus/spam checks in de file '/etc/exim/exim.conf'):
# DMARC warn dmarc_status = quarantine !authenticated = * log_message = Message from $dmarc_used_domain failed sender's DMARC policy; quarantine control = dmarc_enable_forensic set acl_m_quarantine = 1 # this variable to use in a router/transport deny dmarc_status = reject !authenticated = * message = Message from $dmarc_used_domain failed sender's DMARC policy; reject control = dmarc_enable_forensic warn add_header = :at_start: ${authresults {$primary_hostname}}
Berichten (van buiten) waarvoor de DMARC check ('dmarc_status') een 'deny' oplevert, worden geblokkeerd. Is het resultaat een 'quarantine', dan wordt een vlaggetje gezet ('set acl_m_quarantine = 1'), dat we verderop in een router of transport kunnen gebruiken om het betreffende bericht daadwerkelijk in quarantaine te plaatsen.
Het makkelijkst is om voor de DMARC quarantine een aparte gebruiker aan te maken, en mailberichten voor quarantaine af te leveren bij dit account. Op die manier kan een beheerder deze berichten indien nodig met de bekende mail tools benaderen. Daartoe maken we om te beginnen een nieuwe gebruiker 'mail-quarantine' aan:
useradd -c "Quarantine Mail" -s "/bin/bash" mail-quarantine passwd mail-quarantine
Vervolgens zijn er 2 plekken waar we de eerder gezette vlag 'dmarc_enable_forensic' kunnen gebruiken om een bericht daadwerkelijk naar deze nieuwe gebruiker om te leiden: in de 'localuser' router of in het 'local_delivery' transport. Die eerste optie (door alleen het aanpassen van de variabelen 'directory' en 'user') blijkt echter problemen op te leveren met de toegangsrechten van de local delivery agent. Vandaar dat we er hier voor kiezen om het transport aan te passen. We maken daarvoor (uitgaande van het bestaande transport 'local_delivery') een nieuw transport 'local_delivery_quarantine' aan als volgt:
local_delivery: driver = appendfile #file = /var/mail/$local_part directory = $home/Maildir maildir_format delivery_date_add envelope_to_add return_path_add group = mail mode = 0660 local_delivery_quarantine: driver = appendfile #file = /var/mail/$local_part directory = /home/mail-quarantine/Maildir user = mail-quarantine home_directory = /home/mail-quarantine current_directory = /home/mail-quarantine maildir_format delivery_date_add envelope_to_add return_path_add group = mail mode = 0660
Zoals je ziet hebben we in het tweede transport de variabelen 'directory', 'user', 'home_directory' en 'current_directory' aangepast om de aflevering in de Maildir van de gebruiker 'mail-quarantine' te laten plaatsvinden. De selectie van het juiste transport doen we wel in de 'localuser' router, simpelweg door het transport te kiezen op basis van onze variabele 'acl_m_quarantine':
#transport = local_delivery transport = ${if =={$acl_m_quarantine}{1} {local_delivery_quarantine}{local_delivery}}
In de logs hieronder zie je, dat een binnenkomend bericht waarvan de DMARC-check als resultaat 'quarantine' oplevert, (na de restart van Exim) nu inderdaad via het 'local_delivery_quarantine' transport afgeleverd wordt (bij de gebruiker 'mail-quarantine').
2020-06-13 21:27:39 1jkBoZ-0007qb-3a DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=no enforcement='Quarantine' 2020-06-13 21:27:39 1jkBoZ-0007qb-3a H=(sender.afzenderdomein.nl) [203.0.113.109] Warning: Message from afzenderdomein.nl failed sender's DMARC policy; quarantine 2020-06-13 21:27:43 1jkBoZ-0007qb-3a <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9548 DKIM=afzenderdomein.nl id=f894a5b6-3775-42ad-0d2b-f399a7498ca2@afzenderdomein.nl 2020-06-13 21:27:43 1jkBoZ-0007qb-3a => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery_quarantine 2020-06-13 21:27:43 1jkBoZ-0007qb-3a Completed
Kijken we naar de DMARC-logs en -headers van reguliere mailberichten, dan zien we dat deze nu inderdaad ook van een 'Authentication-Results' header met de uitkomsten van de DMARC-validatie zijn voorzien:
2020-06-13 12:56:33 1jk3px-00061C-La DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=yes enforcement='Accept' 2020-06-13 12:56:34 1jk3px-00061C-La <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9492 DKIM=afzenderdomein.nl id=5adb8f49-d6a3-0669-95e6-cd1c81ee2885@afzenderdomein.nl 2020-06-13 12:56:34 1jk3px-00061C-La => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery 2020-06-13 12:56:34 1jk3px-00061C-La Completed
Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Sat, 13 Jun 2020 12:56:34 +0200 X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Spam-Score: 1.1 (+) Authentication-Results: mail.example.nl; iprev=fail smtp.remote-ip=203.0.113.109; dkim=pass header.d=afzenderdomein.nl header.s=dkim20191228 header.a=rsa-sha256; dmarc=pass header.from=afzenderdomein.nl Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jk3px-00061C-La for ontvanger@example.nl; Sat, 13 Jun 2020 12:56:34 +0200 Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id 6B1202800207; Sat, 13 Jun 2020 12:56:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:reply-to:subject:subject:from:from :received:received; s=dkim20191228; t=1592045792; x=1592909793; bh=fV/HSSablAO9dg+XB6nf25aOMgrAYic/C/18QLMt1I0=; b=ergZe22ZNwbI J9tCBPqIUNZZa85wX43BcbC3tjWUND39PVo3Zn/sRmeQf11HtebGhyWXTkKe8F9e S9pYgIbGVpObkBXKD6nq6ugoe6zC0W3/MObTdi+xGBkulYF4mFTr6TK61Yv+h7Kc /wWsVxuUjkgirBZ1UQnHL5r33Fnxi99ENjCB2rHZTdwbfUbpIxJmYbLxoAL9fjin SwOuz2UT9vwQhtdPwohu5wWSGyjKvYwGw8Kisx4J5qCp+lJcXcMrt3PxSEM+xey7 5GLo/wQ8rfxPDiXtN5phRa6G09lrCYY= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id EJs6Rg0U356M; Sat, 13 Jun 2020 12:56:32 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomein.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 2555B280063C; Sat, 13 Jun 2020 12:56:32 +0200 (CEST) From: "Tester" <tester@afzenderdomein.nl> Subject: Test DMARC validatie Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl Message-ID: <5adb8f49-d6a3-0669-95e6-cd1c81ee2885@afzenderdomein.nl> Date: Sat, 13 Jun 2020 12:56:31 +0200
En sturen we een bericht waarvoor de DMARC-validatie niet slaagt, dan wordt deze als volgt afgewezen:
the server did not accept the mail server message: 550 Message from afzenderdomein.nl failed sender's DMARC policy; reject could not send mail
2020-06-09 13:04:29 1jic3R-000372-I7 DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=no enforcement='Reject' 2020-06-09 13:04:29 1jic3R-000372-I7 H=(sender.afzenderdomein.nl) [203.0.113.109] F=<tester@afzenderdomein.nl> rejected after DATA: Message from afzenderdomein.nl failed sender's DMARC policy; reject
Daarmee is onze SPF/DKIM/DMARC set-up helemaal compleet!
Je kunt je mailconfiguratie testen op Internet.nl en via deze portals:
Exim op CentOS 8.2 (in combinatie met de EPEL repository) is na een paar kleine tweaks standaard al voorzien van SPF, DKIM en DMARC (waarvan die eerste 2 ook als onderdeel van de spam scanner). Hoewel de initiële configuratie (op CentOS) helemaal nog geen policies voor deze 3 standaarden bevat, is volwaardige ondersteuning voor SPF, DKIM en DMARC weldegelijk ingebakken in de software. Daarmee ligt er een solide fundament om zelf een veilig mailsysteem gebaseerd op Exim op te bouwen. Daarbij is het wel van belang om de werking en betekenis van de statements in de Exim-configuratie eerst goed te doorgronden. Wat we in dit hands-on artikel hebben besproken levert je een volledige implementatie op van alle 3 mail-beveiligingsstandaarden SPF, DKIM en DMARC. Combineer je deze setup nog met IPv6 en DANE, dan is dit alles genoeg voor een 100 procent score op Internet.nl. Dat betekent dat je mailsystemen helemaal up-to-date zijn en voldoen aan de laatste eisen en inzichten van onder andere Forum Standaardisatie en NCSC. Bovendien is mailbeveiliging op domeinniveau een praktische voorwaarde bij de transitie van IPv4 naar IPv6, waar je niet langer kunt filteren op IP-adres. Als nieuwe SPF/DKIM/DMARC-functionaliteit voor Exim beschikbaar komt, verwerken we die in dit artikel. Ook feedback op dit artikel is uiteraard van harte welkom. Heb je een modulair mailsysteem nodig met meer nadruk op schaalbaarheid en veiligheid, dan raden we je aan om naar Postfix te kijken. In dit hands-on artikel beschrijven we de configuratie van SPF, DKIM en DMARC voor Postfix op vergelijkbare wijze als we dat hier voor Exim hebben gedaan. Postfix vraagt weliswaar om meer uitbouw- en integratiewerk, maar levert een mailinfrastructuur op die je helemaal op maat kunt snijden en dimensioneren naar je eigen specifieke behoeften.