Before starting this post I want to thanks Ilija from matoski.com as this post is based on his work on SPF / DKIM for Debian. Click here to see the original post.
Let’s start with a very short description of DKIM. DomainKeys Identified Mail is a mechanism designated to detect email spoofing (i.e. be sure that the original email content is the same as the received). It works with signatures. The public key is available on a DNS entry of the sender domain zone and the private key used to generate this signature is stored on the mail server.
This post will show how to configure DKIM on a Debian server with Postfix and automatize the configuration with Plesk. I assume you have sufficient permission to run these commands.
Runing configuration
Debian wheezy 3.2.0-4-amd64
Postfix 2.9.6
Plesk 12.0
OpenDKIM 2.6.8
After a great disscussion with M. Lieske who did a new install in August 2016, I can confirm it works with Debian jessie 8.5, OpenDKIM 2.9.2 (with some adjustements in the script below, see notes) and Plesk 12.5
Installation
Let’s start with the installation of the OpenDKIM package
1 |
apt-get install opendkim opendkim-tools |
OpenDKIM configuration
Great ! The next part is to create the directory structure for every parts of OpenDKIM
1 2 3 |
mkdir -pv /etc/opendkim/keys chown -Rv opendkim:opendkim /etc/opendkim chmod go-rwx /etc/opendkim/* |
We have to create the key tables, signing tables and trusted hosts files
1 2 3 |
touch /etc/opendkim/KeyTable touch /etc/opendkim/SigningTable touch /etc/opendkim/TrustedHosts |
Trusted hosts file contain a list of domain / servers that is trusted. In our case every DNS name and IP of the mail server should appear in this list. Let’s setup the local server name
1 2 3 4 |
127.0.0.1 localhost thisserver.domain.com 123.123.123.123 #(public IP) |
Key table file contain information about where the key files are for every domains. Signing Table contain information about which domain / mail address should use DKIM signature.
Next phase is to check the OpenDKIM main configuration located in /etc/opendkim.conf. Here is one possible configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# Log to syslog Syslog yes SyslogSuccess yes LogWhy yes # Required to use local socket with MTAs that access the socket as a non- # privileged user (e.g. Postfix) UMask 002 # Commonly-used options; the commented-out versions show the defaults. Canonicalization relaxed/simple Mode sv PidFile /var/run/opendkim/opendkim.pid # Always oversign From (sign using actual From and a null From to prevent # malicious signatures header fields (From and/or others) between the signer # and the verifier. From is oversigned by default in the Debian pacakge # because it is often the identity key used by reputation systems and thus # somewhat security sensitive. OversignHeaders From # Our KeyTable and SigningTable KeyTable refile:/etc/opendkim/KeyTable SigningTable refile:/etc/opendkim/SigningTable # Trusted Hosts ExternalIgnoreList /etc/opendkim/TrustedHosts InternalHosts /etc/opendkim/TrustedHosts # Hashing Algorithm SignatureAlgorithm rsa-sha256 # Auto restart when the failure occurs. CAUTION: This may cause a tight fork loops AutoRestart Yes AutoRestartRate 10/1h # Set the user and group to opendkim user UserID opendkim:opendkim # Specify the working socket Socket inet:8891@localhost |
After any changes to OpenDKIM configuration file, restart the service
1 |
service opendkim restart |
Postfix configuration
The base setup of OpenDKIM is done we have now to setup Postfix to use it as milter. Open the /etc/postfix/main.cf and add the “inet:127.0.0.1:8891” value to the smtpd_milters and non_smtpd_milters configuration values. There should already have other milters configured (psa-pc-remote / OpenDMARC / SPF), it depends from your actual configuration of Postfix. The order of the milters are important, especially for OpenDMARC that should be the last one as it depends of SPF and OpenDKIM.
1 2 3 4 5 6 |
milter_default_action = accept milter_protocol = 6 # Plesk and then OpenDKIM smtpd_milters = , inet:127.0.0.1:12768, inet:127.0.0.1:8891 # Only OpenDKIM non_smtpd_milters = , inet:127.0.0.1:8891 |
The reason to disable the Plesk milter for non_smtpd_milters is only because I have some local scripts that would be rejected by SPF. When the changes to main.cf is done we have to restart the Postfix daemon to apply the configuration
1 |
service postfix restart |
OpenDKIM domain scripts
At this point OpenDKIM is configured and active in Postfix, last step is to generate Keys for every domains. Here is the full script that will enable DKIM for a specific domain. This script will :
- Create the Keys
- Setup the Signing Table to enable the DKIM signature for all addresses of this domain
- Add the domain to the trusted hosts file
- Create the DNS record on the Plesk Database
- Update DNS configuration via a Plesk CLI command
- Reload services to apply the changes
- Be sure to enable SPF signing for this domain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#!/bin/bash ## Check if this script has been called with 1 unique argument. die () { echo >&2 "$@" exit 1 } [ "$#" -eq 1 ] || die "1 argument required, $# provided, domain required, ex: ./script example.com" cwd=`pwd` ## OpenDKIM configuration folder opendkim="/etc/opendkim" ## OpenDKIM Keys location location="$opendkim/keys/$1" ## Check if keys not already exists (prevent script to be executed twice) [ -d "$location" ] && die "There is already a directory in the folder, delete the folder if you want to create a new one" ## Create folders / generate keys an fill OpenDKIM configuration files echo "Processing domain : $1" mkdir -p "$location" cd "$location" opendkim-genkey -d $1 -s mail chown opendkim:opendkim * chown opendkim:opendkim "$location" chmod u=rw,go-rwx * echo "$1 $1:mail:$location/mail.private" >> "$opendkim/KeyTable" echo "*@$1 $1" >> "$opendkim/SigningTable" echo "$1" >> "$opendkim/TrustedHosts" echo "mail.$1" >> "$opendkim/TrustedHosts" dom="$1" ## Extract the TXT DNS record containing the public Key /!\ WARNING DOESN'T WORKS WITH OPENDKIM 2.9.2 see below /!\ txtdns=`cat "$location/mail.txt" | sed -e 's/.*"\(.*\)".*/\1/g'` ## Use this line with OpenDKIM since version 2.9.2 ## <code>txtdns=`cat "$location/mail.txt" | tr '\n' ' ' | sed -e 's/.*"\(.*\)".*"\(.*\)".*/\1\2/g'` ## Create the DNS record in the Plesk database /!\ It seems with a new install in Plesk 12.5 that the timestamp field has been moved at the first colume /!\ mysql -N -u admin -p`cat /etc/psa/.psa.shadow` psa -e "SELECT id FROM dns_zone WHERE name = '"$dom"'" | awk '{print "INSERT INTO dns_recs VALUES ('\'''\'', "$1",'\''TXT'\'','\''mail._domainkey.'$dom'.'\'','\''mail._domainkey.'$dom'.'\'','\'''"$txtdns"''\'','\'''"$txtdns"''\'','\'''\'','\'''\'',NOW());"}' | mysql -u admin -p`cat /etc/psa/.psa.shadow` psa ## Update DNS configuration on Plesk for this domain (i.e. apply the DNS record created) /opt/psa/admin/bin/dnsmng --update $1 ## Reload OpenDKIM service service opendkim reload ## Reload OpenDMARC service (Will be explained in a future post but TrustedHosts file is also used by OpenDMARC in my configuration) service opendmarc reload /usr/local/psa/bin/subscription_settings -u $1 -sign_outgoing_mail true cd "$cwd" |
When a domain is removed from the server the configuration has to be cleaned here is the script that do all these actions :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#!/bin/bash ## Check if this script has been called with 1 unique argument. die () { echo >&2 "$@" exit 1 } [ "$#" -eq 1 ] || die "1 argument required, $# provided, domain required, ex: ./script example.com" ## OpenDKIM configuration folder opendkim="/etc/opendkim" ## OpenDKIM Keys location location="$opendkim/keys/$1" ## Remove entries in configuration files echo "Processing domain : $1" sed -i "/$1/d" "$opendkim/KeyTable" sed -i "/$1/d" "$opendkim/SigningTable" sed -i "/$1/d" "$opendkim/TrustedHosts" ## Reloads services service opendkim reload service opendmarc reload ## Remove keys rm -r -f "$location" ## When domain is removed from Plesk the DNS record is automatically cleared... echo "Warning : DNS record is not automatically removed"; |
Plesk automation
When a domain is created or removed from Plesk it would be fun to automatize DKIM activation or deactivation. Fist we have to create another custom script to handle every action for a domain creation and another one when the domain is removed. Even if Plesk events handler can have multiple command lines it’s better to only do a call to an external script.
For the example let’s have a script folder at the system root /scripts/ that will contain every scripts. The first one is “openDKIMDomGen.sh”. It contains every lines of the OpenDKIM creation script shown above. The second one is “openDKIMDomRemove.sh” and contain the removal script above. These scripts can be executed only from the root user (chmod 700).
Let’s create the script “pleskDomainCreatedEvent.sh” that will be called when a new domain is created :
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/bash die () { echo >&2 "$@" exit 1 } [ "$#" -eq 1 ] || die "1 argument required, $# provided, domain required, ex: ./script example.com" ## Enable DKIM /scripts/openDKIMDomGen.sh $1 ## Enable PostSRSD (will be explained on another post) /scripts/postSRSDomainAdd.sh $1 |
And the script “pleskDomainRemovedEvent.sh” that will be called when a domain is removed :
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/bash die () { echo >&2 "$@" exit 1 } [ "$#" -eq 1 ] || die "1 argument required, $# provided, domain required, ex: ./script example.com" ## OpenDKIM clean up /scripts/openDKIMDomRemove.sh $1 ## PostSRSD clean up (will be explained in a future post) /scripts/postSRSDomainRemove.sh $1 |
Next phase is to configure Plesk Event Manager. Log on to Plesk with administrator account an go to Server Management -> Tools & Settings -> Tools & Ressources -> Event Manager.
Create a new Event Handler for the domain creation :
Then create a new Event Handler for the domain removal :
Configure OpenDKIM for every active domains on the Plesk server
The Plesk Event Handler doesn’t help to configure OpenDKIM of all already active domains on the Plesk Server. A little command that call the openDKIMDomGen.sh script for every domain can be used :
1 |
mysql -N -u admin -p`cat /etc/psa/.psa.shadow` psa -e "SELECT name FROM dns_zone WHERE status = 0" | awk '{print "/scripts/openDKIMDomGen.sh "$1""}' | bash |
This command line extract all domains that have a DNS zone from Plesk database and call the openDKIMDomGen.sh script with the domain name as parameter.
Tests
The easiest way to test if the DKIM signature has been applied to a specific domain, you just have to send an email from this domain to check-auth@verifier.port25.com you will receive a response with the DKIM result like this :
1 2 3 4 5 6 7 8 |
========================================================== Summary of Results ========================================================== SPF check: pass DomainKeys check: pass DKIM check: pass Sender-ID check: pass SpamAssassin check: ham |
You can also check the headers of an outgoing mail to validate that the mail has been signed :
1 2 3 4 5 6 7 |
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=domain.com; s=mail; t=1440353732; bh=BEznCdSh22jcNBUHh8fT1vhBkBi65DvlXmfkKF3A3O8=; h=Date:Subject:From:To:From; b=mDP8EGXzu/oOSwC162IkVT1/OwsdBIhY70EL0KEMDaHM8z3rSDQPG7xUTXpgnPLCb wCsCF7O24UiwX3ZmEHFbmxjFlLxmCdmi4YkU5X0PqPsC9ZVDGzxR8Zu/4zUsmKYNyO IVf/fXiWfDH5MAxvHMx+YfaVFc7UwMRTirLK1sJE= |
The mail log of the server will also show your the whole process of a mail. To do a real time trace :
1 |
tail -f /var/log/maillog |
And that’s all for this post, thanks for reading.
Can that updated for Debian Jessie also Debian 8?
root@mail:~/scripts# mysql -N -u admin -p
cat /etc/psa/.psa.shadow
psa -e “SELECT name FROM dns_zone WHERE status = 0” | awk ‘{print “/scripts/openDKIMDomGen.sh “$1″”}’ | bashbash: Zeile 1: /scripts/openDKIMDomGen.sh: Datei oder Verzeichnis nicht gefunden
bash: Zeile 2: /scripts/openDKIMDomGen.sh: Datei oder Verzeichnis nicht gefunden
bash: Zeile 3: /scripts/openDKIMDomGen.sh: Datei oder Verzeichnis nicht gefunden
bash: Zeile 4: /scripts/openDKIMDomGen.sh: Datei oder Verzeichnis nicht gefunden
bash: Zeile 5: /scripts/openDKIMDomGen.sh: Datei oder Verzeichnis nicht gefunden
bash: Zeile 6: /scripts/openDKIMDomGen.sh: Datei oder Verzeichnis nicht gefunden
Hello Denis,
This post should also work on Debian 8. The issue you have is that the “openDKIMDomGen.sh” script is not present in the folder /scripts/ or dosen’t have the execute permission.
To change the script location edit the part in bold in this script line :
mysql -N -u admin -p
cat /etc/psa/.psa.shadow
psa -e "SELECT name FROM dns_zone WHERE status = 0" | awk '{print "/scripts/openDKIMDomGen.sh "$1""}' | bashIf this is an issue with permission, please ensure that the script is owned by root and have permission to execute it :
chown root:root /scripts/openDKIMDomGen.sh
chmod 740 /scripts/openDKIMDomGen.sh
Have a nice day,
Stéphane
Hello Stéphane,
I have now against a problem and that is with the domainkeys record. There is only a mail.domainkey created and not a default how can I get it?
Hello Denis,
It’s not mandatory to have the record default._domainkey.domain.tld. You can set mail._domainkey.domain.tld or whateveryouwant._domainkey.domain.tld. The important part is to have _domainkey.domain.tld. (the remote mail server that query the DNS will just get everything in TXT under _domainkey.domain.tld.
If you want to change the script to use default._domainkeys.domain.tld instead of “mail” you just have to change the line 34 of openDKIMDomGen.sh and replace “mail._domainkey” in the INSERT SQL query by “default._domainkey”.
I hope it will help you.
Stéphane
Hi, I have some problems to put in on road. Test from auth-results@verifier.port25.com fail,
==========================================================
Summary of Results
==========================================================
SPF check: pass
DomainKeys check: pass
DKIM check: fail
Sender-ID check: pass
SpamAssassin check: ham
———————————————————-
DKIM check details:
———————————————————-
Result: fail (signature doesn’t verify)
I’m not sure what is wrong, because the mail._default TXT record is available.
Hi Michael,
There is multiple possiblities, but before finding the source of this issue, could you please validate in the e-mail headers that the DKIM signature is present ?
Have a nice day,
Stéphane