綺麗に死ぬITエンジニア

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で初期設定を済ませておいてください。こちらに詳しい解説があります。

以上でファイルの配置は終了です。

動作解説

本スクリプトは、以下のような挙動をします。

  1. AWSの使用IPアドレス範囲を示すJSONファイル(https://ip-ranges.amazonaws.com/ip-ranges.json)を取得する
  2. 取得したJSONファイルのsyncToken(公開時刻に基づく数値)の値を参照し、前回取得分(ip-range/HEADに格納されている)と同じならば何もしない。異なった場合、次に進む
  3. 新しいsyncTokenを保存(ip-range/HEADへ)
  4. 1で取得したJSONファイルを、ip-rangeディレクトリに保存
  5. 前回許可したCloudFrontのIPアドレス範囲を、セキュリティグループの許可対象から削除
  6. 今回取得した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側で簡単に設定できるようにしてくれそうですけどね。

皆さんも是非、設定してみてください。また、スクリプトに関して、バグや改善点等ありましたら、コメントお願いします。

※ 本記事の改良版を後日作成しました! 改良版の記事はこちらからどうぞ。