SPF認証対応

送信者偽装(なりすまし)対策のもう1つの方法としてSPF認証です。
ここではSPF認証で受信メールを拒否するのではなく、送信メールが拒否されないようにのみしています。
すべてのサイトでSPFレコードが記述されているわけでもなく、SPF認証の結果で受信拒否するにはまだまだリスクがあると判断したためです。
なお、SPF受信側の設定を行い判定までは行っていますが、その結果で受信メールを拒否するようにはしていません。

SPF送信側の設定

DNSのレコードにSPF用のレコードを追加します。(さくらの場合は、ドメインメニューからゾーン編集で設定します。)
例) ホスト名での記述例です。

TXT "v=spf1 a:phoenixknight.jp ~all"

末尾は「~all」です。「-all」ではありませんので、あいまいな設定になっています。
いずれ「-all」に変える予定です。

SPF受信側の設定

policyd-spfにはpython版とperl版があるそうですが、ここではいろいろ設定できるpython版にしました。

pythonのバージョン

CentOS7で配布されているpythonは2.7です。
pypolicyd-spf-2.xではpython3.3以降を要件にしています。
python3.3以降をインストールするにはIUSなどのレポジトリを利用すればyumでpythonのrpmパッケージをインストール可能です。
しかし、標準のpythonバージョンを3.xにしてしまうとyumが動作しなくなりますので共存させて利用する必要があります。
python 2.7の場合はpythonまたはpython2、python 3.xの場合はpython3またはpython3.xで起動するように設定すればよいと思います。

iusレポジトリを追加

$ yum install -y https://centos7.iuscommunity.org/ius-release.rpm

ius.repoがenable=1なのでenable=0に変更

$ yum search --enablerepo=ius python3

検索結果からpython36uをインストール

$ yum install -y --enablerepo=ius python36u python36u-libs python36u-devel python36u-pip python36u-tools

python3でも起動できるようにリンクを作成

$ ln -s /bin/python3.6 /bin/python3

PYDNS

python DNS Libraryです。pydnsがpython 2.x、py3dnsがpython 3.x用のようです。
py3dns-3.2.0.tar.gzをダウンロードしてインストールしました。

ダウンロード
$  wget https://files.pythonhosted.org/packages/b9/46/8ea57e6722fec2384d2de7afee81b892693ae3ed99917756d71dd0027739/py3dns-3.2.0.tar.gz

解凍
$ tar zxvf py3dns-3.2.0.tar.gz

インストール
$ cd py3dns-3.2.0
$ python3 setup.py install

PYSPF

Sender-Policy-Framework queries in Python.です。
pydnsまたはpy3dns(python3の場合)が必要です。

ダウンロード
$ wget https://files.pythonhosted.org/packages/88/4d/440c273b6a136b58fad9f779847cc90179d627f8a2f2cd8b36313664cf1b/pyspf-2.0.12t.tar.gz

解凍
$ tar zxvf pyspf-2.0.12t.tar.gz

インストール
$ cd pyspf-2.0.12t
$ python3 setup.py install

PYPOLICYD-SPF

pypolicyd-spf is a Postfix policy engine for Sender Policy Framework (SPF) checking. It is implemented in pure Python and uses the python-spf (pyspf) module. Updated for RFC 7208.です。

ダウンロード
$ wget https://launchpad.net/pypolicyd-spf/2.0/2.0.2/+download/pypolicyd-spf-2.0.2.tar.gz

解凍
$ tar zxvf pypolicyd-spf-2.0.2.tar.gz

インストール
$ cd pypolicyd-spf-2.0.2
$ python3 setup.py install

pypolicyd-spfの設定

デフォルトの設定ファイルはバックアップしておいて、ソースに付属の設定ファイルを使用します。
各設定がコメントアウトされているので有効にしたい場合はコメント文字を削除すればOKです。
今回は、ヘッダーに判定結果を追加するだけで拒否はしないようにしています。

$ cd /etc/python-policyd-spf
$ mv policyd-spf.conf policyd-spf.conf.org
$ cp /usr/local/src/pypolicyd-spf-2.0.2/policyd-spf.conf.commented policyd-spf.conf
$ vi policyd-spf.conf
cat policyd-spf.conf
#  Amount of debugging information logged.  0 logs no debugging messages
#  5 includes all debug messages.
debugLevel = 1

#  If set to 0, no messages are rejected by SPF.  This allows you to see the
#  potential impact of SPF checking in your mail logs without rejecting mail.
TestOnly = 1

#  Reject and deferred reason
#Reason_Message = Message {rejectdefer} due to: {spf}. Please see {url}

#  HELO check rejection policy. Options are:
#  HELO_reject = SPF_Not_Pass (default) - Reject if result not Pass/None/Tempfail.
#  HELO_reject = Softfail - Reject if result Softfail and Fail
#  HELO_reject = Fail - Reject on HELO Fail
#  HELO_reject = Null - Only reject HELO Fail for Null sender (SPF Classic)
#  HELO_reject = False - Never reject/defer on HELO, append header only.
#  HELO_reject = No_Check - Never check HELO.
HELO_reject = False

#  HELO pass restriction policy.
#  HELO_pass_restriction = helo_passed_spf - Apply the given restriction when
#    the HELO checking result is Pass.  The given restriction must be an
#    action as defined for a Postfix SMTP server access table access(5).
#HELO_pass_restriction

#  Mail From rejection policy.  Options are:
#  Mail_From_reject = SPF_Not_Pass - Reject if result not Pass/None/Tempfail.
#  Mail_From_reject = Softfail - Reject if result Softfail and Fail
#  Mail_From_reject = Fail - Reject on Mail From Fail (default)
#  Mail_From_reject = False - Never reject/defer on Mail From, append header only
#  Mail_From_reject = No_Check - Never check Mail From/Return Path.
Mail_From_reject = False

#  Reject only from domains that send no mail. Options are:
#  No_Mail = False - Normal SPF record processing (default)
#  No_Mail = True - Only reject for "v=spf1 -all" records

#  Mail From pass restriction policy.
#  Mail_From_pass_restriction = mfrom_passed_spf - Apply the given
#    restriction when the Mail From checking result is Pass.  The given
#    restriction must be an action as defined for a Postfix SMTP server
#    access table access(5).
#Mail_From_pass_restriction

#  Reject mail for Netural/Softfail results for these domains.
#  Recevier policy option to reject mail from certain domains when SPF is not
#  Pass/None even if their SPF record does not produce a Fail result.  This
#  Option does not change the effect of PermError_reject or TempError_Defer
#  Reject_Not_Pass_Domains = aol.com,hotmail.com

#  Policy for rejecting due to SPF PermError.  Options are:
#  PermError_reject = True
#  PermError_reject = False
PermError_reject = False

#  Policy for deferring messages due to SPF TempError.  Options are:
#  TempError_Defer = True
#  TempError_Defer = False
TempError_Defer = False

#  Prospective SPF checking - Check to see if mail sent from the defined IP
#  address would pass.
#  Prospective = 192.168.0.4

#  Do not check SPF for localhost addresses - add to skip addresses to
#  skip SPF for internal networks if desired. Defaults are standard IPv4 and
#  IPv6 localhost addresses.
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1

#  Whitelist: CIDR Notation list of IP addresses not to check SPF for.
#  Example (default is no whitelist):
#  Whitelist = 192.168.0.0/31,192.168.1.12

# SPF HELO WHITELIST: HELO/EHLO host names to skip SPF checks for.
# Example (default is no HELO_Whitelist):
# HELO_Whitelist = relay.example.com,sender.example.org

#  Domain_Whitelist: List of domains whose sending IPs (defined by passing
#  their SPF check should be whitelisted from SPF.
#  Example (default is no domain whitelist):
#  Domain_Whitelist = pobox.com,trustedforwarder.org

# Domain_Whitelist_PTR: List of domains to whitelist against SPF checks base
# on PTR match.
# Example (default is no PTR whitelist)
# Domain_Whitelist_PTR = yahoo.com

# SPF ENHANCED STATUS CODES: Override Postfix enhanced status codes to use the
# RFC 7372 codes.  Disable by setting this option to "No".
# SPF_Enhanced_Status_Codes = No

# Type of header to insert to document SPF result. Can be Received-SPF (SPF)
# or Authentication Results (AR). It cannot be both.
# Examples: (default is Received-SPF):
# Header_Type = AR
# Header_Type = SPF

# In order to avoid disclosing BCC recipients in SPF header fields,
# Hide_Receiver is set to Yes by default in the interest of maximizing
# privacy.  This setting will replace the actual recipient with <UNKNOWN> both
# in header fields and SMTP responses.  The latter may make it more difficult
# for senders to troubleshoot issues with their SPF deployments.
#Hide_Receiver = No
Hide_Receiver = Yes

# Every Authentication-Results header field has an authentication identifier
# field ('Authserv_Id'). This is similar in syntax to a fully-qualified domain
# name. See policyd-spf.conf.5 and RFC 7001 paragraph 2.4 for details.
# Default is HOSTNAME (as provided by socket.gethostname).  Authserv-Id must
# be provided if Header_Type 'AR' is used.
# Authserv_Id = mx.example.com
Authserv_Id = HEADER

# RFC 7208 recommends an elapsed time limit for SPF checks of at least 20
# seconds.  Lookup_Time allows the maximum time (seconds) to be adjusted.  20
# seconds is the default.
# Lookup_Time = 20

# Some of the available whitelisting mechanisms, i.e. Domain_Whitelist,
# Domain_Whitelist_PTR, and HELO_Whitelist, require specific non-SPF DNS
# lookups to determine if a connection should be white listed from SPF checks.
#  The maximum amount of time (in seconds) allocated for each of these checks,
# when used (none are enabled by default), is controlled by the
# Whitelist_Lookup_Time parameter.  It defaults to 10 seconds and is applied
# independently to each whitelisting method in use.
# Whitelist_Lookup_Time = 10

# RFC 7208 adds a new processing limit called "void lookup limit" (See section
# 4.6.4).  Default is 2, but it can be adjusted.
# Void_Limit = 2

# In some versions of postfix, for bizarre Sendmail compatibility reasons, the
# first header field added by a policy server is not visible to milters.  To
# make this easy to work around, set the Mock value to true and a fixed header
# field will be inserted so the actual SPF check will be the second field and
# visible to milters such as DMARC milter.
# Mock = False

TestOnlyに関して"0"で何もメッセージを記録しなくなるようです。動作確認を含め行いたいのでログに記述するよう"1"にしています。
HELO_rejectとMail_From_rejectとをFalseに変更し、判定結果でreject/deferしないようにヘッダーへの追加のみにしてあります。

Postfix側の変更

main.cfの変更点は以下を追加します。

smtpd_recipient_restrictions        = permit_mynetworks,
                                      permit_sasl_authenticated,
                                      reject_unauth_destination,
                                      reject_unauth_pipelining,
                                      reject_invalid_hostname,
                                      check_sender_access hash:/etc/postfix/check_backscatterer,
                                      reject_rbl_client zen.spamhaus.org,
                                      reject_rbl_client bl.spamcop.net,
                                      reject_rbl_client sbl.spamhaus.org,
                                      check_policy_service unix:private/policy-spf

policy-spf_time_limit = 3600s

master.cfの変更点は以下になります。

# fpr SPF
policy-spf   unix  -       n       n       -       0       spawn
   user=nobody argv=/usr/bin/python3 /usr/bin/policyd-spf /etc/python-policyd-spf/policyd-spf.conf

※スペースは半角スペースです。
※pythonはpython3を指定します。
※spawnでその都度プロセスが起動します。

postfixの再読み込み

$ systemctl reload postfix

SPF認証の動作確認

外部メールからテストメールを送信(SPF受信側の確認)および外部メールへテストメールを送信(SPF送信側の確認)を行います。
確認はpostfixのログファイル(/var/log/maillog)やメールのソースファイルのヘッダー情報です。

例)SPF送信側の確認(メールヘッダー抜粋)

Received-SPF: pass (サーバのホスト名: domain designate client-ip as permitted sender) client-ip=IPアドレス; envelope-from=<送信者のメールアドレス>; helo=送信ホストFQDN名;
Authentication-Results: サーバのホスト名; spf=pass smtp.mailfrom=送信者のメールアドレス

メールログによるSPF送信側の確認は送信先のメールサーバを管理していれば可能ですがそうでないので不可です。
外部メールサーバに送信したメールのヘッダーに判定結果がPassで追加されているのでうまくいっていると思います。

例)SPF受信側の確認(メールヘッダー抜粋)

Received-SPF: Pass (sender SPF authorized) identity=mailfrom; client-ip=IPアドレス; helo=送信ホストFQDN名; envelope-from=送信者メールアドレス; receiver=宛先メールアドレス

※identityの箇所がmaifromとなっているので、HELOでなくMailFromで認証パスしたということです。

例)SPF受信側の確認(メールログ抜粋)

Sep 12 10:14:48 ホスト名 policyd-spf[18707]: None; identity=helo; client-ip=IPアドレス; helo=FQDN名; envelope-from=送信者メールアドレス; receiver=宛先メールアドレス
Sep 12 10:14:48 ホスト名 policyd-spf[18707]: Pass; identity=mailfrom; client-ip=IPアドレス; helo=FQDN名; envelope-from=送信者メールアドレス; receiver=宛先メールアドレス

※HELOではNone、Mail_FromではPassの判定になっているのがわかります。

SPFによるなりすまし判定はMailFromであればすぐにでもできそうですが、ホワイトリストに登録する必要があるサイトなどまだまだデータが蓄積できていないのでとりあえずこの設定で情報収集です。

ディストリビューション

CentOS 7.x