EC2のセキュリティグループにCloudFrontからしかアクセスを許可しない設定を追加する
2015-10-06
※ 本記事の改良版を後日作成しました! 改良版の記事はこちらからどうぞ。
こちらの記事にもあるとおり、当ブログはAWS上で稼働しており、なおかつCloudFront経由で配信しています。
本記事投稿日現在、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 $ 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-********" # 対象セキュリティグループのIDを指定
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
# 前回のsyncTokenの値が空でなければ
if [ -n "${HEAD}" ]; then
# 前回のIP範囲を取得
PREV_IP_RANGE=`cat ip-range/${HEAD}.json`
# 該当のセキュリティグループの許可設定を削除する
for i in `echo $PREV_IP_RANGE | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'`; do
aws ec2 revoke-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol ${PROTOCOL} --port ${PORT} --cidr ${i}
done
fi
# 新しく取得したIP範囲をセキュリティグループの許可設定に追加する
for i in `echo $IP_RANGE | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'`; do
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol ${PROTOCOL} --port ${PORT} --cidr ${i}
done
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アドレス範囲を、セキュリティグループの許可対象に追加
初回実行
初回実行時は、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回実行するように設定しています。
まとめ
結構面倒な設定が必要ですが、セキュリティグループに「0.0.0.0/0」がないのは気持ちがいいです。
ただ、この辺りの指定は将来AWS側で簡単に設定できるようにしてくれそうですけどね。
皆さんも是非、設定してみてください。また、スクリプトに関して、バグや改善点等ありましたら、コメントお願いします。
※ 本記事の改良版を後日作成しました! 改良版の記事はこちらからどうぞ。