自己ホスト型サイトのMail-Receiverでダイレクトデリバリー受信メールを設定する

DMARCサポートを無効にするには、具体的にどうすればよいのでしょうか?

つまり、mail-receiver.ymlenv セクションに INCLUDE_DMARC: false を追加しても、うまくいかないようです。これにより opendkim および opendmarc デーモンが実行されなくなる(ログに警告が表示される)ようですが、SPFチェックは引き続き実行されています。

追記:
さらに、env セクションに次の POSTCONF_ 行を追加することで、SPFチェックを無効にできたようです。

env:
  ...
  INCLUDE_DMARC: false
  POSTCONF_smtpd_recipient_restrictions: check_policy_service unix:private/policy
  ...

これは、DMARCチェックを導入したコミットを見て、INCLUDE_DMARC が false の場合に何が起こるべきかを確認して得たものです。

Dockerイメージがどのようにビルドされるかについてはほとんど知りませんが、INCLUDE_DMARC フラグは、mail-receiver.yml で設定できるものではなく、他の誰かが、どこかで、いつか設定すべきものであるという印象を受けています。

「いいね!」 2

ufw でポート 443 を開く必要がありました。そうしないと、logsAPI Request Preparation Failed というエラーが発生しました。標準のインストール手順で ufw の有効化について言及されているため、これは言及するのが最善だと思いました。

ポート 25 は mail-receiver.yml で言及されており、ufw をバイパスしているようです。

「いいね!」 1

GitHubリポジトリはOPにあるべきですか?

「いいね!」 3

メール受信者をご利用の皆様へ、Remove smtp_should_reject & discourse-smtp-fast-rejection をご覧ください。

元の機能が壊れており、ユーザーに問題を引き起こしていたため、fast-rejection を完全に削除する予定です。具体的には、次のような問題が発生していました。

また、事前配信テストがエンベロープの From と To をチェックしていましたが、Discourse はヘッダーの値のみを使用しているため、転送メールにも影響します。

「いいね!」 1

mail-receiver.yml サンプルファイル内の DISCOURSE_BASE_URL 値の不要な引用符を削除するための PR を提出しました。引用符が原因で私のセットアップが機能しなくなっていました。引用符をなくすことで、このドキュメントの完了が成功します。

どのように壊れるのか説明していただけますか?この値の引用符の有無は違いを生みません。

[2] pry(main)=> YAML::load("env:\n  DISCOURSE_BASE_URL: 'https://discourse.example.com'")
=> {"env"=>{"DISCOURSE_BASE_URL"=>"https://discourse.example.com"}}

[3] pry(main)=> YAML::load("env:\n  DISCOURSE_BASE_URL: https://discourse.example.com")
=> {"env"=>{"DISCOURSE_BASE_URL"=>"https://discourse.example.com"}}

そのコンテナのログを追跡し、メッセージを送信していたとき、discourse.example.com は MX レコードの一部ではありませんのようなエラーが多数表示されていました。引用符を削除し、コンテナを再構築したところ、動作し始めました :person_shrugging:

イベントの順序も関係しているかもしれません。

  1. メールレシーバーコンテナを設定して起動しました
  2. 数日後、MX DNS レコードを設定しました
  3. MX レコードが正しく設定されていることを確認してからテストを開始しました。動作しませんでした
  4. 引用符を削除し、コンテナを再構築したところ、動作し始めました

したがって、解決策が引用符の削除に関連していたのか、それとも MX レコードが作成された後のコンテナの再構築に関連していたのかはわかりません。

最悪の場合、PR は yml を一貫性のあるものにします :slight_smile:

「いいね!」 1

メール受信者が常にベースフォーラムと同じドメインであると想定されているようです。そうでない場合、TLSをどのようにセットアップしますか?

例:
forum => forum.domain.tld
mail-receiver => mail.domain.tld

mail-receiver.ymlでは、TLSがベースフォーラムの証明書を指しています。mail-receiverが独自の証明書を取得する方法はありますか?

直接の答えはわかりませんが、コンテナ構築中にコンテナ内で変更を加えるためのymlに追加のオプションが必要になると思われます。

これについては後ほど詳しく説明しますが、なぜ別のドメインで実行したいのか理由をお伺いしたいです。メールレシーバーはそれに特化しており、変更なしではペアのDiscourseインスタンスのメールを受信するためだけに機能するため、通常はそのインスタンスと同じドメインで動作させることが合理的です。


Discourseのymlに含めるテンプレート(すでに使用されているものもあるでしょう)をいくつか確認すると、yml(コンテナ構築中)を通じてコマンドの実行やファイルの変更を行う方法について、いくつかのヒントが得られるはずです。

web.onion.template.ymlにはファイル内の文字列を置換する方法の例があり、web.letsencrypt.ssl.template.ymlはメインのDiscourseコンテナにLet’s Encryptを追加するものです。

ベースイメージ内のものにどれだけ依存しているかわからないので、メインのDiscourseコンテナに2つ目の証明書を取得させ、その後mail-receiver.yml内の証明書/キーのパスをそれに合わせて変更する方が簡単な場合があります。

このアプローチを取る場合、注意深くこれらの種類の変更を行ってください。変更がどのような影響を及ぼすかを正確に把握していることを確認してください。例えば、Let’s Encryptの処理に誤った変更を加えると、証明書の更新がサイレントに失敗し、約3ヶ月後に訪問者が証明書の期限切れエラーを受け取るまで気づかない可能性があります。

CloudFlare 使用例

この手順は、Cloudflare プロキシを使用しているセルフホスト型 Discourse フォーラム向けです。

Cloudflare プロキシを使用すると、SMTP (ポート 25) トラフィックがサーバーに到達するのをすべて防ぎます。そのため、メールレシーバーを機能させるには、別のサブドメインを設定する必要があります。

たとえば、ドメインが forums.domain.tld の場合、mail.domain.tld のような新しいサブドメインを作成する必要があります。

Cloudflare を使用する場合、以下の追加手順が必要です。

  1. 新しいサブドメインの A レコードを作成します。これは、forums.domain.tld と同じ IP アドレスを使用します。
  2. メインの手順で提供されているように、新しいサブドメインの MX レコードを作成します。

このわずかな変更を加えて、メインの手順に従ってください。TLS セキュリティをオフにしても、すべて正常に機能します。

TLS セキュリティを実行したい場合は、追加の作業が必要になります。

TLS セットアップの概要

これらの手順では、Certbot と CloudFlare Certbot プラグインをインストールします。コマンドは、DNS 証明書プロセスによるスタンドアロンで Let’s Encrypt 証明書を取得します。証明書が利用可能になったら、コンテナが使用できるようにメールレシーバーの共有領域にコピーされます。Discourse はすでにポート 80 を使用しているため、DNS モデルを使用する必要があります。

DNS チャレンジ

証明書の所有権を HTTP 経由で証明する代わりに、certbot はDNS に TXT レコードを作成することによって証明します。DNS が Cloudflare であるため、Cloudflare API トークンを使用して完全に自動化できます。ポート 80 は不要で、Web サーバーをシャットダウンする必要もありません。

仕組み

Certbot → Cloudflare に _acme-challenge.mail.lotuselan.net TXT レコードを作成
Let's Encrypt → その TXT レコードをルックアップ → 検証 → 証明書を発行
Certbot → TXT レコードを削除

これらはすべて、Discourse コンテナ内ではなく、ベースサーバー上で行われます。

セットアップ

1 — certbot と Cloudflare certbot プラグインをインストールします。

bash

apt install certbot python3-certbot-dns-cloudflare -y

2 — Cloudflare API トークンを作成します。

  1. Cloudflare → マイプロフィール → API トークン → トークンの作成に移動します
  2. **「ゾーン DNS の編集」**テンプレートを使用します
  3. 権限: ゾーン → DNS → 編集
  4. ゾーンリソース: 含める → 特定のゾーン → lotuselan.net
  5. IP 制限: サーバーの IP アドレスからのアクセスのみを許可するように設定します
  6. トークンをコピーします

3 — トークンを資格情報ファイルに保存します。

bash

mkdir -p /etc/letsencrypt/cloudflare
nano /etc/letsencrypt/cloudflare/credentials.ini

貼り付け:

dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN

ファイルをロックダウンします:

bash

chmod 600 /etc/letsencrypt/cloudflare/credentials.ini

4 — 証明書をリクエストします:

以下のコマンドを管理者メールアドレスとドメイン名で更新します。

bash

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  --non-interactive \
  --agree-tos \
  --email youremailadress@domain.tld \
  -d mail.domain.tld

結果に、次のステートメントが表示されるはずです。

Certbot has set up a scheduled task to automatically renew this certificate in the background.

Certbot は、証明書の有効期限を 1 日に 2 回チェックするための cron ジョブを設定します。有効期限が 30 日以内の場合、証明書を更新します。これは、次のように検証できます。

# systemd タイマーがアクティブかどうかを確認します (ほとんどの最新の Ubuntu システム)
systemctl status certbot.timer

# または、cron ジョブが追加されたかを確認します
cat /etc/cron.d/certbot

これで、新しいメールレシーバードメイン名の TLS 証明書がサーバーに配置されました。それらは使用できる場所にはありません。

5 — ファイルを移動するためのデプロイスクリプトを設定します
certbot は自動更新を行うため、Discourse 固有の部分のみを処理するスクリプトが必要になります。更新された証明書をコピーし、メールレシーバーを再構築します。certbot の組み込みのデプロイフックを使用すると、スクリプトを大幅に簡素化できます。これは、正常な更新の後に自動的に実行されます。

デプロイフックファイルを作成します:

bash

nano /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh
chmod +x /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh

これを貼り付けます:

bash

#!/bin/bash
DOMAIN="mail.domain.tld"
DISCOURSE_DIR="/var/discourse"
CERT_SRC="/etc/letsencrypt/live/${DOMAIN}"
CERT_DEST_1="${DISCOURSE_DIR}/shared/mail-receiver/letsencrypt/${DOMAIN}"
CERT_DEST_2="${DISCOURSE_DIR}/shared/mail-receiver/letsencrypt/${DOMAIN}_ecc"
ADMIN_EMAIL="admin email address"
LOG_FILE="/var/log/mail-cert-renewal.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "=== Certbot deploy hook triggered for ${DOMAIN} ==="

# 証明書をコピーします (シンボリックリンクを解決するには -L を使用)
for DEST in "$CERT_DEST_1" "$CERT_DEST_2"; do
    mkdir -p "$DEST"
    cp -L "${CERT_SRC}/fullchain.pem" "${DEST}/fullchain.pem"
    cp -L "${CERT_SRC}/privkey.pem"   "${DEST}/privkey.pem"
    cp -L "${CERT_SRC}/cert.pem"      "${DEST}/cert.pem"
    cp -L "${CERT_SRC}/chain.pem"     "${DEST}/chain.pem"
    chmod 644 "${DEST}/fullchain.pem" "${DEST}/cert.pem" "${DEST}/chain.pem"
    chmod 600 "${DEST}/privkey.pem"
    log "Certs copied to ${DEST}"
done

# mail-receiver を再構築します
cd "$DISCOURSE_DIR" || { echo "Cannot cd to ${DISCOURSE_DIR}" | mail -s "[FAILURE] Mail cert deploy hook failed" "$ADMIN_EMAIL"; exit 1; }
log "Rebuilding mail-receiver..."
if ./launcher rebuild mail-receiver >> "$LOG_FILE" 2>&1; then
    log "mail-receiver rebuilt successfully"
else
    log "ERROR: rebuild failed"
    echo "mail-receiver rebuild failed after cert renewal. Check ${LOG_FILE}" | \
        mail -s "[FAILURE] Mail cert deploy hook failed" "$ADMIN_EMAIL"
    exit 1
fi

log "=== Deploy hook completed successfully ==="

手動での cron ジョブはまったく必要ありません。certbot がプロセス全体をオーケストレーションします。デプロイフックは更新が発生した場合にのみトリガーされるため、certbot がチェックしても更新が行われない日には、メールレシーバーが不要に再構築されることはありません。

更新フックをテストするには、以下を実行します。

bash

bash /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh

すべてが正しく設定されていれば、これは
→ 証明書を Discourse ディレクトリにコピーします
→ mail-receiver を再構築します
→ すべてをログに記録します

6 — mail-receiver.yml で TLS を設定します

ここで主な問題は、forum.domain.tld の A レコードがプロキシによってマスクされていることであり、メールサーバーを別のドメインに明示的に設定したいということではないようです。

TLSをネゴシエートする際、証明書のコモンネームは、MXレコードのホスト名、つまりクライアント(別のメールサーバーである可能性あり)が接続しようとしているホスト名と比較され、それが参照するAレコードとは比較されません。 これは、mail.domain.tld の A レコードを DNS Mode Only に設定し、次に forum.domain.tld の MX レコードを mail.domain.tld を参照するように設定すれば、その構成ではそれ以上の特別な手順は必要ないことを意味します。

はい、メインのフォーラムの A レコードには DNS のみを使用できます。このアプローチを使用すると、CloudFlare のリバースグローバルプロキシ機能を失うことになります。 (私の Discourse のインストールではこれは選択肢ではありませんでした。)

そのため、このソリューションを定義する最初の行は、CloudFlare プロキシを使用しているサイト向けになっています。

私が言及していたのは、forum.domain.tldのAレコードではなく、mail.domain.tldのAレコードがDNSモードのみに設定されていることについてでしたが、SMTPクライアントがTLS証明書を認証する方法を誤解していたことに気づきました。

私が目撃していた動作は、ホスト名を検証しないデフォルトのオポチュニスティック(機会的)メソッドのアーティファクトであり、MXレコードのターゲットではなくMXレコードのホスト名を検証するという私の主張は誤りでした。ほとんどのケースでは機能しますが、DANEやMTA-STSがTLSアイデンティティ認証の強制に使用されている場合は機能しません。

Aレコードをproxied(プロキシ済み)にし、MXレコードをDNS Only(DNSのみ)にしても機能しません。CloudFlareのドキュメントによると、Aレコードがプロキシされているドメインでは、すべてのSMTPトラフィックがブロックされるとのことです。

いくつかのテストラウンドでこれを検証しました。Aレコードのプロキシを解除するとすぐにSMTPデータが流れます。プロキシを有効にすると、SMTPデータは決して流れませんでした。(テストはポート25へのTELNETを使用して実行されました。)

したがって、以下を希望する場合:

  • DiscourseフォーラムでCloudFlareプロキシサービスを使用したい
  • SMTPメール受信側でメールを受信させたい

インバウンドメールには異なるドメインを使用する必要があります。

SMTPメール受信側でTLSを希望する場合:

  • DNSチェックを介してLetsEncryptを設定する必要があります。

手順は難しそうに見えますが、手順を作成するのにかかった時間よりも、ソリューションを実際に実装するのにかかった時間の方が長かったです。

私が意図したのは、具体的には3つのDNSレコードを提案したということです。
A: forum.domain.tld → ホストIPアドレス(プロキシ有効)
A: mail.domain.tld → ホストIPアドレス(DNSモードのみ)
MX: forum.domain.tldmail.domain.tld

しかし、前述したように、これはデフォルトのオポチュニスティックTLSモードでのみ機能し、DANEやMTA-STSを有効にしてID認証を強制したい場合(暗号化されたトラフィックだけでなく、正しいサーバーに接続されていることを確認したい場合)は機能しないことに後で気づきました。

それらは非常によくできており、分かりやすく、Discourseのアップデートで壊れる可能性がないように、コンテナの外で必要なすべてを実行します。特に、以前は知らなかったcertbotの更新フックの使用が気に入っています。

「いいね!」 1

ただし、これによりフォーラムの IP アドレスが公開され、DDoS 保護や WAF などの Cloudflare 保護メカニズムを回避することが可能になることに注意してください。メールレシーバーは別のサーバーで実行する方が良いでしょう。

私の最初の意図はこの理由のためにメールレシーバーを別のサーバーで実行することでした。メールレシーバーを起動するためにランチャーアプリを試すたびに、完全なDiscourseシステムをインストールする必要がありました。スタンドアロンのDockerサーバーでのみメールレシーバーを起動して実行する簡単な方法はありますか?

私はランチャーアプリにあまり慣れていません。なぜなら、私たちは独自のインフラを構築しているからです。しかし、私の考えは、ランチャーアプリを使用せず、他のコンテナと同様に起動するというものです。必要な環境変数はreadmeにあります。

@Simon_Manning さん、@RGJ さん:Cloudflare の記事を更新し、3 つの主要なオプションとそれぞれのトレードオフを記載しました。これで、最初に提示されたオプションについてご指摘いただいた各種の問題が解決されることを願っています。

@kelv さん:メインの説明に、CloudFlare の記事に関する脚注を追加することを検討してください。これにより、該当する人々の時間を節約できます。Configure direct-delivery incoming email for self-hosted sites with Mail-Receiver - #541 by LotusJeff

「いいね!」 2