ぼくのかんがえたさいきょうのPostfix(設定編)

前回のエントリーを踏まえて、具体的な設定を明かしていきます。

main.cfの基本設定

#smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
readme_directory = no
disable_vrfy_command = yes
あまり奇をてらったものはありません。バナーで自ホスト名は名乗った方がいいかと思いつつ、ディストリ名まで名乗る筋合いはないかなと。biffは今さら使い道があるようにも思わないので無効にしてしまっていいですし、readmeもググればもっと気の利いたものが見つかるので必要ないでしょう。
VRFY/EXPNコマンドはユーザ名が漏洩する可能性に繋がるので無効にしてしまいます。これが必須なクライアントやサーバってのも聞いたことがありませんし。

main.cfのドメインとIPアドレス関連設定

# Domain Part Settings
myhostname = phase-d.com
myorigin = $myhostname
append_dot_mydomain = no
mydestination = phase-d.com, mail.phase-d.com, www.phase-d.com, localhost.phase-d.com, localhost

mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 49.212.48.187/32 [2401:2500:102:1114:133:242:186:20]
inet_interfaces = all
inet_protocols = ipv4,ipv6

 ここは素直に名乗ります。mynetworksにはlocalhost関連と自身のグローバルIPアドレスを書いておけば十分です。IPv6対応は一応この辺だけですね、必要なのかっていう気はしますし、ログとかを見てるとGoogleくらいしかIPv6で送ってくる人いないわけで。送信時も今のところは問題に遭遇してませんが、先にIPv6を試すようなので、問題があるなら無効にしてしまうのもまたアリかもしれません。

main.cfのローカルパート設定

# Local Part Settings
alias_maps = hash:/etc/postfix/aliases
alias_database = hash:/etc/postfix/aliases
recipient_delimiter = +
unknown_local_recipient_reject_code = 550
# Local Settings
home_mailbox = Maildir/
mailbox_size_limit = 0
 これもあまり複雑なことはないです。一時的なメアドを作りたい時とかもaliasesに書いてしまう運用。
存在しないユーザ宛のメールは550を返してやるのが正義だと思います。デフォルトで550なので書かなくてもいいですが、運用上450を返したい時とかもあるかなということでとりあえず明記しています。

main.cfのSSL設定

# TLS parameters
smtpd_tls_cert_file=/etc/ssl/mail.phase-d.com/mail.phase-d.com.cert
smtpd_tls_key_file=/etc/ssl/mail.phase-d.com/mail.phase-d.com.key
smtpd_tls_CAfile=/etc/ssl/CA/cacert.pem
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
smtpd_tls_security_level=may

smtpd_tls_received_header = yes
smtp_use_tls=yes
smtp_tls_security_level=may

この辺りから徐々に本気。証明書の配置とプロトコルバージョンについて定義しておきます。もはやSSLv2とSSLv3を無効にするのは大人のマナーですよね。
クライアント認証の証明書関連も一応ここで定義しておきます。ちなみに証明書自体は独自認証局で作ったオレオレ証明書です。
あとはsmtpd_tls_security_level=mayを設定しておくことで、GmailなどのSTARTLESに対応したMTAからはSSL送信を受け付けます。逆にsmtp_use_tls=yesとsmtp_tls_security_level=mayを設定しておけばGmailなどに送る時にSSL送信を試みます。もうちょっと言うと、25番に接続してEHLO後にサーバ側から提示される可能コマンドの中にSTARTTLSがあれば、以降はSTARTTLSをしてしまう感じですね。こちらのサーバもEHLO後にSTARTTLSを提示しています。どちらもsecurity_level=mayが今のところの世の中のコンセンサスというところ。これだと証明書の検証もちゃんとしないので良いのかどうか際どいところです。とはいえmayとencryptの間に位置するパラメータもないし、少なくとも2015年末現在ではGmailを始めとしてオレオレ証明書であってもちゃんとSSLで送ってきているのでよしとします。将来、ちゃんとした証明書が必要になったとしても個人向けで数千円/年の証明書を使うか、Let’s Encryptなどの無料証明書での運用もありかなぁとは思います。

main.cfのSASL認証設定

# SASL parameters
smtpd_sasl_auth_enable = no
smtpd_sasl_local_domain = $myhostname
smtpd_sasl_security_options = noanonymous,noplaintext
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_local_domain = $myhostname
認証周りの設定はまるっとSASL経由でDovecotに投げています。Dovecot側ではpasswd-fileで認証をしてることもあり、smtpd_sasl_security_optionsにnoplaintextを指定しています。なのでMUA側では「暗号化されたパスワード」を指定できます。SSLしてるのでどっちでもという感はありますが、できるならやっとこうと。
あとはsmtp_sasl_auth_enable = yesをmain.cfに書いている設定例が多いですが、これはやっちゃダメだと思ってます。なのでここではnoを指定しておいて、後述のmaster.cfで書きます。
さらにここから先が本気でアクセス制御を試みます。

main.cfの受信制御関連設定

接続元による判定
# Chek by SMTP Connection
smtpd_client_restrictions =
 permit_mynetworks,
 check_client_access hash:/etc/postfix/access
 SMTPの接続元での制御は、まず自ホストからの接続を無条件で許可しつつ、IPアドレスで拒否できるようなブラックリストを用意します。これは運用しながら柔軟に追記できるようにしています。これを潜り抜ければ他は許可してしまいます。RBLを参照するパターンも状況によっては有用だと思いますが、少なくとも私の運用では誤爆リスクの方が高いので入れていません。
HELOコマンドによる判定
# Check by HELO
smtpd_helo_restrictions =
 permit_mynetworks,
 check_helo_access hash:/etc/postfix/access,
 reject_invalid_hostname
ここも似たような感じでブラックリストでの運用ができるようにします。ただしログなどを見ているとHELOで名乗るホスト名は結構自由な感じなので、ここで弾ける可能性はあまり期待していません。ただ特定のbotなどが送ってくるスパムの場合は一意なHELOを名乗ることが多いので、一定の有効性はあるかなとも思います。
reject_non_fqdn_hostnameやreject_unknown_hostnameを指定している設定例もよく見かけますが、誤爆リスクの方が大きいように感じるのであえて入れてません。 reject_invalid_hostnameもなんとなくで書いてる感は否めませんが。
Envelope-Fromによる判定
# Check by MAIL FROM
smtpd_sender_restrictions =
 reject_unknown_sender_domain,
 check_sender_access hash:/etc/postfix/access
ここではEnvelope-Fromのドメインが存在しない場合に拒否するようにします。
また諸般の事情から存在しないEnvelope-Fromで受け取りたい場合のホワイトリストとしても、あるいはブラックリストとしても利用できるようにします。ただしここのパラメータで引っ掛けられるのはHeader-FromではなくEnvelope-Fromであることには注意が必要です。
宛先にまつわる判定
# Check by RCPT TO
smtpd_recipient_restrictions =
 permit_mynetworks,
 reject_unauth_destination,
 check_policy_service unix:private/policy-spf
 ここが最大の肝ですかね。上から順番にこんな条件で処理されます。
・自ホストが送信する場合は許可
・宛先が自ドメイン宛て($mydestination)でないものは拒否=不正中継対策
・SPF判定を実施
これで自ドメイン宛のメールはpostfix-policyd-spfに処理が回され、SPF判定が実施されます。SPF関連の細かい設定は後ほど。
上記の通り、25番宛のアクセスに対してはSMTP-Authを許可していないので、permit_ssl_authenticatedをここで書いてやる筋合いはありません。この辺も後ほどのmaster.cfに関する辺りで。
その他ヘッダや本文による判定

# Header and Body Check
header_checks = regexp:/etc/postfix/content_checks
body_checks = regexp:/etc/postfix/content_checks

これも対スパムのブラックリストとして利用します。IPアドレスやHELOやFROMで落としにくいものを、例えばHeader-FromやMessage-IDやReceivedに書かれている中継サーバ、あるいはX-MailerやUser-Agentやcharsetなどを条件として弾いたり、添付ファイルの拡張子フィルタなどの運用を想定します。body_checksの方は、本文中のURLやNGワードの登録を想定。定義ファイルを分ける意義もあまりないと思うので、同じファイルを参照するようにしちゃいます。

main.cfのDKIMとDMARC設定

# DKIM and DMARC
smtpd_milters = inet:127.0.0.1:8891, inet:127.0.0.1:8893
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
#milter_default_action = tempfail
milter_protocol = 2
送信時・受信時共にsmtpdからmilterを経由してOpenDKIMとOpenDMARCに処理を投げて、それぞれのヘッダを付加します。現在のところはとりあえず付けてるだけで、スパム除去には積極的には利用していませんが、ゆくゆくは主流になってくる可能性はあると思うので、早めに対応しておきたいところ。この辺の解説は別エントリーに回します。万が一、OpenDKIMやOpenDMARCが落ちていた場合は、とりあえず判定をせずにスルーするため、milter_default_action = acceptを指定しておきます。
ここでmilter経由でアンチウィルスするプランも想定はしたんですが、JessieのClamAVのバージョンがやや古いのと、どうせMacでしか受信しないという割り切りで棄却しました。

main.cfの再送設定

# TempError Retry
minimal_backoff_time = 300s
maximal_backoff_time = 600s
maximal_queue_lifetime = 24h
bounce_queue_lifetime = 24h
queue_run_dely = 120s
delay_warning_time = 4h
 メール送信時に一時エラーが発生した際の再送間隔をデフォルトより短くしてます。特にqueue_lifetimeがデフォルトだと5日とかなのでちょっと長すぎ。それ以外も常識的な範囲で再送はしたいところなので、デフォルトよりは短くしてみてます。そんなアクティブに大量送信する予定もないのでという割り切りもありますが、送信量などによってはある程度の一時エラーは想定されるのでその辺は利用状況に応じて適宜というところ。

main.cfのパフォーマンスとその他制限

# Performance and Regulation
message_size_limit = 10240000
default_process_limit = 30
default_destination_concurrency_limit = 5
initial_destination_concurrency = 3

anvil_rate_time_unit = 60s
smtpd_client_message_rate_limit = 100
smtpd_client_recipient_rate_limit = 100
smtpd_client_connection_rate_limit = 100
smtpd_client_connection_count_limit = 15

メッセージサイズ制限はデフォルトのままですが、状況によっては上下させる可能性もあるのでとりあえず書いておきます。
他もデフォルトでもいいとは思いますが、流量がそんなに多くなくてしかもさくらのVPSの最安プランでリッチな構成のサーバではないにも関わらずPostfix以外にApacheやMySQLなんかも動いているので、プロセス数などは少なめに絞っています。テストしてみたところSPF判定で意外とメモリを食ってて、コンテンツサイズにもよりますがメール1通の処理に20MBくらいは使っていたので、万が一DoSを食らったりしてもスワップが発生しないようにはしたいなという目論見。
SMTPの送信・接続制限もある程度のDoS耐性になるかなという緩い期待です。本気でヤバければiptablesとかもっと低いレイヤーで叩き落とした方が正解かなとも思います。ただ企業ユースで「全従業員に個別メールを一括送信したい」みたいな使い方だと制限が発動されがちなので、流量やユーザ数などに応じて適切に調整したいところです。
ここまででmain.cfの設定は以上。

master.cfの設定

smtp      inet  n       –       –       –       –       smtpd
(中略)
smtps     inet  n       –       –       –       –       smtpd
   -o smtpd_tls_wrappermode=yes
   -o smtpd_tls_req_ccert=yes
   -o smtpd_tls_security_level=encrypt
   -o smtpd_sasl_auth_enable=yes
   -o smtpd_client_restrictions=permit_sasl_authenticated,reject
(中略)
# for SPF
policy-spf  unix  –       n       n       –       0       spawn
   user=nobody argv=/usr/bin/python /usr/bin/policyd-spf /etc/postfix-policyd-spf-python/policyd-spf.conf
デフォルトから変更しているのは最初の方にあるsmtpとsmtpsの設定、それから最後にSPFの設定を追記しています。
まずSMTPの設定では特に指定するものはなし。main.cfの設定がそのまま引き継がれる格好ですね。一方、smtpsの方ではsmtpd_sasl_auth_enable=yesを指定し、smtpd_tls_wrappermodeを有効にすることでSTARTTLSではなくセッションの最初からSSL通信を実施しつつ、同時にクライアント認証の証明書を要求します。で、SMTP-Authの認証に成功したら場合のみ配送を許可します。つまりmain.cfの設定ではなく、465番固有の設定を適用したい項目をここで書くわけです。特にSSLと認証周りの挙動は25番と分けたいのでこうしています。
smtpd_sasl_auth_enableにはちょっと思うところがあります。main.cfでyesにしていてmaster.cfに何も書いていない設定例をよくみかけますが、この状態で試してみたところ25番に接続してSMTP-Authが通ってしまうということに気がついてしまいました。実に無意味なので、25番というかデフォルトではSMTP-Authは拒否しつつ、465番で接続した場合のみ許可をします。こうしておくことで25番に接続してきたクライアントがSMTP-Authを試みた場合、仮に認証に成功するコマンドを送ってきたとしても「authentication not enabled」というエラーを返すことができます。ちなみにmain.cfにsmtpd_sasl_auth_enable=yesを書いている場合は、パスワード等が間違っていれば「authentication failed」を返すのでエラい違いです。
さて、最後にSPF。Debianではpostfix-policyd-spf-pythonとpostfix-policyd-spf-perlが利用できます。perlの方が設定はシンプル、pythonの方ができることが多い代わりにやや設定が複雑です。スパム除去のためにきめ細かい判定と拒否を行いたいのでpython版を使っています。

/etc/postfix-policyd-spf-python/policyd-spf.confの設定

debugLevel = 1
defaultSeedOnly = 1
HELO_reject = SPF_Not_Pass
Mail_From_reject = Fail
Reject_Not_Pass_Domains = hotmail.com,yahoo.co.jp,yahoo.com,ezweb.ne.jp,softbank.ne.jp,docomo.ne.jp,me.com
PermError_reject = False
TempError_Defer = False
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
HELO_rejectに関してはデフォルトのままですが、Mail_From_rejectはfail判定が出た場合Rejectするようにしてしまっています。とはいえ~allまでしか指定していないドメインも多いので、あんまり弾ける気はしていません。ちなみに当ドメインは-allを指定しているので、未知のホストから来たメールが当ドメインを名乗った場合はここに引っかかってrejectされます。
またいくつかの指定したドメイン(携帯キャリア、大手フリーメールなど。得てして~allを指定している)に関しては、Passしない限りRejectします。これでかなり多くのスパムメールを受信時にSMTPのセッション中に拒否することができます。この辺は考え方次第で、ヘッダにsoftfailを付記するだけにして、あとはMUA側の振り分けルールで対応するというのもありだとは思います。
設定としては以上です。
この設定でしばらく運用して、ブラックリストも充実してくるとSPAMの9割くらいはSMTPのセッション中にエラーを返すことができています。ざっとログを見ている感じでも誤爆はほぼ皆無といっていい状況。

コメント

タイトルとURLをコピーしました