はじめに
とある案件で、Cloudflare
が保持するIPレンジを自動で取得する機能があればなぁと思い、Cloudflare
の使用IPレンジを更新する処理を作ったので紹介したいと思います。
機能を導入したきっかけ
先日始めてCDNサービスのCloudflare
を使う機会がありました。
同じCDNサービスの1つであるAWSのCloudFront
は仕事でも使用しており、その際、オリジンサーバへ直接アクセスさせないようにするため、CloudFront
からオリジンサーバへアクセスするときに使用するIPアドレスが登録されているマネージドプレフィックスリストをセキュリティグループと一緒に使用して、オリジンサーバへのアクセスはCloudFront
からのアクセスに限定するようなことを行っておりました。
CloudFront
であれば、VPC設定の「マネージドプレフィックスリスト」にCloudFront
が使用するIPレンジの一覧が登録されているため、簡単にセキュリティグループ設定で送信元IPリストとして使用することができますが、Cloudflare
はAWSサービスではないので登録されておりません。
幸い、Cloudflare
が使用するIPレンジは以下で公開されており、APIも存在するということで、じゃあ定期的に公開サイトから一覧を取得して登録してしまおうと思ったのがきっかけです。
マネージドプレフィックスリストとは
AWSのVPC設定の中にある「マネージドプレフィックスリスト」は複数IPアドレスをまとめたリストで、セキュリティグループ等を作成する際に送信元・送信先IPアドレスとしてまとめて指定するようなことができる便利な機能です。
また、CloudFront
の使用IPやRoute53
のヘルスチェック時のIPなど、AWSのサービスが使用するIPはあらかじめ登録されているため、登録済みのマネージドプレフィックスリストを指定するだけで、各サービスからのアクセスに限定するようなことができます。
あらかじめ登録されているリスト以外でも自分で新たなリストを登録することもできるので、今回はCloudflare
用のマネージドプレフィックスリストを作成しようと思います。
マネージドプレフィックスリスト定期更新処理
以下より実際にCloudflare
の使用IPを登録したマネージドプレフィックスリストを更新する処理を作成していきたいと思います。
今回導入する機能の概要は以下となります。
処理の内容としてはEventBridge Scheduler
で定期的にCodeBuild
を実行し、CodeBuild
でCloudflare
の使用IPレンジ公開サイトと、あらかじめ登録済みのマネージドプレフィックスリストのアドレスを比較して、IPアドレスが追加・削除されていればマネージドプレフィックスリストを更新するといったものとなります。
今回はCodeBuild
で処理を実行していますが、Lambda
が書けるならLambda
でも問題ありません。
以下より実際に各種サービスを作成していきます。
マネージドプレフィックスリストの作成
Cloudflare
のIPアドレスを格納するマネージドプレフィックスリストをあらかじめ作成しておきます。
VPCダッシュボードより「マネージドプレフィックスリスト」の「プレフィックスリストの作成」から作成していきます。
IPv4用、IPv6用それぞれ作成しようと思うので、以下のように設定しました。
項目 | IPv4プレフィックスリスト用設定 | IPv6プレフィックスリスト用設定 |
---|---|---|
プレフィックスリスト名 | cloudflare.global.ipv4.ips | cloudflare.global.ipv6.ips |
最大エントリ | 20 | 10 |
アドレスファミリー | IPv4 | IPv6 |
プレフィックスリストのエントリ | 未設定 | 未設定 |
タグ | ※今回は無し | ※今回は無し |
プレフィックスリスト名は特に指定があるわけではないので任意の名前で問題ありませんが、最大エントリには注意する必要があります。
マネージドプレフィックスリストは作成時、あらかじめ「最大エントリ」で記載されている値の分の領域を確保する必要がありますが、あまりに大きい値を最初から確保してしまうと、セキュリティグループで使用する際にセキュリティグループのデフォルトのルール上限の60に引っかかってしまいます。
※見かけ上IPをまとめているだけで実際には1エントリずつ登録しているのと変わらないため
「最大エントリ」で設定している上限に達した場合、拡張はできますが、縮小はできず、拡張する場合も自動で拡張されるわけではなく、オペレーションを行う必要があるので、ギリギリの値に設定すると、アドレスの追加があった場合、エラーとなってしまいます。
2024年6月現在、Cloudflare
のIPv4アドレス使用レンジ数が15アドレスレンジ、IPv6アドレス使用レンジ数が7アドレスレンジのため、多少余裕を持たせてIPv4の最大エントリを20、IPv6の最大エントリを10としました。
また、プレフィックスリストのエントリですが、後ほどCodeBuild
でまとめて登録するため、何も登録しなくてOKです。
CodeBuildの設定
更新処理を行うCodeBuild
を作成していきます。
CodeBuild用IAMポリシーの作成
後ほどCodeBuild
に割り当てる必要があるため、あらかじめIAMポリシーとIAMロールを作成しておきます。
IAMダッシュボードの「ポリシー」→「ポリシーの作成」から、ポリシーエディタより以下のJSONポリシーを流し込みます。
今回はリソースを「*」としてしまっているので、実際にシステムに導入するような場合はリソース指定するなど適宜調整してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CodeBuildPolicy",
"Effect": "Allow",
"Action": [
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:UpdateReport",
"codebuild:BatchPutCodeCoverages",
"codebuild:BatchPutTestCases"
],
"Resource": ["*"]
},
{
"Sid": "ManagedPrefixListPolicy",
"Effect": "Allow",
"Action": [
"ec2:GetManagedPrefixListEntries",
"ec2:ModifyManagedPrefixList",
"ec2:DescribeManagedPrefixLists"
],
"Resource": ["*"]
},
{
"Sid": "CloudWatchLogsPolicy",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": ["*"]
}
]
}
今回はポリシー名を「cloudflare-update-ip-codebuild-policy」として進めます。
CodeBuild用IAMロールの作成
作成したIAMポリシーを使い、IAMロールも作成します。
「ロール」→「ロールを作成」から、以下のように設定していき、CodeBuild用IAMロールを作成します。
項目 | 設定 |
---|---|
信頼されたエンティティタイプ | AWSのサービス |
サービスまたはユースケース | CodeBuild |
ユースケース | CodeBuild |
許可ポリシー | cloudflare-update-ip-codebuild-policy |
許可の境界を設定 | 許可の境界なしでロールを作成 |
ロール名 | cloudflare-update-ip-codebuild-role |
説明 | ※デフォルトのまま |
タグ | ※今回は無し |
CodeBuildの作成
CodeBuildダッシュボードの「ビルドプロジェクト」→「プロジェクトを作成する」よりCodeBuildを作成していきます。
項目 | 設定 |
---|---|
プロジェクト名 | cloudflare-update-ip-codebuild-proj |
追加設定 | ※デフォルトのまま |
ソースプロバイダ | 指定なし |
プロビジョニングモデル | オンデマンド |
環境イメージ | マネージド型イメージ |
コンピューティング | EC2 |
オペレーティングシステム | Amazon Linux |
ランタイム | Standard |
イメージ | aws/codebuild/amazonlinux2-x86_64-standard:5.0 |
イメージのバージョン | このランタイムバージョンには常に最新のイメージを使用してください |
GPU強化コンピューティングを使用 | チェック無し |
サービスロール | 既存のサービスロール |
ロールのARN | ※先ほど作成したCodeBuild用IAMロールのARNを指定 |
AWS CodeBuildにこのサービスロールの編集を許可し、このビルドプロジェクトでの使用を可能にする | チェック |
追加設定 | ※環境変数のみ指定(環境変数は次表に記載) |
Buildspec | ビルドコマンドの挿入 ※Buildspecは後述 |
バッチ設定を定義 | チェック無し |
アーティファクト1タイプ | アーティファクトなし |
追加設定 | ※デフォルトのまま |
CloudWatch Logs | チェック |
グループ名 | /codebuild/cloudflare-update-ip-logs |
ストリーム名のプレフィックス | cloudflare-update-ip-stream |
S3ログ | チェックなし |
上記の追加設定で行う環境変数では以下IPv4用、IPv6用の変数を追加し、値には先ほど作成したマネージドプレフィックスリストID(pl-xxx
で始まるプレフィックスID)を入力します。
名前 | 値 | タイプ |
---|---|---|
PREFIX_LIST_ID_IPV4 | pl-xxxxxxxxxxxxxxxxx | プレーンテキスト |
PREFIX_LIST_ID_IPV6 | pl-yyyyyyyyyyyyyyyyy | プレーンテキスト |
Buildspec
については以下を貼り付けます。
version: 0.2
env:
variables:
# AWS_DEFAULT_REGION : デフォルトリージョン
# OLD_LIST_FILE_IPv4_FILE : 古いIPv4アドレスリストファイル名
# NEW_LIST_FILE_IPv4_FILE : 新しいIPv4アドレスリストファイル名
# DELETE_LIST_IPv4_FILE : 削除IPv4アドレスリストファイル名
# ADD_LIST_IPv4_FILE : 追加IPv4アドレスリストファイル名
# OLD_LIST_FILE_IPv6_FILE : 古いIPv6アドレスリストファイル名
# NEW_LIST_FILE_IPv6_FILE : 新しいIPv6アドレスリストファイル名
# DELETE_LIST_IPv6_FILE : 削除IPv6アドレスリストファイル名
# ADD_LIST_IPv6_FILE : 追加IPv6アドレスリストファイル名
# PREFIX_LIST_ID_IPV4 : IPv4マネージドプレフィックスリストID
# PREFIX_LIST_ID_IPV6 : IPv6マネージドプレフィックスリストID
AWS_DEFAULT_REGION: "ap-northeast-1"
OLD_LIST_IPV4_FILE: "list_ipv4_old"
NEW_LIST_IPV4_FILE: "list_ipv4_new"
DELETE_LIST_IPV4_FILE: "delete_ipv4_list"
ADD_LIST_IPV4_FILE: "add_ipv4_list"
OLD_LIST_IPV6_FILE: "list_ipv6_old"
NEW_LIST_IPV6_FILE: "list_ipv6_new"
DELETE_LIST_IPV6_FILE: "delete_ipv6_list"
ADD_LIST_IPV6_FILE: "add_ipv6_list"
# PREFIX_LIST_IDは外部変数で指定するためコメントアウト
# PREFIX_LIST_ID_IPV4: "pl-xxxxxxxxxxxxxxxxx"
# PREFIX_LIST_ID_IPV6: "pl-yyyyyyyyyyyyyyyyy"
phases:
pre_build:
on-failure: ABORT
commands:
# IPv4マネージドプレフィックスリストのエントリを取得
# buildspec.yml内ではステップをまたぐと終了コードで判定できないため、エラーチェック用変数で判定
- |
aws ec2 get-managed-prefix-list-entries --prefix-list-id ${PREFIX_LIST_ID_IPV4} | jq -r '.Entries[].Cidr' | sort > ${OLD_LIST_IPV4_FILE}
ERRCHK_IPV4=${PIPESTATUS[0]}
# エラー処理
- |
if [ ${ERRCHK_IPV4} -ne 0 ]; then
exit 1
fi
# IPv6マネージドプレフィックスリストのエントリを取得
# buildspec.yml内ではステップをまたぐと終了コードで判定できないため、エラーチェック用変数で判定
- |
aws ec2 get-managed-prefix-list-entries --prefix-list-id ${PREFIX_LIST_ID_IPV6} | jq -r '.Entries[].Cidr' | sort > ${OLD_LIST_IPV6_FILE}
ERRCHK_IPV6=${PIPESTATUS[0]}
# エラー処理
- |
if [ ${ERRCHK_IPV6} -ne 0 ]; then
exit 1
fi
# CloudflareのIPv4IPアドレスリストを取得
- |
curl -s --request GET --url https://api.cloudflare.com/client/v4/ips --header 'Content-Type: application/json' | jq -r '.result.ipv4_cidrs[]' | sort > ${NEW_LIST_IPV4_FILE}
ERRCHK_IPV4=${PIPESTATUS[1]}
# エラー処理
- |
if [ ${ERRCHK_IPV4} -ne 0 ]; then
exit 1
fi
# CloudflareのIPv6IPアドレスリストを取得
- |
curl -s --request GET --url https://api.cloudflare.com/client/v4/ips --header 'Content-Type: application/json' | jq -r '.result.ipv6_cidrs[]' | sort > ${NEW_LIST_IPV6_FILE}
ERRCHK_IPV6=${PIPESTATUS[1]}
# エラー処理
- |
if [ ${ERRCHK_IPV6} -ne 0 ]; then
exit 1
fi
# 比較用ファイル作成
- diff ${OLD_LIST_IPV4_FILE} ${NEW_LIST_IPV4_FILE} | awk '/^</ {print $2}' > ${DELETE_LIST_IPV4_FILE}
- diff ${OLD_LIST_IPV4_FILE} ${NEW_LIST_IPV4_FILE} | awk '/^>/ {print $2}' > ${ADD_LIST_IPV4_FILE}
- diff ${OLD_LIST_IPV6_FILE} ${NEW_LIST_IPV6_FILE} | awk '/^</ {print $2}' > ${DELETE_LIST_IPV6_FILE}
- diff ${OLD_LIST_IPV6_FILE} ${NEW_LIST_IPV6_FILE} | awk '/^>/ {print $2}' > ${ADD_LIST_IPV6_FILE}
build:
on-failure: ABORT
commands:
# IPv4マネージドプレフィックスリストの更新判定
- |
if [ ! -s ${DELETE_LIST_IPV4_FILE} ] && [ ! -s ${ADD_LIST_IPV4_FILE} ]; then
# 何も実行しない
:
else
# マネージドプレフィックスリストの現在のバージョン取得と削除&追加用コマンド作成
LIST_VERSION_IPV4=$(aws ec2 describe-managed-prefix-lists --prefix-list-ids ${PREFIX_LIST_ID_IPV4} | jq -r '.PrefixLists[].Version')
DELETE_LIST_IPV4_COMMAND="$(awk '{printf "Cidr=%s ", $1}' ${DELETE_LIST_IPV4_FILE})"
ADD_LIST_IPV4_COMMAND="$(awk -v timestamp="$(date +'%Y-%m-%d')" '{printf "Cidr=%s,Description=Add_" timestamp "_Cloudflare ", $1}' ${ADD_LIST_IPV4_FILE})"
# マネージドプレフィックスリストへの追加&削除がある場合
if [ -s ${DELETE_LIST_IPV4_FILE} ] && [ -s ${ADD_LIST_IPV4_FILE} ]; then
aws ec2 modify-managed-prefix-list \
--prefix-list-id ${PREFIX_LIST_ID_IPV4} \
--remove-entries ${DELETE_LIST_IPV4_COMMAND} \
--add-entries ${ADD_LIST_IPV4_COMMAND} \
--current-version ${LIST_VERSION_IPV4} 2>&1 > /dev/null
ERRCHK_IPV4=$(echo $?)
# マネージドプレフィックスリストへの削除がある場合
elif [ -s ${DELETE_LIST_IPV4_FILE} ] && [ ! -s ${ADD_LIST_IPV4_FILE} ]; then
aws ec2 modify-managed-prefix-list \
--prefix-list-id ${PREFIX_LIST_ID_IPV4} \
--remove-entries ${DELETE_LIST_IPV4_COMMAND} \
--current-version ${LIST_VERSION_IPV4} 2>&1 > /dev/null
ERRCHK_IPV4=$(echo $?)
# マネージドプレフィックスリストへの追加がある場合
else
aws ec2 modify-managed-prefix-list \
--prefix-list-id ${PREFIX_LIST_ID_IPV4} \
--add-entries ${ADD_LIST_IPV4_COMMAND} \
--current-version ${LIST_VERSION_IPV4} 2>&1 > /dev/null
ERRCHK_IPV4=$(echo $?)
fi
fi
# IPv6マネージドプレフィックスリストの更新判定
- |
if [ ! -s ${DELETE_LIST_IPV6_FILE} ] && [ ! -s ${ADD_LIST_IPV6_FILE} ]; then
# 何も実行しない
:
else
# マネージドプレフィックスリストの現在のバージョン取得と削除&追加用コマンド作成
LIST_VERSION_IPV6=$(aws ec2 describe-managed-prefix-lists --prefix-list-ids ${PREFIX_LIST_ID_IPV6} | jq -r '.PrefixLists[].Version')
DELETE_LIST_IPV6_COMMAND="$(awk '{printf "Cidr=%s ", $1}' ${DELETE_LIST_IPV6_FILE})"
ADD_LIST_IPV6_COMMAND="$(awk -v timestamp="$(date +'%Y-%m-%d')" '{printf "Cidr=%s,Description=Add_" timestamp "_Cloudflare ", $1}' ${ADD_LIST_IPV6_FILE})"
# マネージドプレフィックスリストへの追加&削除がある場合
if [ -s ${DELETE_LIST_IPV6_FILE} ] && [ -s ${ADD_LIST_IPV6_FILE} ]; then
aws ec2 modify-managed-prefix-list \
--prefix-list-id ${PREFIX_LIST_ID_IPV6} \
--remove-entries ${DELETE_LIST_IPV6_COMMAND} \
--add-entries ${ADD_LIST_IPV6_COMMAND} \
--current-version ${LIST_VERSION_IPV6} 2>&1 > /dev/null
ERRCHK_IPV6=$(echo $?)
# マネージドプレフィックスリストへの削除がある場合
elif [ -s ${DELETE_LIST_IPV6_FILE} ] && [ ! -s ${ADD_LIST_IPV6_FILE} ]; then
aws ec2 modify-managed-prefix-list \
--prefix-list-id ${PREFIX_LIST_ID_IPV6} \
--remove-entries ${DELETE_LIST_IPV6_COMMAND} \
--current-version ${LIST_VERSION_IPV6} 2>&1 > /dev/null
ERRCHK_IPV6=$(echo $?)
# マネージドプレフィックスリストへの追加がある場合
else
aws ec2 modify-managed-prefix-list \
--prefix-list-id ${PREFIX_LIST_ID_IPV6} \
--add-entries ${ADD_LIST_IPV6_COMMAND} \
--current-version ${LIST_VERSION_IPV6} 2>&1 > /dev/null
ERRCHK_IPV6=$(echo $?)
fi
fi
# エラー処理
- |
if [ ${ERRCHK_IPV4} -ne 0 ] || [ ${ERRCHK_IPV6} -ne 0 ]; then
exit 1
fi
post_build:
on-failure: CONTINUE
commands:
# 作業用ファイル削除
- rm -f ${OLD_LIST_IPV4_FILE} ${NEW_LIST_IPV4_FILE} ${DELETE_LIST_IPV4_FILE} ${ADD_LIST_IPV4_FILE}
- rm -f ${OLD_LIST_IPV6_FILE} ${NEW_LIST_IPV6_FILE} ${DELETE_LIST_IPV6_FILE} ${ADD_LIST_IPV6_FILE}
CodeBuildのテスト
CodeBuild
が完成したので、一度実行して動作するかを確認しておきます。
CodeBuildダッシュボードの「ビルドプロジェクト」から作成したCodeBuild
のビルドプロジェクトを選択し、「ビルドを開始」で実行してみます。
問題なければ先程何もエントリの登録をしなかったIPv4とIPv6のプレフィックスリストに以下のようにCloudflare
のIPリストが追加されているはずです。
CodeBuild
で追加されたエントリについては説明欄に追加した日付が出るようにしているため、説明欄を見てCodeBuild
で追加されたものなのか確認してもOKです。
EventBridge Schedulerの設定
ここまでの設定で一通りの処理はできるようになりましたが、先程作成したCodeBuild
を定期実行するため、EventBridge Scheduler
で実行するようにします。
- EventBridge Scheduler用IAMポリシーの作成
- EventBridge Scheduler用IAMロールの作成
- EventBridge Schedulerの作成
- EventBridge Schedulerのテスト
EventBridge Scheduler用IAMポリシーの作成
先程のCodeBuild
のIAMポリシーと同じように作成します。
ポリシー名は「cloudflare-update-ip-eb-policy」として進めます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EventBridgeSchedulerPolicy",
"Effect": "Allow",
"Action": "codebuild:StartBuild",
"Resource": "*"
}
]
}
EventBridge Scheduler用IAMロールの作成
IAMロールも同様に作成します。
ただし、EventBridge Scheduler
はユースケースに登録されていないようなので、「カスタム信頼ポリシー」から作成していきます。
項目 | 設定 |
---|---|
信頼されたエンティティタイプ | カスタム信頼ポリシー |
サービスまたはユースケース | CodeBuild |
ユースケース | CodeBuild |
許可ポリシー | cloudflare-update-ip-eb-policy |
許可の境界を設定 | 許可の境界なしでロールを作成 |
ロール名 | cloudflare-update-ip-eb-role |
説明 | ※デフォルトのまま |
タグ | ※今回は無し |
カスタム信頼ポリシーは以下で指定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EventBridgeSchedulerAssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "scheduler.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EventBridge Schedulerの作成
EventBridgeダッシュボードの「Scheduler」→「スケジュール」の「スケジュールを作成」から作成します。
cron式で指定している時間はお好きな時間に指定してください。
項目 | 設定 |
---|---|
スケジュール名 | cloudflare-update-ip-schedule |
説明 | ※今回は無し |
スケジュールグループ | default |
頻度 | 定期的なスケジュール |
タイムゾーン | (UTC+09:00) Asia/Tokyo |
スケジュールの種類 | cronベースのスケジュール |
cron式 | cron(0 1 * * ? *) |
フレックスタイムウィンドウ | オフ |
時間枠 | ※サマータイム設定は行わないためスキップ |
ターゲットAPI | テンプレート化されたターゲット |
選択ターゲット | CodeBuild StartBuild |
CodeBuildプロジェクト | ※先ほど作成したCodeBuildプロジェクトを指定 |
リクエストパラメータ | {} |
スケジュールを有効化 | 有効化 |
スケジュール完了後のアクション | NONE |
再試行ポリシー | 再試行 |
イベントの最大経過時間 | 24hour(s) 0minute(s) |
再試行回数 | 185times |
デッドレターキュー(DLQ) | なし |
暗号化 | チェックなし |
アクセス許可 | ※先ほど作成したEventBridge Scheduler用IAMロールを指定 |
EventBridge Schedulerのテスト
先ほど手動でCodeBuild
を実行して確認しましたが、EventBridge Scheduler
から実行して動作するかを確認してみます。
cron式で指定した時間に、EventBridge Scheduler
からCodeBuild
が実行され、成功していれば完了です。
(おまけ)terraformとシェル
今回の手順をまとめたterraform
コードをGitHubに置いたので手作業でやりたくない方はお使いください。
※terraform化にあたり、IAMポリシー部分はリソース指定して厳格化しています。
また、CodeBuild
でやっていた更新作業をシェルにしました。
IPv4、IPv6プレフィックスリストIDをシェル内のPREFIX_LIST_ID_IPV4
とPREFIX_LIST_ID_IPV6
に記載し、AWSにアクセス権限がある端末から以下のように実行すれば更新されます。
./cloudflare_update_ip.sh
また、更新があれば以下のように結果を確認できます。
--------------------
削除リスト
--------------------
# IPv4
192.0.2.0/24
# IPv6
2001:db8::/32
--------------------
追加リスト
--------------------
# IPv4
103.31.4.0/22
104.16.0.0/13
# IPv6
2c0f:f248::/32
手軽にシェル実行で更新したい方はこちらをお使いください。
おわりに
今回はCodeBuild
で処理を行うようにしましたが、Lambda
等他の方法でも実装できるので、やりやすい方法で実装してもらえれば良いかなと思います。
なにかのお役に立てれば幸いです。