Microsoft Teams の Webhook URL 変更に伴い AWS Organizations 配下の EventBridge API 送信先を一括更新してみた
はじめに
複数の AWS アカウントで、Amazon EventBridge の API 送信先から Microsoft Teams へ通知していました。
今回、Teams 側の Webhook URL を再発行したため、各 AWS アカウントに存在する EventBridge API 送信先のエンドポイントを変更する必要がありました。
アカウントごとにログインして手動で変更することもできますが、対象アカウントが多い場合は手間がかかります。
そこで今回は、以下の流れで EventBridge API 送信先を一括更新しました。
- AWS Resource Explorer で組織内の API 送信先を検索する
- 更新対象の ARN を CSV ファイルにまとめる
- AWS CloudFormation StackSets でメンバーアカウントにクロスアカウントロールを展開する
- 管理アカウントの AWS CloudShell から Dry run を実行する
- 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 が対応しているリソースタイプは、以下のドキュメントで確認できます。
対象となるアカウント 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 ロールを展開する手順は、以下の記事で紹介しています。
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:DescribeApiDestinationevents: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 を呼び出します。
update-api-destination では、--name で API 送信先名を指定し、--invocation-endpoint で新しい Webhook URL を指定します。
スクリプトの処理内容
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 を設定しました。









