自動で最新の国別IPリストを取得し、Nginxに反映させます

とあるサイトで日本のIPのみアクセスを許可する必要がありました。
特定の国のIPだけを許可をする方法はApacheの.htaccessを使う方法はよく解説されていますが、Nginxの情報はあまり見なかったので、このページでまとめました。
応用すれば複数の国のIPを許可したり、特定の国のみ拒否、といったことも実現できます。
最新のIPリストを毎日取得して更新
各国に割り振られているIPリストは日々変動します。そのため定期的にリストを更新する必要があります。このページではcronを利用して毎日最新のIPリストを取得します。
IPリストを取得する方法は以前投稿したiptablesの解説で作成しました。
冗長なほどコメントを入れましたが、さらに解説が必要な方は「iptablesの設定ファイルをシェルスクリプトを利用して動的に作成」というページの「世界の国別 IPv4 割り当てリストを毎日ダウンロードして最新の状態に保つ」という部分を読んでください。
このページではiptables用の部分は削除しています。(通知するメールアドレスの部分は適宜変更してください)
「/etc/cron.daily/」に「iplist_check.sh」というスクリプトを作成します。
# vi /etc/cron.daily/iplist_check.sh
#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# ファイルの取得
wget -q http://nami.jp/ipv4bycc/cidr.txt.gz -P /tmp
# 解凍してリネーム
gunzip -q -f -c /tmp/cidr.txt.gz > /tmp/iplist.new
# /tmp下なので240時間で自動的に削除されますが、念のため削除
rm -f /tmp/cidr.txt.gz
# iplistが存在しているかチェック
if [ -s /tmp/iplist ]; then
# diffでエラーが出るので実行権限の変更。「-f」を付けて初回にファイルがない場合に対応
chmod -f 700 iplist.new
chmod -f 700 iplist
# 差分チェック(正常にファイルが取得できたかチェック)
# grep -c パターンに一致した行の行数のみを出力する。diffで差分(違い)のある行は「< hoge」と表示されるため、行頭に「<」がある行をカウントしている。「=」の前後にスペースがあると正常に代入できないので注意。
# $()の中に記述しているので、変数ipdiffに代入。
ipdiff=$(diff /tmp/iplist /tmp/iplist.new | grep -c "^<")
# 上記が存在していて、差分の数が1000行以上の場合
# (通常考えられない量の変更=ファイルが正常に取得できていないと判断)
if [ $ipdiff -gt 1000 ]; then
# メールでIPリストが正常に取得できなかった由を伝える。
cat <<EOM | mail -s "iplist_check" info@example.com
iplist false
EOM
# 正常に取得できなかったリストを破棄
rm -f /tmp/iplist.new
else
# 正常に取得できた場合ファイル名をiplistに変更
mv /tmp/iplist.new /tmp/iplist
fi
else
# 初回実行時は差分をチェックすべきバックアップがないのでそのままファイル名を変更して利用
mv /tmp/iplist.new /tmp/iplist
fi
これで「/tmp/iplist」というIPリストが作成されます。
Nginxの設定ファイルを書き出すスクリプトを作成
取得したIPリストを元に、日本のIPだけを許可するNginxの設定ファイルを書き出します。
「/usr/script/」に「nginx_allow_jp.sh」というスクリプトを作成します。
# vi /usr/script/nginx_allow_jp.sh
IPリストの行頭がJPのIPを取得しNginxの形式に合わせてファイルを書き出しています。
#!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # iplistが存在しているかチェック if [ -s /tmp/iplist ]; then # 行頭がJPのIPを取得してnginxの形式に変換 if [ -s /tmp/iplist ]; then sed -n 's/^JP\t//p' /tmp/iplist | while read address; do echo allow $address";"; done echo deny all";"; fi fi
具体的には以下のような形式で出力されます。
allow 191.168.l.l; allow 191.168.l.2; (同じようなIPがずらーっと続く) deny all;
あとでcronから実行するので実行権限をあたえます。
# chmod 755 /usr/script/nginx_allow_jp.sh
cronで最新の設定ファイルを適応するスクリプトを作成
上で作成したnginx_allow_jp.shを実行し、Nginxで反映させるスクリプトを作ります。
# vi /etc/cron.daily/nginx_allow_jp_daily.sh
#!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # 日本のIPリストをnginx用の設定形式で書き出す sh /usr/script/nginx_allow_jp.sh >/etc/nginx/global/nginx_allow_jp.conf # 設定を反映 /usr/sbin/nginx -s reload
設定ファイルは「/etc/nginx/global/」に「nginx_allow_jp.conf」というファイル名で作成しました。
実行権限を当たえます。
# chmod 755 /usr/script/nginx_allow_jp_daily.sh
Nginxで外部の設定ファイルを読み込む
あとはNginx側で読み込むだけです。
設定ファイルにて、適応させたいディレクティブで、includeを利用しパスを指定するだけです。
(リバースプロキシを利用している場合はリバースプロキシ側に記述します。バックエンドサーバ側に追加しても意味が無いので注意)
# vi /etc/nginx/nginx.conf
location /hoge/ {
# 日本のIPのみ許可する
include /etc/nginx/global/nginx_allow_jp.conf;
}
これで最新のIPリストで日本のみアクセスを許可するNginxの設定は完了です。
毎日更新する必要がなければcron.weeklyやcron.monthlyをご利用ください。
正しくアクセス規制できていれば以下のように「403 Forbidden」と表示されます。
当初は「日本のIPのみアクセスを許可する」という要望に対し、上流で遮断するというセオリー通りiptablesで遮断しようと考えていました。
iptablesにはリクエストの文字列を条件にして、通信をコントロールするstringモジュールがあります。「stringモジュールを使えば日本のIPだけを許可するのも簡単だ。」と高を括っていたのですが、どうにも挙動が不安定でした。(ごく単純な指定でしか動作しませんでした)
またiptablesの解説にもstringモジュールで通信を遮断する方法は非推奨とあったので、すっぱり諦めました。
何かもっと良い方法をご存知の方がいたらコメント欄にでも一言いただけると助かります。

iptablesの設定とログファイルのローテーション
iptablesの設定ファイルをシェルスクリプトを利用して動的に作成
iptablesをシェルスクリプトで設定していて動作が遅い場合の対処法
サーバの処理を自動実行するcronの仕組みと応用法
Swatchでログを監視して、攻撃に合わせた対策を自動で実行する方法
fluentdとNorikraでDoS攻撃を遮断し、メールで通知する方法
ユーザーの環境変数を設定するbashの設定ファイルと、カスタムプロンプトについて









