EC2のセキュリティグループにCloudFrontからしかアクセスを許可しない設定を追加する(改良版)
2015-10-09
※ AWSのLambda, SNS, S3を活用した、本記事のサーバーレス版を後日作成しました! こちらからどうぞ。
本記事投稿日現在、AWSのセキュリティグループの受信許可設定には、IPアドレスの指定および他のセキュリティグループのID指定しか対応していません。つまり、CloudFront経由でしかアクセスさせたくない場合であっても、そのような設定をすることは、現時点ではできません。
そこで今回、定期的に自動でCloudFrontのIPアドレスを調べ、セキュリティグループを設定してくれるシェルスクリプトを作成しました。
今回の記事のスクリプトは、前回の記事の改良版のスクリプトです。根本的な動作について知りたい方は、前回の記事も合わせてご覧ください。
本スクリプトのメリット
このスクリプトを利用することにより、WebサーバへのアクセスがCloudFrontからのみに絞られるため、外部からWebサーバ本体(EC2)のIPアドレスを特定することが困難となり、サーバ本体に対するHTTP/S経由以外の攻撃を強力に軽減することができます。
SSH経由で不正アクセスを試みようとも、IPアドレスがわからなければ攻撃などできません。
よって、悪意のある攻撃者に、意図的に狙い撃ちされるリスクが軽減されます。
スクリプトの設定
以下のように設定してください。
# スクリプトを動かす為のディレクトリ・ファイル群を作成します(ここでは、/root/aws_cli以下に作成します)
~ $ pwd
/root
~ $ mkdir aws_cli # ここは任意の名称で構いません。ただし、以下のディレクトリは名称を合わせてください
~ $ cd aws_cli
~/aws_cli $ mkdir ip-range # IP範囲設定を保存するディレクトリの作成
~/aws_cli $ touch ip-range/HEAD # 現在の許可IP範囲設定を保存するファイルの作成
~/aws_cli $ mkdir log # ログを保存するディレクトリの作成
~/aws_cli $ vi put-ip-range-of-cloudfront-to-security-group.sh #本体スクリプトを作成
作成するスクリプト「put-ip-range-of-cloudfront-to-security-group.sh」の内容は以下です。
#!/bin/bash
# 設定値の指定(パラメータの変更はここで行います)
SECURITY_GROUP_ID="sg-********"
PROTOCOL="tcp"
PORT="80"
# 作業ディレクトリへ移動
cd `dirname "${0}"`
# IP範囲をJSON形式で取得
IP_RANGE=`curl -s https://ip-ranges.amazonaws.com/ip-ranges.json`
# syncToken(UNIX エポック時刻形式での公開時刻)を取得
SYNC_TOKEN=`echo ${IP_RANGE} | jq -r '.syncToken'`
# 現在セキュリティグループで許可設定しているIP範囲のsyncTokenを取得
HEAD=`cat ip-range/HEAD`
# 新しく取得したIP範囲のsyncTokenと許可設定しているIP範囲のsyncTokenが違ったら
if [ "${SYNC_TOKEN}" != "${HEAD}" ]; then
# syncTokenを新しいものに変更
echo $SYNC_TOKEN > ip-range/HEAD
# JSONファイルを保存
echo $IP_RANGE > ip-range/${SYNC_TOKEN}.json
# IP_RANGEをJSON形式からCLOUDFRONTで利用中のIPアドレスの改行区切りに変換
IP_RANGE=`echo $IP_RANGE | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'`
# 前回のsyncTokenの値が空でなければ
if [ -n "${HEAD}" ]; then
# 前回のIP範囲を取得(IPアドレスの改行区切り)
PREV_IP_RANGE=`cat ip-range/${HEAD}.json | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'`
# 前回のIP範囲との差分を取得する
DIFF=`diff <(echo "${PREV_IP_RANGE}") <(echo "${IP_RANGE}")`
# 今回のIP範囲にしかないものを追加する
for i in `echo "$DIFF" | awk '{if($1==">")print $2}'`; do
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol ${PROTOCOL} --port ${PORT} --cidr ${i} &&
echo `date "+%Y/%m/%d %H:%M:%S"` "Added ${i} to the security group ${SECURITY_GROUP_ID}. Protocol=${PROTOCOL} Port=${PORT}" >> log/job.log
done
# 前回のIP範囲にしかないものを削除する
for i in `echo "$DIFF" | awk '{if($1=="<")print $2}'`; do
aws ec2 revoke-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol ${PROTOCOL} --port ${PORT} --cidr ${i} &&
echo `date "+%Y/%m/%d %H:%M:%S"` "Removed ${i} from the security group ${SECURITY_GROUP_ID}. Protocol=${PROTOCOL} Port=${PORT}" >> log/job.log
done
# 前回のsyncTokenの値が空なら(初回実行時)
else
# 新しく取得したIP範囲を全てセキュリティグループの許可設定に追加する
for i in ${IP_RANGE}; do
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol ${PROTOCOL} --port ${PORT} --cidr ${i} &&
echo `date "+%Y/%m/%d %H:%M:%S"` "Added ${i} to the security group ${SECURITY_GROUP_ID}. Protocol=${PROTOCOL} Port=${PORT}" >> log/job.log
done
fi
fi
4〜6行目の設定値については、それぞれの環境に合わせて変更してください。
また、スクリプト内部でjqコマンドを利用しています。インストールされていない場合は、以下のコマンドでインストールしてください。(Amazon Linux)
~ $ yum -y install jq
また、awsコマンドを実行しているため、aws configureで初期設定を済ませておいてください。こちらに詳しい解説があります。
以上でファイルの配置は終了です。
動作解説
本スクリプトは、以下のような挙動をします。
- AWSの使用IPアドレス範囲を示すJSONファイル(https://ip-ranges.amazonaws.com/ip-ranges.json)を取得する
- 取得したJSONファイルのsyncToken(公開時刻に基づく数値)の値を参照し、前回取得分(ip-range/HEADに格納されている)と同じならば何もしない。異なった場合、次に進む
- 新しいsyncTokenを保存(ip-range/HEADへ)
- 1で取得したJSONファイルを、ip-rangeディレクトリに保存
- 前回の情報が見つからなければ、今回取得したCloudFrontのIPアドレス範囲を、全てセキュリティグループの許可対象に追加し、ここで終了する。前回の情報があれば、次に進む
- 前回許可したCloudFrontのIPアドレス範囲と今回取得したCloudFrontのIPアドレス範囲の差分を計算
- 今回取得分にしか含まれないIPアドレス範囲を、セキュリティグループの許可対象に追加
- 前回取得分にしか含まれないIPアドレス範囲を、セキュリティグループの許可対象から削除
初回実行
初回実行時は、上記手順5.により、正常に稼働するようになっています。下記のように入力し、シェルスクリプトを実行させ、実際にセキュリティグループの設定が正しく動作しているか確認してください。
~ $ /root/aws_cli/put-ip-range-of-cloudfront-to-security-group.sh
cronの設定
問題がなければ、AWSのIPアドレス範囲変更時に自動で新IP範囲を取得・設定するように、本スクリプトを定期的に実行するようcronの設定を行います。
~ $ crontab -e
15 * * * * /root/aws_cli/put-ip-range-of-cloudfront-to-security-group.sh
ここでは、1時間に1回実行するように設定しています。
ログの閲覧
セキュリティグループへの追加&削除の操作情報は、全てログに保存されます。エラー情報は保存されません。実際に操作し成功した情報のみです。
以下のディレクトリに保存するようになっていますので、実際に正常動作しているかどうかの確認にご利用ください。
~/aws_cli $ less log/job.log
前回の記事からの変更点
前回の記事から、以下の部分について改良を加えました。
- 前回取得分と今回取得分のIPアドレス範囲の差分を計算し、セキュリティグループへの操作を最小限に抑えるように変更
- 動作ログを出力するように変更
前回のスクリプトを数日間運用してみたのですが、AWS全体のIPアドレス範囲の更新があっても、CloudFront自体のIPアドレス範囲は変わらないことが多く、無駄な処理が多いことに気づきました。なので、CloudFrontのIPが変わったときにだけ、変わったIPアドレスの分だけ最小限にリクエストを送るように修正しました。おかげで、実行速度も大分改善されたと思います。
なお、差分の計算にはdiffコマンドを使い、極力処理や記述が肥大化しないようにも努めました。
まとめ
結構面倒な設定が必要ですが、セキュリティグループに「0.0.0.0/0」がないのは気持ちがいいです。
ただ、この辺りの指定は将来AWS側で簡単に設定できるようにしてくれそうですけどね。
皆さんも是非、設定してみてください。また、スクリプトに関して、バグや改善点等ありましたら、コメントお願いします。
※ AWSのLambda, SNS, S3を活用した、本記事のサーバーレス版を後日作成しました! こちらからどうぞ。