riq0h.jp/content/post/test.md
Rikuoh 74d328a952
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
'fox'
2023-08-26 12:20:23 +09:00

163 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "MailuでWeb UI付きのメールサーバを所有する"
date: 2023-08-26T11:42:25+09:00
draft: true
tags: ['tech']
---
初夏の頃には[こんなこと](https://riq0h.jp/2023/05/05/213838/)を言っていたが、VPSの計算資源が余っているのでやはり自前でメールサーバを建てた。なにしろ数多の艱難辛苦に見舞われた10年前と今では状況がずいぶん違う。今やDockerがあり、優れたOSSがあり、これまでに培ってきたトラブルシューティングの知見がある。躓いたらいつでもコンテナを破棄してやり直せばいい。なにかを建てるたびどこかに引っかき傷を残す恐れはもうない。
[Mailu](https://github.com/Mailu/Mailu)というOSSがある。メールサーバに必要な構成が統合されていて全部よしなにやってくれる上にWeb UIまで付いてくるすごいやつだ。こんなのがあるんだったらVPSを契約している身でわざわざ他所に金を払っている場合ではない。こうして、僕は意気揚々とサーバの構築に乗り出したのだった。**結論から言うと、考えが甘かった。** 相変わらずメールサーバは手強い。本稿はMailuを利用したメールサーバの構築について記す。
## ファイルの取得と編集
`docker`および`docker-compose`はすでに導入されているものとする。専用のユーザでホームディレクトリ直下に作業フォルダを作成する。以降は`docker-compose.yml`の雛形をコピペして持ってくる形が一般的だが、Mailuの場合は[セットアップユーティリティ](https://setup.mailu.io/2.0/)を使うとユーザの意図に適った設定ファイルを出力してくれる。これらのファイルは`docker-compose.yml`と`mailu.env`ファイルなので後からでも編集できる。ただし、動作検証を済ませるまでは使う予定がなくともWebメールクライアントを有効にした方がよい。
重要なポイントとして、ポート設定の`80:80`と`443:443`の左側は必ず他の番号に変更しなければらない。メールサーバ以外のサービスを一切立ち上げていないのならともかく、これらの内向きポートは確実に専有されている。僕は`7900:80`、`8443:443`にした。メールサーバなのにHTTP/HTTPSポートが必要な理由はタイトル通りWeb UIが備わっているためだ。この修正に際して`mailu.env`に以下の追記が求められる。
```env
REAL_IP_HEADER=X-Real-IP
REAL_IP_FROM=あんたのグローバルIP
```
ユーティリティの"Choose how you wish to handle security"の欄はいまいちピンと来ないかもしれない。しかし最終的には大抵`letsencrypt`か`mail-letsencrypt`を選択することになる。SSL証明書をすでに持っているかどうかは関係がない。理由は後述する。セットアップの段階ではむやみな連続試行による取得規制を避けたいので`cert`を選択しておく。
自動生成の甲斐あって他に修正すべき箇所はあまりない。もし皆さんがCloudflareなどのCDNを間に挟んで**いなければ**ここからの話はかなりスムーズになる。……だが、今どきそんな人いるか それこそ10年前とは違う。今はCDNの時代だ。なんでもCDNの上に乗っているものだからオリジンサーバが文字通り雲隠れしたように見える。ところがメールサーバにはそれが通用しない。だから面倒くさいんだ。
## DNSレコードの設定
Cloudflareの設定を例にとる。メールサーバに用いるドメインを選択して**DNS → レコード**からレコードを設定する。通常であればサーバのIPアドレスをAレコードでドメインと紐づけて、MXレコードに当該のドメインを登録しておしまいだが、メールサーバの場合は「プロキシ」を有効にしては**ならない。** 適用後の表示が「DNSのみ」になるように設定する。
これはあえて有効にした状態でMXレコードを引いてみると事情がよく判る。CDNを挟んだ過程でなにかがおかしくなってしまうのか、サブドメイン部分が不正な値に変換されて出力されてしまうようだ。この状態ではメールの送受信に支障が生じたり、メールクライアントから接続できないなどの不具合に見舞われる。公式のドキュメントでも[やるなと書いてある。](https://developers.cloudflare.com/support/other-languages/%E6%97%A5%E6%9C%AC%E8%AA%9E/cloudflare%E3%81%AE%E4%BD%BF%E7%94%A8%E6%99%82%E3%81%AB%E3%83%A1%E3%83%BC%E3%83%AB%E3%81%8C%E9%85%8D%E4%BF%A1%E4%B8%8D%E8%83%BD%E3%81%AB%E3%81%AA%E3%82%8B/)以下に実例を示す。
```zsh
$ dig mx mystech.ink
...中略...
;; ANSWER SECTION:
mystech.ink. 300 IN MX 10 mx.mystech.ink. #正常なら設定した通りのMXレコードが引ける。
$ dig mx mystech.ink
...中略...
;; ANSWER SECTION:
mystech.ink. 300 IN MX 10 _dc_24782934.mystech.ink. #プロキシが有効だと不正な値に変換される。
```
なるほどでは「DNSのみ」にすれば万事解決か、というとそうでもない。証明書の問題が残る。Cloudflareの恩恵を受けられるのなら自動的にエッジ証明書があてがわれて、望むならオリジンサーバ証明書も無料で取得できるが今回はそれが使えない。したがって、メールサーバ用に個別のSSL証明書を用意しなければならないのである。
すでに持っているSSL証明書がCloudflareやCDNのものでなく、自前で取得したLet's encryptとかZero SSLなら通常は使い回せる。しかし、本稿で紹介するのはDockerを利用した構築方法だ。Dockerコンテナの内部で動いているサーバから外部の証明書を直接参照する方法は用意されていない。公式の手段では特定のディレクトリにコピペする方式を採っている。
とはいえ、大半の人が使っている無料のSSL証明書は有効期限が3ヶ月しかない。3ヶ月ごとにいちいち更新した証明書を貼り直したり、わざわざスクリプトを書いてcronに実行させるのは洗練されているとは言いがたい。僕たちは最先端の統合スイートOSSでちょっぱやのメールサーバを建てているのではなかったのか
そこで、前述した`letsencrypt`が顔を覗かせる。これはメールサーバが初回起動時に決めたドメイン用の証明書を自動で取得して、更新作業も実行してくれる設定なのだ。こいつに任せておけば証明書の問題はDockerコンテナの内部で完結する。このOSSが単純なメールサーバであれば話はこれで終わりだった。
だが、僕たちはWeb UI付きのメールサーバを建てようとしている。つまり、Web経由で管理画面なりWebメールクライアントに接続する。`letsencrypt`の自動取得機能は80番ポートで通信を行う。ここで懸念となるのは、管理画面やWebメールクライアントに用いるサブドメインと、メールサーバのサブドメインが同じだった時に、80番ポートが露出したWebサーバを公開してしまうことだ。
通常、80番ポートへのアクセスはCDNかリバースプロキシでリダイレクト処理を行うが、Cloudflareの恩恵を受けられない上に`letsencrypt`の設定で証明書周りを済ませたい僕たちにこれらの手段は使えない。では、どうすべきか。Web UI自体を使わない手もあるが、それならわざわざMailuのようなOSSを選ぶ理由はない。面倒な管理をGUIで完結させたいからWeb UI付きのメールサーバを選んでいるんじゃないか。
幸いにも解決策はある。管理画面用のサブドメインと、メールサーバ用のサブドメインをそれぞれ別に設ければいい。`hogefuga.com`というドメインを例にとると、管理画面用に`mail.hogefuga.com`、メールドメイン用に`mx.hogefuga.com`をそれぞれ作る。単なるWebフロントエンドに過ぎない前者はプロキシを有効化してCDNのSSLを証明書を得られるし、後者は安全に80番ポートを開けて`letsencrypt`の設定を使えるようになる。以上を踏まえた例を下記に示す。
```dns
hogefuga.com IN A 111.111.111.111 #メールアドレスの後ろ半分となるルートドメイン。プロキシ化する。
mail.hogefuga.com IN A 111.111.111.111 #管理画面用のAレコード。プロキシ化する。
mx.hogefuga.com IN A 111.111.111.111 #メールサーバ用のAレコード。プロキシ化しない。
hogefuga.com IN MX mx.hogefuga.com #メールサーバ用のMXレコード。プロキシ化しない。
```
この例では管理画面に接続する時は`mail.hogefuga.com`を使って、メールクライアント上でドメインを指定する際には`mx.hogefuga.com`を用いる形となる。`mailu.env`ファイルの方もこれに合わせた形に修正する。ユーティリティから再度作成し直しても構わない。
```env
# Main mail domain
DOMAIN=あんたのドメイン
# Hostnames for this server, separated with comas
HOSTNAMES=mx.あんたのドメイン
...中略...
# Website name
SITENAME=mail.あんたのドメイン
# Linked Website URL
WEBSITE=https://mail.あんたのドメイン
```
## ポートとリバースプロキシの設定
予めポートの開放を済ませる。公式のドキュメントではメールプロコトルに関するすべてのポートを開放するように書かれているが、ほとんどのユースケースではSSL接続のIMAPしか利用しないと考えられる。よって、ポートの開放も最低限のみ行う。特に25番ポートはよく攻撃の標的にされるのでできれば閉じておきたい。
```zsh
$ ufw allow 80
$ ufw allow 443
$ ufw allow 993
$ ufw allow 465
$ ufw reload
```
続いて、リバースプロキシの設定を書く。先に書いたように管理画面ないしはWebメールクライアント用と、メールサーバ用の2つのファイルを用意する。
```conf
#Webメールクライアント用
server {
server_name mail.あんたのドメイン;
location / {
proxy_pass https://localhost:8443;
proxy_set_header Host $host;
proxy_set_header Connection $http_connection;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
listen 443 ssl http2;
ssl_certificate /etc/ssl/certs/あんたのドメイン.pem;
ssl_certificate_key /etc/ssl/private/あんたのドメイン.key;
}
```
CloudflareのSSL/TLS設定で「フル厳密」を用いる環境でなければオリジンサーバの証明書は除いても問題ない。自動で付与されるエッジ証明書でも機能する。なぜか判らないがMailuは内向きでもHTTPSで通信しているため、`proxy_pass`をその通りに記す必要がある。
```
#メールサーバ用
server {
listen 80;
server_name mx.mystech.ink;
location / {
proxy_pass http://localhost:7900;
proxy_set_header Host $host;
proxy_set_header Connection $http_connection;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
メールサーバ用は`letsencrypt`のために80番ポートが欲しいだけなので簡素な設定で済む。2つのファイルを作成したら`systemctl restart nginx`でnginxを再起動する。
## 初回起動と管理画面の設定
いよいよ初回起動に入る。`docker-compose up`でバックエンドではなくログ垂れ流しで起動する。文字列の中でSSL証明書が取得されたっぽい英文を見つけたら、ひとまず成功と見ていいだろう。Ctrl+cでサーバを停止させた後に`docker-compose up -d`で立ち上げ直して、アドミンユーザの作成を行う。
```zsh
$ docker-compose exec admin flask mailu admin ユーザ名 ドメイン 'パスワード'
```
上記の操作で`ユーザ名@ドメイン`をID、`パスワード`をパスワードとするアドミンアカウントが作成される。ここでようやく管理画面用のURLにアクセスして諸々の設定を済ませる過程に入る。**メールドメイン → ドメイン名を追加**からメールアドレスの後ろ半分にしたいルートドメインを記入する。
![](/img/207.png)
次に「管理」カテゴリの赤丸で囲ったアイコンからユーザを作成する。ここで作成するユーザの名前がメールアドレスの前半分になる。最後に「操作」カテゴリの赤丸で囲ったアイコンから右上の「鍵を再生成」を押す。すると、SPF/DKIM/DMARCの設定に必要な情報が表示されるのでそれぞれをDNSレコードに登録しに行く。これらは現代のメールにおいて必須のセキュリティであるため、必ず登録しなければならない。
## 送受信の確認
管理画面からWebメールに移動して実際に送受信を行う。向こうからのメールがこちらに届いて、その逆にも問題がなければメッセージのソースからSPF/DKIM/DMARCの認証が行えているか確認する。すべてがクリアなら以下のように承認される。
![](/img/208.png)
さらに同様の作業を普段使っているメールクライアントからも行えるかチェックしておく。ログイン情報は管理画面の「クライアント設定」から見ることができる。いずれの場合でも問題がなければメールサーバの構築作業は完了である。
## スパムの検証
MailuにはRspamdというかなり強力なスパムメールフィルタが入っている。大抵はデフォルト設定でよしなにやってくれるが、万が一届くべきメールが届いていない気がする時には管理画面の「スパムメール対策」から検証できる。「History」を覗くとこれまでにフィルタされたメールの一覧が表示される。たまに見ると面白い。適正なメールがスパム判定されすぎている場合は管理画面の「設定」でスパムフィルターの閾値を上げると改善する。