[小ネタ]未使用のWACL一覧を作成する
1. はじめに
こんにちは。クラスメソッドオペレーションズのあのふじたです。
最近、AWS WAF(v2) で未使用のWACL一覧を作成した際に躓いたので備忘録として記事を書こうと思います。
2. 利用するAWS CLI と公式リファレンス
- aws wafv2 list-web-acls
- https://docs.aws.amazon.com/cli/latest/reference/wafv2/list-web-acls.html
- scope別(CLOUDFRONT/REGIONAL)のWACL一覧取得に利用
- 関連付けられたリソースは記載されない。
- aws wafv2 list-resources-for-web-acl
- https://docs.aws.amazon.com/cli/latest/reference/wafv2/list-resources-for-web-acl.html
- 関連付けられたリソース(Resource Type別)が記載される。
- この記載を見落としており、API Gateway等がアタッチされたWACLを未アタッチと誤認識した一覧を生成して躓きました...。
- aws cloudfront list-distributions
- https://docs.aws.amazon.com/cli/latest/reference/cloudfront/list-distributions.html
- 関連付けられた
Distributionsが記載される。
3. 実装ステップ
具体的には、以下のステップで進めていきます。
- リソースタイプ、取得先のプロファイル、リージョンをあらかじめリストで保持。
aws wafv2 list-web-aclsでプロファイル、リージョンを指定し scope(REGIONAL)のWACL一覧を取得- scope(REGIONAL)のWACL一覧から
aws wafv2 list-resources-for-web-aclを利用してリソースタイプ別に関連付けられたリソースを全て検索 aws wafv2 list-web-aclsでプロファイル、リージョン(us-east-1固定)を指定し scope(CLOUDFRONT)のWACL一覧を取得- scope(CLOUDFRONT)のWACL一覧から
aws cloudfront list-distributionsを利用してリソースタイプ別に関連付けられたDistributionsを検索 - 関連付けられたリソースがないWACLをリストアップしcsvファイルで保存
- 最後に見やすいように markdown の表を表示しながら.mdファイルにも保存
必要なパッケージのインストール(動作確認した環境のみ)
jq Install
MacOS: brew install jq
Amzn2023: yum install jq
DuckDB Install
MacOS/Amzn2023: curl https://install.duckdb.org | sh
4. ファイル構成
.
├── get-all-wacl-unattached-info.sh # メインスクリプト
├── profile_list.txt # プロファイル一覧
└── region_list.txt # リージョン一覧
以下のような構成で実装しました。AIの支援を活用しながら作成しています。
profile_list.txt
account_a
account_b
account_c
account_d
region_list.txt
ap-northeast-1
ap-northeast-3
us-east-1
us-east-2
us-west-1
us-west-2
get-all-wacl-unattached-info.sh
#!/bin/bash
# ==================================================
# 設定: 外部ファイルパス
# ==================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROFILE_FILE="${SCRIPT_DIR}/profile_list.txt"
REGION_FILE="${SCRIPT_DIR}/region_list.txt"
OUTPUT_FILE="${SCRIPT_DIR}/report_unattached_webacl_$(date -Idate).csv"
# ==================================================
# REGIONAL WebACL がアタッチ可能なリソースタイプ一覧
# https://docs.aws.amazon.com/cli/latest/reference/wafv2/list-resources-for-web-acl.html
# https://docs.aws.amazon.com/waf/latest/APIReference/API_ListResourcesForWebACL.html
# (--resource-type 未指定時は APPLICATION_LOAD_BALANCER のみ返却される)
# ※ 新しいリソースタイプが追加された場合は手動で追記すること
# ==================================================
RESOURCE_TYPES=(
"APPLICATION_LOAD_BALANCER"
"API_GATEWAY"
"APPSYNC"
"COGNITO_USER_POOL"
"APP_RUNNER_SERVICE"
"VERIFIED_ACCESS_INSTANCE"
"AMPLIFY"
)
# ==================================================
# 外部ファイル存在チェック
# ==================================================
if [ ! -f "${PROFILE_FILE}" ]; then
echo "エラー: ${PROFILE_FILE} が見つかりません" >&2
exit 1
fi
if [ ! -f "${REGION_FILE}" ]; then
echo "エラー: ${REGION_FILE} が見つかりません" >&2
exit 1
fi
# ==================================================
# 外部ファイル読み込み(空行・コメント行を除外)
# ==================================================
profile_list=()
while IFS= read -r line; do
profile_list+=("$line")
done < <(grep -v -e '^\s*$' -e '^\s*#' "${PROFILE_FILE}")
region_list=()
while IFS= read -r line; do
region_list+=("$line")
done < <(grep -v -e '^\s*$' -e '^\s*#' "${REGION_FILE}")
# 読み込み件数チェック
if [ ${#profile_list[@]} -eq 0 ]; then
echo "エラー: プロファイルが0件です" >&2
exit 1
fi
if [ ${#region_list[@]} -eq 0 ]; then
echo "エラー: リージョンが0件です" >&2
exit 1
fi
echo "プロファイル数: ${#profile_list[@]}" >&2
echo "リージョン数: ${#region_list[@]}" >&2
echo "" >&2
# ==================================================
# AWS SSO ログインを実行する
# ==================================================
# aws sso login --sso-session xxx-sso
# ==================================================
# CSVヘッダー出力
# ==================================================
echo "Profile,Scope,Region,WebACL Name,WebACL ARN,Status" > "${OUTPUT_FILE}"
# ==================================================
# main
# ==================================================
for aws_profile in "${profile_list[@]}"; do
# ===== Regional WebACL チェック =====
for aws_region in "${region_list[@]}"; do
echo "Checking [REGIONAL] ${aws_profile} ${aws_region} ..." >&2
aws wafv2 list-web-acls \
--profile "${aws_profile}" \
--region "${aws_region}" \
--scope REGIONAL \
--query "WebACLs[*].[Name,ARN]" \
--output text 2>/dev/null | \
while IFS=$'\t' read -r name arn; do
[ -z "$name" ] && continue
# まず --resource-type 未指定(= ALB のみ)で高速チェック
count=$(aws wafv2 list-resources-for-web-acl \
--web-acl-arn "$arn" \
--profile "${aws_profile}" \
--region "${aws_region}" \
--query "length(ResourceArns)" \
--output text 2>/dev/null)
# ALB にアタッチされていれば確実にアタッチ済み → スキップ
if [ -n "$count" ] && [ "$count" != "0" ] && [ "$count" != "None" ]; then
continue
fi
# ALB 未アタッチの場合、他の全リソースタイプを順にチェック
attached=false
for rtype in "${RESOURCE_TYPES[@]}"; do
# ALB は既にチェック済みなのでスキップ
[ "$rtype" = "APPLICATION_LOAD_BALANCER" ] && continue
rt_count=$(aws wafv2 list-resources-for-web-acl \
--web-acl-arn "$arn" \
--resource-type "$rtype" \
--profile "${aws_profile}" \
--region "${aws_region}" \
--query "length(ResourceArns)" \
--output text 2>/dev/null)
if [ -n "$rt_count" ] && [ "$rt_count" != "0" ] && [ "$rt_count" != "None" ]; then
attached=true
break
fi
done
if [ "$attached" = false ]; then
echo "\"${aws_profile}\",\"REGIONAL\",\"${aws_region}\",\"${name}\",\"${arn}\",\"未アタッチ\"" >> "${OUTPUT_FILE}"
fi
done
done
# ===== CloudFront WebACL チェック(us-east-1 固定) =====
echo "Checking [CLOUDFRONT] ${aws_profile} ..." >&2
aws wafv2 list-web-acls \
--scope CLOUDFRONT \
--profile "${aws_profile}" \
--region us-east-1 \
--query "WebACLs[*].[Name,ARN]" \
--output text 2>/dev/null | \
while IFS=$'\t' read -r name arn; do
[ -z "$name" ] && continue
count=$(aws cloudfront list-distributions \
--profile "${aws_profile}" \
--query "length(DistributionList.Items[?WebACLId=='${arn}'])" \
--output text 2>/dev/null)
if [ "$count" = "0" ] || [ -z "$count" ]; then
echo "\"${aws_profile}\",\"CLOUDFRONT\",\"us-east-1\",\"${name}\",\"${arn}\",\"未アタッチ\"" >> "${OUTPUT_FILE}"
fi
done
done
# ==================================================
# 完了メッセージ
# ==================================================
echo "" >&2
echo "==============================" >&2
echo "完了: ${OUTPUT_FILE}" >&2
echo "未アタッチ WebACL 件数: $(( $(wc -l < "${OUTPUT_FILE}") - 1 )) 件" >&2
echo "==============================" >&2
echo "" >&2
duckdb -markdown -c "SELECT Profile, Scope, Region, \"WebACL Name\", Status FROM read_csv_auto('${OUTPUT_FILE}');" | tee "${OUTPUT_FILE%.csv}.md"
実行イメージ
プロファイル数: 4
リージョン数: 6
Checking [REGIONAL] account_a ap-northeast-1 ...
Checking [REGIONAL] account_a ap-northeast-3 ...
Checking [REGIONAL] account_a us-east-1 ...
Checking [REGIONAL] account_a us-east-2 ...
Checking [REGIONAL] account_a us-west-1 ...
Checking [REGIONAL] account_a us-west-2 ...
Checking [CLOUDFRONT] account_a ...
Checking [REGIONAL] account_b ap-northeast-1 ...
Checking [REGIONAL] account_b ap-northeast-3 ...
Checking [REGIONAL] account_b us-east-1 ...
Checking [REGIONAL] account_b us-east-2 ...
Checking [REGIONAL] account_b us-west-1 ...
Checking [REGIONAL] account_b us-west-2 ...
Checking [CLOUDFRONT] account_b ...
Checking [REGIONAL] account_c ap-northeast-1 ...
Checking [REGIONAL] account_c ap-northeast-3 ...
Checking [REGIONAL] account_c us-east-1 ...
Checking [REGIONAL] account_c us-east-2 ...
Checking [REGIONAL] account_c us-west-1 ...
Checking [REGIONAL] account_c us-west-2 ...
Checking [CLOUDFRONT] account_c ...
Checking [REGIONAL] account_d ap-northeast-1 ...
Checking [REGIONAL] account_d ap-northeast-3 ...
Checking [REGIONAL] account_d us-east-1 ...
Checking [REGIONAL] account_d us-east-2 ...
Checking [REGIONAL] account_d us-west-1 ...
Checking [REGIONAL] account_d us-west-2 ...
Checking [CLOUDFRONT] account_d ...
==============================
完了: /xxxx/xxxx/report_unattached_webacl_2026-05-12.csv
未アタッチ WebACL 件数: 8 件
==============================
| Profile | Scope | Region | WebACL Name | Status |
|-------------------------------|------------|----------------|----------------------------------------------------------|------------|
| account_a | REGIONAL | ap-northeast-1 | account_a-wacl | 未アタッチ |
| account_a | CLOUDFRONT | us-east-1 | account_a-cf-wacl | 未アタッチ |
| account_b | REGIONAL | ap-northeast-1 | account_b-wacl | 未アタッチ |
| account_b | CLOUDFRONT | us-east-1 | account_b-cf-wacl | 未アタッチ |
| account_c | REGIONAL | ap-northeast-1 | account_c-wacl | 未アタッチ |
| account_c | CLOUDFRONT | us-east-1 | account_c-cf-wacl | 未アタッチ |
| account_d | REGIONAL | ap-northeast-1 | account_d-wacl | 未アタッチ |
| account_d | CLOUDFRONT | us-east-1 | account_d-cf-wacl | 未アタッチ |
$ ls
get-all-wacl-unattached-info.sh profile_list.txt report_unattached_webacl_2026-05-12.csv
region_list.txt report_unattached_webacl_2026-05-12.md
おまけ
検証時の副産物として作成した全WACL一覧スクリプト(アタッチ済みリソース有無・名前、ログ出力先も網羅)も合わせて記載します。
get-all-wacl.sh
#!/bin/bash
set -eou pipefail
# ==================================================
# 設定: 外部ファイルパス
# ==================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROFILE_FILE="${SCRIPT_DIR}/profile_list.txt"
REGION_FILE="${SCRIPT_DIR}/region_list.txt"
OUTPUT_FILE="${SCRIPT_DIR}/report_wacl_all_$(date -Idate).csv"
# ==================================================
# REGIONAL WebACL がアタッチ可能なリソースタイプ一覧
# https://docs.aws.amazon.com/cli/latest/reference/wafv2/list-resources-for-web-acl.html
# https://docs.aws.amazon.com/waf/latest/APIReference/API_ListResourcesForWebACL.html
# (--resource-type 未指定時は APPLICATION_LOAD_BALANCER のみ返却される)
# ※ 新しいリソースタイプが追加された場合は手動で追記すること
# ==================================================
RESOURCE_TYPES=(
"APPLICATION_LOAD_BALANCER"
"API_GATEWAY"
"APPSYNC"
"COGNITO_USER_POOL"
"APP_RUNNER_SERVICE"
"VERIFIED_ACCESS_INSTANCE"
"AMPLIFY"
)
# ==================================================
# 外部ファイル存在チェック
# ==================================================
if [ ! -f "${PROFILE_FILE}" ]; then
echo "エラー: ${PROFILE_FILE} が見つかりません" >&2
exit 1
fi
if [ ! -f "${REGION_FILE}" ]; then
echo "エラー: ${REGION_FILE} が見つかりません" >&2
exit 1
fi
# ==================================================
# 外部ファイル読み込み(空行・コメント行を除外)
# ==================================================
profile_list=()
while IFS= read -r line; do
profile_list+=("$line")
done < <(grep -v -e '^\s*$' -e '^\s*#' "${PROFILE_FILE}")
region_list=()
while IFS= read -r line; do
region_list+=("$line")
done < <(grep -v -e '^\s*$' -e '^\s*#' "${REGION_FILE}")
# 読み込み件数チェック
if [ ${#profile_list[@]} -eq 0 ]; then
echo "エラー: プロファイルが0件です" >&2
exit 1
fi
if [ ${#region_list[@]} -eq 0 ]; then
echo "エラー: リージョンが0件です" >&2
exit 1
fi
echo "プロファイル数: ${#profile_list[@]}" >&2
echo "リージョン数: ${#region_list[@]}" >&2
echo "" >&2
# ==================================================
# AWS SSO ログイン
# ==================================================
# aws sso login --sso-session xxx-sso
# CSVヘッダー出力
echo "Profile,Scope,Region,WebACL_Name,WebACL_Id,WebACL_ARN,Attached,Attached_Resources,Logging_Enabled,Log_Destination" > "$OUTPUT_FILE"
# -------------------------------------------------------
# 関数: Web ACL一覧取得 → アタッチ確認 → ログ設定確認 → CSV出力
# 引数: $1=profile, $2=scope, $3=region
# -------------------------------------------------------
check_waf_logging() {
local profile="$1"
local scope="$2"
local region="$3"
echo "[INFO] Checking: profile=${profile}, scope=${scope}, region=${region}" >&2
# Web ACL一覧を取得
local wacl_json
wacl_json=$(aws wafv2 list-web-acls \
--scope "${scope}" \
--profile "${profile}" \
--region "${region}" \
--output json 2>/dev/null || echo '{"WebACLs":[]}')
# Web ACLが0件ならスキップ
local wacl_count
wacl_count=$(echo "$wacl_json" | jq '.WebACLs | length')
if [ "$wacl_count" -eq 0 ]; then
return
fi
# 各Web ACLについてループ
echo "$wacl_json" | jq -c '.WebACLs[]' | while IFS= read -r wacl; do
local wacl_name wacl_id wacl_arn
wacl_name=$(echo "$wacl" | jq -r '.Name')
wacl_id=$(echo "$wacl" | jq -r '.Id')
wacl_arn=$(echo "$wacl" | jq -r '.ARN')
# --------------------------------------------------
# アタッチ済みリソースを取得
# --------------------------------------------------
local attached="false"
local attached_resources="N/A"
# REGIONAL scope: 全リソースタイプをループして取得
if [ "$scope" = "REGIONAL" ]; then
local all_resources=""
for rtype in "${RESOURCE_TYPES[@]}"; do
local res_json
res_json=$(aws wafv2 list-resources-for-web-acl \
--web-acl-arn "${wacl_arn}" \
--resource-type "${rtype}" \
--profile "${profile}" \
--region "${region}" \
--output json 2>/dev/null || echo '{"ResourceArns":[]}')
local res_arns
res_arns=$(echo "$res_json" | jq -r '.ResourceArns[]' 2>/dev/null || true)
if [ -n "$res_arns" ]; then
if [ -n "$all_resources" ]; then
all_resources="${all_resources}; ${res_arns//$'\n'/; }"
else
all_resources="${res_arns//$'\n'/; }"
fi
fi
done
if [ -n "$all_resources" ]; then
attached="true"
attached_resources="$all_resources"
fi
fi
# CLOUDFRONT scope: cloudfront list-distributions で WebACLId を照合
if [ "$scope" = "CLOUDFRONT" ]; then
local cf_json
cf_json=$(aws cloudfront list-distributions \
--profile "${profile}" \
--output json 2>/dev/null || echo '{"DistributionList":{"Items":[]}}')
local matched_distributions
matched_distributions=$(echo "$cf_json" | jq -r --arg arn "$wacl_arn" \
'[.DistributionList.Items // [] | .[] | select(.WebACLId == $arn) | .ARN] | join("; ")' 2>/dev/null || echo "")
if [ -n "$matched_distributions" ] && [ "$matched_distributions" != "" ]; then
attached="true"
attached_resources="$matched_distributions"
fi
fi
# --------------------------------------------------
# ログ設定を取得
# --------------------------------------------------
local log_json logging_enabled log_destination
log_json=$(aws wafv2 get-logging-configuration \
--resource-arn "${wacl_arn}" \
--profile "${profile}" \
--region "${region}" \
--output json 2>/dev/null || echo "NONE")
if [ "$log_json" = "NONE" ]; then
logging_enabled="false"
log_destination="N/A"
else
logging_enabled="true"
log_destination=$(echo "$log_json" | jq -r '.LoggingConfiguration.LogDestinationConfigs // [] | join("; ")' 2>/dev/null || echo "N/A")
[ -z "$log_destination" ] && log_destination="N/A"
fi
# CSV出力
echo "\"${profile}\",\"${scope}\",\"${region}\",\"${wacl_name}\",\"${wacl_id}\",\"${wacl_arn}\",\"${attached}\",\"${attached_resources}\",\"${logging_enabled}\",\"${log_destination}\""
done >> "$OUTPUT_FILE"
}
# -------------------------------------------------------
# メイン: REGIONAL scope(全リージョン)
# -------------------------------------------------------
for aws_profile in "${profile_list[@]}"; do
for aws_region in "${region_list[@]}"; do
check_waf_logging "${aws_profile}" "REGIONAL" "${aws_region}"
done
done
# -------------------------------------------------------
# メイン: CLOUDFRONT scope(us-east-1 固定)
# -------------------------------------------------------
for aws_profile in "${profile_list[@]}"; do
check_waf_logging "${aws_profile}" "CLOUDFRONT" "us-east-1"
done
echo "[INFO] 完了: ${OUTPUT_FILE}" >&2
duckdb -markdown -c "SELECT Profile, Scope, Region, WebACL_Name, Attached, Attached_Resources, Logging_Enabled, Log_Destination FROM read_csv_auto('${OUTPUT_FILE}');" | tee "${OUTPUT_FILE%.csv}.md"
最後に
未アタッチのWACLも料金が発生するのでチェックの頻度は高くなくても、定期的に実施してこまめにコストカットしていきたいですね。
このブログがどなたかの役に立てば幸いです。
クラスメソッドオペレーションズ株式会社について
クラスメソッドグループのオペレーション企業です。
運用・保守開発・サポート・情シス・バックオフィスの専門チームが、IT・AIをフル活用した「しくみ」を通じて、お客様の業務代行から課題解決や高付加価値サービスまでを提供するエキスパート集団です。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、クラスメソッドオペレーションズ株式会社 コーポレートサイト をぜひご覧ください。※2026年1月 アノテーション㈱から社名変更しました。








