AWS CLI を使って Web ACL を作成し CloudFront にアタッチする
はじめに
以前、AWS 上での S3 + CloudFront 構成のフロントエンドでメンテナンス状態にする方法を紹介しました。
この際に、メンテナンス中でも特定の IP からのアクセスは許可する方法として WAF を使って行いました。しかし上記の記事で紹介したフローを、メンテナンスの度にやっていては時間がかかりますし、手作業によるミスも発生します。
そこで AWS CLI を使ってスクリプトにしておこうと思いやってみました。
作ったもの
コードは下記に公開しています。全体を見たい方はご覧ください。
AWS CLI を使う準備
macOS 上で行っていきます。
$ sw_vers
ProductName: macOS
ProductVersion: 11.0.1
BuildVersion: 20B50
使用するコマンドです。
# AWS CLI
$ aws --version
aws-cli/2.0.6 Python/3.7.4 Darwin/20.1.0 botocore/2.0.0dev10
# JSON を整形するのに jq を使います。
$ jq --version
jq-1.6
WAF を作成
IP set を作成
まずは、Web ACL に使う許可された IP set を作成します。リモートワークで作業している場合などを考えると、複数の IP アドレスを許可したい(個数は場合によって変動する)ということが想定されます。なので、テキストファイルを別で管理します。
XXX.XX.XXX.XX/32
YYY.YY.YYY.YYY/32
次に AWS CLI を使って IP set を作っていきます。
サブコマンドは create-ip-set
です。
$ IP_LIST=$(paste -s -d " " ./ip.txt) # こういうふうにスペース区切りで連結される -> "XXX.XX.XXX.XX/32 YYY.YY.YYY.YYY/32"
$ IP_SET=$(aws wafv2 create-ip-set \
--name "maintenance-developers" \
--description "This is a test ip set" \
--scope CLOUDFRONT \
--region us-east-1 \
--ip-address-version IPV4 \
--addresses $IP_LIST)
ここでのポイントは、--scope
, --region
オプションです。CloudFront に対して Web ACL を適用する場合は、--scope CLOUDFRONT
の他に --region us-east-1
を指定する必要があります。
また、--addresses
オプションでは先程のテキストファイルを paste
コマンドで連結して引数に与えています。
後で使用するため IP_SET
という変数に入れています。
Output は下記のように Summary オブジェクトが返却されます。
{
"Summary": {
"Name": "maintenance-developers",
"Id": "XXXXX-XXXXX-XXXXX",
"Description": "This is a test ip set",
"ARN": "XXXXX-XXXXX-XXXXX",
"LockToken": "XXXXX-XXXXX-XXXXX"
}
}
Web ACL を作成
続いて Web ACL を作成していきます。サブコマンドは create-web-acl
です。
先んじてルールを定義したテンプレート用の JSON ファイルを用意しておきます。
[
{
"Name": "project-maintenance",
"Priority": 0,
"Statement": {
"IPSetReferenceStatement": {
"ARN": "" // 後の jq の処理でここに先程作成した IP set の ARN が入ります
}
},
"Action": {
"Allow": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "project-maintenance"
}
}
]
上のテンプレート用の JSON ファイルを jq
で整形し、IP set の Arn を代入して Web ACL 作成コマンドのオプションに指定します。
# jq で Summary.Arn を取り出す
$ IPSET_ARN=$(echo $IP_SET | jq .Summary.ARN)
# 取りだした Arn を付け加えた状態で JSON ファイルを作成する
$ cat ./waf-rule.json | jq '.[].Statement.IPSetReferenceStatement.ARN |= '"${IPSET_ARN}"'' > ./tmp-waf-rule.json
$ WEB_ACL=$(aws wafv2 create-web-acl \
--name "maintenance" \
--scope CLOUDFRONT \
--region us-east-1 \
--default-action Block={} \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=MaintenanceWebAclMetrics \
--rules file://tmp-waf-rule.json)
IP set と同様に --scope CLOUDFRONT
, --region us-east-1
をオプションで付与する必要があります。またもや CloudFront へのアタッチ時に使用するため変数に格納しています。
CloudFront へアタッチ
いよいよ作成した Web ACL を該当の CloudFront ディストリビューションにアタッチしていきます。WAF API を見てみると、associate-web-acl
というサブコマンドが用意されています。
しかし、これは CloudFront に対しては使用できないようです。
For Amazon CloudFront, don't use this call. Instead, use your CloudFront distribution configuration. To associate a web ACL, in the CloudFront call UpdateDistribution , set the web ACL ID to the Amazon Resource Name (ARN) of the web ACL. For information, see UpdateDistribution.
仕方ないので、CloudFront の update-distribution
コマンドを使ってやっていきます。
ただ、コマンドのオプションを見てみるとめちゃくちゃ項目が多くネストしているので、--cli-input-json
を使った方が楽そうです。以下の手順でやっていきます。
- CloudFront ディストリビューションの構成情報を JSON で取得
- 取得した JSON を整形
- CloudFront ディストリビューションを変更
では、順番にやっていきます。
CloudFront ディストリビューションの構成情報を JSON で取得
AWS CLI の CloudFront のサブコマンド get-distribution-config
を使用します。
$ DISTRIBUTION_ID=XXXXXXXX
$ aws cloudfront get-distribution-config --id $DISTRIBUTION_ID | jq . > ./dist.json
取得した JSON を整形
# 先程作成した Web ACL から ARN を抜き出す
$ WEB_ACL_ARN=$(echo $WEB_ACL | jq .Summary.ARN)
$ ERROR_RESPONSE=$(cat << EOS
{
"ErrorCode" : 403,
"ResponsePagePath": "/maintenance.html",
"ResponseCode": "503",
"ErrorCachingMinTTL": 0
}
EOS
)
$ cat ./dist.json | jq '. |= .+ {"IfMatch": .ETag} | del(.ETag)
| .DistributionConfig.CustomErrorResponses.Items |= map((select(.ErrorCode == 403) |= '"$ERROR_RESPONSE"') // .)
| (.DistributionConfig.WebACLId |= '"${WEB_ACL_ARN}"')' \
> ./new-dist.json
非常に可読性の低いコードになってしまいました…。ぱっと見何を行っているかわかりにくいので簡単に解説します。
-
ETag
-
get-distribution-config
で取得した JSON には ETag プロパティが存在するので jq のdel
関数で削除します。
-
-
CustomErrorResponses
- メンテナンス状態の場合 503 エラーを返したいため 403 エラー(= IP set に定義していないアクセス)の場合は、503 エラーを返すようにします。
-
WebACLId
- 作成した Web ACL の ARN を設定します。
そしてその整形した JSON を別ファイルに書き出しています。
CloudFront ディストリビューションを変更
最後に CloudFront の設定を変更します。
$ aws cloudfront update-distribution \
--id $DISTRIBUTION_ID \
--cli-input-json file://new-dist.json > ./result.json
以上で完了です。ここまできて S3 + CloudFront で配信しているドメインにアクセスしてみて許可 IP からは通常にアクセスできて、それ以外からはメンテナンス画面が表示されていたら無事に達成です。
まとめ
今回は、S3 + CloudFront 環境下におけるメンテナンス状態を AWS CLI を使って行いました。以下にポイントをまとめます。
- 簡単にできるだろうと思ったら AWS CLI では必須オプションが多かったり、ユースケースによってはコマンドが使えなかったりして大変だった。
- ただ、一度書いてしまえば 2 回目からはスクリプトを実行するだけなので、手作業よりは遥かに効率的できた。
- JSON の整形に jq が非常に便利だが、可読性も考慮したい。
ここまで読んでくださりありがとうございます。
他にも「こういった方法があるよ」「こっちが楽にできるよ」などありましたらコメントいただけると幸いです。
参考にさせていただいたサイト
Discussion