docker-mailserverの設定
無償版g suiteが終わるので、久しぶりに自前でメールサーバを立ててみる。
実際に運用するかどうかは、さておき・・・
dockerでたてたら楽できるかな。
githubからcloneしてmailserver.envをちょいちょいいじってdocker-compose upするも動かず。
./configに設定ファイルがあるが、docker-compose.ymlではvolumes定義が以下のようになってるせい、っぽい。
./docker-data/dms/config/:/tmp/docker-mailserver/
setup.shは./configがあればそれを使う、なかったら./docker-data/dms/configを使う動きをする。
docker-compose.yml側がなんでこんな設定になっているのか謎。
docker-compose upする前にsetup.shで1つはメールアドレス作っておかないとエラーになって起動できない(LDAP有効にしている場合は、また別)。
./setup.sh email add user@example.com password
作成したメールアドレスの確認は以下。
./setup.sh email list
以下の文言見ると、setup.shはもう使わずにdocker execを使え、ということだろうか。docker-compose runのほうが、ボリュームとの整合性取れてよさそう。
Since Docker Mailserver v10.2.0, setup.sh functionality is included within the Docker image. The external convenience script is no longer required if you prefer using docker exec <CONTAINER NAME> setup <COMMAND> instead.
公式には「If you're new to docker-mailserver, it is recommended to use the script setup.sh for convenience.」って書いてある。
ただ、以下の状況を考えると、docker-compose runでコンテナ内のsetupを呼んだほうが混乱がなくてよさそう。
- setup.shが最終的にコンテナ内の/usr/local/bin/setupを実行するだけのラッパーである
- configディレクトリをdocker inspectで取得しているので、コンテナが起動していないとvolume情報を正しく取得できない
- 初めて起動する前に、setupを叩く必要がある
あと、個人的には以下も理由になるか。
- setup.sh実行にはbash5.4以降が必要。自分が使っている環境がCentOS7系で、bash5.2なので。
setup.sh叩いたらエラーになった。
shopt -s inherit_errexitでエラー。
これが使えるのはbash4.4以降。
今の環境はCentOS7で、bashは4.2。
ひとまずbash5系をローカルにインストール。
setup.shを書き換えてbash5で動くように変更。
サーバ証明書はLet's Encryptが使えるみたい。
mailserver.envでSSL_TYPE=letsencryptを定義。
docker-compose.ymlで/etc/letsencryptをコンテナの/etc/letsencryptにマウントしてあげる。
OP25Bの中に住んでいるので、メール送信はプロバイダのsmptサーバに投げる。
mailserver.envでRELAY設定ができる。
DEFAULT_RELAY_HOSTとRELAY_HOSTの使い分けがわからん・・・
postfixのmain.cfでrelayhostの指定がDEFAULT_RELAY_HOSTかな。
docker-compose upした後、コンテナに潜り込んで/etc/postfix配下を確認したら、上記であってた。
ただ、/etc/postfix/sasl_passwdは作成されていたけど/etc/postfix/sasl_passwd.dbがないな。
これ、大丈夫なんだろうか。
postfix3系だとtexthashが追加されているので、これでよさそう。
https://docker-mailserver.github.io/docker-mailserver/edge/config/advanced/mail-forwarding/relay-hosts/ を読むと、「Basic configuration is done via environment variables」とある。
なので、とりあえずなんでも間でもrelay host経由でメール配信するだけならmailserver.envだけでよい。
以下機能を使いたい場合は、setupコマンドをたたく必要がある。
・Sender-dependent Authentication
・Sender-dependent Relay Host
・Excluding Sender Domains
DEFAULT_RELAY_HOSTは、記載した内容がそのままrelayhostに設定される。
RELAY_HOSTは[]でくくって記載される。
[]がついてないとMXレコードを引きに行くので、ISPからリレー用メールサーバを指定されている場合は、[]でくくっておいたほうがよさそう。
DKIMの設定。まずは鍵生成。以下URL参照。
まずは鍵作る。
./setup.sh config dkim
ドキュメントにはDNSの伝搬待って、って書いてあるけど、setup.shでDNS設定まではやってくれないよねぇ。
DNSレコード登録で設定は完了。これは楽でいいわ。
テスト運用なので、t=y; を入れておく。
ローカルなネットワークから試しに動作確認。
PCにthunderbird入れて、受信サーバ・送信サーバのホスト名はコンテナが動いているホストのIPアドレス直打ち。
受信はIMAPで993ポート、送信は465ポート。
thunderbirdの設定画面で「完了」がグレーアウトされていて押せず。
再テストボタン押しても画面変わらず。どこでエラー起きているかわからず。
以下で落ち着いた。
・受信
IMAP, 993ポート, SSL/TLS
・送信
465ポート、SSL/TLS
thunderbirdを使ってメールの送信テスト実施。relayhost経由でメールが飛んでいることを確認。
受信は外からの接続許可しないといけないので、まだ怖いのでもう少し確認してから。
メール飛んでなかった。
以下のpostfixログが出てた。
status=deferred (TLS is required, but was not offered by host relayホスト名[xxx.xxx.xxx.xxx])
今物理マシンで動かしているpostfixのバージョンみたら2.10でした。
sasl周りも設定変わってるっぽい。
使っているコンテナのイメージに含まれているpostfixは3.5.6かな。
ググったらsmtp_tls_security_levelをmayにするとよい、という情報があった。
でも./target/scripts/helpers/relay.shの中でsmtp_tls_security_level = encryptがハードコーディングされてるっぽい。
これ、mayにしたらうまくいくかなぁ。
リレーに使っているISPのメールサーバからEHLOで返ってくる応答はこんなかんじ。
220 smtp-gw ESMTP
EHLO user@example.com
250-smtp.example.com
250-PIPELINING
250-SIZE 14336000
250-ETRN
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
tcpdumpでpostfixからISPのメールサーバ間の通信をのぞいてみた。
何もしないであきらめてるっぽい。
220 smtp-gw ESMTP
EHLO mail.example.com
250-smtp.example.com
250-PIPELINING
250-SIZE 14336000
250-ETRN
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
QUIT
221 2.0.0 Bye
動いている環境(物理サーバ、postfix2.10)でパケットキャプチャ採取。
EHLOのあとAUTH PLAINで認証している。
docker環境でもあきらめずに認証してくれる道はないものか。
コンテナ起動後、コンテナの中に入ってpostconf smtp_tls_security_level=mayしてあげたら、当初のエラーは回避できた。
ただ、別物の問題が発生。「450 4.1.8 <user@example.com: Sender address rejected: Domain not found」とな。
送信できている環境(postfix2.19)と今回の環境(postfix3.5.6)で、認証回り比較してみると、以下の違いがあった。
うまくいってる(2.10):AUTH PLAINで認証
うまくいかない(3.5.6):AUTH LOGINで認証
MAIL FROMで指定しているメールアドレスは、どちらもISPで提供されているメールのドメイン名ではない。
postconf smtp_sasl_mechanism_filter=plainで認証情報合わせたけどダメだった。
よく見るとエラーメッセージは「Domain not found」だな・・・
動いている環境と動いてない環境で、MAIL FROMが違ってた・・・
動いていない環境のMAIL FROMは、テスト用だからいいやと思ってMXレコードも作ってなかった。
試しにMXレコード登録したら、エラーなく通った。
postconf smtp_tls_security_level=mayは、使うISPのsmtpサーバの都合上、どうしても入れないといけない。これをどこで入れるか。
configディレクトリの中にuser-patches.sh.distなんてファイルがあるけど、うまいこと設定入れ込める仕掛けあるのかな。
user-patches.shとしてスクリプト作っておくことで、デーモン起動前に叩いてくれる。
postconf smtp_tls_security_level=mayをここで叩いて対応完了。
マルチドメインの設定、docker-mailserverでどうやるか試行錯誤。
ふと思い立って、何もせずに「setup email add 別のドメインのメールアドレス」叩いたら、コンテナ起動時にいい感じに設定してくれることが分かった。
設定ファイルいじるよりも、setupコマンド通していろいろ設定したほうがいいのかな?
/etc/postfix/relayhost_mapはsetup email addで追加したメールアドレスからよろしく作ってくれる。
main.cfのrelayhostはmailserver.envで書かないとダメっぽい。
受信専用サーバとして建てられるかな、と思って、設定をすべて消したうえで以下のようにエイリアスのみ追加して起動してみた。
docker-compose run mailserver setup alias add user@example.jp user@example.com
結果、Dovecotでエラーになる。なので、最低1アカウントはユーザ定義が必要になる。
mailserver | [ TASKLOG ] Welcome to docker-mailserver 10.4.0
mailserver | [ TASKLOG ] Initializing setup
mailserver | [ TASKLOG ] Checking configuration
mailserver | [ TASKLOG ] Configuring mail server
mailserver | [ FATAL ] Unless using LDAP, you need at least 1 email account to start Dovecot.
mailserver | 2022-01-31 17:26:24,772 WARN received SIGTERM indicating exit request
手順の整理
- git clone
- docker-compose.yaml修正
- volume周り
- 基本いじらなくてよい
- let's encrypt使う場合は、キー周りのディレクトリをマウント
- domainname: 基本、いじらなくていいんだけど、いじっておく
- mailserver.env
- SSL使う場合はSSL_TYPE
- リレー使う場合はRELAY_*
- imap/pop3使わない場合はSMTP_ONLY=1
- ユーザを追加する
- このタイミングではまだコンテナは起動しない
- setup.shは使わない。docker-composeでsetupをたたく
- ユーザ追加はemail add
- エイリアス追加はalias add
- DKIM設定
- 鍵作る
- DNS設定する
- コンテナ起動
- クライアントから接続確認
git cloneではなく、公式の手順通りdocker-compose.ymlとmailserver.envを入手する方法がよさそう。setup.shは使わない。
configディレクトリ問題も起こらないし。
setupはpostfixやdovecotのコマンドを叩いていない。ということは、コンテナが動いているときに
setupコマンドを叩いた場合、動いているpostfixやdovecotに設定は反映されない?
実際にやってみると、postfix, dovecotともに再起動された。何か仕組みがありそう。
check-for-changes.shでファイルのアップデートをチェックしている。保持しているチェックサムと一致しなかったらアップデートがあったとして処理をする。
チェック対象のファイルはで定義されている_monitored_files_checksums中で定義されている。
local CERT_FILES=(
/etc/letsencrypt/live/"${SSL_DOMAIN}"/*.pem
/etc/letsencrypt/live/"${HOSTNAME}"/*.pem
/etc/letsencrypt/live/"${DOMAINNAME}"/*.pem
)
# CERT_FILES should expand to separate paths, not a single string;
# otherwise fails to generate checksums for these file paths.
#shellcheck disable=SC2068
(
cd /tmp/docker-mailserver || exit 1
exec sha512sum 2>/dev/null -- \
postfix-accounts.cf \
postfix-virtual.cf \
postfix-aliases.cf \
dovecot-quotas.cf \
/etc/letsencrypt/acme.json \
${CERT_FILES[@]}
)
設定ファイル再作成後supervisorctl restart postfixでpostfixを再起動している。
update-check.shでdocker-mailserverのバージョンを定期的にチェックし、アップデートがあったらPOSTMASTER_ADDRESSへメールを送ってる。
POSTMASTER_ADDRESSの使われ方がちょっと想定外。
SSL_TYPE, let's encrypt以外も調べてみた。
せっかくなので安いところでサーバ証明書1枚買ってみた。let's encrypt以外で初めてのサーバ証明書。
SSL_TYPE=manualにして、証明書と鍵を指定。起動はするが、「SSL alert number 48」が出て動かず。
取得したサーバ証明書と鍵使って試しにngix(SSLあり)立ち上げてみたけど、特に問題なくアクセスできるし、chromeからはサーバ証明書も問題なく認識できる。
上記で建てたnginxに対してCentOS7からアクセスしたらうまくいかない。CA周り?
エラー: xxx の証明書(発行者:`xxx')の検証に失敗しました: 発行者の権限を検証できませんでした。
サーバ証明書に中間証明書を入れていなかった。この辺はやっぱり初めてやるので、知らないことあるな~。
TYPE=customの場合、1つのファイルに証明書と鍵を入れればいいみたい。
試しに入れてみたらerror loading chainでkey not firstと突っ込まれた。ファイルの中で先に鍵がくればいいのかな、と思い、鍵+証明書にしたらうまくいった。
customの場合は証明書ファイル名が固定になる。
/tmp/docker-mailserver/ssl/(ドメイン付きホスト名)-full.pem
ログローテートは、LOGROTATE_INTERVALでdaily、weekly、monthlyが選べる。取得世代数は4で固定。
mailserver.envに定義はまだない。
1か月ちょいテストドメインで運用してみて、いろいろ出てきた。
・アップデートチェックが動いていない?
・設定ファイルのチェックが1秒ごとに動くんだけど、貧弱環境で動かしているのでちょっと重い
・ログ保持期間がデフォルトだと短い気がする
docker-mailserverのアップデートチェック、ログを見ていると動いている。でも、アップデート土メールが飛んできてない。
2022-03-02 13:23:54 Info: Remote version information fetched
2022-03-02 13:23:54 Info: No update available
2022-03-03 13:23:54 Info: Remote version information fetched
2022-03-03 13:23:55 Info: Update available [ 10.4.0 --> 10.5.0 ]
アップデート通知は以下のような処理になっている。
echo "${MAIL}" | mail -s "Mailserver update available! [ ${VERSION} --> ${LATEST} ]" "${POSTMASTER_ADDRESS}" && \
docker-compose execで動いているコンテナの中に入って、手で上記コマンドを打ってみたところ、リレー先のメールサーバからはじかれてた。
メール送付先はPOSTMASTER_ADDRESSなんだけど、メール送付元がmail.example.comになっていた。で、mail.example.comに対するMXレコードが存在しないため、はじかれていた。
メールサーバ自体のホスト名は適当でいいや、と思ってたけど、まさかここで使われているとは。
メールアドレスが「example.com」なので、ホスト名もこれに合わせてしまう。
docker-compose.ymlのコメントを見ると、この場合hostnameに書いてしまえばよいっぽい。
# If the FQDN for your mail-server is only two labels (eg: example.com),
# you can assign this entirely to `hostname` and remove `domainname`.
hostname: example.com
#domainname: example.com
コンテナの中からmailコマンドを実行し、問題なくメールが飛ばせることを確認。
docker-compose.ymlをgithubから取り直して、新しい環境でdocker-compose upしたらエラーになった。
$ docker-compose up -d
ERROR: The Compose file './docker-compose.yml' is invalid because:
Unsupported config option for services: 'mailserver'
docker-composeのバージョンで動く・動かないがありそう。
バージョン | 動作 |
---|---|
docker-compose version 1.25.0, build unknown | 動かない |
docker-compose version 1.29.1, build c34c88b2 | 動く |
動かない場合、docker-compose.ymlの先頭にversionの定義を入れてあげれば動いた。
version: "3"
services:
mailserver:
image: docker.io/mailserver/docker-mailserver:latest
ログ保持期間はLOGROTATE_INTERVALでログローテとの期間を選ぶことしかできない。
何世代保持するかは4で固定。
case "${LOGROTATE_INTERVAL}" in
( 'daily' )
_log 'trace' 'Setting postfix logrotate interval to daily'
LOGROTATE="${LOGROTATE} rotate 4\n daily\n"
;;
( 'weekly' )
_log 'trace' 'Setting postfix logrotate interval to weekly'
LOGROTATE="${LOGROTATE} rotate 4\n weekly\n"
;;
( 'monthly' )
_log 'trace' 'Setting postfix logrotate interval to monthly'
LOGROTATE="${LOGROTATE} rotate 4\n monthly\n"
;;
( * )
_log 'warn' 'LOGROTATE_INTERVAL not found in _setup_logrotate'
;;
2月にいじった際LOGROTATE_INTERVAL定義がなかったが、最近のものには定義が入っている。
# Changes the interval in which log files are rotated
# **weekly** => Rotate log files weekly
# daily => Rotate log files daily
# monthly => Rotate log files monthly
#
# Note: This Variable actually controls logrotate inside the container
# and rotates the log files depending on this setting. The main log output is
# still available in its entirety via `docker logs mail` (Or your
# respective container name). If you want to control logrotation for
# the Docker-generated logfile see:
# https://docs.docker.com/config/containers/logging/configure/
#
# Note: This variable can also determine the interval for Postfix's log summary
reports, see [`PFLOGSUMM_TRIGGER`](#pflogsumm_trigger).
LOGROTATE_INTERVAL=weekly
なるべく長く保持したいので、LOGROTATE_INTERVAL=monthlyを追加。
設定ファイルのチェックが1秒ごとに動くんだけど、貧弱環境で動かしているのでちょっと重い件、Feb 8, 2022の変更でチェック間隔が1秒から2秒になった。
根本的に止める方法はなさそう
- supervisor-app.confでchangedetectorとして定義されている
- daemons-stack.sh内で_start_daemon_changedetectorが「_default_start_daemon 'changedetector'」として定義されている
- ENABLE_LDAPが0の場合、_start_daemon_changedetectorが呼ばれる
[[ ${ENABLE_LDAP} -eq 0 ]] && _register_start_daemon '_start_daemon_changedetector'
ミスって永遠にキューにたまってしまったメール、どうやって消そう。
postsuperでキュー指定して削除するようなパターン。
特にsetup.shとしては機能は提供されていない。
gmailで使っていたメールアドレスをdocker-mailserverで運用を始めてみました。
サーバ側でメールの振り分けできたらいいなぁ、ともったらちゃんと機能入ってた。ちょっと調べてみよう・・
ユーザごとの定義は2つの方法でできる。
To specify a user-defined Sieve filter place a .dovecot.sieve file into a virtual user's mail folder e.g. /var/mail/example.com/user1/.dovecot.sieve. If this file exists dovecot will apply the filtering rules.
./docker-data/dms/mail-data/<ドメイン>/<ユーザ名>/配下に.dovcot.sieveでフィルタファイルを作る方法。
It's even possible to install a user provided Sieve filter at startup during users setup: simply include a Sieve file in the docker-data/dms/config/ path for each user login that needs a filter. The file name provided should be in the form <user_login>.dovecot.sieve, so for example for user1@example.com you should provide a Sieve file named docker-data/dms/config/user1@example.com.dovecot.sieve.
./docker-data/dms/config/<メールアドレス>.dovecot.sieveでフィルタファイルを作る方法。
ユーザに設定方法を開放するとなると、どっちも一長一短で迷う。前者は該当フォルダにdovecotのファイルがあって、間違えて壊したら怖い。
後者はユーザごとに設定フォルダが分かれてない。
Manage Sieveなんて機能があるみたい。これ使えばファイルを直接いじらなくてもよさそう。
The Manage Sieve extension allows users to modify their Sieve script by themselves. The authentication mechanisms are the same as for the main dovecot service. ManageSieve runs on port 4190 and needs to be enabled using the ENABLE_MANAGESIEVE=1 environment variable.
アクセスは、個別のクライアントが必要。thunderbirdなら、以下のアドオン。
docker-mailserver公式だと、thunderbirdのアドオンは以下が提示されてました。
同じだった・・・
そしてthunderbird78より後だと、このアドオン動かないらしい。
sieveってソフトウェアの名前だと思ってたら、プロトコルだった。RFC5228。
↓ ↓
rfc5228のupdateが6785なので、読むならこっちかな?
少し設定してみた。もうちょい落ち着いたらまとめてみよう。
subjectは日本語も通るっぽいけど、メールのヘッダで複数行に分かれている場合(メーラだと1行にしてくれる)は、それを意識してフィルタを書かないといけない・・・?
ちょいちょいimapに総当たりでログインしようとする悪い子が出てきた。
FAIL2BANは有効にしているんだけど、それなりにアタックの間隔開けてくるので、現状ひっかからない。
Apr 12 21:04:13 mailsv dovecot: auth: passwd-file(operator,141.98.11.17): unknown user (SHA1 of given password: 4aed7f)
Apr 12 21:25:21 mailsv dovecot: auth: passwd-file(dispatch,141.98.11.17): unknown user (SHA1 of given password: 7db432)
Apr 12 21:46:15 mailsv dovecot: auth: passwd-file(exmerge,141.98.11.17): unknown user (SHA1 of given password: f04095)
root@mailsv:/etc/fail2ban# fail2ban-client status dovecot
Status for the jail: dovecot
|- Filter
| |- Currently failed: 1
| |- Total failed: 51
| `- File list: /var/log/mail.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
「Fail2Ban is installed automatically and bans IP addresses for 3 hours after 3 failed attempts in 10 minutes by default.」とのこと。身内で使っているので、ちょっとでも変だったら容赦なくbanしたい。
カスタマイズは、config配下にfail2ban-jail.cfを置いてあげればよい、と。
11.0が出たので差分調査
mailserver.envから。
- PERMIT_DOCKERのデフォルトが空文字列からnoneに変更された。10.5のデフォルトと同じ動作は、11.0ではcontainerが新設された。ちなみに、11.0で空文字列入れるとnoneと同じになる。postfixのmynetworksに影響があるので、「同じネットワーク内だったら問答無用で受け入れる」設定が必要な人は対処が必要。
- DMS_DEBUG廃止。11.0のmailserver.envでも定義は残ってるけど「REMOVED in version v11.0.0! Use LOG_LEVEL instead.」って書いてある。そしてスクリプト中では誰もDMS_DEBUGを使ってない。LOG_LEVELを使う。
- TZ追加。docker-compose.ymlのボリューム定義で/etc/localtime:/etc/localtime:roがあったはず。後で関係を調べる。
- CLAMAV_MESSAGE_SIZE_LIMIT追加。clamavでスキャンする対象のリミットを設定できる。デフォルトで25M
- REPORT_RECIPIENTの意味がちょっと変わってる。10.5のデフォルトは0で、これはメールでのレポートを行わない、という意味だった。11.0では何か指定すると、それをメールアドレスとして取り扱う。ここは対処必須な気がする。
- ログローテーション定義がREPORT_INTERVALからLOGROTATE_INTERVALに変更。動作が他にも影響していないか確認しておこう。
docker-compose.ymlに差分はなかった
setup.shは微妙に変わっていた。バグフィックスかな。
10.4から11.0へのバージョンアップ。手順としてはよくないかも。実際にやった手順をそのまま示しておきます。
- setup.sh入れ替え
- docker-compose pull
- docker-data/dms/config/postfix-main.cfから「myorigin=$mydomain」を削除。11.0から送信ドメインがdomainに従うようになったらしい。試してみる。
- docker-data/dms/config/fail2ban-jail.cf更新。banactionを「nftables-allports」に修正。
- docker-compose down
- mailserver.env修正
- TZ=Asia/Tokyoを追加
- PERMIT_DOCKERをnoneに変更
- REPORT_RECIPIENTを空欄に変更
-
11.0にしてみて気が付いたこと。
- メールデータはそのまま移行できた(何も手を入れなかったけど大丈夫だった)
- ./setup.sh debug fail2banが、./setup.sh fail2banに変更されていた。
- fail2banのbanlistは、./setup.sh fail2banの実行結果からは、引き継がれているように見える。実際に正しく動いているのは、後で調べる。
- TZを設定した影響はよくわからない・・・
- myoriginは$myhostname。