前書き

弊LAN内でグローバルからのIPv4着信接続が必要な唯一のサービスがメールサーバー。IPv4デフォルトルートと着信接続可能なグローバルIPv4は別のため、PBRが必要になってしまう。

LAN内ではなく、ルーターでメールサーバーを動かしちゃえばIPv4周りのめんどくささを解消できるので、やってみることにした。

前提

要求スペック(私の環境での実測)

  • RAM: 1GB (最低512MB)
  • ストレージ: 1.5GB以上(+メールの保存領域)

環境

  • FriendlyElec NanoPi R2S
  • MicroSD 4GB
  • OpenWrt 22.03.4 ext4版
  • root領域を拡張して4GBいっぱいまで使用できる(パーティション操作済み)

rootに余裕がない場合は、外部ストレージをつけていい感じにパスを管理してください。
私の環境ではrootに余裕があるので、全部この下に入れます。

条件

  • MyDNS.jp で取得可能な無料サブドメインを使う
    • MXレコードをいい感じにしてくれるので楽ちん
  • TLSを使用可能にする
  • メールサーバー内にはメールを残さない
    • メールはPOP3SでGmail経由で取得する

導入

証明書の取得

acmeパッケージを導入します。管理を簡単にするため、luciアプリを入れます。

opkg install luci-app-acme

LuCI(WebUI)にログインし、「Servces」→「ACME certs」を開きます。

Account emailにメールアドレスを入力

Certificate config で証明書を取得するドメインと方法を設定します。

Domain namesにはドメインを入力

Validation methodはStandaloneが便利です。ファイアウォールとかの設定が不要。
ただ、証明書発行処理をしている数十秒間はLuCI(WebUI)にアクセスできなくなります。

もし、LuCI(WebUI)と共存させたい場合、おすすめしませんが、ファイアウォールでLuCIへのグローバルからのアクセスを許可し、Validation methodにWebrootを指定し、Webroot directoryを「/www」にすると動く気配を感じます(未確認)

Save & Applyで証明書取得処理が走ります。

メールサーバーの導入

docker-compose を使って導入します。

LuCI(WebUI)から実行すると、時間のかかる処理でタイムアウトが発生することがあるので、この項目はSSHで実施することを強く推奨します。

パッケージのインストール

opkg install wget dockerd luci-app-dockerman docker-compose bash zoneinfo-all

docker-composeのファイルを保存するディレクトリを作成する

ここでは、/root以下に保存します。
/の容量が少ない環境の場合は、いい感じに外部ストレージを使用してください。

mkdir docker
mkdir docker/docker-mailserver
cd docker/docker-mailserver/

便利なファイルをいただいてきます。

DMS_GITHUB_URL='https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master'
wget "${DMS_GITHUB_URL}/mailserver.env"
wget "${DMS_GITHUB_URL}/setup.sh"
chmod a+x ./setup.sh

docker-compose.ymlもダウンロードできますが、どうせ全部書き直すので、ダウンロードは不要です。一応、ダウンロードのためのコマンドは紹介しておきます。

wget "${DMS_GITHUB_URL}/docker-compose.yml"

docker-compose.yml を作成する

hostnameの部分は、各自用意したFQDNを入力してください。このドメインにはMXレコードを設定しておく必要があります。

vi docker-compose.yml
version: "3"
services:
  mailserver:
    image: ghcr.io/docker-mailserver/docker-mailserver:latest
    network_mode: "host"
    deploy:
      endpoint_mode: dnsrr
    container_name: mailserver
    # Provide the FQDN of your mail server here (Your DNS MX record should point to this value)
    hostname: example.jp
    env_file: mailserver.env
    # More information about the mail-server ports:
    # https://docker-mailserver.github.io/docker-mailserver/latest/config/security/understanding-the-ports/
    # To avoid conflicts with yaml base-60 float, DO NOT remove the quotation marks.
    ports:
      - "25:25"    # SMTP  (explicit TLS => STARTTLS)
     # - "110:110"  # POP3  (explicit TLS => STARTTLS)
     # - "143:143"  # IMAP4 (explicit TLS => STARTTLS)
      - "465:465"  # ESMTP (implicit TLS)
     # - "587:587"  # ESMTP (explicit TLS => STARTTLS)
     # - "993:993"  # IMAP4 (implicit TLS)
      - "995:995"  # POP3  (implicit TLS)
    volumes:
      - ./docker-data/dms/mail-data/:/var/mail/
      - ./docker-data/dms/mail-state/:/var/mail-state/
      - ./docker-data/dms/mail-logs/:/var/log/mail/
      - ./docker-data/dms/config/:/tmp/docker-mailserver/
      - /usr/share/zoneinfo/Japan:/etc/localtime:ro
      - /etc/acme:/etc/acme
    environment:
      - ENABLE_POP3=1
    restart: always
    stop_grace_period: 1m
    # Uncomment if using `ENABLE_FAIL2BAN=1`:
    # cap_add:
    #   - NET_ADMIN
    healthcheck:
      test: "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"
      timeout: 3s
      retries: 0
ちょっとした解説
network_mode: "host"

これがないと、TCPの接続元がDocker内のGateway扱いされて、そのIPアドレスの逆引きが接続元ドメインと一致しない(それはそう)というエラーが出てメールを受け取ることができない

ports:

network_modeをhostにしたらportsは不要説あるけど、、知らん(雑)

/usr/share/zoneinfo/Japan:/etc/localtime:ro

タイムゾーンファイルへアクセスできる必要があります。もともと指定されていた場所にはシンボリックリンクがありましたが、実態がありませんでした。
代わりに、直接zoneinfoファイルを指定します。

/etc/acme:/etc/acme

certbotではなくacmeを使うので、letsencryptディレクトリではないところに証明書が作成されます。

.envファイルの編集

vi mailserver.env
SSL_TYPE=manual
SSL_CERT_PATH=/etc/acme/example.jp/fullchain.cer
SSL_KEY_PATH=/etc/acme/example.jp/example.jp.key
ちょっとした解説
SSL_CERT_PATH

GmailからPOP3Sアクセスすとき、fullchainである必要があるらしいです。fullchainではない証明書を使用すると、中間証明書が追跡できなくてエラーが出ました。

メールアドレスの追加

./setup.sh email add [email protected] "P@ssWord"

エイリアスの追加

./setup.sh alias add [email protected] [email protected]

この例では、postmasterとかrootに届くメールをaccountで受信できます。
postmasterにはアップデートの通知とかも行くようなので、いつも見ているメールボックスに飛ばしておくと便利だと思います。

root宛はpostmasterがaliasに設定されているそうです。

CatchAllアカウント

docker-mailserver では、キャッチオールにも対応しています。
アカウント名なしメールアドレスを受信したいメールボックスのエイリアスに設定すると、これがキャッチオールのアドレスになります。

./setup.sh alias add @example.jp [email protected]

メールアドレスとエイリアスの一覧

現在のアカウント周りの設定が確認できます。

./setup.sh email list
./setup.sh alias list

postfixのmain.cfを編集

Postfixやシステムで使用する自分のドメインを宣言しておくことで、警告やエラーを回避します。

vi ./docker-data/dms/config/postfix-main.cf
myorigin = $mydomain
mydestination = $mydomain

起動

docker-compose up -d

ファイアウォールの設定

LuCI(WebUI)にログインします。

「Network」→「Firewall」と進んで、「Traffic Rules」タブを選択します。

左下の「Add」をクリックして、外からの 25 465 995 を Accept にします。

メンテナンス

コマンドの実行

./setup.sh debug login bash
docker-compose exec mailserver bash

メールキュー詰まり確認

postqueue -p

アップデート

まだアップデートをしたことがないのですが、一応やり方載せておきます。

DMS_GITHUB_URL='https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master'
wget -O setup.sh "${DMS_GITHUB_URL}/setup.sh"
chmod a+x ./setup.sh
docker-compose pull
docker-compose down
docker-compose up -d

参考資料

docker-mailserverをさくっと立ち上げる(令和4年2月版)
Catch-All Address · Issue #217 · docker-mailserver/docker-mailserver · GitHub

備考

証明書の更新は、「Scheduled Tasks」に登録され、毎日0時0分に実行されます。
この処理は、下記ですぐできます。

# /etc/init.d/acme start

エラーはSystem Logに吐かれます。WebUIからも確認ができます。"acme"とかで検索をかけるとよいです。

acmeでワイルドカード証明書を取得するには、ドメインルートを持っている必要があるみたいで、今回のようなサブドメインを使う方法(DNS-01)は利用できませんでした。

docker image の certbot/certbot は動かせなかった……(力尽きた)