[小ネタ]未使用のWACL一覧を作成する

[小ネタ]未使用のWACL一覧を作成する

2026.05.12

1. はじめに

こんにちは。クラスメソッドオペレーションズのあのふじたです。

最近、AWS WAF(v2) で未使用のWACL一覧を作成した際に躓いたので備忘録として記事を書こうと思います。

2. 利用するAWS CLI と公式リファレンス

3. 実装ステップ

具体的には、以下のステップで進めていきます。

  1. リソースタイプ、取得先のプロファイル、リージョンをあらかじめリストで保持。
  2. aws wafv2 list-web-acls でプロファイル、リージョンを指定し scope(REGIONAL)のWACL一覧を取得
  3. scope(REGIONAL)のWACL一覧からaws wafv2 list-resources-for-web-acl を利用してリソースタイプ別に関連付けられたリソースを全て検索
  4. aws wafv2 list-web-acls でプロファイル、リージョン(us-east-1固定)を指定し scope(CLOUDFRONT)のWACL一覧を取得
  5. scope(CLOUDFRONT)のWACL一覧から aws cloudfront list-distributions を利用してリソースタイプ別に関連付けられたDistributions を検索
  6. 関連付けられたリソースがないWACLをリストアップしcsvファイルで保存
  7. 最後に見やすいように 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月 アノテーション㈱から社名変更しました。

この記事をシェアする

関連記事