大規模にも応用できるメール送受信基盤をしっかりつくる - (1) 概要編

このシリーズでは、電子メールの「ちゃんとした」送受信基盤を作る方法を説明することを試みます。

「簡単な」メールサーバのつくり方は検索するとたくさん出てきますが、クラウド企業などの運用する大規模なメールシステムはそのようにはしばしば構築されていません。そこで、この記事では大規模になってもきちんと応用できるような、メールシステムの作り方を説明していきます。

なお、このシリーズでは一般的な「基本」についてのみ説明しますので、当然、これを実用する場合はあなたの要件に合うように詳細を変更してください。

## この記事の対象
1. 簡単なメールサーバの構築のやり方はだいたい分かるが、それだけでは限界を感じているサーバ構築・設計者
1. メールの基本については理解したが、それ以上の「本格的」なメールインフラがどうなっているのか知りたい読者
    - こちらの場合は、具体的なシステムの構築に関する内容は読み飛ばしても構いません

:::note info
SMTP という単語が良く分からないとか、メール送受信がどのような仕組みで動いているのか分からない方は、インターネットで基本について調べてから読むことをおすすめします。
:::

## この記事で解決する課題
- 1台のメールサーバでは扱えない量の電子メールトラフィックに対処したい (備えたい)
- 1台のサーバに全てを詰め込むのはセキュリティ的に不安
- 非標的型の一般的なサイバー攻撃からは最低限守れるくらいのセキュリティを確保したい

:::note warn
この記事では基本的なセキュリティの原則について解説します。

あなたの組織が標的型の攻撃に晒される場合 (あなたの会社が十分大きいか、十分に重要な会社である場合、このような攻撃はほぼ 100% 発生します)、その守り方はその組織やその組織のインフラの個別具体的な詳細によって異なります。

そのような場合、あなたのチームにはそのようなリスクを評価・対処できる専門家がいることを前提とします。

__重要なインフラを構築する場合、必ず自分たちの要件に合った適正な設計・設定をしてください。この記事の著者は法律によって認められる最大限の範囲において、この記事を元に構築したシステムによって起きた損害などの責任は負いません。__
:::

## この記事における仮定
- このシリーズでは、特に注記がない限り、小規模から大中規模まで広く用いられるメールサーバソフトウェアである Postfix を仮定します。実際には、十分に能力があるソフトウェアであれば、他のソフトウェアでも応用可能です
- この記事では具体的なネットワークの構築手段には触れません。本記事の例においては、論理的に以下のような簡単なネットワークがあることを前提とします：

### ネットワーク構成
同じ構成の Site A および Site B があります。

![email-example-setup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653437/c837f108-2dbe-4137-a643-a73a36c3544e.png)


ルータは VRRP やネットワーク仮想化などで十分に冗長化されているものとします。

SMTP においてはサーバ間の通信は、自動でフォールバックするので、 MX レコードを複数入れることで冗長化できます。(同じ priority の MX レコードの間で load balancing されます。)

メールクライアントが接続する IMAP サーバおよび SMTP Submission サーバは、自動でフォールバックするとは限らないので IP/TCP レベルでの冗長化が必要です。

クライアント接続用に複数用意した LB (ロードバランサ) はここでは BGP を動作させ、Anycast IP アドレスを持たせることで冗長化しています。

### メールサーバの種類
- クライアントが接続するもの
    - __MSA__: Mail Submission Agent - submission (TCP 587 番) ポートに listen していて、メールクライアントからの送信を受けつけます
    - __MDA + IMAP サーバ__: Mail Delivery Agent - MDA は他のシステムからのメールを受けとり、ユーザのメールボックスに配送します。 IMAP サーバは、メールボックスへのアクセスをメールクライアントに提供します
- 外部のサーバとやりとりするもの
    - __MTA__: Mail Transfer Agent - 外部のメールサーバ (MX レコードのサーバ) に接続し、メールを送ります
    - __MX__: Mail Exchange - MX レコードがこのサーバの IP アドレスを指しています。外部のメールサーバからの接続をポート 25 番で受けつけ、メールを受けとります

## アカウント管理
ここでは、メールのアカウント管理について以下の2つのユースケースを例にします。

### 中規模な自社向け - Active Directory
このケースでは、メールの送受信は自社の社員向けで、アカウントは自社の Active Directory で管理されているものとします。メールサーバは AD に join することで、ローカル UNIX アカウントをメールアカウントとして利用可能です。

### 大規模または外部向け - DB上のアカウント
アカウントは UNIX アカウントとしては存在せず、RDBMS 上にのみ存在します。

## メールの流れ
### 送信
TLS 暗号化は適切に設定されているものとします。

1. クライアントが MSA (Mail Submission Agent) に接続します
1. クライアントは MSA に対して SMTP 認証を送信し、 MSA はクライアントを認証します
1. クライアントは MSA にメールを送信します。MSA はメールをキューに入れます (権限のないアドレスなどから送ろうとしている場合などはここで弾きます)
1. MSA が MTA に接続します (通常、ここでも認証を行います)
1. MSA は MTA にメールを送信します。MTA はメールをキューに入れます
1. MTA は DKIM 署名など必要な処理を行います
1. MTA は送信先のメールアドレスから MX レコードを DNS を使って解決します
1. MTA は MX レコードにある相手先の MX サーバに接続し、メールを送信します

### 受信
1. 送信元ドメインの MTA から自分たちの MX サーバに接続されます (ここで送信元の IP や逆引き DNS などをチェックします)
1. 送信元 MTA が MAIL FROM アドレスを提示します (ここで SPF などをチェックします)
1. 送信元 MTA がメールのデータを送信します (ここで DKIM と DMARC、および一部のスパムチェックを行います)
1. 受理した場合、MX サーバはメールをキューに入れます
    - 受理されなかった場合、送信元は bounce 処理を行います
1. MX サーバは MDA に接続します (通常、認証を行います)
1. MX サーバは MDA にメールを送信します
1. メールを受けとった MDA はユーザのアカウントに配送を行います。あるいはメーリングリストや自動処理システムなど宛ての場合は、該当のプログラムを実行します
1. IMAP サーバの場合、クライアントが接続してきてメールを確認してくれます

## DNS レコードの例
IPv6 も適切に設定されているものとする。

### 正引き

```:zone: example.com.
; MX records
example.com.            IN    MX    10 mx-a1.example.com.
example.com.            IN    MX    10 mx-a2.example.com.
example.com.            IN    MX    10 mx-b1.example.com.
example.com.            IN    MX    10 mx-b2.example.com.


; SPF レコードは TXT で指定する。SPF 型は廃止された
example.com.            IN    TXT   "v=spf1 redirect=_spf.example.com"
_spf.example.com.       IN    TXT   "v=spf1 include:a._spf.example.com include:b._spf.example.com -all"
a._spf.example.com.     IN    TXT   "v=spf1 ip4:192.0.2.28 ip4:192.0.2.29 ip6:..."
b._spf.example.com.     IN    TXT   "v=spf1 ip4:192.0.2.44 ip4:192.0.2.45 ip6:..."

;; bounce のときなど、 MAIL FROM が行われないときにホスト名の SPF レコードが利用される
mta-a1.example.com.     IN    TXT    "v=spf1 a aaaa -all"
mta-a2.example.com.     IN    TXT    "v=spf1 a aaaa -all"
mta-b1.example.com.     IN    TXT    "v=spf1 a aaaa -all"
mta-b2.example.com.     IN    TXT    "v=spf1 a aaaa -all"
*.example.com.          IN    TXT    "v=spf1 -all"

; DKIM records
202401._domainkey.example.com.       IN    TXT    "v=DKIM1; h=sha256; k=rsa; ..."

;; ADSP
adsp._domainkey.example.com.         IN    TXT    "dkim=all"


; DMARC records
_dmarc.example.com.     IN    TXT   "v=DMARC1;p=reject;sp=reject;np=reject;adkim=r;aspf=r;rf=afrf;rua=mailto:dmarc@example.com"

;; allow reports (required when on different domains)
example.com._report._dmarc.example.com.    IN     TXT    "v=DMARC1;"


; A/AAAA
;; MTA
mta-a1.example.com.     IN    A      192.0.2.28
mta-a1.example.com.     IN    AAAA   ...
mta-a2.example.com.     IN    A      192.0.2.29
mta-a2.example.com.     IN    AAAA   ...
mta-b1.example.com.     IN    A      192.0.2.44
mta-b1.example.com.     IN    AAAA   ....
mta-b2.example.com.     IN    A      192.0.2.45
mta-b2.example.com.     IN    AAAA   ...

;; MX
mx-a1.example.com.      IN    A      192.0.2.24
mx-a1.example.com.      IN    AAAA   ...
mx-a2.example.com.      IN    A      192.0.2.25
mx-a2.example.com.      IN    AAAA   ...
mx-b1.example.com.      IN    A      192.0.2.40
mx-b1.example.com.      IN    AAAA   ...
mx-b2.example.com.      IN    A      192.0.2.41
mx-b2.example.com.      IN    AAAA   ...

;; Client access (on anycast IPs)
imap.example.com.       IN    A      192.0.2.1
imap.example.com.       IN    AAAA   ...
smtp.example.com.       IN    A      192.0.2.2
smtp.example.com.       IN    AAAA   ...
```

### 逆引き
MTA のグローバル IP アドレスに対しては逆引きが必須。IPv6 の逆引きも同様に設定する。

```:zone: 2.0.192.in-addr.arpa.
28.2.0.192.in-addr.arpa.      IN     PTR    mta-a1.example.com.
29.2.0.192.in-addr.arpa.      IN     PTR    mta-a2.example.com.
44.2.0.192.in-addr.arpa.      IN     PTR    mta-b1.example.com.
45.2.0.192.in-addr.arpa.      IN     PTR    mta-b2.example.com.
```

## メールのルーティング
これらの通信のみをファイアウォールで許可する。

### MSA
- __自ドメイン宛てのメール__ (`virtual_transport`): MTA へ (内部でメールを転送する場合は MDA)
- __そのほかのメール__ (`relayhost`): MTA へ

### MX
自ドメイン宛て以外のメールは外部から受けとることはない。

- __自ドメイン宛てのメール__ (`virtual_transport`): MDA へ
- __そのほかのメール__ (自サーバ内で bounce した場合に発生する) (`relayhost`): MTA へ

### MTA
(すべて直接外部に送信)

### MDA
(MDA どうしがメールボックスを同期するために通信)

## ロードバランサ
ここでは、高効率な擬似的なロードバランサとして、汎用の Linux サーバで HAProxy (TCP mode) を BGP (bird や FRRouting) といっしょに走らせている。このサーバはそれぞれ Anycast IP アドレスをループバックに持ち、そのアドレスを BGP で広告する。 HAProxy はこのループバックのアドレスに Listen している。 HAProxy は生きているバックエンドのサーバに負荷分散する。

なお、ここでは ECMP が per-flow であること (anycast で TCP 接続が壊れない) を前提としている。

適宜死活監視と BGP 広告の連動など設定すること。

## FW
すべてのサーバで FW を有効にする。必要な通信のみを許可する。特に、外部からの SSH 通信などは受けつけないこと。(IPv6 では ICMPv6 を全部ブロックすると通信できなくなるので注意すること)

### MSA
- 内向き通信は TCP port 587 番 (submission) のみ開放
- 外向き port 25 番は MTA に対してのみ許可

### MDA
- 内向き通信は TCP port 993 番 (IMAPS) のみ開放
- 外向き port 25 番を許可する必要は通常ない

### MX
- 内向き通信は TCP port 25 番 (SMTP) のみ開放
- 外向き port 25 番は MTA, MDA に対してのみ許可

### MTA
- 内向き通信は許可する必要がない
- 外向き port 25 番はインターネットに対して許可すること

## この回のまとめ
この記事では、メールサーバを「きちんと」役割別に分けることを解説しました。これによって攻撃を受ける面積を最小化できるほか、カジュアルな攻撃の場合はログが汚染されて読みにくくなることを避けることができます。また、大規模な運用に応用することが容易になりました。

----
修正点、ご意見などありましたらお知らせください。