CentOS7サーバーには新しいファイアウォール firewalldサービスが搭載されました。
firewalldは内部的に従来の iptablesを使用していますが、ファイアウォール機能としては新しいものと考えられます。
従来の iptablesサービスを使用することもできるのですが、当サーバーでは firewalldサービスをファイアウォールとして使用することにします。
※firewalldとサービスとしての iptablesは同時に起動できません。

なお、ネットワーク構成は ネットワーク設定 を元にしています。
ネットワークデバイス名はそれぞれの環境(ip aコマンドにより確認できます)により適宜読み替えでください。
また、内部I/Fゾーンが存在しない場合は当該箇所を読み飛ばしてください。

執筆対象 CentOSバージョン 7.1 (2015/12/02時点)
コンテンツのご利用 内容を十分に理解され、自らの判断と責任においてご利用ください。
ご利用の結果について当サイトは一切の責任を負いかねます。

ipsetのインストールと初期設定

SSHサーバーや FTPサーバー、さらに POPサーバーへの不正接続アタック、SMTPサーバーへスパムメールの送信を目的とした接続などをファイアウォールでブロックするため ipsetをインストールします。

ipsetはカーネルバージョン 2.4以降から使用できる IPアドレス管理用のユーティリティです。

以前の当サイトでは同じ目的のため、iptablesに 4000近くの IPアドレス範囲を直接エントリしていました。
CentOS7.xの firewalldにおいても iptablesコマンドを直接使用してルールをエントリすることができるのですが、その場合は firewalldの管理外となるためファイアウォール設定の一元管理ができなくなります。

また、iptables自体に 4000を超えるルールを登録するとスループットの低下のみならず、動作が不安定になることもあります。

そこで、CentOS7サーバーからは、ipsetで拒否/許可する IPアドレスを管理することにしました。

なお、国別のアクセス制御には xtables-addonsによる geoipで行う方法もありますが、カーネルのバージョンが上がったときに都度再インストールする必要がありそうなため、当サーバーでは採用を見送りました。

ipsetのインストールとスクリプト作成
# yumからパッケージをインストールします
[root]  yum -y install ipset

# firewalld(iptables)から参照しますので、firewalld起動前にipsetのセットを作成します
# (※firewalldの起動前に実行するスクリプトとして作成しています)
[root]  mkdir /usr/local/sbin/firewall
[root]  vi /usr/local/sbin/firewall/ExecStartPre.sh
----------(vi ここから)----------
#!/bin/sh
cd $(dirname $0)
# ipset用保存ディレクトリを作成します
[ ! -e ./ipset ] && mkdir ipset

# ipsetのすべてのセットを削除します
ipset destroy

# ipsetのセット/エントリ作成を行います
# それぞれ、悪質な接続元,信頼できる海外接続元,日本国内接続元になります
# ipsetのセーブデータが存在した場合はリストアします
# 存在しない場合はセットを作成します
# 存在しない場合のエントリ作成をfirewalldの起動前に行うと時間がかかり過ぎ
# firewalldがタイムアウトしますので、エントリ作成の作成はfirewalldの起動後に行います
for LIST in BLACK-LIST TRUST-LIST JP-LIST; do
  if [ -e ipset/$LIST ] ; then
    ipset restore < ipset/$LIST
  else
    ipset create $LIST hash:net
  fi
done

# 接続を一時的にブロックするIPのセットを作成します
# エントリの作成はswatchによるアクセス監視で行います
# このセットにエントリしたIPアドレスは1時間(3600秒)で自動的に削除されるようにしています
ipset create BLOCK-LIST hash:net timeout 3600
----------(vi ここまで)----------

# 実行属性を付与します
[root]  chmod +x /usr/local/sbin/firewall/ExecStartPre.sh
  

firewalldの初期設定と起動

最初に準備した ipsetのスクリプト(ExecStartPre.sh)が firewalldの起動前に実行されるように systemd - firewalld.service.dの定義をドロップインスニペットで変更します。
ドロップインスニペットの作成と反映
# systemd - firewalld.serviceの定義を変更するカスタムファイル(ドロップインスニペット)を作成します
[root]  mkdir /etc/systemd/system/firewalld.service.d
[root]  vi /etc/systemd/system/firewalld.service.d/custom.conf
----------(vi ここから)----------
[Service]
ExecStartPre=/usr/local/sbin/firewall/ExecStartPre.sh
----------(vi ここまで)----------

# systemdをリロードして、ドロップインスニペットを反映させます
[root]  systemctl daemon-reload

# firewalldを再起動してドロップインスニペットの適用を確認します
[root]  systemctl restart firewalld.service
[root]  systemctl status  firewalld.service
firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled)
  Drop-In: /etc/systemd/system/firewalld.service.d
           └─custom.conf
   Active: active (running) ...
  Process: ... ExecStartPre=/usr/local/sbin/firewall/ExecStartPre.sh (code=exited, status=0/SUCCESS)
     :

# ipsetのセットが作成されていることを確認します
# この時点では各セット内のIPアドレスエントリは空です
[root]  ipset list
Name: BLACK-LIST
Type: hash:net
     :
Name: TRUST-LIST
Type: hash:net
     :
Name: JP-LIST
Type: hash:net
     :
Name: BLOCK-LIST
Type: hash:net
     :
  

firewalldのルール定義を行う前に firewalld設定用の環境変数や関数の定義ファイルを作成します

/usr/local/sbin/firewall/environment
# 外部I/Fゾーンのネットワーク情報
EXTIF="virbr1"
EXTCIDR=$(ip a show dev $EXTIF | grep "inet " | sed -e "s/^ *inet \([0-9\.]\+\/[0-9]\+\).*$/\1/")
EXTIP=$(echo $EXTCIDR | sed "s/^\([0-9.]\+\)\/.*$/\1/")
EXTLAN=$(echo $EXTCIDR | sed "s/^\(.\+\)\.[0-9]\+\/24/\1.0\/24/")
ALLLAN=$(echo $EXTCIDR | sed "s/^\(.\+\)\.[0-9]\+\.[0-9]\+\/24/\1.0.0\/16/")

# 内部I/Fゾーンのネットワーク情報
INTIF="enp2s0"
INTCIDR=$(ip a show dev $INTIF | grep "inet " | sed -e "s/^ *inet \([0-9\.]\+\/[0-9]\+\).*$/\1/")
INTIP=$(echo $INTCIDR | sed "s/^\([0-9.]\+\)\/.*$/\1/")
INTLAN=$(echo $INTCIDR | sed "s/^\(.\+\)\.[0-9]\+\/24/\1.0\/24/")

# パラメータ
LOGLIMIT="1/s"     # ログマッチ許可    1回/秒
LOGBURST="3"       # ログマッチ最大値  3回

# IPアドレス用正規表現
IPREGEX="\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}"

# firewallコマンドエイリアス関数
# 再起動しても定義が消えないように--permanent付のコマンドも同時に実行します
FwCmd() {
  echo "firewall-cmd $*"
  eval "firewall-cmd $*"
  eval "firewall-cmd --permanent $*"
}

# firewallチェイン追加コマンドエイリアス関数
FwAddChain() {
  FwCmd "--direct --add-chain ipv4 filter $*"
}

# firewallルール追加コマンドエイリアス関数
FwAddRule() {
  FwCmd "--direct --add-rule ipv4 filter $*"
}
  
root権限で編集しています
2行目
ネットワーク環境により virbr1 を適宜変更してください。
ip aコマンドにより確認できます。
8-12行目
内部I/Fゾーンが存在しなければ不要です。

firewalldのルール定義を行います。

firewalldのルール定義
# 作成した定義ファイルを読み込みます
[root]  source /usr/local/sbin/firewall/environment

# ゾーン定義を行います
# 外部I/Fはdmzゾーン、内部I/Fはtrustedゾーンにしています
[root]  FwCmd --zone=dmz     --change-interface=$EXTIF
[root]  FwCmd --zone=trusted --change-interface=$INTIF

# ログを取得してDROPするためのLOG-DROPチェインを作成します
[root]  FwAddChain LOG-DROP
[root]  FwAddRule  LOG-DROP 1 -m limit --limit $LOGLIMIT --limit-burst $LOGBURST \
      >   -j LOG --log-prefix 'FIREWALL DROP '
[root]  FwAddRule  LOG-DROP 2 -j DROP

# 日本国内IPアドレスのチェックチェインを作成します
# LAN内(192.168.0.0/24)はすべてチェックOKにしています
# ipsetのJP-LISTに存在するIPアドレスをチェックOKにしています
# この時点では存在しない場合もチェックOK(DROPしない)にしています
# (存在しない場合にすべてのIPアドレスがDROPされてしまうのを防ぎます)
[root]  FwAddChain CHK-JP
[root]  FwAddRule  CHK-JP 1 -s $ALLLAN -j RETURN
[root]  FwAddRule  CHK-JP 2 -i $EXTIF -m set --match-set JP-LIST src -j RETURN

# IPアドレスのチェックチェインを作成します
# ipsetのTRUST-LISTに存在するIPアドレスをチェックOKにしています
# この時点では存在しない場合もチェックOK(DROPしない)にしています
# (存在しない場合にすべてのIPアドレスがDROPされてしまうのを防ぎます)
[root]  FwAddChain CHK-IP
[root]  FwAddRule  CHK-IP 1 -i $EXTIF -m set --match-set TRUST-LIST src -j RETURN

# 外部I/Fからの悪質なIPアドレスをDROPします
# BLACK-LISTは恒久的な拒否IPアドレスリストです
# BLOCK-LISTは一定時間で削除される一時的な拒否IPアドレスリストです
[root]  FwAddRule  INPUT  1 -i $EXTIF -m set --match-set BLACK-LIST src -j DROP
[root]  FwAddRule  INPUT  2 -i $EXTIF -m set --match-set BLOCK-LIST src -j DROP

# 外部I/FをDMZゾーンにしましたのでSSH接続が許可されています
# 日本国外からのSSH接続を拒否します
# ※この時点ではJP-LISTが作成されていないためCHK-JPではすべてチェックOKになります
[root]  FwAddRule  INPUT  3 -i $EXTIF -p tcp --dport 22 -j CHK-JP

# 設定したルールを確認します
[root]  iptables -nvL
  
7行目
内部I/Fゾーンが存在しなければ不要です。

日本国外IPアドレスチェック有効/無効スクリプトの作成

メールサーバーへの接続を日本国内のみに制限した場合、日本国外のサービスを利用するときなどは、日本国外 SMTPサーバーの IPアドレスを TRUST-LISTに登録することにより、サービス提供事業者からのメールが届くようにします。
しかし、新たに日本国外のサービスを利用する場合は、メール送信元の IPアドレスが不明であるため最初の認証メールが DROPされてしまい、届かないといったことが発生します。
そのため、新たに日本国外のサービスを利用する場合は
  1. 一時的に日本国外からのメールサーバー接続をすべて許可
  2. メールが届いた後にメール送信元の IPアドレスを確認
  3. TRUST-LISTに登録
  4. 日本国外からのメールサーバー接続を改めて拒否
といった手順が必要になりますので、日本国外の IPアドレスチェックを有効/無効にするスクリプトを作成しておきます。

日本国外の IPアドレスチェックを有効にするスクリプトを作成します。

/usr/local/sbin/firewall/IpCheckEnable.sh
#!/bin/sh
if [ -e $(dirname $0)/ipset/TRUST-LIST ] ; then
  RULE="ipv4 filter CHK-IP 3 -g CHK-JP"
  FWDCMD="firewall-cmd --direct"
  if [ "$($FWDCMD --query-rule $RULE)" = "no" ] ; then
    $FWDCMD --add-rule $RULE
    echo "--- 日本国外IPアドレスチェックを有効にしました"
  fi
fi
  
root権限で編集しています

日本国外の IPアドレスチェックを無効にするスクリプトを作成します。

/usr/local/sbin/firewall/IpCheckDisable.sh
#!/bin/sh
RULE="ipv4 filter CHK-IP 3 -g CHK-JP"
FWDCMD="firewall-cmd --direct"
if [ "$($FWDCMD --query-rule $RULE)" = "yes" ] ; then
  $FWDCMD --remove-rule $RULE
  echo "--- 日本国外IPアドレスチェックを無効にしました"
fi
  
root権限で編集しています

作成したスクリプトに実行属性を付与します。

[root]  chmod +x /usr/local/sbin/firewall/IpCheckEnable.sh
[root]  chmod +x /usr/local/sbin/firewall/IpCheckDisable.sh
  

firewalld起動後実行スクリプトの作成

firewalld起動後の実行スクリプトを作成して、TRUST-LISTや JP-LISTのエントリが存在していれば、日本国外の IPアドレスチェックが有効になるようします。
/usr/local/sbin/firewall/ExecStartPost.sh
#!/bin/sh
SHDIR=$(cd $(dirname $0); pwd)
# TRUST-LIST(信頼できる海外IPリスト)が存在する場合、日本国内IPのチェックを有効にします
# TRUST-LISTの存在チェックはスクリプト内部で行っています
$SHDIR/IpCheckEnable.sh

# JP-LIST(日本国内IPリスト)が存在する場合、リスト外IPをDROPするルールをfirewalldに追加します
if [ -e $SHDIR/ipset/JP-LIST ] ; then
  FWDCMD="firewall-cmd --direct"
  RULE="ipv4 filter CHK-JP 3 -j DROP"
  [ "$($FWDCMD --query-rule $RULE)" = "no" ] && $FWDCMD --add-rule $RULE
fi
  
root権限で編集しています
firewalld起動後に実行されるようにドロップインスニペットを変更します。
# 作成したスクリプトに実行属性を付与します
[root]  chmod +x /usr/local/sbin/firewall/ExecStartPost.sh

# ドロップインスニペットを変更します
[root]  vi /etc/systemd/system/firewalld.service.d/custom.conf
----------(vi ここから)----------
[Service]
ExecStartPre=/usr/local/sbin/firewall/ExecStartPre.sh
ExecStartPost=/usr/local/sbin/firewall/ExecStartPost.sh
----------(vi ここまで)----------

# ドロップインスニペットを作成したのでsystemdをリロードします
[root]  systemctl daemon-reload

# firewalldを再起動してドロップインスニペットの適用を確認します
[root]  systemctl restart firewalld.service
[root]  systemctl status  firewalld.service
firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled)
  Drop-In: /etc/systemd/system/firewalld.service.d
           └─custom.conf
   Active: active (running) ...
  Process: ... ExecStartPost=/usr/local/sbin/firewall/ExecStartPost.sh (code=exited, status=0/SUCCESS)
  Process: ... ExecStartPre=/usr/local/sbin/firewall/ExecStartPre.sh (code=exited, status=0/SUCCESS)
     :

ipset JP-LISTの作成

ipset用の日本国内IPリストを作成します。
日本国内IPアドレスのデータは PHPの GeoIPライブラリのデータ提供元でもある MaxMind社のオープンソースデータ GeoLite Countryを使用します。
2015/07/02時点の MaxMind社FAQページによりますと、無償(Lite)版のデータベースは毎月第一火曜日に更新されるとなっていますが、実際は 1日程度遅れることもあるようです。

当サーバーでは念のため第一火曜日の翌々日(木曜日)に更新するようにしています。

GeoLite Countryデータベースから ipset用の日本国内IPリスト(JP-LIST)を作成するスクリプトを作成します。

/usr/local/sbin/firewall/ipset_JP-LIST.sh
#!/bin/sh
cd $(dirname $0)
# GeoLite Countryデータベースを元に日本国内IPアドレスでJP-LISTを作成します
IPREGEX="\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}"
ipset flush JP-LIST
for IPADDR in $(cat /usr/local/share/GeoIP/GeoIPCountryWhois.csv \
              | sed -e '/,"JP",/!d' \
              | sed -e "s/^\"\($IPREGEX\)\",\"\($IPREGEX\)\",.*$/\1-\3/") ; do
  ipset add JP-LIST $IPADDR
done
ipset save JP-LIST > ipset/JP-LIST

# JP-LISTを作成したので、ファイアウォールに国外をDROPするルールを追加します
FWDCMD="firewall-cmd --direct"
RULE="ipv4 filter CHK-JP 3 -  j DROP"
[ "$($FWDCMD --query-rule $RULE)" = "no" ] && $FWDCMD --add-rule $RULE
  
root権限で編集しています

MaxMind社から GeoIPデータベースをダウンロードして、日本国内IPリスト(JP-LIST)を作成します。

GeoIPデータベースの更新
# GeoIPデータベースの格納ディレクトリを作成します
[root]  mkdir -p /usr/local/share/GeoIP

# JP-LIST作成スクリプトに実行権限を付与します
[root]  chmod +x /usr/local/sbin/firewall/ipset_JP-LIST.sh

# スクリプトファイルを作成します
[root]  vi /usr/local/sbin/update-geoip-database
----------(vi ここから)----------
#!/bin/sh
DATE=${1:-$(date +"%F")}
# 木曜日でなければ何もしないで終了します
[ $(date -d $DATE +"%w") -ne 4 ] && exit

cd /usr/local/share/GeoIP
# MaxMind社からGeoIP国別データベースをダウンロードします
wget -nc --timeout=60 --tries=3 http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip
unzip -o GeoIPCountryCSV.zip
rm -f GeoIPCountryCSV.zip

# 当月のデータベースではない場合はエラー終了します
if [ $(date -r GeoIPCountryWhois.csv +"%Y%m") -lt $(date -d $DATE +"%Y%m") ] ; then
  echo "MaxMind社のGeoIPデータベースが更新されていませんでした!" 1>&2
  exit 1
fi

# ipset用日本国内IPリストを作成します
$(dirname $0)/firewall/ipset_JP-LIST.sh
----------(vi ここまで)----------

# 初回のみスクリプトファイルを手動で実行して、日本国内IPリスト(JP-LIST)を作成します
[root]  chmod +x /usr/local/sbin/update-geoip-database
[root]  /usr/local/sbin/update-geoip-database $(date -d 2015-06-25 +"%F")
  
33行目
実行日が第一火曜日以降の最初の木曜日を過ぎていれば第二木曜日を指定して実行します。
過ぎていなければ前月の最終木曜日を指定して実行します。

火曜日が 1日だった場合の翌々日の木曜日は 3日ですので、3日から 1週間毎日実行するよう cronに登録します。
※木曜日以外はスクリプト内で何もしないで終了します。
※時差を考慮して 17時台に実行するようにします。

/etc/cron.d/update-geoip-database
PATH=/sbin:/bin:/usr/sbin:/usr/bin

22 17  3-9 *  *   root /usr/local/sbin/update-geoip-database
  
root権限で編集しています

ipset BLACK-LISTの作成

日本国内であっても執拗なアタック攻撃を繰り返すなど悪質な SSH接続元や HELOホストを切り替えながら数秒間隔でスパムメールを送信してくるメール送信元、継続的に DNSアタックやサイトへの不正ログインを繰り返す接続元などは ipsetの BLACK-LISTに登録してファイアウォールで拒否するようにします。

ipsetの BLACK-LISTに登録する IPアドレスのリストを準備します。
BLACK-LIST用ディレクトリ /usr/local/sbin/firewall/blackは事前に作成しておきます。
IPアドレスは単一/範囲/CIDRのフォーマットで指定でき、各フォーマットが混在しても構いません。

/usr/local/sbin/firewall/black/ip_list
# 単一指定のサンプルフォーマット
xxx.xxx.xxx.xxx                     # コメント

# 範囲指定のサンプルフォーマット
xxx.xxx.xxx.xxx-yyy.yyy.yyy.yyy     # コメント

# CIDR指定のサンプルフォーマット
xxx.xxx.xxx.xxx/nn                  # コメント
  
root権限で編集しています
空白行や #で始まる行、各行の空白以降の文字は無視されます
各IPアドレス行のコメントには後日再調査するときのための情報を入れておきます

IPアドレスのリストを元に ipsetの BLACK-LISTに登録するスクリプトを作成します。

/usr/local/sbin/firewall/ipset_BLACK-LIST.sh
#!/bin/sh
SHDIR=$(cd $(dirname $0); pwd)
LIST=BLACK-LIST

# IPアドレスリストからBLACK-LISTに拒否IPを追加します
ipset flush $LIST
cd $SHDIR/black
if [ -s ip_list ]; then
  for IPADDR in $(cat ip_list | sed -e "/^#/d" -e "s/^\([^[:blank:]]*\).*/\1/"); do
    ipset add $LIST $IPADDR
  done
fi
ipset save $LIST > $SHDIR/ipset/$LIST
  
root権限で編集しています

作成したスクリプトを実行して、ipsetの BLACK-LISTを登録します。

ipsetのBLACK-LISTを登録
[root]  chmod +x /usr/local/sbin/firewall/ipset_BLACK-LIST.sh
[root]  /usr/local/sbin/firewall/ipset_BLACK-LIST.sh
  

ipset TRUST-LISTの作成

当サーバーでは海外から毎日のように届く大量のスパムメールを弾くため、日本国外からのメールサーバー接続をファイアウォールで拒否 (JP-LISTで日本国内からのメールサーバーのみ接続を許可) していますので、海外のサービスを利用している場合にメールが届かなくなってしまいます。
そのため、TRUST-LISTを準備して信頼できる海外からのメールサーバーの接続を許可するようにします。

信頼できるメールサーバーの IPリストを作成するために、DNSの SPFレコードを持つドメインのリストを作成します。
TRUST-LIST用ディレクトリ /usr/local/sbin/firewall/trustは事前に作成しておきます。

/usr/local/sbin/firewall/trust/spf_list
amazon.com                      # Amazon
amazonaws.com                   # Amazon Web Service
dropbox.com                     # Dropbox
facebook.com                    # Facebook
google.com                      # Google
hotmail.com                     # Hotmail
isc.org                         # ISC(Internet Systems Consortium)
microsoft.com                   # Microsoft
microsoftonline.com             # Microsoft Online
startcom.org                    # StartSSL
twitter.com                     # Twitter
  
root権限で編集しています
空白行や #で始まる行、各行の空白以降の文字は無視されます
必要に応じて追加/削除してください

DNSの SPFレコードを持たない場合で DNSから IPアドレスが取得できるホストのリストを作成します。

/usr/local/sbin/firewall/trust/host_list
# ホスト指定のサンプルフォーマット
# ホストをFQDNで指定します
www.example.com                 # コメント
  
root権限で編集しています
空白行や #で始まる行、各行の空白以降の文字は無視されます

DNSからではなく IPアドレスを直接指定するリストを作成します。
IPアドレスは単一/範囲/CIDRのフォーマットで指定でき、各フォーマットが混在しても構いません。

/usr/local/sbin/firewall/trust/ip_list
# 単一指定のサンプルフォーマット
xxx.xxx.xxx.xxx                     # コメント

# 範囲指定のサンプルフォーマット
xxx.xxx.xxx.xxx-yyy.yyy.yyy.yyy     # コメント

# CIDR指定のサンプルフォーマット
xxx.xxx.xxx.xxx/nn                  # コメント
  
root権限で編集しています
空白行や #で始まる行、各行の空白以降の文字は無視されます
各IPアドレス行のコメントには後日再調査するときのための情報を入れておきます

各リストを元に ipsetの TRUST-LISTに登録するスクリプトを作成します。

/usr/local/sbin/firewall/ipset_TRUST-LIST.sh
#!/bin/sh
SHDIR=$(cd $(dirname $0); pwd)
LIST=TRUST-LIST
cd $SHDIR/trust

# TRUST-LISTを再作成する前にIPアドレスチェックを無効にします
$SHDIR/IpCheckDisable.sh
ipset flush $LIST

# DNSのSPFレコードからTRUST-LISTを追加します
# SPFレコードでredirectやincludeが指定されることがあるため子スクリプトで処理します
if [ -s spf_list ]; then
  for SPF in $(cat spf_list | sed -e "/^#/d" -e "s/^\([^[:blank:]]*\).*/\1/"); do
    ./dns_spf.sh $SPF 1
  done
fi
# ホスト名からTRUST-LISTを追加します
# SPFレコードでaやmxが指定されることがあるため子スクリプトで処理します
if [ -s host_list ]; then
  for HOST in $(cat host_list | sed -e "/^#/d" -e "s/^\([^[:blank:]]*\).*/\1/"); do
    ./dns_a.sh $HOST 1
  done
fi
# IPアドレスリストからTRUST-LISTを追加します"
if [ -s ip_list ]; then
  for IPADDR in $(cat ip_list | sed -e "/^#/d" -e "s/^\([^[:blank:]]*\).*/\1/"); do
    if [ "$(ipset test $LIST $IPADDR 2>&1 | grep 'NOT in set')" = "" ] ; then
      echo "$IPADDR は追加済です"
    else
      ipset add $LIST $IPADDR
    fi
  done
fi
ipset save $LIST > $SHDIR/ipset/$LIST

# TRUST-LISTを再作成したのでIPアドレスチェックを有効にします
$SHDIR/IpCheckEnable.sh
  
root権限で編集しています

DNSの SPFレコードを検索し、ipsetの TRUST-LIST にエントリを追加する子スクリプトを作成します。

/usr/local/sbin/firewall/trust/dns_spf.sh
#!/bin/sh
DOMAIN=$1
NEST=$(($2 + 1))
cd $(dirname $0)
# DNSのSPFレコードを取得します
SPF=$(dig txt $DOMAIN | grep v=spf1)
[ "$SPF" = "" ] && echo "[$DOMAIN] SPFが存在しません!" && exit

# DNSのSPFレコードからipsetのTRUST-LISTにエントリを追加します
for SPF_TEXT in $(dig txt $DOMAIN | grep v=spf1 | sed "s/^.*\"\(.*\)\".*$/\1/"); do
  KEYWORD=$(echo $SPF_TEXT | sed "s/^[^a-z]\?\([^:=]*\).*$/\1/")
  case $KEYWORD in
  redirect)
    REDIRECT=$(echo $SPF_TEXT | cut -d "=" -f 2)
    ./dns_spf.sh $REDIRECT $NEST
    ;;
  include)
    INCLUDE_SPF=$(echo $SPF_TEXT | cut -d ":" -f 2)
    ./dns_spf.sh $INCLUDE_SPF $NEST
    ;;
  ip4)
    IPADDR=$(echo $SPF_TEXT | cut -d ":" -f 2)
    ipset -q add TRUST-LIST $IPADDR
    ;;
  a)
    if [ $(echo $SPF_TEXT | grep ":") ]; then
      INCLUDE_A=$(echo $SPF_TEXT | cut -d ":" -f 2)
      ./dns_a.sh $INCLUDE_A $NEST
    fi
    ;;
  mx)
    if [ $(echo $SPF_TEXT | grep ":") ]; then
      INCLUDE_MX=$(echo $SPF_TEXT | cut -d ":" -f 2)
      DNS_MX=$(dig mx $INCLUDE_MX | grep "^$INCLUDE_MX")
      for MX_NAME in $(echo "$DNS_MX" | sed "s/[[:blank:]]\+/ /g" | cut -d " " -f 6); do
        ./dns_a.sh $MX_NAME $NEST
      done
    fi
    ;;
  esac
done
  
root権限で編集しています

ホスト名から DNSを検索し、ipsetの TRUST-LIST にエントリを追加する子スクリプトを作成します。

/usr/local/sbin/firewall/trust/dns_a.sh
#!/bin/sh
HOST=$1
NEST=$2
# DNSレコードを取得します
DNS=$(dig a $HOST | grep "^$1")
if [ "$DNS" = "" ] ; then
  [ $NEST -eq 1 ] && echo "[$HOST] DNSが存在しません!"
  exit
fi
# DNSレコードからipsetのTRUST-LISTにエントリを追加します
IFS_BACKUP=$IFS
IFS=$'\n'
for LINE in $(echo "$DNS" | sed "s/[[:blank:]]\+/ /g"); do
  TYPE=$(echo $LINE | cut -d " " -f 4)
  ADDR=$(echo $LINE | cut -d " " -f 5)
  case $TYPE in
  A)
    ipset -q add TRUST-LIST $ADDR
    ;;
  CNAME)
    ./dns_a.sh $ADDR $((NEST + 1))
    ;;
  esac
done
IFS=$IFS_BACKUP
  
root権限で編集しています

作成したスクリプトを実行して、ipsetの TRUST-LIST を登録します。

ipsetのTRUST-LISTを登録
[root] chmod +x /usr/local/sbin/firewall/ipset_TRUST-LIST.sh
[root] chmod +x /usr/local/sbin/firewall/trust/*.sh
[root] /usr/local/sbin/firewall/ipset_TRUST-LIST.sh
  

TRUST-LISTは DNSの情報を利用しています。
DNS情報は随時変更されますので、毎日 cronで再作成します。

TRUST-LISTの再作成
# 毎日実行するため cron.daily内にスクリプトを作成します
[root]  vi /etc/cron.daily/update_TRUST-LIST
----------(vi ここから)----------
#!/bin/sh
EXECSH=/usr/local/sbin/firewall/ipset_TRUST-LIST.sh
[ ! -e $EXECSH ] && exit
$EXECSH
----------(vi ここまで)----------

# 実行属性を付与します
[root]  chmod +x /etc/cron.daily/update_TRUST-LIST
  

システム起動後にipsetのエントリを作成

ipsetのエントリ作成は時間がかかり過ぎ、firwalldの起動後スクリプトで作成すると起動処理がタイムアウトしてしまいます。
そのため起動後スクリプトではセットの作成のみを行っていました。

システム起動後にエントリを作成するため、/etc/rc.d/rc.localでエントリ作成スクリプトを実行するようにします。

エントリ作成スクリプトを作成します。

/usr/local/sbin/firewall/rc.sh
#!/bin/sh
# ipset用国別IPアドレスデータベース(GeoIP)のディレクトリを作成します
GEOIPDIR=/usr/local/share/GeoIP
[ ! -e $GEOIPDIR ] && mkdir -p $GEOIPDIR

# ipset用国別IPアドレスデータベース(GeoIP)が存在しない場合は作成します
[ ! -e $GEOIPDIR/GeoIPCountryWhois.csv ] && /usr/local/sbin/update-geoip-database

# ipsetのエントリ作成を行います
# 悪質な接続元,信頼できる海外接続元,日本国内
cd $(dirname $0)
for LIST in BLACK-LIST TRUST-LIST JP-LIST; do
  [ ! -e ./ipset/$LIST ] && ./ipset_$LIST.sh
done

# 各LISTを作成したので、firewall起動後処理を再実行します
./ExecStartPost.sh
  

エントリ作成スクリプトがシステム起動後に実行されるようにします。

エントリ作成のシステム起動後実行
# エントリ作成スクリプトに実行属性を付与します
[root]  chmod +x /usr/local/sbin/firewall/rc.sh

# /etc/rc.d/rc.localでエントリ作成スクリプトが実行されるようにします
[root]  vi /etc/rc.d/rc.local
----------(vi ここから)----------
     :
RCSH=/usr/local/sbin/firewall/rc.sh ; [ -e $RCSH ] && $RCSH
----------(vi ここまで)----------
[root]  chmod +x /etc/rc.d/rc.local
  

ファイアウォール起動後の操作

新規に海外のサービスを利用するときは、サービスからのメールを受信する前に下記のスクリプトを実行して、一時的にファイアウォールの日本国外 IPアドレスチェックを無効にします。
メールを受信した後に、メールヘッダーから接続元メールサーバーの IPアドレスを調べ TRUST-LISTに登録します。
TRUST-LISTを再作成すると日本国外IPアドレスチェックは有効になります。
新規に海外のサービスを利用する場合の手順
# ファイアウォールの日本国外IPアドレスチェックを無効にします
[root]  /usr/local/sbin/firewall/IpCheckDisable.sh
     :
海外サービス利用手続きを行います
海外サービスからのメールを受信します
     :
# メールヘッダからTRUST-LISTの登録情報を調べます
# 下記はSPFレコードの存在を調べる例です
[root]  dig txt ドメイン名
     :
TRUST-LIST登録用のspf_list/host_list/ip_listの何れかに登録します
     :
# TRUST-LISTを再作成します
[root]  /usr/local/sbin/firewall/ipset_TRUST-LIST.sh
  

フィードバック

記事の内容についてのご質問、ご指摘、その他ご意見等は下記にてお願いいたします。
System House ACT公式ブログ内記事 :
ファイアウォール Firewalld

トラックバックまたはコメントにてお寄せください。