🔧
AWS 環境でメンテナンスモードを実現する
目的
- メンテナンスモード中は全てのリクエストをメンテナンスページへ誘導したい
- メンテナンスモードと非メンテナンスモードの切り替えを簡単にしたい
- プロダクトの関係者が動作確認することができる
さっそく結論ですが、WAF でメンテナンスモードを実現することにした。理由は以下の通り。
- ルールの切り替えだけで済むため非常にシンプル
- ルールを工夫しておけばプロダクトの関係者が動作確認するため穴を開けておくことができる
ざっくりとしたインフラ構成図は以下の通りで、前段にいる WAF でブロックしメンテナンスページを返すイメージ。
実装にあたり以下のページを参考にさせていただいた。
実装内容
インフラリソースの管理をCDK にて行っていた関係上、メンテナンスモード用の WAF のルールも CDK 管理をするべきか検討したが、メンテナンスモードと非メンテナンスモードの切り替えが困難かつ手順が複雑化するため CDK 管理は断念した。 (CDK で管理するべき内容ではないのもある)
シンプルに AWSCLI を利用してスクリプトにて実現した。
利用する wafv2 コマンド は以下。
- list-web-acls
- get-web-acl
- update-web-acl
メンテナンスモードの開始
メンテナンスモードの開始方法は以下の通り。
-
wafv2 list-web-acls
コマンドを利用して、操作対象の WebACL の Id と Name を取得する - 1で取得した WebACL の Id と Name で
wafv2 get-web-acl
コマンドを利用して、WebACL の現在の状態を取得する - 2で取得した WebACL から LockToken を取得する
- 2で取得した WebACL から現在のルールを取得する
- メンテナンスモード用のルールを取得する
- すでにメンテナンスモードが開始されている場合は処理を終了する
- 現在のルールにメンテナンスモード用のルールを追加して新しいルールを作成する
- 3で作成した LockToken と7で作成した新しいルールで、
wafv2 update-web-acl
コマンドを利用してルールを更新する
メンテナンスモードのコード例 (一部抜粋)
メンテナンスモード用ルール
Priority は現在のルールの最大より大きなものを設定し、最後に評価されるように調整する。
この例では、CustomResponseBodyKey が JSONMaintenanceModeResponse のカスタムレスポンスがレスポンスとして返却される設定。
maintenance_mode_rule.json
{
"Name": "MaintenanceMode",
"Priority": 100,
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "MaintenanceMode"
},
"Statement": {
"RegexMatchStatement": {
"FieldToMatch": {
"UriPath": {}
},
"TextTransformations": [
{
"Type": "NONE",
"Priority": 0
}
],
"RegexString": "/.*"
}
},
"Action": {
"Block": {
"CustomResponse": {
"ResponseHeaders": [
],
"ResponseCode": 503,
"CustomResponseBodyKey": "JSONMaintenanceModeResponse"
}
}
}
}
カスタムレスポンス
上記ルールの CustomResponseBodyKey に設定したカスタムレスポンスの実際の内容。
custom_response_bodies.json
{
"JSONMaintenanceModeResponse": {
"ContentType": "APPLICATION_JSON",
"Content": "{\"type\": \"xxxx\",\"message\": \"Service Temporarily Unavailable\"}"
}
}
スクリプトの抜粋
メンテナンスモード開始用のスクリプト。
start_maintenance_waf.sh
#!/usr/bin/env bash
... 省略 ...
get_web_acl_details() {
# 1. 操作対象の WebACL の Id と Name を取得する
local web_acl_details=$(aws wafv2 list-web-acls --scope $SCOPE --region $REGION)
local rule=$(echo $web_acl_details | jq ".WebACLs[] | select(.Name|test(\".*$APP\"))")
WEB_ACL_ID=$(echo $rule | jq -r '.Id')
WEB_ACL_NAME=$(echo $rule | jq -r '.Name')
if [ -z "$WEB_ACL_ID" ] || [ -z "$WEB_ACL_NAME" ]; then
echo "Error: WEB_ACL_ID or WEB_ACL_NAME not found" >&2
exit 1
fi
}
create_update_rule() {
# 2. IdとNameを利用して WebACL の現在の状態を取得する
local current_web_acl=$(aws wafv2 get-web-acl --id $WEB_ACL_ID --name $WEB_ACL_NAME --scope $SCOPE --region $REGION)
# 3. LockToken を取得する
LOCK_TOKEN=$(echo $current_web_acl | jq -r '.LockToken')
# 4. 現在のルールを取得する
local current_rules=$(echo $current_web_acl | jq '.WebACL.Rules')
# 5. メンテナンスモード用のルールを取得する
local new_rule=$(jq maintenance_mode_rule.json)
local new_rule_name=$(echo $new_rule | jq -r '.Name')
# 6. すでにメンテナンスモードが開始されている場合は処理を終了する
if echo $current_rules | jq -e ".[] | select(.Name == \"$new_rule_name\")" > /dev/null; then
echo "MaintenanceMode rule already exists."
exit 1
fi
# 7. 現在のルールにメンテナンスモード用のルールを追加して新しいルールを作成する
UPDATED_RULES=$(echo $current_rules | jq ". += [$new_rule]")
}
... 省略 ...
get_web_acl_details
create_update_rule
echo $UPDATED_RULES | jq
echo -n "↑↑↑ Ready to update?(y/n) :"
read INPUT
if [[ "$INPUT" == "y" ]]; then
# 8. LockToken と新しいルールで更新する
aws wafv2 update-web-acl \
--id $WEB_ACL_ID \
--scope $SCOPE \
--region $REGION \
--name $WEB_ACL_NAME \
--default-action Block={} \
--visibility-config SampledRequestsEnabled=false,CloudWatchMetricsEnabled=false,MetricName="$WEB_ACL_NAME" \
--custom-response-bodies file://custom_response_bodies.json \
--rules "$UPDATED_RULES" \
--lock-token $LOCK_TOKEN
echo "Maintenance mode rule added."
else
echo "Update cancelled."
fi
手順
- スクリプトを実行する
bash start_maintenance_waf.sh
- AWS マネージメントコンソールでWAFのルールにメンテナンスモード用のルールが追加されていることを確認する
- なんらかの API を実行し、ステータスコードが503かつ設定したJSONが返却されることを確認する
メンテナンスモードの終了
手順としては以下の通り。
-
wafv2 list-web-acls
コマンドを利用して、操作対象の WebACL の Id と Name を取得する - 1で取得した WebACL の Id と Name で
wafv2 get-web-acl
コマンドを利用して、WebACL の現在の状態を取得する - 2で取得した WebACL から LockToken を取得する
- 2で取得した WebACL から現在のルールを取得する
- メンテナンスモード用のルールを取得する
- メンテナンスモードが開始されていない場合は処理を終了する
- 4で取得した現在のルールからメンテナンスモード用のルールを削除して新しいルールを作成する
- 3で作成した LockToken と7で作成した新しいルールで、
wafv2 update-web-acl
コマンドを利用してルールを更新する
スクリプトの抜粋
メンテナンスモード終了用のスクリプト。
end_maintenance_waf.sh
#!/usr/bin/env bash
... 省略 ...
get_web_acl_details() {
# 1. 操作対象の WebACL の Id と Name を取得する
local web_acl_details=$(aws wafv2 list-web-acls --scope $SCOPE --region $REGION)
local rule=$(echo $web_acl_details | jq ".WebACLs[] | select(.Name|test(\".*$APP\"))")
WEB_ACL_ID=$(echo $rule | jq -r '.Id')
WEB_ACL_NAME=$(echo $rule | jq -r '.Name')
if [ -z "$WEB_ACL_ID" ] || [ -z "$WEB_ACL_NAME" ]; then
echo "Error: WEB_ACL_ID or WEB_ACL_NAME not found" >&2
exit 1
fi
}
create_update_rule() {
# 2. IdとNameを利用して WebACL の現在の状態を取得する
local current_web_acl=$(aws wafv2 get-web-acl --id $WEB_ACL_ID --name $WEB_ACL_NAME --scope $SCOPE --region $REGION)
# 3. LockToken を取得する
LOCK_TOKEN=$(echo $current_web_acl | jq -r '.LockToken')
# 4. 現在のルールを取得する
local current_rules=$(echo $current_web_acl | jq '.WebACL.Rules')
## 5. メンテナンスモード用のルールを取得する
local new_rule_name=$(cat maintenance_mode_rule.template.json | jq -r '.Name')
# 6. メンテナンスモードが開始されていない場合は処理を終了する
local rule_exists=$(echo $current_rules | jq -e ".[] | select(.Name == \"$new_rule_name\")")
if [ ! "$rule_exists" ]; then
echo "No maintenance mode rule found."
exit 1
fi
# 7. 現在のルールからメンテナンスモード用のルールを削除して新しいルールを作成する
UPDATED_RULES=$(echo $current_rules | jq "del(.[] | select(.Name == \"$new_rule_name\"))")
}
... 省略 ...
get_web_acl_details
create_update_rule
echo $UPDATED_RULES | jq
echo -n "↑↑↑ Ready to update?(y/n) :"
read INPUT
if [[ "$INPUT" == "y" ]]; then
# 8. LockToken と 新しいルールで更新する
aws wafv2 update-web-acl \
--id $WEB_ACL_ID \
--scope $SCOPE \
--region $REGION \
--name $WEB_ACL_NAME \
--default-action Allow={} \
--visibility-config SampledRequestsEnabled=false,CloudWatchMetricsEnabled=false,MetricName="$WEB_ACL_NAME" \
--rules "$UPDATED_RULES" \
--lock-token $LOCK_TOKEN
echo "Maintenance mode rule deleted."
else
echo "Update cancelled."
fi
手順
- スクリプトを実行する
bash end_maintenance_waf.sh
- AWS マネージメントコンソールでWAFのルールにメンテナンスモード用のルールが存在しないことを確認する
- なんらかの API を実行し、正常レスポンスが返却されることを確認する
Discussion