Microsoft Teams の Webhook URL 変更に伴い AWS Organizations 配下の EventBridge API 送信先を一括更新してみた

Microsoft Teams の Webhook URL 変更に伴い AWS Organizations 配下の EventBridge API 送信先を一括更新してみた

複数のAWSアカウントに存在するEventBridge API送信先のWebhook URLを一括更新したので、Resource ExplorerとStackSetsを活用した実装方法を紹介します。
2026.08.27

はじめに

複数の AWS アカウントで、Amazon EventBridge の API 送信先から Microsoft Teams へ通知していました。

今回、Teams 側の Webhook URL を再発行したため、各 AWS アカウントに存在する EventBridge API 送信先のエンドポイントを変更する必要がありました。

アカウントごとにログインして手動で変更することもできますが、対象アカウントが多い場合は手間がかかります。

そこで今回は、以下の流れで EventBridge API 送信先を一括更新しました。

  1. AWS Resource Explorer で組織内の API 送信先を検索する
  2. 更新対象の ARN を CSV ファイルにまとめる
  3. AWS CloudFormation StackSets でメンバーアカウントにクロスアカウントロールを展開する
  4. 管理アカウントの AWS CloudShell から Dry run を実行する
  5. Dry run の結果を確認後、Webhook URL を一括更新する

今回の検証では、管理アカウントと2つのメンバーアカウントに存在する、合計6つの API 送信先を更新できることを確認しました。

検証構成

検証には以下の3アカウントを使用しました。

アカウント ID は例示です。

管理アカウント:111111111111
メンバーアカウント1:222222222222
メンバーアカウント2:333333333333

各アカウントには、次の2種類の API 送信先が存在する想定です。

管理アカウント
├─ management-teams
└─ ccoe-security-alert-teams

メンバーアカウント1
├─ sandbox1-teams
└─ ccoe-security-alert-teams

メンバーアカウント2
├─ sandbox2-teams
└─ ccoe-security-alert-teams

API 送信先名に応じて、変更する Webhook URL を振り分けます。

API 送信先名 通知先
ccoe-security-alert-teams CCoE 用 Teams チャネル
その他の *-teams アカウント担当者用 Teams チャネル

検証リージョンは ap-northeast-1 です。

実際の Webhook URL には認証情報に相当する値が含まれるため、記事中では以下のプレースホルダーに置き換えています。

<CCOE_WEBHOOK_URL>
<ACCOUNT_WEBHOOK_URL>

全体の流れ

今回の処理の流れは以下です。

Resource ExplorerでAPI送信先を検索

更新対象のARNをCSVファイルにまとめる

StackSetsでメンバーアカウントにIAMロールを展開

管理アカウントのCloudShellにCSVファイルをアップロード

Dry runで変更対象を確認

Webhook URLを更新

更新後のWebhook URLを再取得して確認

管理アカウント内の API 送信先は、CloudShell の現在の認証情報で操作します。

メンバーアカウント内の API 送信先は、対象アカウントに作成した IAM ロールを AssumeRole で引き受けて操作します。

Resource Explorer で対象リソースを確認する

AWS Resource Explorer では、EventBridge API 送信先を以下のリソースタイプで検索できます。

events:api-destination

Resource Explorer の組織全体を検索できるビューから、対象の API 送信先を検索しました。

今回は、検索結果から以下の命名規則に該当する API 送信先を更新対象とします。

ccoe-security-alert-teams
*-teams

Resource Explorer が対応しているリソースタイプは、以下のドキュメントで確認できます。

https://docs.aws.amazon.com/resource-explorer/latest/userguide/supported-resource-types.html

対象となるアカウント ID、リージョン、API 送信先 ARN を別の方法で取得できる場合は、必ずしも Resource Explorer を使う必要はありません。

更新対象の CSV ファイルを作成する

Resource Explorer の検索結果を CSV ファイルとしてエクスポートし、更新対象の ARN のみに整理しました。

今回使用するファイル名は以下です。

teams-api-destination-targets.csv

CSV ファイルの内容は以下の形式です。

ARN
arn:aws:events:ap-northeast-1:111111111111:api-destination/management-teams/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa
arn:aws:events:ap-northeast-1:111111111111:api-destination/ccoe-security-alert-teams/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb
arn:aws:events:ap-northeast-1:222222222222:api-destination/sandbox1-teams/cccccccc-cccc-4ccc-8ccc-cccccccccccc
arn:aws:events:ap-northeast-1:222222222222:api-destination/ccoe-security-alert-teams/dddddddd-dddd-4ddd-8ddd-dddddddddddd
arn:aws:events:ap-northeast-1:333333333333:api-destination/sandbox2-teams/eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee
arn:aws:events:ap-northeast-1:333333333333:api-destination/ccoe-security-alert-teams/ffffffff-ffff-4fff-8fff-ffffffffffff

スクリプトでは ARN から以下の情報を取得します。

  • AWS アカウント ID
  • リージョン
  • API 送信先名

そのため、これらを別の列として用意する必要はありません。

CSV ファイルを表計算ソフトで編集する場合は、AWS アカウント ID の先頭の 0 が削除されないよう注意してください。

クロスアカウントロールを展開する

管理アカウントからメンバーアカウントの API 送信先を操作するため、各メンバーアカウントにクロスアカウントロールを作成します。

今回は、CloudFormation StackSets のサービスマネージドアクセス許可を利用しました。

サービスマネージドアクセス許可を使用すると、AWS Organizations で管理されているアカウントへ、同じ CloudFormation テンプレートを基にスタックインスタンスを展開できます。

StackSets を使って組織内のアカウントへ IAM ロールを展開する手順は、以下の記事で紹介しています。

https://dev.classmethod.jp/articles/aws-cloudformation-stacksets-iam-role-deployment/

CloudFormation テンプレート

今回使用したテンプレートは以下です。

ManagementAccountId は、自身の管理アカウント ID に置き換えてください。

AWSTemplateFormatVersion: '2010-09-09'
Description: Create IAM role for updating EventBridge API destinations

Parameters:
  ManagementAccountId:
    Type: String
    Default: '111111111111'
    AllowedPattern: '^\d{12}$'

Resources:
  CrossAccountRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: CrossAccountEventBridgeApiDestinationUpdateRole
      Path: /

      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowAssumeRoleFromManagementAccount
            Effect: Allow
            Principal:
              AWS: !Sub arn:${AWS::Partition}:iam::${ManagementAccountId}:root
            Action:
              - sts:AssumeRole

      Policies:
        - PolicyName: UpdateTeamsApiDestinations
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: DescribeTeamsApiDestinations
                Effect: Allow
                Action:
                  - events:DescribeApiDestination
                Resource:
                  - !Sub arn:${AWS::Partition}:events:ap-northeast-1:${AWS::AccountId}:api-destination/*-teams

              - Sid: UpdateTeamsApiDestinations
                Effect: Allow
                Action:
                  - events:UpdateApiDestination
                Resource: '*'
                Condition:
                  StringEquals:
                    aws:RequestedRegion: ap-northeast-1

Outputs:
  CrossAccountRoleArn:
    Description: ARN of the cross-account role
    Value: !GetAtt CrossAccountRole.Arn

ロールに付与する権限は以下です。

  • events:DescribeApiDestination
  • events:UpdateApiDestination

events:UpdateApiDestination は、aws:RequestedRegion 条件により ap-northeast-1 での操作に限定しています。

また、信頼ポリシーでは、管理アカウントからのみロールを引き受けられるようにしています。

管理アカウント自身には StackSets からスタックインスタンスがデプロイされないため、管理アカウント内の API 送信先は CloudShell の実行権限で更新します。

CSV ファイルを CloudShell へアップロードする

管理アカウントで ap-northeast-1 の AWS CloudShell を開き、以下の CSV ファイルをアップロードします。

teams-api-destination-targets.csv

アップロード後、ファイルが存在することを確認します。

ls -l teams-api-destination-targets.csv

ヘッダーを除いた対象件数も確認します。

tail -n +2 teams-api-destination-targets.csv | grep -c '^arn:'

今回の検証用 CSV では、以下が出力されます。

6

CloudShell から一括更新する

今回はスクリプトファイルを作成せず、以下のコードを CloudShell へ直接貼り付けました。

環境に合わせて変更する項目は以下です。

変数 内容
CSV 更新対象を記載した CSV ファイル
MANAGEMENT_ACCOUNT 管理アカウント ID
ROLE_NAME メンバーアカウントで引き受ける IAM ロール名
CCOE_URL CCoE 用 Teams チャネルの Webhook URL
ACCOUNT_URL アカウント担当者用 Teams チャネルの Webhook URL
APPLY false で Dry run、true で更新
LOG="update-result-$(date +%Y%m%d-%H%M%S).log"

(
set -euo pipefail
export AWS_PAGER=""

CSV="teams-api-destination-targets.csv"
MANAGEMENT_ACCOUNT="111111111111"
ROLE_NAME="CrossAccountEventBridgeApiDestinationUpdateRole"

# false:Dry run、true:実際に更新
APPLY=false

CCOE_URL='<CCOE_WEBHOOK_URL>'
ACCOUNT_URL='<ACCOUNT_WEBHOOK_URL>'

[[ -f "$CSV" ]] || {
  echo "ERROR: $CSV が見つかりません。"
  exit 1
}

CURRENT_ACCOUNT="$(
  aws sts get-caller-identity \
    --query Account \
    --output text
)"

[[ "$CURRENT_ACCOUNT" == "$MANAGEMENT_ACCOUNT" ]] || {
  echo "ERROR: 管理アカウント $MANAGEMENT_ACCOUNT で実行してください。"
  exit 1
}

echo "開始日時 : $(date '+%Y-%m-%d %H:%M:%S')"
echo "実行モード: $([[ "$APPLY" == true ]] && echo APPLY || echo DRY_RUN)"
echo

while IFS= read -r ARN || [[ -n "$ARN" ]]; do
  ARN="${ARN%$'\r'}"
  [[ -z "$ARN" ]] && continue

  REGION="$(cut -d: -f4 <<< "$ARN")"
  ACCOUNT_ID="$(cut -d: -f5 <<< "$ARN")"
  NAME="$(cut -d/ -f2 <<< "$ARN")"

  if [[ "$NAME" == "ccoe-security-alert-teams" ]]; then
    URL="$CCOE_URL"
    TYPE="CCOE"
  elif [[ "$NAME" == *-teams ]]; then
    URL="$ACCOUNT_URL"
    TYPE="ACCOUNT"
  else
    echo "SKIP    $ACCOUNT_ID $NAME(対象外)"
    continue
  fi

  if [[ "$ACCOUNT_ID" == "$MANAGEMENT_ACCOUNT" ]]; then
    run_aws() {
      aws "$@"
    }
  else
    read -r AK SK ST <<< "$(
      aws sts assume-role \
        --role-arn "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME" \
        --role-session-name UpdateTeamsWebhook \
        --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
        --output text
    )"

    run_aws() {
      AWS_ACCESS_KEY_ID="$AK" \
      AWS_SECRET_ACCESS_KEY="$SK" \
      AWS_SESSION_TOKEN="$ST" \
      aws "$@"
    }
  fi

  CURRENT_URL="$(
    run_aws events describe-api-destination \
      --region "$REGION" \
      --name "$NAME" \
      --query InvocationEndpoint \
      --output text \
      --no-cli-pager
  )"

  if [[ "$CURRENT_URL" == "$URL" ]]; then
    echo "SKIP    $ACCOUNT_ID $NAME [$TYPE](更新済み)"
  elif [[ "$APPLY" != true ]]; then
    echo "CHANGE  $ACCOUNT_ID $NAME [$TYPE](Dry run)"
  else
    run_aws events update-api-destination \
      --region "$REGION" \
      --name "$NAME" \
      --invocation-endpoint "$URL" \
      --no-cli-pager \
      >/dev/null

    UPDATED_URL="$(
      run_aws events describe-api-destination \
        --region "$REGION" \
        --name "$NAME" \
        --query InvocationEndpoint \
        --output text \
        --no-cli-pager
    )"

    if [[ "$UPDATED_URL" == "$URL" ]]; then
      echo "UPDATED $ACCOUNT_ID $NAME [$TYPE]"
    else
      echo "ERROR   $ACCOUNT_ID $NAME(更新後の確認に失敗)"
      exit 1
    fi
  fi

done < <(tail -n +2 "$CSV")

echo

if [[ "$APPLY" == true ]]; then
  echo "更新処理が完了しました。"
else
  echo "Dry runのため変更していません。"
fi

echo "終了日時 : $(date '+%Y-%m-%d %H:%M:%S')"

) 2>&1 | tee "$LOG"

echo
echo "ログファイル: $LOG"

AWS Security Token Service(AWS STS)の AssumeRole では、一時的なアクセスキー ID、シークレットアクセスキー、セッショントークンを取得できます。スクリプトでは、この一時認証情報を使ってメンバーアカウントの EventBridge API を呼び出します。

https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html

update-api-destination では、--name で API 送信先名を指定し、--invocation-endpoint で新しい Webhook URL を指定します。

https://docs.aws.amazon.com/cli/latest/reference/events/update-api-destination.html

スクリプトの処理内容

ARN から必要な情報を取得する

CSV ファイルに記載された ARN から、リージョン、アカウント ID、API 送信先名を取得します。

REGION="$(cut -d: -f4 <<< "$ARN")"
ACCOUNT_ID="$(cut -d: -f5 <<< "$ARN")"
NAME="$(cut -d/ -f2 <<< "$ARN")"

たとえば、以下の ARN を読み込んだ場合を考えます。

arn:aws:events:ap-northeast-1:222222222222:api-destination/sandbox1-teams/<resource-id>

取得結果は以下です。

REGION=ap-northeast-1
ACCOUNT_ID=222222222222
NAME=sandbox1-teams

API 送信先名から Webhook URL を決める

API 送信先名が ccoe-security-alert-teams の場合は、CCoE 用の Webhook URL を使用します。

それ以外の *-teams は、アカウント担当者用の Webhook URL を使用します。

if [[ "$NAME" == "ccoe-security-alert-teams" ]]; then
  URL="$CCOE_URL"
  TYPE="CCOE"
elif [[ "$NAME" == *-teams ]]; then
  URL="$ACCOUNT_URL"
  TYPE="ACCOUNT"
fi

固有名の判定を先に行っているため、ccoe-security-alert-teams が汎用的な *-teams の分岐で処理されることを防いでいます。

管理アカウントとメンバーアカウントで処理を分ける

管理アカウント自身の API 送信先は、CloudShell の現在の認証情報で操作します。

if [[ "$ACCOUNT_ID" == "$MANAGEMENT_ACCOUNT" ]]; then
  run_aws() {
    aws "$@"
  }
fi

メンバーアカウントの場合は、対象アカウントのクロスアカウントロールを引き受けます。

aws sts assume-role \
  --role-arn "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME" \
  --role-session-name UpdateTeamsWebhook

これにより、同じ CSV ファイル内に管理アカウントとメンバーアカウントの ARN が混在していても処理できます。

Dry run を実行する

最初は、以下の設定で実行します。

APPLY=false

Dry run では、現在の Webhook URL と変更先 URL を比較しますが、update-api-destination は実行しません。

実行結果は以下です。

開始日時 : 2026-08-18 02:35:10
実行モード: DRY_RUN

CHANGE  111111111111 management-teams [ACCOUNT](Dry run)
CHANGE  111111111111 ccoe-security-alert-teams [CCOE](Dry run)
CHANGE  222222222222 sandbox1-teams [ACCOUNT](Dry run)
CHANGE  222222222222 ccoe-security-alert-teams [CCOE](Dry run)
CHANGE  333333333333 sandbox2-teams [ACCOUNT](Dry run)
CHANGE  333333333333 ccoe-security-alert-teams [CCOE](Dry run)

Dry runのため変更していません。
終了日時 : 2026-08-18 02:35:16

6つの API 送信先が CHANGE となりました。

この結果から、以下を確認できました。

  • 管理アカウント内の API 送信先を参照できる
  • メンバーアカウントのクロスアカウントロールを引き受けられる
  • メンバーアカウント内の API 送信先を参照できる
  • API 送信先名に応じた Webhook URL の振り分けができる

Webhook URL を更新する

Dry run の結果を確認後、以下へ変更して再実行します。

APPLY=true

実行結果は以下です。

開始日時 : 2026-08-18 02:54:52
実行モード: APPLY

UPDATED 111111111111 management-teams [ACCOUNT]
UPDATED 111111111111 ccoe-security-alert-teams [CCOE]
UPDATED 222222222222 sandbox1-teams [ACCOUNT]
UPDATED 222222222222 ccoe-security-alert-teams [CCOE]
UPDATED 333333333333 sandbox2-teams [ACCOUNT]
UPDATED 333333333333 ccoe-security-alert-teams [CCOE]

更新処理が完了しました。
終了日時 : 2026-08-18 02:55:03

スクリプトでは、更新後に describe-api-destination を実行しています。

取得した InvocationEndpoint が指定した Webhook URL と一致した場合に、UPDATED を出力します。

今回の検証では、管理アカウントと2つのメンバーアカウントに存在する、合計6つの API 送信先を更新できました。

実行結果は以下の形式のログファイルにも保存されます。

update-result-YYYYMMDD-HHMMSS.log

作成されたログファイルは、以下のコマンドで確認できます。

ls -lt update-result-*.log

まとめ

Resource Explorer で確認した API 送信先の ARN を CSV ファイルにまとめ、管理アカウントの CloudShell から、複数アカウントの EventBridge API 送信先を一括更新できました。

管理アカウント自身は CloudShell の実行権限、メンバーアカウントは StackSets で展開したクロスアカウントロールを利用しています。また、Dry run で対象を確認してから、API 送信先名に応じた Webhook URL を設定しました。


そのマルチアカウント運用、気合いで支えていませんか

Organizations や Control Tower で土台は作れても、アカウントもポリシーも増えるほど、運用は「詳しい一人」に寄りかかっていく。属人化が限界を迎える前に、組織として回す仕組み=CCoEへ。5,600社の支援から得た立ち上げの型を、無料資料にまとめました。

CCoE総合支援

組織で回す仕組みの資料をもらう

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事