Table of Contents

OpenDKIM on Postfix with virtual domains

In this tutorial we will install OpenDKIM on a GNU/Linux mail server based on Debian 11 Buster. The mail service is provided by Postfix configured for virtual domains using virtual_alias_domains.

apt install opendkim opendkim-tools

In Debian 11 Bullseye the service is controlled (enable, start, stop, etc.) by Systemd:

systemctl status opendkim.service

Because Postfix is running into a chroot, it cannot access the /run/opendkim/opendkim.sock Unix socket to communicate with opendkim, so we change the Socket option into /etc/opendkim.conf and make the daemon to be listening on port 127.0.0.1:8891/TCP:

Socket  inet:8891@localhost

The same daemon is used both for signing and verifying. Signing is performed when the client connecting to the MUA is authenticated and the From: address matches the domains to be signed (see the command line option -d or the SigningTable option of the /etc/opendkim.conf configuration file), verifying is performed in other cases.

Create the keys in /etc/dkimkeys/

The canonical directory to keep the keys is /etc/dkimkeys/. For each domain we can have more than one key (e.g. when a key is to be renewed, etc.), so each key is identified by the domain name and by an arbitrary selector; it is a common practice to use the current year as the selector.

We create one subdirectory for each virtual domain:

DOMAIN='rigacci.org'
SELECTOR='2022'
mkdir /etc/dkimkeys/"$DOMAIN"
chown opendkim:opendkim /etc/dkimkeys/"$DOMAIN"
chmod 700 /etc/dkimkeys/"$DOMAIN"
sudo -u opendkim opendkim-genkey -D /etc/dkimkeys/"$DOMAIN" -d "$DOMAIN" -s "$SELECTOR"

This will create two files:

Add the private key in /etc/dkimkeys/keytable

It is necessary to tell OpenDKIM what is the private key to use when it wants to sign a mail from a domain. We must add one line for each domain into /etc/dkimkeys/keytable:

[SELECTOR]._domainkey.[DOMAIN] [DOMAIN]:[SELECTOR]:/etc/dkimkeys/[DOMAIN]/[SELECTOR].private

Add the public key into the DNS zone

Now it is necessary to publish the public key into the DNS. Just copy and paste the .txt file into the zone file:

2022._domainkey IN      TXT     ( "v=DKIM1; h=sha256; k=rsa; "
          "p=MIIBIjANBgkqhkiG9w0BAQ..."
          "W0CdtxNd+xRgCopJCp93CLiD..." )  ; ----- DKIM key 2022 for rigacci.org

Add the domain (or single sender) to be signed

Into the file /etc/dkimkeys/signingtable we declare that mails originating from that domain must be signed:

*@[DOMAIN] [SELECTOR]._domainkey.[DOMAIN]

NOTICE: The use of the wildcard (to indicate all the senders from a domain) is possibile if signingtable is declared with refile (regular expression file) into the configuration file. Otherwise you have to specify every single sender address where signing is to be applied.

Remember to reload OpenDKIM after changing the signingtable:

systemctl reload opendkim.service

Configure OpenDKIM

Into the /etc/opendkim.conf file we inform OpenDKIM to look into a KeyTable to find keys and into a SigningTable to know which domains require signing. The service will listen on port 8891/TCP (should use Unix domain socket instead? Better performances? More painfull because Postfix runs in chroot).

# We use virtual domains, so we use KeyTable and SigningTable
KeyTable      file:/etc/dkimkeys/keytable
SigningTable  refile:/etc/dkimkeys/signingtable

# Match a list of hosts whose messages will be signed.
# By default, only localhost is considered as internal host.
#InternalHosts refile:/etc/dkimkeys/trustedhosts

# Socket for the MTA connection (required).
Socket        inet:8891@localhost

NOTICE: refile means that the file contains regular expressions (e.g. asterisk wildcard to indicate all the mail addresses into a domain).

Test the OpenDKIM configuration

Reload the DNS Bind service and test that OpenDKIM can properly use the keys (it is not necessary to reload the OpenDKIM service):

# opendkim-testkey -v -v
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: record 0 for '2022._domainkey.rigacci.org' retrieved
opendkim-testkey: checking key '2022._domainkey.rigacci.org'
opendkim-testkey: key 2022._domainkey.rigacci.org not secure
opendkim-testkey: 1 keys checked; 1 pass, 0 fail

Signing message test

cat message.txt \
    | opendkim-testmsg -d "$DOMAIN" -k "/etc/dkimkeys/$DOMAIN/$SELECTOR.private" -s "$SELECTOR.$DOMAIN"

Configure Postfix

Message signing with OpenDKIM is performed as a milter (mail filter) in Postfix; milters are declared into the /etc/postfix/main.cf configuration file.

Using the non_smtpd_milters directive we may add DKIM for locally generated mails, i.e. local submissions via sendmail command line, submissions to the qmqpd, (Quick Mail Queuing Protocol daemomn), re-injected mails. More generally we may apply DKIM signature for all the messages received by the SMTP daemon, using the smtpd_milters directive.

Using custom settings in /etc/postfix/master.cf, you can declare specific milters for messages received from your users over the submission protocol only (port 587/TCP). In this snippet of master.cf we use a custom mua_milters directive:

submission inet n   -   y   -   -   smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_milters=$mua_milters
  -o smtpd_sender_restrictions=$mua_sender_restrictions
  -o smtpd_relay_restrictions=$mua_relay_restrictions

Having done this, we define the custom mua_milters directive in main.cf to apply SpamAssassin and DKIM filtering on sumbitted messages:

# Locally generated mails (e.g. from command line Mutt) are filtered with OpenDKIM.
non_smtpd_milters = inet:localhost:8891

# Mails received via SMTP protocol are filtered with OpenDKIM;
# messages created using SoGO webmail go through this milter.
smtpd_milters = inet:localhost:8891

# Filters applied (as smtpd_milters) to messages received via SUMBISSION/587;
mua_milters =
    unix:spamass/spamass.sock,
    inet:localhost:8891

Another important Postfix setting is milter_default_action, the default is tempfail which means that if the milter does not respond, the message will be held into the queue and retried later. Other settings can be accept or reject:

milter_default_action = tempfail

Logging

When a message passes through the OpenDKIM filter, you get the following line into mail.log:

opendkim[983999]: 37FDD7D659: DKIM-Signature field added (s=2022, d=rigacci.org)

If a message does not match any entry in /etc/dkimkeys/signingtable, it will not be signed; the log is:

opendkim[983999]: 4778D7D610: no signing table match for 'testmail@rigacci.org'
opendkim[983999]: 4778D7D610: no signature data

Web References