{"id":319,"date":"2016-03-22T12:00:43","date_gmt":"2016-03-22T04:00:43","guid":{"rendered":"http:\/\/www.behindgfw.com\/archives\/?p=319"},"modified":"2019-06-23T16:31:16","modified_gmt":"2019-06-23T08:31:16","slug":"configure-spf-and-dkim-in-postfix-on-ubuntu-14-04-4","status":"publish","type":"post","link":"https:\/\/www.behindgfw.com\/archives\/2016\/03\/configure-spf-and-dkim-in-postfix-on-ubuntu-14-04-4.html","title":{"rendered":"Configure SPF and DKIM in Postfix on Ubuntu 14.04.4"},"content":{"rendered":"<p>&nbsp;<\/p>\n<p><a href=\"http:\/\/www.openspf.org\/\">SPF (Sender Policy Framework)<\/a> is a system that identifies to mail servers what hosts are allowed to send email for a given domain. Setting up SPF helps to prevent your email from being classified as spam.<\/p>\n<p><a href=\"http:\/\/www.dkim.org\/\">DKIM (DomainKeys Identified Mail)<\/a> is a system that lets your official mail servers add a signature to headers of outgoing email and identifies your domain\u2019s public key so other mail servers can verify the signature. As with SPF, DKIM helps keep your mail from being considered spam. It also lets mail servers detect when your mail has been tampered with in transit.<\/p>\n<p><a href=\"http:\/\/dmarc.org\/\">DMARC (Domain Message Authentication, Reporting &amp; Conformance)<\/a> allows you to advertise to mail servers what your domain\u2019s policies are regarding mail that fails SPF and\/or DKIM validations. It additionally allows you to request reports on failed messages from receiving mail servers.<\/p>\n<p>The DNS instructions for setting up SPF, DKIM and DMARC are generic. The instructions for configuring the SPF policy agent and OpenDKIM into Postfix should work on any distribution after making respective code adjustments for the package tool, and identifying the exact path to the Unix socket file.<\/p>\n<blockquote class=\"note\"><p><strong class=\"callout-title\">Note<\/strong><\/p>\n<div>The steps required in this guide require root privileges. Be sure to run the steps below as <strong>root<\/strong> or with the <code>sudo<\/code> prefix. For more information on privileges see our <a href=\"https:\/\/www.linode.com\/docs\/tools-reference\/linux-users-and-groups\">Users and Groups<\/a> guide.<\/div>\n<\/blockquote>\n<blockquote class=\"caution\"><p><strong class=\"callout-title\">Caution<\/strong><\/p>\n<div>\n<p>You must already have Postfix installed, configured and working. Refer to the <a href=\"https:\/\/www.linode.com\/docs\/email\/postfix\/\">Linode Postfix Guides<\/a> for assistance.<\/p>\n<p>Publishing an SPF DNS record without having the SPF policy agent configured within Postfix is safe; however, publishing DKIM DNS records without having OpenDKIM working correctly within Postfix can result in your email being discarded by the recipient\u2019s email server.<\/p>\n<\/div>\n<\/blockquote>\n<h2 id=\"install-dkim-spf-and-postfix\">Install DKIM, SPF and Postfix<i class=\"fa fa-link\"><\/i><\/h2>\n<ol>\n<li>Install the four required packages:\n<div class=\"btn-copy\"><\/div>\n<pre><code>apt-get install opendkim opendkim-tools postfix-policyd-spf-python postfix-pcre\r\n<\/code><\/pre>\n<\/li>\n<li>Add user <code>postfix<\/code> to the <code>opendkim<\/code> group so that Postfix can access OpenDKIM\u2019s socket when it needs to:\n<div class=\"btn-copy\"><\/div>\n<pre><code>adduser postfix opendkim\r\n<\/code><\/pre>\n<\/li>\n<\/ol>\n<h2 id=\"set-up-spf\">Set up SPF<i class=\"fa fa-link\"><\/i><\/h2>\n<h3 id=\"add-spf-records-to-dns\">Add SPF records to DNS<i class=\"fa fa-link\"><\/i><\/h3>\n<p>The value in an SPF DNS record will look something like the following examples. The full syntax is at<a href=\"http:\/\/www.openspf.org\/SPF_Record_Syntax\">the SPF record syntax page<\/a>.<\/p>\n<p><strong>Example 1<\/strong> Allow mail from all hosts listed in the MX records for the domain:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>v=spf1 mx -all\r\n<\/code><\/pre>\n<p><strong>Example 2<\/strong> Allow mail from a specific host:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>v=spf1 a:mail.example.com -all\r\n<\/code><\/pre>\n<ul>\n<li>The <code>v=spf1<\/code> tag is required and has to be the first tag.<\/li>\n<li>The last tag, <code>-all<\/code>, indicates that mail from your domain should only come from servers identified in the SPF string. Anything coming from any other source is forging your domain. An alternative is <code>~all<\/code>, indicating the same thing but also indicating that mail servers should accept the message and flag it as forged instead of rejecting it outright. <code>-all<\/code> makes it harder for spammers to forge your domain successfully; it is the recommended setting. <code>~all<\/code> reduces the chances of email getting lost because an incorrect mail server was used to send mail. <code>~all<\/code> can be used if you don\u2019t want to take chances.<\/li>\n<\/ul>\n<p>The tags between identify eligible servers from which email to your domain can originate.<\/p>\n<ul>\n<li><code>mx<\/code> is a shorthand for all the hosts listed in MX records for your domain. If you\u2019ve got a solitary mail server, <code>mx<\/code> is probably the best option. If you\u2019ve got a backup mail server (a second MX record), using <code>mx<\/code> won\u2019t cause any problems. Your backup mail server will be identified as an authorized source for email although it will probably never send any.<\/li>\n<li>The <code>a<\/code> tag lets you identify a specific host by name or IP address, letting you specify which hosts are authorized. You\u2019d use <code>a<\/code> if you wanted to prevent the backup mail server from sending outgoing mail or if you wanted to identify hosts other than your own mail server that could send mail from your domain (e.g., putting your ISP\u2019s outgoing mail servers in the list so they\u2019d be recognized when you had to send mail through them).<\/li>\n<\/ul>\n<p>For now, we\u2019re going to stick with the <code>mx<\/code> version. It\u2019s simpler and correct for most basic configurations, including those that handle multiple domains. To add the record, go to your DNS management interface and add a record of type TXT for your domain itself (i.e., a blank hostname) containing this string:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>v=spf1 mx -all\r\n<\/code><\/pre>\n<p>If you\u2019re using Linode\u2019s DNS Manager, go to the domain zone page for the selected domain and add a new TXT record. The screen will look something like this once you\u2019ve got it filled out:<\/p>\n<p>If your DNS provider allows it (DNS Manager doesn\u2019t), you should also add a record of type SPF, filling it in the same way as you did the TXT record.<\/p>\n<blockquote class=\"note\"><p><strong class=\"callout-title\">Note<\/strong><\/p>\n<div>The values for the DNS records above &#8211; and for the rest of this guide &#8211; are done in the style that Linode\u2019s DNS Manager needs them to be in. If you\u2019re using another provider, that respective system may require the values in a different style. For example freedns.afraid.org requires the values to be written in the style found in BIND zonefiles. Thus, the above SPF record\u2019s value would need to be wrapped in double-quotes like this: <code>\"v=spf1 mx -all\"<\/code>. You\u2019ll need to consult your DNS provider\u2019s documentation for the exact style required.<\/div>\n<\/blockquote>\n<h3 id=\"add-the-spf-policy-agent-to-postfix\">Add the SPF policy agent to Postfix<i class=\"fa fa-link\"><\/i><\/h3>\n<p>The Python SPF policy agent adds SPF policy-checking to Postfix. The SPF record for the sender\u2019s domain for incoming mail will be checked and, if it exists, mail will be handled accordingly. Perl has its own version, but it lacks the full capabilities of Python policy agent.<\/p>\n<ol>\n<li>If you are using SpamAssassin to filter spam, you may want to edit <code>\/etc\/postfix-policyd-spf-python\/policyd-spf.conf<\/code> to change the <code>HELO_reject<\/code> and <code>Mail_From_reject<\/code> settings to <code>False<\/code>. This edit will cause the SPF policy agent to run its tests and add a message header with the results in it while <em>not<\/em> rejecting any messages. You may also want to make this change if you want to see the results of the checks but not actually apply them to mail processing. Otherwise, just go with the standard settings.<\/li>\n<li>Edit <code>\/etc\/postfix\/master.cf<\/code> and add the following entry at the end:\n<dl class=\"file-excerpt\">\n<dt>\/etc\/postfix\/master.cf<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\"><\/code><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\">policyd-spf  unix  -       n       n       -       0       spawn\r\n    user=policyd-spf argv=\/usr\/bin\/policyd-spf<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<\/li>\n<li>Open <code>\/etc\/postfix\/main.cf<\/code> and add this entry to increase the Postfix policy agent timeout, which will prevent Postfix from aborting the agent if transactions run a bit slowly:\n<dl class=\"file-excerpt\">\n<dt>\/etc\/postfix\/main.cf<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-aconf\" data-lang=\"aconf\"><span class=\"err\">policyd-<\/span><span class=\"nb\">spf_time_limit<\/span> = <span class=\"m\">3600<\/span><\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<\/li>\n<li>Edit the <code>smtpd_recipient_restrictions<\/code> entry to add a <code>check_policy_service<\/code> entry:\n<dl class=\"file-excerpt\">\n<dt>\/etc\/postfix\/main.cf<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-aconf\" data-lang=\"aconf\"><span class=\"nb\">smtpd_recipient_restrictions<\/span> =\r\n    ...\r\n    <span class=\"err\">reject_unauth_destination,<\/span>\r\n    <span class=\"nb\">check_policy_service<\/span> unix:private\/policyd-spf,\r\n    ...<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>Make sure to add the <code>check_policy_service<\/code> entry <strong>after<\/strong> the <code>reject_unauth_destination<\/code> entry to avoid having your system become an open relay. If <code>reject_unauth_destination<\/code> is the last item in your restrictions list, add the comma after it and omit the comma at the end of the<code>check_policy_service<\/code> item above.<\/li>\n<li>Restart Postfix:\n<div class=\"btn-copy\"><\/div>\n<pre><code>systemctl restart postfix\r\n<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>You can check the operation of the policy agent by looking at raw headers on incoming email messages for the SPF results header. The header the policy agent adds to messages should look something like this:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>Received-SPF: Pass (sender SPF authorized) identity=mailfrom; client-ip=127.0.0.1; helo=mail.example.com; envelope-from=text@example.com; receiver=tknarr@silverglass.org\r\n<\/code><\/pre>\n<p>This header indicates a successful check against the SPF policy of the sending domain. If you changed the policy agent settings in Step 1 to not reject mail that fails the SPF check, you may see Fail results in this header. You won\u2019t see this header on outgoing or local mail.<\/p>\n<p>The SPF policy agent also logs to <code>\/var\/log\/mail.log<\/code>. In the <code>mail.log<\/code> file you\u2019ll see messages like this from the policy agent:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>Jan  7 06:24:44 arachnae policyd-spf[21065]: None; identity=helo; client-ip=127.0.0.1; helo=mail.example.com; envelope-from=test@example.com; receiver=tknarr@silverglass.org\r\nJan  7 06:24:44 arachnae policyd-spf[21065]: Pass; identity=mailfrom; client-ip=127.0.0.1; helo=mail.example.com; envelope-from=test@example.com; receiver=tknarr@silverglass.org\r\n<\/code><\/pre>\n<p>The first message is a check of the HELO command, in this case indicating that there wasn\u2019t any SPF information matching the HELO (which is perfectly OK). The second message is the check against the envelope From address, and indicates the address passed the check and is coming from one of the outgoing mail servers the sender\u2019s domain has said should be sending mail for that domain. There may be other statuses in the first field after the colon indicating failure, temporary or permanent errors and so on.<\/p>\n<h2 id=\"set-up-dkim\">Set up DKIM<\/h2>\n<p>DKIM involves setting up the OpenDKIM package, hooking it into Postfix, and adding DNS records.<\/p>\n<h3 id=\"configure-opendkim\">Configure OpenDKIM<\/h3>\n<ol>\n<li>The main OpenDKIM configuration file <code>\/etc\/opendkim.conf<\/code> needs to look like this:\n<dl class=\"file\">\n<dt>\/etc\/opendkim.conf<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><code class=\"language-aconf\" data-lang=\"aconf\"> <\/code><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-aconf\" data-lang=\"aconf\"><span class=\"c\"># This is a basic configuration that can easily be adapted to suit a standard<\/span>\r\n<span class=\"c\"># installation. For more advanced options, see opendkim.conf(5) and\/or<\/span>\r\n<span class=\"c\"># \/usr\/share\/doc\/opendkim\/examples\/opendkim.conf.sample.<\/span>\r\n\r\n<span class=\"c\"># Log to syslog<\/span>\r\n<span class=\"nb\">Syslog<\/span>          yes\r\n<span class=\"c\"># Required to use local socket with MTAs that access the socket as a non-<\/span>\r\n<span class=\"c\"># privileged user (e.g. Postfix)<\/span>\r\n<span class=\"nb\">UMask<\/span>           <span class=\"m\">002<\/span>\r\n<span class=\"c\"># OpenDKIM user<\/span>\r\n<span class=\"c\"># Remember to add user postfix to group opendkim<\/span>\r\n<span class=\"nb\">UserID<\/span>          opendkim\r\n\r\n<span class=\"c\"># Map domains in From addresses to keys used to sign messages<\/span>\r\n<span class=\"nb\">KeyTable<\/span>        <span class=\"sx\">\/etc\/opendkim\/key.table<\/span>\r\n<span class=\"nb\">SigningTable<\/span>        refile:\/etc\/opendkim\/signing.table\r\n\r\n<span class=\"c\"># Hosts to ignore when verifying signatures<\/span>\r\n<span class=\"nb\">ExternalIgnoreList<\/span>  <span class=\"sx\">\/etc\/opendkim\/trusted.hosts<\/span>\r\n<span class=\"nb\">InternalHosts<\/span>       <span class=\"sx\">\/etc\/opendkim\/trusted.hosts<\/span>\r\n\r\n<span class=\"c\"># Commonly-used options; the commented-out versions show the defaults.<\/span>\r\n<span class=\"nb\">Canonicalization<\/span>    relaxed\/simple\r\n<span class=\"nb\">Mode<\/span>            sv\r\n<span class=\"nb\">SubDomains<\/span>      no\r\n<span class=\"c\">#ADSPAction     continue<\/span>\r\n<span class=\"nb\">AutoRestart<\/span>     yes\r\n<span class=\"nb\">AutoRestartRate<\/span>     <span class=\"m\">10<\/span><span class=\"sx\">\/1M<\/span>\r\n<span class=\"nb\">Background<\/span>      yes\r\n<span class=\"nb\">DNSTimeout<\/span>      <span class=\"m\">5<\/span>\r\n<span class=\"nb\">SignatureAlgorithm<\/span>  rsa-sha256\r\n\r\n<span class=\"c\"># Always oversign From (sign using actual From and a null From to prevent<\/span>\r\n<span class=\"c\"># malicious signatures header fields (From and\/or others) between the signer<\/span>\r\n<span class=\"c\"># and the verifier.  From is oversigned by default in the Debian package<\/span>\r\n<span class=\"c\"># because it is often the identity key used by reputation systems and thus<\/span>\r\n<span class=\"c\"># somewhat security sensitive.<\/span>\r\n<span class=\"nb\">OversignHeaders<\/span>     From<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>Edit <code>\/etc\/opendkim.conf<\/code> and replace it\u2019s contents with the above, or download <a href=\"https:\/\/www.linode.com\/docs\/assets\/postfix-opendkim.conf.txt\">a copy of opendkim.conf<\/a>, upload it to your server and copy it over <code>\/etc\/opendkim.conf<\/code>.<\/li>\n<li>Ensure that file permissions are set correctly:\n<div class=\"btn-copy\"><\/div>\n<pre><code>chmod u=rw,go=r \/etc\/opendkim.conf\r\n<\/code><\/pre>\n<\/li>\n<li>Create the directories to hold OpenDKIM\u2019s data files, assign ownership to the <code>opendkim<\/code> user, and restrict the file permissions:\n<div class=\"btn-copy\"><\/div>\n<pre><code>mkdir \/etc\/opendkim\r\nmkdir \/etc\/opendkim\/keys\r\nchown -R opendkim:opendkim \/etc\/opendkim\r\nchmod go-rw \/etc\/opendkim\/keys\r\n<\/code><\/pre>\n<\/li>\n<li>Create the signing table <code>\/etc\/opendkim\/signing.table<\/code>. It needs to have one line per domain that you handle email for. Each line should look like this:\n<dl class=\"file-excerpt\">\n<dt>\/etc\/opendkim\/signing.table<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\">*@example.com   example<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>Replace <code>example.com<\/code> with your domain and <code>example<\/code> with a short name for the domain. The first field is a pattern that matches e-mail addresses. The second field is a name for the key table entry that should be used to sign mail from that address. For simplicity\u2019s sake, we\u2019re going to set up one key for all addresses in a domain.<\/li>\n<li>Create the key table <code>\/etc\/opendkim\/key.table<\/code>. It needs to have one line per short domain name in the signing table. Each line should look like this:\n<dl class=\"file-excerpt\">\n<dt>\/etc\/opendkim\/key.table<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\"><\/code><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\">example     example.com:YYYYMM:\/etc\/opendkim\/keys\/example.private<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>Replace <code>example<\/code> with the <code>example<\/code> value you used for the domain in the signing table (make sure to catch the second occurrence at the end, where it\u2019s followed by <code>.private<\/code>). Replace<code>example.com<\/code> with your domain name and replace the <code>YYYYMM<\/code> with the current 4-digit year and 2-digit month (this is referred to as the selector). The first field connects the signing and key tables.<\/p>\n<p>The second field is broken down into 3 sections separated by colons.<\/p>\n<ul>\n<li>The first section is the domain name for which the key is used.<\/li>\n<li>The second section is a selector used when looking up key records in DNS.<\/li>\n<li>The third section names the file containing the signing key for the domain.<\/li>\n<\/ul>\n<blockquote class=\"note\"><p><strong class=\"callout-title\">Note<\/strong><\/p>\n<div>The flow for DKIM lookup starts with the sender\u2019s address. The signing table is scanned until an entry whose pattern (first item) matches the address is found. Then, the second item\u2019s value is used to locate the entry in the key table whose key information will be used. For incoming mail the domain and selector are then used to find the public key TXT record in DNS and that public key is used to validate the signature. For outgoing mail the private key is read from the named file and used to generate the signature on the message.<\/div>\n<\/blockquote>\n<\/li>\n<li>Create the trusted hosts file <code>\/etc\/opendkim\/trusted.hosts<\/code>. Its contents need to be:\n<dl class=\"file\">\n<dt>\/etc\/opendkim\/trusted.hosts<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\">127.0.0.1\r\n::1\r\nlocalhost\r\nmyhostname\r\nmyhostname.example.com\r\nexample.com<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>When creating the file, change <code>myhostname<\/code> to the name of your server and replace <code>example.com<\/code>with your own domain name. We\u2019re identifying the hosts that users will be submitting mail through and should have outgoing mail signed, which for basic configurations will be your own mail server.<\/li>\n<li>Make sure the ownership and permissions on <code>\/etc\/opendkim<\/code> and it\u2019s contents are correct (<code>opendkim<\/code> should own everything, the <code>keys<\/code> directory should only be accessible by the owner) by running the following commands:\n<div class=\"btn-copy\"><\/div>\n<pre><code>chown -R opendkim:opendkim \/etc\/opendkim\r\nchmod -R go-rwx \/etc\/opendkim\/keys\r\n<\/code><\/pre>\n<\/li>\n<li>Generate keys for each domain:\n<div class=\"btn-copy\"><\/div>\n<pre><code>opendkim-genkey -b 2048 -h rsa-sha256 -r -s YYYYMM -d example.com -v\r\n<\/code><\/pre>\n<p>Replace <code>YYYYMM<\/code> with the current year and month as in the key table. This will give you two files,<code>YYYYMM.private<\/code> containing the key and <code>YYYYMM.txt<\/code> containing the TXT record you\u2019ll need to set up DNS. Rename the files so they have names matching the third section of the second field of the key table for the domain:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>mv YYYYMM.private example.private\r\nmv YYYYMM.txt example.txt\r\n<\/code><\/pre>\n<p>Repeat the commands in this step for every entry in the key table. The <code>-b 2048<\/code> indicates the number of bits in the RSA key pair used for signing and verification. 1024 bits is the minimum, but with modern hardware 2048 bits is safer. (It\u2019s possible 4096 bits will be required at some point.)<\/li>\n<li>Make sure the ownership, permissions and contents on <code>\/etc\/opendkim<\/code> are correct by running the following commands:\n<div class=\"btn-copy\"><\/div>\n<pre><code>cd \/etc\r\nchown -R opendkim:opendkim \/etc\/opendkim\r\nchmod -R go-rw \/etc\/opendkim\/keys\r\n<\/code><\/pre>\n<\/li>\n<li>Check that OpenDKIM starts correctly:\n<div class=\"btn-copy\"><\/div>\n<pre><code>systemctl restart opendkim\r\n<\/code><\/pre>\n<p>You should not get error messages, but if you do, use:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>systemctl status -l opendkim\r\n<\/code><\/pre>\n<p>to get the status and untruncated error messages.<\/li>\n<\/ol>\n<h3 id=\"set-up-dns\">Set up DNS<\/h3>\n<p>As with SPF, DKIM uses TXT records to hold information about the signing key for each domain. Using YYYYMM as above, you need to make a TXT record for the host <code>YYYYMM._domainkey<\/code> for each domain you handle mail for. Its value can be found in the <code>example.txt<\/code> file for the domain. Those files look like this:<\/p>\n<dl class=\"file\">\n<dt>example.txt<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\">201510._domainkey  IN  TXT ( \"**v=DKIM1; h=rsa-sha256; k=rsa; s=email; \"\r\n    \"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA......\"\r\n    \"ZksfuXh7m30......\" )  ; ----- DKIM key 201510 for example.com<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>The value inside the parentheses is what you want. Select and copy the entire region from (but not including) the double-quote before <code>v=DKIM1<\/code> on up to (but not including) the final double-quote before the closing parentheses. Then edit out the double-quotes within the copied text and the whitespace between them. Also change <code>h=rsa-sha256<\/code> to <code>h=sha256<\/code>. From the above file the result would be:<\/p>\n<dl class=\"file-excerpt\">\n<dt>example-copied.txt<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\"><\/code><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-resource\" data-lang=\"resource\">v=DKIM1; h=sha256; k=rsa; s=email; p=MIIBIjANBgkqhkiG9w0BAQEFAA......<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>Paste that into the value for the TXT record.<\/p>\n<p>If you\u2019re using Linode\u2019s DNS manager, this is what the add TXT record screen will look like when you have it filled out:<\/p>\n<p>Repeat this for every domain you handle mail for, using the <code>.txt<\/code> file for that domain.<\/p>\n<h3 id=\"test-your-configuration\">Test your configuration<\/h3>\n<p>Test the keys for correct signing and verification using the <code>opendkim-testkey<\/code> command:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>opendkim-testkey -d example.com -s YYYYMM\r\n<\/code><\/pre>\n<p>If everything is OK you shouldn\u2019t get any output. If you want to see more information, add <code>-vvv<\/code> to the end of the command. That produces verbose debugging output. The last message should be \u201ckey OK\u201d. Just before that you may see a \u201ckey not secure\u201d message. That\u2019s normal and doesn\u2019t signal an error, it just means your domain isn\u2019t set up for DNSSEC yet.<\/p>\n<h3 id=\"hook-opendkim-into-postfix\">Hook OpenDKIM into Postfix<\/h3>\n<ol>\n<li>Create the OpenDKIM socket directory in Postfix\u2019s work area and make sure it has the correct ownership:\n<div class=\"btn-copy\"><\/div>\n<pre><code>mkdir \/var\/spool\/postfix\/opendkim\r\nchown opendkim:postfix \/var\/spool\/postfix\/opendkim\r\n<\/code><\/pre>\n<\/li>\n<li>Set the correct socket for Postfix in the OpenDKIM defaults file <code>\/etc\/default\/opendkim<\/code>:\n<dl class=\"file\">\n<dt>\/etc\/default\/opendkim<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><\/code><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"c1\"># Command-line options specified here will override the contents of\r\n<\/span><span class=\"c1\"># \/etc\/opendkim.conf. See opendkim(8) for a complete list of options.\r\n<\/span><span class=\"c1\">#DAEMON_OPTS=\"\"\r\n<\/span><span class=\"c1\">#\r\n<\/span><span class=\"c1\"># Uncomment to specify an alternate socket\r\n<\/span><span class=\"c1\"># Note that setting this will override any Socket value in opendkim.conf\r\n<\/span><span class=\"nv\">SOCKET<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"local:\/var\/spool\/postfix\/opendkim\/opendkim.sock\"<\/span>\r\n<span class=\"c1\">#SOCKET=\"inet:54321\" # listen on all interfaces on port 54321\r\n<\/span><span class=\"c1\">#SOCKET=\"inet:12345@localhost\" # listen on loopback on port 12345\r\n<\/span>#SOCKET<span class=\"o\">=<\/span><span class=\"s2\">\"inet:12345@192.0.2.1\"<\/span> # listen on <span class=\"m\">192<\/span>.0.2.1 on port <span class=\"m\">12345<\/span><\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>Uncomment the first SOCKET line and edit it so it matches the uncommented line in the above file. The path to the socket is different from the default because on Debian 8 the Postfix process that handles mail runs in a chroot jail and can\u2019t access the normal location.<\/li>\n<li>Edit <code>\/etc\/postfix\/main.cf<\/code> and add a section to activate processing of e-mail through the OpenDKIM daemon:\n<dl class=\"file-excerpt\">\n<dt>\/etc\/postfix\/main.cf<\/dt>\n<dd>\n<div class=\"highlight\">\n<div class=\"chroma\">\n<table class=\"lntable\">\n<tbody>\n<tr>\n<td class=\"lntd\">\n<pre class=\"chroma\"><code class=\"language-aconf\" data-lang=\"aconf\"><\/code><\/pre>\n<\/td>\n<td class=\"lntd\">\n<div class=\"btn-copy\"><\/div>\n<pre class=\"chroma\"><code class=\"language-aconf\" data-lang=\"aconf\"><span class=\"c\"># Milter configuration<\/span>\r\n<span class=\"c\"># OpenDKIM<\/span>\r\n<span class=\"nb\">milter_default_action<\/span> = accept\r\n<span class=\"c\"># Postfix \u2265 2.6 milter_protocol = 6, Postfix \u2264 2.5 milter_protocol = 2<\/span>\r\n<span class=\"nb\">milter_protocol<\/span> = <span class=\"m\">6<\/span>\r\n<span class=\"nb\">smtpd_milters<\/span> = local:\/opendkim\/opendkim.sock\r\n<span class=\"nb\">non_smtpd_milters<\/span> = local:\/opendkim\/opendkim.sock<\/code><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/dd>\n<\/dl>\n<p>You can put this anywhere in the file. The usual practice is to put it after the<code>smtpd_recipient_restrictions<\/code> entry. You\u2019ll notice the path to the socket isn\u2019t the same here as it was in the <code>\/etc\/defaults\/opendkim<\/code> file. That\u2019s because of Postfix\u2019s chroot jail, the path here is the path within that restricted view of the filesystem instead of within the actual filesystem.<\/li>\n<li>Restart the OpenDKIM daemon so it sets up the correct socket for Postfix:\n<div class=\"btn-copy\"><\/div>\n<pre><code>systemctl restart opendkim\r\n<\/code><\/pre>\n<\/li>\n<li>Restart Postfix so it starts using OpenDKIM when processing mail:\n<div class=\"btn-copy\"><\/div>\n<pre><code>systemctl restart postfix\r\n<\/code><\/pre>\n<\/li>\n<\/ol>\n<h3 id=\"verify-that-everything-s-fully-operational\">Verify that everything\u2019s fully operational<\/h3>\n<p>The easiest way to verify that everything\u2019s working is to send a test e-mail to <code>check-auth@verifier.port25.com<\/code> using an email client configured to submit mail to the submission port on your mail server. It will analyze your message and mail you a report indicating whether your email was signed correctly or not. It also reports on a number of other things such as SPF configuration and SpamAssassin flagging of your domain. If there\u2019s a problem, it\u2019ll report what the problem was.<\/p>\n<h2 id=\"optional-set-up-author-domain-signing-practices-adsp\"><strong>Optional:<\/strong> Set up Author Domain Signing Practices (ADSP)<\/h2>\n<p>As a final item, you can add an ADSP policy to your domain saying that all emails from your domain should be DKIM-signed. As usual, it\u2019s done with a TXT record for host <code>_adsp._domainkey<\/code> in your domain with a value of <code>dkim=all<\/code>. If you\u2019re using Linode\u2019s DNS Manager, the screen for the new text record will look like this:<\/p>\n<p>You don\u2019t need to set this up, but doing so makes it harder for anyone to forge email from your domains because recipient mail servers will see the lack of a DKIM signature and reject the message.<\/p>\n<h2 id=\"optional-set-up-domain-message-authentication-reporting-conformance-dmarc\"><strong>Optional:<\/strong> Set up Domain Message Authentication, Reporting &amp; Conformance (DMARC)<\/h2>\n<p>The DMARC DNS record can be added to advise mail servers what you think they should do with emails claiming to be from your domain that fail validation with SPF and\/or DKIM. DMARC also allows you to request reports about mail that fails to pass one or more validation check. DMARC should only be set up if you have SPF and DKIM set up and operating successfully. If you add the DMARC DNS record without having both SPF and DKIM in place, messages from your domain will fail validation which may cause them to be discarded or relegated to a spam folder.<\/p>\n<p>The DMARC record is a TXT record for host <code>_dmarc<\/code> in your domain containing the following recommended values:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>v=DMARC1;p=quarantine;sp=quarantine;adkim=r;aspf=r\r\n<\/code><\/pre>\n<p>This requests mail servers to quarantine (do not discard, but separate from regular messages) any email that fails either SPF or DKIM checks. No reporting is requested. Very few mail servers implement the software to generate reports on failed messages, so it is often unnecessary to request them. If you do wish to request reports, the value would be similar to this example, added as a single string:<\/p>\n<div class=\"btn-copy\"><\/div>\n<pre><code>v=DMARC1;p=quarantine;sp=quarantine;adkim=r;aspf=r;fo=1;rf=afrf;rua=mailto:user@example.com\r\n<\/code><\/pre>\n<p>Replace <code>user@example.com<\/code> in the <code>mailto:<\/code> URL with your own email or an email address you own dedicated to receiving reports (an address such as <code>dmarc@example.com<\/code>). This requests aggregated reports in XML showing how many messages fell into each combination of pass and fail results and the mail server addresses sending them. If you\u2019re using Linode\u2019s DNS Manager, the screen for the new text record will look like this:<\/p>\n<p>DMARC records have a number of available tags and options. These tags are used to control your authentication settings:<\/p>\n<ul>\n<li><code>v<\/code> specifies the protocol version, in this case <code>DMARC1<\/code>.<\/li>\n<li><code>p<\/code> determines the policy for the root domain, such as \u201cexample.com.\u201d The available options:\n<ul>\n<li><code>quarantine<\/code> instructs that if an email fails validation, the recipient should set it aside for processing.<\/li>\n<li><code>reject<\/code> requests that the receiving mail server reject the emails that fail validation.<\/li>\n<li><code>none<\/code> requests that the receiver take no action if an email does not pass validation.<\/li>\n<\/ul>\n<\/li>\n<li><code>sp<\/code> determines the policy for subdomains, such as \u201csubdomain.example.com.\u201d It takes the same arguments as the <code>p<\/code> tag.<\/li>\n<li><code>adkim<\/code> specifies the alignment mode for DKIM, which determines how strictly DKIM records are validated. The available options are:\n<ul>\n<li><code>r<\/code> relaxed alignment mode, DKIM authentication is less strictly enforced.<\/li>\n<li><code>s<\/code> strict alignment mode. Only an exact match with the DKIM entry for the root domain will be seen as validated.<\/li>\n<\/ul>\n<\/li>\n<li><code>aspf<\/code> determines the alignment mode for SPF verification. It takes the same arguments as<code>adkim<\/code>.<\/li>\n<\/ul>\n<p>If you wish to receive authentication failure reports, DMARC provides a number of configuration options. You can use the following tags to customize the formatting of your reports, as well as the criteria for report creation.<\/p>\n<ul>\n<li><code>rua<\/code> specifies the email address that will receive aggregate reports. This uses the<code>mailto:user@example.com<\/code> syntax, and accepts multiple addresses separated by commas. Aggregate reports are usually generated once per day.<\/li>\n<li><code>ruf<\/code> specifies the email address that will receive detailed authentication failure reports. This takes the same arguments as <code>rua<\/code>. With this option, each authentication failure would result in a separate report.<\/li>\n<li><code>fo<\/code> allows you to specify which failed authentication methods will be reported. One or more of the following options can be used:\n<ul>\n<li><code>0<\/code> will request a report if <em>all<\/em> authentication methods fail. For example, if an SPF check were to fail but DKIM authentication was successful, a report would not be sent.<\/li>\n<li><code>1<\/code> requests a report if <em>any<\/em> authentication check fails.<\/li>\n<li><code>d<\/code> requests a report if a DKIM check fails.<\/li>\n<li><code>s<\/code> requests a report if an SPF check fails.<\/li>\n<\/ul>\n<\/li>\n<li><code>rf<\/code> determines the format used for authentication failure reports. Available options:\n<ul>\n<li><code>afrf<\/code> uses the Abuse Report format as defined by <a href=\"https:\/\/www.ietf.org\/rfc\/rfc5965.txt\">RFC 5965<\/a>.<\/li>\n<li><code>iodef<\/code> uses the Incident Object Description Exchange format as defined by <a href=\"https:\/\/www.ietf.org\/rfc\/rfc5070.txt\">RFC 5070<\/a>.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2 id=\"key-rotation\">Key rotation<\/h2>\n<p>The reason the YYYYMM format is used for the selector is that best practice calls for changing the DKIM signing keys every so often (monthly is recommended, and no longer than every 6 months). To do that without disrupting messages in transit, you generate the new keys using a new selector. The process is:<\/p>\n<ol>\n<li>Generate new keys as in step 8 of <a href=\"https:\/\/www.linode.com\/docs\/email\/postfix\/configure-spf-and-dkim-in-postfix-on-debian-8\/#configure-opendkim\">Configure OpenDKIM<\/a>. Do this in a scratch directory, not directly in <code>\/etc\/opendkim\/keys<\/code>. Use the current year and month for the YYYYMM selector value, so it\u2019s different from the selector currently in use.<\/li>\n<li>Use the newly-generated <code>.txt<\/code> files to add the new keys to DNS as in the DKIM <a href=\"https:\/\/www.linode.com\/docs\/email\/postfix\/configure-spf-and-dkim-in-postfix-on-debian-8\/#set-up-dns\">Set Up DNS<\/a>section, using the new YYYYMM selector in the host names. Don\u2019t remove or alter the existing DKIM TXT records. Once this is done, verify the new key data using the following command (replacing example.com, example and YYYYMM with the appropriate values):\n<div class=\"btn-copy\"><\/div>\n<pre><code>opendkim-testkey -d example.com -s YYYYMM -k example.private\r\n<\/code><\/pre>\n<p>Add the <code>-vvv<\/code> switch to get debugging output if you need it to diagnose any problems. Correct any problems before proceeding, beginning to use the new private key file and selector when<code>opendkim-testkey<\/code> doesn\u2019t indicate a successful verification will cause problems with your email including non-receipt of messages.<\/li>\n<li>Stop Postfix and OpenDKIM with <code>systemctl stop postfix opendkim<\/code> so that they won\u2019t be processing mail while you\u2019re changing out keys.<\/li>\n<li>Copy the newly-generated <code>.private<\/code> files into place and make sure their ownership and permissions are correct by running these commands from the directory in which you generated the key files:\n<div class=\"btn-copy\"><\/div>\n<pre><code>cp *.private \/etc\/opendkim\/keys\/\r\nchown opendkim:opendkim \/etc\/opendkim\/keys\/*\r\nchmod go-rw \/etc\/opendkim\/keys\/*\r\n<\/code><\/pre>\n<p>Use the <code>opendkim-testkey<\/code> command as described above to ensure that your new record is propagated before you continue.<\/li>\n<li>Edit <code>\/etc\/opendkim\/key.table<\/code> and change the old YYYYMM values to the new selector, reflecting the current year and month. Save the file.<\/li>\n<li>Restart OpenDKIM and Postfix by:\n<div class=\"btn-copy\"><\/div>\n<pre><code>systemctl start opendkim\r\nsystemctl start postfix\r\n<\/code><\/pre>\n<p>Make sure they both start without any errors.<\/li>\n<li>After a couple of weeks, all email in transit should either have been delivered or bounced and the old DKIM key information in DNS won\u2019t be needed anymore. Delete the old <code>YYYYMM._domainkey<\/code>TXT records in each of your domains, leaving just the newest ones (most recent year and month). Don\u2019t worry if you forget and leave the old keys around longer than planned. There\u2019s no security issue. Removing the obsolete records is more a matter of keeping things neat and tidy than anything else.<\/li>\n<\/ol>\n<h2>More Information<\/h2>\n<p>You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.<\/p>\n<ul>\n<li><a href=\"http:\/\/www.openspf.org\/\">Sender Policy Framework<\/a><\/li>\n<li><a href=\"http:\/\/www.dkim.org\/\">DomainKeys Identified Mail<\/a><\/li>\n<li><a href=\"http:\/\/dmarc.org\/\">DMARC<\/a><\/li>\n<li><a href=\"http:\/\/www.opendkim.org\/\">OpenDKIM<\/a><\/li>\n<li>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Sender_Policy_Framework\">Sender Policy Framework<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/DomainKeys_Identified_Mail\">DomainKeys Identified Mail<\/a> Wikipedia pages should not be considered authoritative but do provide helpful discussion and additional references.<\/li>\n<li><a href=\"http:\/\/kitterman.com\/dmarc\/assistant.html\">DMARC Record Assistant<\/a> provides a web form to generate a DMARC record for you based on your selections.<\/li>\n<\/ul>\n<p>\u53c2\u8003\u8d44\u6599<\/p>\n<p>1\u3001<a href=\"https:\/\/www.linode.com\/docs\/email\/postfix\/configure-spf-and-dkim-in-postfix-on-debian-8\" target=\"_blank\" rel=\"noopener noreferrer\">Configure SPF and DKIM in Postfix on Debian 8<\/a><\/p>\n<p>2\u3001<a href=\"http:\/\/www.postfix.org\/STANDARD_CONFIGURATION_README.html#null_client\" target=\"_blank\" rel=\"noopener noreferrer\">Postfix on a null client<\/a><\/p>\n<pre>1 \/etc\/postfix\/<a href=\"http:\/\/www.postfix.org\/postconf.5.html\">main.cf<\/a>:\r\n2     <a href=\"http:\/\/www.postfix.org\/postconf.5.html#myhostname\">myhostname<\/a> = hostname.example.com\r\n3     <a href=\"http:\/\/www.postfix.org\/postconf.5.html#myorigin\">myorigin<\/a> = $<a href=\"http:\/\/www.postfix.org\/postconf.5.html#mydomain\">mydomain<\/a>\r\n4     #<a href=\"http:\/\/www.postfix.org\/postconf.5.html#relayhost\">relayhost<\/a> = $<a href=\"http:\/\/www.postfix.org\/postconf.5.html#mydomain\">mydomain<\/a>\r\n5     <a href=\"http:\/\/www.postfix.org\/postconf.5.html#inet_interfaces\">inet_interfaces<\/a> = loopback-only\r\n6     <a href=\"http:\/\/www.postfix.org\/postconf.5.html#mydestination\">mydestination<\/a> =<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp; SPF (Sender Policy Framework) is a system that i [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[74,41,54,73],"class_list":["post-319","post","type-post","status-publish","format-standard","hentry","category-blog","tag-dkim","tag-email","tag-ip","tag-spf"],"_links":{"self":[{"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/posts\/319","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/comments?post=319"}],"version-history":[{"count":16,"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/posts\/319\/revisions"}],"predecessor-version":[{"id":1043,"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/posts\/319\/revisions\/1043"}],"wp:attachment":[{"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/media?parent=319"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/categories?post=319"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.behindgfw.com\/archives\/wp-json\/wp\/v2\/tags?post=319"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}