Googleの送信ガイドラインが更新されるのでSPF/DKIM/DMARC/ARCをpostfixで署名+検証できるようにしたメモ

# 概要
2024年2月から、Googleからメール送信のガイドラインが更新されるとのことで、SPF・DKIM・DMARC・ARCの設定を推奨するような内容だったので、

https://support.google.com/mail/answer/81126?hl=ja

改めてそれぞれの調査とpostfixへの設定をしてみたのでメモ

:::note warn
メールの送受信に関わる設定ですので、記載内容を鵜呑みにするのではなく、其々の組織やグループのポリシに合わせて設定することが重要です。それぞれの設定項目の意味や影響範囲などを正しく理解した上で設定を行い、十分に検証を行ってください。その際に、もしこの記事の内容が参考になれば幸いです。
~~私自身100％分かっているかと言われると怪しいので~~
:::

## SPF（Sender Policy Framework）
SPFは<font color="red">メール送信元ドメインが正当な経路からメールを送信しているかを検証するための仕組み</font>です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/c7438d6a-7570-1b9b-c862-962e3e0193d4.png)

一般的に、組織は特定のメールサーバーを通じてメールを送信します。たとえば、自社のメールサーバーを使うか、Googleのメールサーバーを経由してGmailを使用するなどです。

メールFromアドレスは容易に偽装できますが、SPFはこの問題を軽減することが可能です。悪意のあるユーザーが偽装されたメールを送信する場合、通常の経路とは違うメールサーバー（例えば脆弱なメールサーバー）を利用します。<font color="blue">このため、偽装メールは正規のメールとは異なる送信経路が生じます。</font>

これを踏まえて、SPFではメールを受け取ったメールサーバーが

**「これは正当な経路で送られてきたメールか？」**

というのを検証します。

検証はSPFレコードを参照して行われます。このSPFレコードはDNSのTXTレコードとして設定され、ドメインからのメールが許可されたメールサーバーのリストを含みます。

```
v=spf1 include:_spf.example.com ip4:**.**.**.**/32 ~all
```

組織はSPFレコードを通じて、

<font color="green">**「当組織からのメールは指定されたサーバーからのみ送信される」**</font>

と宣言します。

SPFレコードに含まれていないサーバーから、対象ドメインのメールが送信されてきた場合、偽装されたメールの可能性がある、として扱います。

:::note warn
SPFの検証は通常、メールヘッダのReturn-Pathが使用されます。Return-Pathはメール送信者によって直接設定されるものではなく、メールを受信したサーバーが生成するため、偽装はより困難だからです。
:::

## DKIM（DomainKeys Identified Mail）
DKIMは、<font color="red">メールの内容が送信時に改ざんされていないことを確認するために用いられるメール署名の仕組み</font>です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/fcf7a80f-8c48-f14d-dab6-df4c2893b967.png)

メール送信時に送信者は、

**「選択したメールヘッダフィールドと本文に基づいて暗号化された署名をヘッダに追加」**

します。この署名を通じて、メールが送信経路上で改ざんされていないかどうかを受信側が検証できます。例えば、メール内容が改ざんされていなければ、署名された内容も一貫性を保つはずです。

ポイントとして、DKIMの署名に使用される秘密鍵に対応する公開鍵は、<font color="blue">ドメインのDNSレコードに公開されています。</font>例えば以下のような感じです。

```
_domainkey      IN      TXT     ( "v=DKIM1; k=rsa; " "p=[公開鍵]" ) 
```

これにより、メールを受信するサーバーは、公開鍵を使用して署名を検証し、メールが送信以来改ざんされていないことを確認できます。 **暗号化された署名が一致すれば、「改ざん無し」と判断され、一致しない場合は「改ざんの可能性あり」としてDKIM検証が失敗します。**

:::note warn
DKIMは署名された部分のみを検証するため、署名されていない部分の改ざんは検出できません。また、メーリングリスト管理や自動返信のような正当な処理によってメールヘッダや本文が変更される場合、DKIM検証は失敗する可能性がありますが、これは必ずしも不正行為を意味しません。
:::

## DMARC（Domain-based Message Authentication, Reporting & Conformance）
DMARCは、<font color="red">特定のメールドメインに対して、SPF・DKIMの検証結果に伴い、不正なメールをどのように処理するべきかを表明するための仕組み</font>です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/60c93b77-6a96-bc0e-80b2-2e1b10a4ae6e.png)

SPFやDKIMはメール認証に役立ちますが、その結果どのようにメールを処理するかまでは規定しません。そのため、DMARCを利用することで、組織は明示的に認証失敗時のメール処理ポリシを宣言することができます。

処理ポリシはDMARCレコードで宣言され、DNSのTXTレコードで公開します。例えば、SPFとDKIMのどちらかが失敗した場合に、迷惑メールに振り分ける、などです。

具体的には以下のようなレコードを設定します。

```
_dmarc IN TXT "v=DMARC1; p=[ポリシ]; ruf=mailto:[通知先]"
```

- p = [ポリシ] ... メールドメインの処理ポリシーを設定します。

|値|内容|
|:-:|:-:|
|none|DMARC失敗時に何もしない|
|quarantine|DMARC失敗時にメールを隔離(迷惑メール扱い)|
|reject|DMARC失敗時にメールを拒否|

- rua = 定期的な集計レポートを送信するためのメールアドレスを指定します
- ruf = 特定のメール送信失敗事例に関する詳細なレポートを送信するためのメールアドレスを指定します

DMARCを公開することにより、SPF・DKIM検証が失敗したメールの扱いをメールクライアントに委ねるのではなく、組織が表明することができます。これにより、偽装や改ざんの可能性があるメールをユーザーに届くことを防ぐことができます。

:::note warn
p = quarantineに設定しても、迷惑メールに振り分けられるかどうかはメールクライアントの設定に依ります。
:::

:::note warn
DMARCポリシーは、SPFまたはDKIMのどちらか一方が成功し、その結果が送信ドメインと一致している場合にメールを合格と見なします。つまり、SPFとDKIMの両方が失敗する必要はないということです。重要なのは、成功した認証結果が送信ドメインとアラインメントしているかどうかです。単にSPFまたはDKIMが成功しても、アラインメントが取れていなければDMARC検証は不合格となります。
:::

## ARC（Authenticated Received Chain）
ARCは、メールが転送や中継される各段階でのSPF、DKIM、DMARCの認証状態を記録し、この情報をメールヘッダに追加する仕組みです。この仕組みは、特にメーリングリストやメール転送のような状況でSPFとDKIMの認証が失敗する問題に対処するために重要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/196d91da-1064-2dd6-8d43-dc92379a40bf.png)

メーリングリストやメール転送を行うと、メールの送信元IPアドレスが変わるため、元の送信元と異なるIPから送信されたとみなされ、SPFの検証が失敗することがあります。また、メールの内容やヘッダが変更されると、DKIM署名も無効になる可能性があります。

ARCヘッダを用いることで、これらの認証状態の変化を追跡し、メールが最終的な受信者に到達した時に、元の認証状態を考慮に入れて認証判断を行うことを可能にします。これにより、正当なメールが誤って拒否されたり、迷惑メールとして扱われるリスクを減らすことができ、メールの到達率が向上します。

:::note warn
ARCはメールが転送や中継される際の認証状態を記録しますが、全てのメールサーバーがARCに対応しているわけではありません。しかし、ARCは部分的な情報でも機能するよう設計されており、いくつかのサーバーがARCに非対応であっても、使用可能なARC情報を利用してメールの信頼性を評価することができます
:::

## SPF / DKIM / DMARC / ARC関係性
SPF・DKIM・DMARC・ARCはそれぞれメール送受信についての重要な役割をそれぞれ担っていますが、それぞれの役割と関連をシンプルにまとめるならば、

まず、

- SPF ･･･ メール送信経路チェック
- DKIM ･･･ メール改ざんチェック

が前提としてあり、これらのチェック結果に対する処理を能動的に決定するために、

- DMARC ･･･ SPF/DKIMが失敗した場合の処理（何もしない/隔離/拒否）を表明

があります。

これらの検証状態を各メール中継地点毎にヘッダに記載するのが

- ARC ･･･ SPF・DKIM・DMARCの検証結果をメール中継地点毎にメールヘッダに追記する

になります。


# postfixへの設定
ここからはpostfixへの具体的な設定について記載します。
## 設定方針
### SPF
SPFレコードは任意のDNSサーバーに対応するSPFレコードを登録します。

postfixでのSPF検証は```policyd-spf``` を利用したSPF検証および処理が一般的（？）にも見えるが、今回はopendmarcのSPF検証機能を使ってSPF検証を実施します。

:::note warn
```policyd-spf```はpostfixの設定項目上は ```smtpd_recipient_restrictions``` に設定するのだが、対してDKIMやDMARCは```smtpd_milter``` に設定する。postfixの処理の順序としては、```smtpd_milter```が処理された後に、```smtpd_recipient_restrictions```処理される(ように見える)ので、SPF検証がされていない状態でDMARC検証が走ってしまい、結果が曖昧な状態になることが懸念される。openDMARCは、自身の機能の一部でSPF検証が可能なため、そちらを利用することで、SPF・DKIM・DMARC・ARCを順序立てて正しく処理できるようになる。
:::

### DKIM
```opendkim```を使って、DKIM署名とDKIM検証を設定します。

### DMARC
DMARCレコードは任意のDNSサーバーにレコードを登録します。

```opendmarc```を使って、DMARC検証を設定します。
なお、DMARC検証に失敗した場合でもメールはrejectしません。

### ARC
```openarc```を使って、ARC署名および検証を設定します。

## 【DKIM対応】opendkimの導入
postfixでDKIM署名を付与するためにopendkimを導入します。

```
# dnf install opendkim
```

設定を行います。

```config:/etc/opendkim.conf
# 署名モード(s) + 検証モード(v)に設定
Mode	sv
# キーテーブルを利用する（コメントアウトを外す）
KeyTable        /etc/opendkim/KeyTable
# 署名テーブルを利用する（コメントアウトを外す）
SigningTable    refile:/etc/opendkim/SigningTable
# 信頼するホストの設定を利用する（コメントアウトを外す）
InternalHosts   refile:/etc/opendkim/InternalHosts
```

:::note warn
openDKIMは初期設定のままの場合、検証に失敗したメールも配送します。```On-BadSignature```という設定項目で制御されており、初期値はacceptです。
:::

http://www.opendkim.org/opendkim.conf.5.html

DKIM署名の鍵ファイルを生成します。
```
# opendkim-genkey --directory=/etc/opendkim/keys --domain=[認証ドメイン] --selector=[セレクタ名]
# ls -l /etc/opendkim/keys
total 8
-rw------- 1 opendkim opendkim 887 11月  6 15:37 [セレクタ名].private // 秘密鍵
-rw------- 1 opendkim opendkim 318 11月  6 15:37 [セレクタ名].txt // DNS設定レコード
```
認証ドメインは対象となるメールドメイン、セレクタ名は任意の公開名称になります。

署名テーブルを作成します。
```config:/etc/opendkim/SigningTable
# どのメールアドレスに対して、どういった署名を行うかを設定する
# [対象メールアドレス] [Selector]._domainkey.[認証ドメイン]の形式で指定する

*@example.com dkimtest._domainkey.example.com
```

鍵テーブルを作成します。

```config:/etc/opendkim/KeyTable
# 「セレクタ」「認証ドメイン」「秘密鍵」のペアを設定する
# [Selector]._domainkey.[認証ドメイン] [メールドメイン]:[セレクタ名]:[秘密鍵パス] の形式で指定する

dkimtest._domainkey.example.com example.com:dkimtest:/etc/opendkim/keys/dkimtest.private
```

信頼ホストテーブルを作成します。ここに記載したIPアドレスからのメールに対して、DKIM署名を実施するようになります。

```config: /etc/opendkim/InternalHosts
# DKIM署名を実施するメール送信サーバーのIPアドレスを設定する
127.0.0.1
172.20.xxx.xxx
```

postfixを設定します。

```config:etc/postfix/main.cf
# 最下部に追記
# Milterが利用不可の場合でもメールを受け入れる。
milter_default_action = accept

# 非SMTPデーモン（例：メール送信時に使用されるSubmissionプロトコル）で使用するMilterを、
# SMTPデーモンで使用されるものと同じに設定。
non_smtpd_milters = $smtpd_milters

# SMTPデーモン用のMilterの設定。opendkimに処理を流す
smtpd_milters = inet:127.0.0.1:8891
```

設定を反映します。

```console
// postfixリロード
# postfix check
# systemctl reload postfix
// opendkim起動+自動起動設定
# systemctl start opendkim
# systemctl enable opendkim
```

DNSにDKIMレコードを登録します。
opendkimが鍵作成時にDNSレコードのサンプルを作成しているので、そちらを参照して設定します。

```console
# cat /etc/opendkim/keys/[セレクタ名].txt
// 出力結果をDNSに登録
```

### 検証
#### DKIM署名
DKIM署名を実施するホストからメールが送信されると以下のようなログが出力されます。

```log:/var/log/maillog
opendkim[3214]: [id] : DKIM-Signature field added (s=[セレクタ], d=[ドメイン])
```

また、受信メールのヘッダにも以下のような文字列が追加されています。
```
DKIM-Filter: OpenDKIM Filter v2.11.0 [サーバー] [id]
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=[ドメイン]; s=[セレクタ]; ...
```

#### DKIM検証
正当な外部ホストからメールを受信した際に、DKIM署名がある場合は検証が行われる。
以下のようにログが出ていれば検証は成功していることになる。

```
opendkim: DKIM verification successful
```

また、メールヘッダにもdkim検証についてのAuthentication-Resultsヘッダが追加される。

```
Authentication-Results: [dkim検証サーバー]; dkim=pass (2048-bit key) ...
```


### 補足: opendikm : not authenticated
以下のようなログが出る場合

```
opendkim[27299]: 3F54E301D5EFC: not authenticated
```

これは該当のメール通信が認証されていない場合に出力される。具体的には、MTAマクロの{auth_type}に値が入っていないことに起因している。

http://www.opendkim.org/opendkim.8.html

> A message will be verified unless it conforms to the signing criteria, which are: (1) the domain on the From: address (if present) must be listed by the −d command line switch or the Domain configuration file setting, and (2) (a) the client connecting to the MTA must have authenticated, or (b) the client connecting to the MTA must be listed in the file referenced by the InternalHosts configuration file setting (or be in the default list for that option), or (c) the client must be connected to a daemon port named by the MTAs configuration file setting, or (d) the MTA must have set one or more macros matching the criteria set by the MacroList configuration file setting.
> For (a) above, the test is whether or not the MTA macro "{auth_type}" is set and contains any non-empty value. This means the MTA must pass the value of that macro to the filter before or during the end-of-header (EOH) phase in order for its value to be tested. Check your MTA’s configuration documentation for details.

opendkimのソースも以下のように確認できる。
```c:opendikm.c
if (authtype == NULL || authtype[0] == '\0')
{
        syslog(LOG_INFO, "%s: not authenticated",
               dfc->mctx_jobid);
}
```

MTAマクロの{auth_type}の値はSASL ログインメソッドであり、SASLで認証していない通信の場合にこのログがでるものと考えられる。（おそらく）

https://www.postfix-jp.info/trans-2.3/jhtml/MILTER_README.html

## 【SPF+DMARC対応】opendmarcの導入
postfixでdmarcの検証をするにはopendmarcを導入します。

```console
# dnf install opendmarc
```

設定を行います。

```config:/etc/opendmarc.conf
# DMARC検証で認証失敗したメールを拒否しない。レポートのみ生成する。
RejectFailures false

# メールヘッダのSPF検証結果を無視します（後述の自前のSPF検証をするため）
SPFIgnoreResults true

# openDMARC自身でSPF検証をするようにします
SPFSelfValidate true

# DMARCポリシー検証のために、FromとDateのヘッダが必須であることを指定。
RequiredHeaders true

# 特定のホストからのメールはDMARC検証をスキップする。
IgnoreHosts /etc/opendmarc/ignore.hosts

# 認証されたクライアントからのメールはDMARC検証をスキップする。
IgnoreAuthenticatedClients true
```

IngoneHostsは必要に応じて設定します。

```config:/etc/opendmarc/ignore.hosts
localhost
127.0.0.1
::1
...
```

postfixとの連動を設定します。

```config:/etc/postfix/main.cf
# 8891: oepndkim / 8893: opendmarc
smtpd_milters = inet:127.0.0.1:8891,inet:127.0.0.1:8893
```

設定の反映を行います。
```console
# systemctl start opendmarc
# systemctl enable opendmarc
# postfix check
# systemctl restart postfix
```

### 検証
メールログに以下のような文字列が出ていることを確認します。それぞれSPF検証とDMARC検証の結果が出力されています。
```
opendmarc[51724]: D2DBC3002DB62: SPF(mailfrom): [検証ドメイン] pass
opendmarc[51724]: D2DBC3002DB62: [検証ドメイン] pass
```

また、メールヘッダにもSPFとDMARCに関するヘッダが追加されていることが確認できます。
```
DMARC-Filter: OpenDMARC Filter v1.4.1 [ホスト名] E614A301D5CE1
Authentication-Results: [ホスト名]; dmarc=pass (p=none dis=none) header.from=gmail.com
Authentication-Results: [ホスト名]; spf=pass smtp.mailfrom=gmail.com
```

### 補足
#### opendmarc : ignoring Authentication-Results
以下のようなログが出る場合

```
opendmarc[42707]: 25C313020F22A ignoring Authentication-Results at 0 from [サーバー名]
```

これは、検証する```Authentication-Results``` ヘッダに記載されているホスト名が、opendmarc自身が認識しているホスト名と異なる場合に「このヘッダは無視するね」ということを知らせるログになる。

このエラーが発生する場合、```/etc/opendmarc.conf``` の ```AuthservID``` の値がサーバー名と一致していない可能性がある。通常はサーバーのホスト名が使用されるため、メールサーバーのホスト名と ```Authentication-Results``` ヘッダのホスト名を確認してみてください。

ソース側でも以下のように確認できる

```c:opendmarc.c
/* skip it if it's not one of ours */
if (strcasecmp(ar.ares_host, authservid) != 0 &&
    (conf->conf_trustedauthservids == NULL ||
     !dmarcf_match(ar.ares_host, conf->conf_trustedauthservids,
                   FALSE)))
{
        unsigned char *slash;

        if (!conf->conf_authservidwithjobid)
        {
                if (conf->conf_dolog)
                {
                        syslog(LOG_DEBUG,
                               "%s ignoring Authentication-Results at %d from %s",
                               dfc->mctx_jobid, c,
                               ar.ares_host);
                }

                continue;
        }
...
```

## 【ARC対応】openarcの導入
postfixでARCに対応するにはopenarcを導入します。

```console
# dnf install openarc
```

設定します。ARCはDKIMと同じ証明書を使うことができるので、今回は流用します。

```config:/etc/openarc.conf
# s = 署名 / v = 検証
Mode                    sv
Canonicalization        relaxed/relaxed
Domain                  [ドメイン名]
Selector                [セレクタ名]
KeyFile                 [証明書ファイルパス] 
```

postfixに統合します。
```smtpd_milters```に記載する順番を注意してください。
DKIM→DMARC(SPF)→ARCの順番で処理されるようにします。

```config:/etc/postfix/main.cf
# Mfilterリスト(8891: opemnDKIM / 8894: openDMARC / 8893: openARC)
smtpd_milters = inet:127.0.0.1:8891,inet:127.0.0.1:8894,inet:127.0.0.1:8893
```

反映します。

```console
# systemctl start openarc
# systemctl enable openarc
# postfix check
# systemctl restart postfix
```

### 検証
ARCヘッダがメールに付与されていることを確認する。

- ARC-Authentication-Results
メールが受信された際に実行された認証チェックの結果を含みます。これには、SPF、DKIM、DMARCの検証結果が含まれます。
```
ARC-Authentication-Results: i=1; [host]; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) ...
```

- ARC-Message-Signature
ARC-Authentication-Resultsヘッダーとメールのその他の部分（例えば、メールのヘッダーやボディ）を含むデジタル署名です。この署名は、メールが転送される過程で元の認証結果が変更されていないことを保証するために使用されます。
```
ARC-Message-Signature: i=1; a=rsa-sha256; d=[domain]; s=[selector]; ...
```

- ARC-Seal
メールのARCチェーン全体に対するデジタル署名です。ARC-Sealは、メールが転送チェーンを通過する際に、以前のARC-Authentication-ResultsとARC-Message-Signatureが変更されていないことを保証します。
```
ARC-Seal: i=1; a=rsa-sha256; d=[domain]; s=[selector];...
```

# まとめ
個人的には今回のGoogleガイドラインの変更によって、SPFとDKIMが設定されていないメールの到達率が更に厳しくなるのではないかと考えてます。

DMARCについては、できるなら設定しておくのが吉。DNSレコードを設定するただけなので、それほど難しくはないので・・・

ARCについては、（GoogleやOutlookとかはともかく）そもそもどこまで一般組織のメールサーバーに普及しているのかは怪しいところです。対応できるならそれに越したことはないですが、元々ARC自体が歯抜けでも検証できる仕組みなので、対応していなかったとしてもそれほど大きな問題にはならないと思っています。

メールログを眺めていて思いますが、世に出回っている不埒なメールはSPFやDKIM署名がないことが大半です。これらが無いメールを一律ブロックしますよと言われても文句が言えない世の中になってきているので、最低限SPFとDKIMはやっておくことをおすすめします。

## 各種設定や検証に役立つWebサイト

- SPF・DKIM・DMARCの設定状態を確認

https://mxtoolbox.com

- オープンリレー状態を確認

http://check.jippg.org

## 各種ソースコード
メール周りの処理は非常に複雑なので、「なんでこのログ出てるんだろう」とか「ここの処理どーなってん？」と思ったらソースコードを眺めることをおすすめします。それほど難しいコードではないので、C言語が読めれば多少は読解できると思います。

- openDKIM

https://sourceforge.net/p/opendkim/git/ci/master/tree/

- openDMARC

https://github.com/trusteddomainproject/OpenDMARC

- openARC

https://github.com/trusteddomainproject/OpenARC

## manページ
ネットで情報を調べても出てこない場合はmanコマンドで公式マニュアルを見てみると意外に情報が手に入ったりします。

```console
// サービス名なら基本的な情報が確認可能
# man opendkim
// 設定ファイル名なら全設定項目の詳細が確認可能
# man opendkim.conf
```