OpenDKIM で迷惑メール判定を回避する

CentOS 7 で [OpenDKIM](http://opendkim.org/) を利用して DKIM に対応する手順をまとめた。

## DKIM
DKIM (Domainkeys Identified Mail) は送信者のなりすましやメールの改ざんを検知できるようにする電子署名方式の送信ドメイン認証。
公開鍵を送信ドメインの DNS レコードで公開することで、メールが本当にそのドメインからのものであること、途中で変更されていないことを確認できる。

### 参考情報
* [インターネット用語1分解説～DKIMとは～ - JPNIC](https://www.nic.ad.jp/ja/basics/terms/dkim.html)
* [DKIM を設定してメールのなりすましを防ぐ - G Suite 管理者 ヘルプ](https://support.google.com/a/answer/174124)
* [DKIM (Domainkeys Identified Mail) : 迷惑メール対策委員会](http://salt.iajapan.org/wpmu/anti_spam/admin/tech/explanation/dkim/)
* [総務省｜JPドメイン名における送信ドメイン認証技術の設定状況の調査](http://www.soumu.go.jp/menu_news/s-news/01kiban18_01000035.html)

## OpenDKIM

### インストール
```
yum install epel-release
yum install opendkim
```

[EPEL](https://fedoraproject.org/wiki/EPEL/ja) リポジトリに opendkim パッケージが存在する。

### opendkim.conf ファイル
インストールした状態では機能しないので設定を変更する必要がある。

```bash
sed -i /etc/opendkim.conf \
    -e '/^# Domain/ s/^# //' \
    -e '/^Domain/ s/example.com/example.jp/' \
    -e '/^Mode/ s/v/sv/'
```

#### `Domain` ディレクティブ
このフィルターによってメールが署名されるべきドメイン。 他のドメインからのメールは署名されずに検証される。 [^Domain]
SigningTable が使用されている場合、このパラメータは必要ない。 [^Domain2]

[^Domain]: "Domain(s) whose mail should be signed by this filter. Mail from other domains will  be verified rather than being signed. Uncomment and use your domain name."
[^Domain2]: "This parameter is not required if a SigningTable is in use."

#### `Mode` ディレクティブ
`Mode` は `s` (sign, 署名) と `v` (verify, 検証) があり、デフォルトは `v` になっている。送信メッセージに署名するには `s` または `sv` に変更する必要がある。 [^Mode]

[^Mode]: "Selects operating modes. Valid modes are s (sign) and v (verify). Default is v. Must be changed to s (sign only) or sv (sign and verify) in order to sign outgoing messages."

#### `KeyFIle` ディレクティブ

**すべての**メッセージの署名に使用する秘密鍵の場所を指定する。 `KeyTable` が有効な場合はこのディレクティブは無視される。 [^KeyFIle]
デフォルト値は `/etc/opendkim/keys/default.private` になっているので、 `--selector` オプションでセレクター名を `default` から変更した場合は当該ディレクティブの値を変更する必要がある。

[^KeyFIle]: "Gives the location of a private key to be used for signing ALL messages. This directive is ignored if KeyTable is enabled."

#### `KeyTable` ディレクティブ

キー名を署名鍵にマッピングするファイルの場所を指定する。つまり OpenDKIM に鍵の場所を知らせる。存在する場合、設定ファイル内のすべての `KeyFile` ディレクティブを上書きする。 `SigningTable` を有効にする必要がある。 [^KeyTable]

このディレクティブを利用する記事が散見されるが、複数ドメインのメール送信サーバーを同一サーバー内で管理したり、複数の鍵を使い分ける運用でない限り不要。

[^KeyTable]: "Gives the location of a file mapping key names to signing keys. In simple terms, this tells OpenDKIM where to find your keys. If present, overrides any KeyFile directive in the configuration file. Requires SigningTable be enabled."

#### `SigningTable` ディレクティブ

`From：` ヘッダーフィールドにあるアドレスに基づいて、メッセージに適用する1つまたは複数の署名を選択するために使用されるテーブルを定義する。つまり OpenDKIM に鍵の使い方を知らせる。 `KeyTable` を有効にする必要がある。 [^SigningTable]

`KeyTable` 同様、複数ドメインや複数の鍵を使い分ける運用でない限り不要。

[^SigningTable]: "Defines a table used to select one or more signatures to apply to a message based on the address found in the From: header field. In simple terms, this tells  OpenDKIM how to use your keys. Requires KeyTable be enabled."

#### `ExternalIgnoreList` ディレクティブ

証明書なしで署名ドメインの1つとしてサーバーを介してメールを送信する「外部」ホストを特定する。 [^ExternalIgnoreList]

[^ExternalIgnoreList]: "Identifies a set of "external" hosts that may send mail through the server as one of the signing domains without credentials as such."

#### `InternalHosts` ディレクティブ

メールが署名されなければならない「内部」ホストを特定する。 [^InternalHosts]

Docker からメールを送信する場合などは、このディレクティブを有効にして、参照先の `TrustedHosts` ファイルで Docker のネットワークの IP アドレスを指定する必要がある。

[^InternalHosts]: "Identifies a set "internal" hosts whose mail should be signed rather than verified."


### SigningTable ファイル

```bash
sed -i /etc/opendkim.conf -e '/^# SigningTable/ s/^# //'
sed -i /etc/opendkim/SigningTable -e '/*@/a *@example.jp default._domainkey.example.jp'
```
OpenDKIM 署名テーブルは `From：` ヘッダーフィールドにあるアドレスに基づいて発信メッセージに1つ以上の署名を適用する方法を制御する。OpenDKIM に鍵の適用方法を伝える。 [^SiginingTable2]

このファイルを使用するには `/etc/opendkim.conf` の `SigningTable` オプションのコメントを外し OpenDKIM を再起動する。 [^SiginingTable3]

複数ドメインや複数の鍵を使い分ける運用でない限り設定不要。

[^SiginingTable2]: "This table controls how to apply one or more signatures to outgoing messages based on the address found in the From: header field. In simple terms, this tells OpenDKIM "how" to apply your keys."

[^SiginingTable3]: "To use this file, uncomment the SigningTable option in /etc/opendkim.conf, then uncomment one of the usage examples below and replace example.com with your domain name, then restart OpenDKIM."


### KeyTable ファイル

```bash
sed -i /etc/opendkim.conf -e '/^# KeyTable/ s/^# //'
echo 'default._domainkey.example.jp example.jp:default:/etc/opendkim/keys/default.private' >> /etc/opendkim/KeyTable
```
このファイルを使用するには `KeyTable` ディレクティブを有効にして OpenDKIM を再起動する必要がある。 [^KeyTable2]

複数ドメインや複数の鍵を使い分ける運用でない限り設定不要。

[^KeyTable2]: "To use this file, uncomment the #KeyTable option in /etc/opendkim.conf, then uncomment the following line and replace example.com with your domain name, then restart OpenDKIM. Additional keys may be added on separate lines." - /etc/opendkim/KeyTable

### TrustedHosts ファイル

```bash
docker ps -q | xargs docker inspect -f "{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}"
echo '172.18.0.0/24' >> /etc/opendkim/TrustedHosts
sed -i /etc/opendkim.conf -e '/^# InternalHosts/ s/^# //'
```
このファイルを使用するには `ExternalIgnoreList` ディレクティブや `InternalHosts` ディレクティブを有効にして OpenDKIM を再起動する必要がある。 [^TrustedHosts]

このファイルの最初のエントリーは常にローカルホストIP (`127.0.0.1`) である必要がある。 [^TrustedHosts2]

Docker などからメールを送信する場合はこのファイルで Docker のネットワークの IP アドレスを指定して `InternalHosts` を有効にする必要がある。

[^TrustedHosts]: "To use this file, uncomment the #ExternalIgnoreList and/or the #InternalHosts option in /etc/opendkim.conf then restart OpenDKIM. Additional hosts may be added on separate lines (IP addresses, hostnames, or CIDR ranges)." - /etc/opendkim/TrustedHosts

[^TrustedHosts2]: "The localhost IP (127.0.0.1) should always be the first entry in this file." - /etc/opendkim/TrustedHosts

## opendkim-genkey

### opendkim-genkey コマンドのオプション

| オプション | 別名 | デフォルト値 | 説明 |
|:--|:--|:--|:--|
| `--bit` | `-b` | `1024` | 生成されるキーのサイズ。 |
| `--directory` | `-D` | 現在のディレクトリ | 生成されるファイルの保存場所。 |
| `--domain`  | `-d` | `example.com` | ドメイン名。 |
| `--selector` | `-s` | `default` | セレクター名。生成されるファイル名にも使用される。 |
| `--note` | `-n` | 未指定 | キーレコードに任意のノートテキストを含める。 |
| `--verbose` | `-v` | | 詳細を出力。 |
| `--version` | `-V` | | バージョン情報を出力して終了。 |

#### `--directory` (`-D`)
デフォルトではカレントディレクトリになるため、標準の `/etc/opendkim/keys` ディレクトリに移動するかオプションで明示的に指定してやる必要がある。

#### `--selector` (`-s`)
なぜか日付を指定する記事が多いが、一つのドメインで複数の鍵を指定する場合以外に特に意味がなく、設定項目が増えるだけなので未指定でデフォルト値の `default` を利用するのが妥当。

### ファイルの生成
```bash
opendkim-genkey --directory=/etc/opendkim/keys --domain=example.jp --verbose
```
```
opendkim-genkey: generating private key
opendkim-genkey: private key written to default.private
opendkim-genkey: extracting public key
opendkim-genkey: DNS TXT record written to default.txt
```
`--directory` オプションで指定した `/etc/opendkim/keys` に、 `--selector` オプションのデフォルト値である `default` が利用されて `default.private` と `default.txt` が生成される。


### 所有権の変更

```bash
chown -v opendkim. /etc/opendkim/keys/*
```
```text
`/etc/opendkim/keys/default.private' の所有者を root:root から opendkim:opendkim へ変更しました
`/etc/opendkim/keys/default.txt' の所有者を root:root から opendkim:opendkim へ変更しました
```

## Postfix
### main.cf の設定
```bash
cat << '_EOF_' >> /etc/postfix/main.cf
milter_default_action = accept
non_smtpd_milters = $smtpd_milters
smtpd_milters = inet:127.0.0.1:8891
_EOF_
```

#### milter_default_action
Milter (メールフィルタ) アプリケーションが使えなかったり設定が間違えているときのデフォルトの動作。 [^milter_default_action]

|値|説明|
|----------|---|
|`accept`  |メールフィルタが存在しないかのように進めます。|
|`reject`  |このセッションのそれ以降のすべてのコマンドを永久状態コードで拒否します。|
|`tempfail`|このセッションのそれ以降のすべてのコマンドを一時状態コードで拒否します。|

[^milter_default_action]: [milter_default_action - Postfix設定パラメータ](http://www.postfix-jp.info/trans-2.3/jhtml/postconf.5.html#milter_default_action)

#### non_smtpd_milters
Postfix smtpd(8) サーバーを通さずに届いた新しいメールに対する Milter (メールフィルタ) アプリケーションのリスト。 [^non_smtpd_milters]

[^non_smtpd_milters]: [non_smtpd_milters - Postfix設定パラメータ](http://www.postfix-jp.info/trans-2.3/jhtml/postconf.5.html#non_smtpd_milters)

#### smtpd_milters
Postfix smtpd(8) サーバーを通して届いた新しいメールに対する Milter (メールフィルタ) アプリケーションのリスト。 [^smtpd_milters]

[^smtpd_milters]: [smtpd_milters - Postfix設定パラメータ](http://www.postfix-jp.info/trans-2.3/jhtml/postconf.5.html#smtpd_milters)

### Postfix の設定再読み込み
```bash
postfix check
systemctl reload postfix
```

### 設定反映の確認
```bash
postconf | egrep 'milter_default_action|non_smtpd_milters|smtpd_milters'
```
```
milter_default_action = accept
non_smtpd_milters = $smtpd_milters
smtpd_milters = inet:127.0.0.1:8891
```

## OpenDKIM の起動と自動起動設定
```bash
systemctl start opendkim
systemctl status opendkim
systemctl enable opendkim
```

## DNS レコードの設定

### 公開鍵レコード
```bash
cat /etc/opendkim/keys/default.txt
```
```
default._domainkey	IN	TXT	( "v=DKIM1; k=rsa; "
	  "p=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" )  ; ----- DKIM key default for example.jp
```

### ADSP レコード
Author Domain Signing Practice (ADSP) とは、 DKIM の認証結果をどのように扱うべきかを示すポリシーを送信側で公開するもの。

```
_adsp._domainkey.example.jp.	IN	TXT	"dkim=unknown"
```
| 値 | 説明 |
|:--------------|:--|
| `all`         |  このドメインから送信されるメールはすべてメール作成者署名が与えられる。 |
| `unknown`     |  このドメインから送信されるメールのいくつか、またはすべてにメール作成者<br>署名が得られる。 |
| `discardable` |  このドメインから送信されるメールはすべてメール作成者署名が与えられる。<br>そして、もしメール作成者署名が得られない場合は、受信者はそのメールを<br>破棄することが望まれる。 |

### 動作テスト

#### dig (bind-utils)
```bash
dig ANY default._domainkey.example.jp +noall +answer
```
```
; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7_5.1 <<>> ANY default._domainkey.example.jp +noall +answer
;; global options: +cmd
default._domainkey.example.jp. 120 IN	TXT	"v=DKIM1\; k=rsa\; p=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
```
```bash
dig ANY _adsp._domainkey.example.jp +noall +answer
```
```
; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7_5.1 <<>> ANY _adsp._domainkey.example.jp +noall +answer
;; global options: +cmd
_adsp._domainkey.example.jp. 120	IN	TXT	"dkim=unknown"
```

#### MXToolBox DKIM Record Lookup
https://mxtoolbox.com/dkim.aspx

「Domain Name」に任意のドメインを、「Selector」に `default` を指定して「DKIM Lookup」ボタンを押下。

![dkim.png](https://qiita-image-store.s3.amazonaws.com/0/15175/3d03500e-e86c-3746-0857-102a6cdfccbb.png)

#### Check MX: Check MX と SPF レコード
https://toolbox.googleapps.com/apps/checkmx/

「ドメイン名」に任意のドメインを、「DKIM セレクタ（省略可）」に `default` を指定して「チェックを実行」を押下。

#### Sendmail
##### テストメールの送信
```bash
echo -e 'Subject: DKIM setting confirmation\n\nDKIM setting confirmation message.' | sendmail -f admin@example.jp example@gmail.com
```

Postfix の sendmail コマンドでメールをテスト送信。宛先は `example@gmail.com` は管理権限のある Gmail のメールアドレスを指定。

##### Gmail のヘッダー情報
```
DKIM:  'PASS'（ドメイン: example.jp）
```
```
Authentication-Results: mx.google.com;
       dkim=pass header.i=@example.jp header.s=default header.b=XXXXXXXX;
DKIM-Filter: OpenDKIM Filter v2.11.0 example.jp XXXXXXXXXXXX
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.jp; s=default; t=NNNNNNNNNN; 
```
「メッセージのソースを表示する」を開くとメールヘッダーが表示され、正しく設定されていれば上記の内容が含まれている。

##### メールログの確認
```bash
grep DKIM /var/log/maillog
```
```
Nov 15 00:00:00 example opendkim[NNNNN]: XXXXXXXXXXXX: DKIM-Signature field added (s=default, d=example.jp)
```

##### Sendmail DKIM - Eland Systems
```
echo -e 'DKIM setting confirmation message.' | sendmail -f admin@example.jp autorespond+dkim@dk.elandsys.com
```
http://www.elandsys.com/resources/sendmail/dkim.html

##### その他のリフレクター

https://www.cisco.com/c/ja_jp/support/docs/security/email-security-appliance/118571-technote-esa-00.html