ルートアクセス管理を有効化している組織に対して全メンバーアカウントの認証情報をスクリプトで一括で無効化する
いわさです。
ルートアクセス管理機能を有効化すると、メンバーアカウントのルートユーザーの認証情報を削除することができます。
先日まで、API でどのように操作できるかいくつかの記事で確認していました。
- AWS CLI を使って AWS Organizations のルートアクセス管理機能を操作する | DevelopersIO
- ルートアクセス管理機能を AWS CLI から使ってメンバーアカウントのルートユーザー認証情報を確認してみた | DevelopersIO
- AWS CLI でルートアクセス管理機能を使ってメンバーアカウントのルートユーザー認証情報の削除と回復を行ってみた | DevelopersIO
今回いくつかの AWS Organizations 組織に対してルートユーザー認証情報の利用状況を確認し、安全に削除できそうであれば一括で組織のルートユーザー認証情報を削除する機会がありました。
今まで検証した内容を使って、スクリプトによってメンバーアカウントのルートユーザー認証情報を一括で削除するスクリプトを作成したので紹介します。
作成したスクリプトと使い方
Kiro CLI とやり取りしながら bash スクリプトを作成しました。
作成したスクリプトは以下です。
#!/bin/bash
set -e
# 使用方法を表示
usage() {
echo "使用方法: $0 [--profile プロファイル名]"
echo " --profile: 使用するAWS CLIプロファイル (オプション)"
exit 1
}
# コマンドライン引数の解析
PROFILE=""
while [[ $# -gt 0 ]]; do
case $1 in
--profile)
PROFILE="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "不明なオプション: $1"
usage
;;
esac
done
echo "ルート認証情報削除処理を開始します..."
if [ -n "$PROFILE" ]; then
echo "使用するAWSプロファイル: $PROFILE"
export AWS_PROFILE="$PROFILE"
fi
# ルートアクセス一元管理の確認
check_root_access_management() {
echo "ルートアクセス管理が有効化されているかを確認中..."
local enabled_services=$(aws organizations list-aws-service-access-for-organization \
--query "EnabledServicePrincipals[?ServicePrincipal=='iam.amazonaws.com']" \
--output text)
if [ -z "$enabled_services" ]; then
echo "ルートアクセス管理が有効化されていません。処理を停止します。"
exit 1
fi
echo "ルートアクセス管理が有効化されています。処理を続行します..."
}
# 管理アカウントIDを取得
get_management_account_id() {
aws organizations describe-organization --query "Organization.MasterAccountId" --output text
}
# 全アカウント一覧を取得
get_all_accounts() {
aws organizations list-accounts \
--query "Accounts[?Status=='ACTIVE'].{Id:Id,Name:Name}" \
--output json
}
# メンバーアカウント一覧を取得(管理アカウント除く)
get_member_accounts() {
local management_account_id=$1
aws organizations list-accounts \
--query "Accounts[?Status=='ACTIVE' && Id!='$management_account_id'].{Id:Id,Name:Name}" \
--output json
}
# アカウントのルート認証情報を確認
check_account_credentials() {
local account_id=$1
local account_name=$2
local management_account_id=$3
# 管理アカウントの場合は処理をスキップ
if [ "$account_id" = "$management_account_id" ]; then
echo "$account_id|$account_name (管理アカウント)|-|-|-|-"
return
fi
# assume-rootでセッションを取得
local assume_error=$(aws sts assume-root \
--target-principal="$account_id" \
--duration-seconds=900 \
--task-policy-arn="arn=arn:aws:iam::aws:policy/root-task/IAMAuditRootUserCredentials" \
--output=json 2>&1)
local assume_exit_code=$?
if [ $assume_exit_code -ne 0 ]; then
echo "$account_id|$account_name|assume-rootエラー($assume_exit_code)|エラー|エラー|エラー"
return
fi
local credentials="$assume_error"
local access_key=$(echo "$credentials" | jq -r '.Credentials.AccessKeyId')
local secret_key=$(echo "$credentials" | jq -r '.Credentials.SecretAccessKey')
local session_token=$(echo "$credentials" | jq -r '.Credentials.SessionToken')
# 一時的に認証情報を設定
export AWS_ACCESS_KEY_ID="$access_key"
export AWS_SECRET_ACCESS_KEY="$secret_key"
export AWS_SESSION_TOKEN="$session_token"
unset AWS_PROFILE
# 認証情報のテスト
local caller_identity=$(aws sts get-caller-identity 2>&1)
if [ $? -ne 0 ]; then
echo "$account_id|$account_name|認証エラー|エラー|エラー|エラー"
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
return
fi
# ログインプロファイルの確認
local login_profile="なし"
if aws iam get-login-profile >/dev/null 2>&1; then
login_profile="あり"
fi
# MFAデバイスの確認
local mfa_devices="なし"
local mfa_count=$(aws iam list-mfa-devices --query "length(MFADevices)" --output text 2>/dev/null)
if [ -n "$mfa_count" ] && [ "$mfa_count" -gt 0 ]; then
mfa_devices="あり"
fi
# アクセスキーの確認
local access_keys="なし"
local key_count=$(aws iam list-access-keys --query "length(AccessKeyMetadata)" --output text 2>/dev/null)
if [ -n "$key_count" ] && [ "$key_count" -gt 0 ]; then
access_keys="あり"
fi
# 署名証明書の確認
local certificates="なし"
local cert_count=$(aws iam list-signing-certificates --query "length(Certificates)" --output text 2>/dev/null)
if [ -n "$cert_count" ] && [ "$cert_count" -gt 0 ]; then
certificates="あり"
fi
echo "$account_id|$account_name|$login_profile|$mfa_devices|$access_keys|$certificates"
# 認証情報をクリア
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
if [ -n "$PROFILE" ]; then
export AWS_PROFILE="$PROFILE"
fi
}
# テーブル形式で結果を表示
display_results() {
local results="$1"
echo ""
echo "ルート認証情報ステータスレポート:"
echo "============================================================================================================"
printf "%-15s %-20s %-15s %-10s %-15s %-15s\n" "アカウントID" "アカウント名" "ログインプロファイル" "MFA" "アクセスキー" "署名証明書"
echo "============================================================================================================"
echo "$results" | while IFS='|' read -r account_id account_name login_profile mfa access_keys certificates; do
printf "%-15s %-20s %-15s %-10s %-15s %-15s\n" "$account_id" "$account_name" "$login_profile" "$mfa" "$access_keys" "$certificates"
done
echo "============================================================================================================"
echo ""
}
# アクセスキーまたは署名証明書の存在をチェック
check_blocking_credentials() {
local results="$1"
local has_blocking=false
echo "$results" | while IFS='|' read -r account_id account_name login_profile mfa access_keys certificates; do
if [ "$access_keys" = "あり" ] || [ "$certificates" = "あり" ]; then
echo "アクセスキーまたは署名証明書が存在するアカウント: $account_id ($account_name)"
has_blocking=true
fi
done
# アクセスキー列(5列目)または署名証明書列(6列目)が「あり」かチェック
if echo "$results" | awk -F'|' '$5=="あり" || $6=="あり"' | grep -q .; then
echo "アクセスキーまたは署名証明書が存在するアカウントが見つかりました。処理を継続できません。"
echo "このスクリプトを実行する前に、アクセスキーと署名証明書を削除してください。"
exit 1
fi
}
# ユーザー確認
confirm_deletion() {
echo "ルート認証情報の削除を実行しますか? (y/N): "
read -r response
case "$response" in
[yY][eE][sS]|[yY])
echo "削除処理を実行します..."
;;
*)
echo "操作がキャンセルされました。"
exit 0
;;
esac
}
# メンバーアカウントのルート認証情報を削除
delete_root_credentials() {
local account_id=$1
local account_name=$2
echo "アカウント $account_id ($account_name) のルート認証情報を削除中..."
# assume-rootでセッションを取得
local credentials=$(aws sts assume-root \
--target-principal="$account_id" \
--duration-seconds=900 \
--task-policy-arn="arn=arn:aws:iam::aws:policy/root-task/IAMDeleteRootUserCredentials" \
--output=json 2>/dev/null)
if [ $? -ne 0 ]; then
echo " アカウント $account_id のassume-rootに失敗しました"
return
fi
local access_key=$(echo "$credentials" | jq -r '.Credentials.AccessKeyId')
local secret_key=$(echo "$credentials" | jq -r '.Credentials.SecretAccessKey')
local session_token=$(echo "$credentials" | jq -r '.Credentials.SessionToken')
# 一時的に認証情報を設定
export AWS_ACCESS_KEY_ID="$access_key"
export AWS_SECRET_ACCESS_KEY="$secret_key"
export AWS_SESSION_TOKEN="$session_token"
# ログインプロファイルの削除
if aws iam delete-login-profile >/dev/null 2>&1; then
echo " ログインプロファイルを削除しました"
fi
# MFAデバイスの無効化
local mfa_serials=$(aws iam list-mfa-devices --query "MFADevices[].SerialNumber" --output text 2>/dev/null)
for serial in $mfa_serials; do
aws iam deactivate-mfa-device --serial-number "$serial" >/dev/null 2>&1
echo " MFAデバイスを無効化しました: $serial"
done
echo " アカウント $account_id の処理が完了しました"
# 認証情報をクリア
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
}
# メイン処理
main() {
# ルートアクセス一元管理の確認
check_root_access_management
# 管理アカウントIDを取得
local management_account_id=$(get_management_account_id)
echo "管理アカウントID: $management_account_id (処理対象外)"
# 全アカウントの認証情報確認
local all_accounts=$(get_all_accounts)
local member_accounts=$(get_member_accounts "$management_account_id")
local results=""
echo "全アカウントのルート認証情報を確認中..."
local total_accounts=$(echo "$all_accounts" | jq length)
local current=0
# 結果を一時ファイルに保存
> /tmp/credential_results.txt
# 並列処理用の関数
process_account() {
local account_id=$1
local account_name=$2
local management_account_id=$3
local current=$4
local total=$5
echo "[$current/$total] $account_id ($account_name) を確認中..."
result=$(check_account_credentials "$account_id" "$account_name" "$management_account_id")
echo "$result" >> /tmp/credential_results_$account_id.txt
}
# 並列でアカウント処理を実行
while IFS=' ' read -r account_id account_name; do
current=$((current + 1))
process_account "$account_id" "$account_name" "$management_account_id" "$current" "$total_accounts" &
# 同時実行数を制限(最大5並列)
if (( current % 5 == 0 )); then
wait
fi
done < <(echo "$all_accounts" | jq -r '.[] | "\(.Id) \(.Name)"')
# 残りの処理完了を待機
wait
# 結果をマージ
for account_file in /tmp/credential_results_*.txt; do
if [ -f "$account_file" ]; then
cat "$account_file" >> /tmp/credential_results.txt
rm -f "$account_file"
fi
done
# 結果を読み込み
results=$(cat /tmp/credential_results.txt 2>/dev/null || echo "")
rm -f /tmp/credential_results.txt
# テーブル形式で表示
display_results "$results"
# アクセスキーまたは署名証明書の存在チェック
check_blocking_credentials "$results"
# ユーザー確認
confirm_deletion
# ルート認証情報の削除(メンバーアカウントのみ)
echo "$member_accounts" | jq -r '.[] | "\(.Id) \(.Name)"' | while read -r account_id account_name; do
delete_root_credentials "$account_id" "$account_name"
done
echo ""
echo "ルート認証情報削除処理が完了しました。"
}
# スクリプト実行
main "$@"
使い方ですが、このスクリプトは管理アカウント上で実行します。
最初に管理アカウント上でルートアクセス管理機能が有効化されているかチェックされます。有効化されていなければそこで処理は中断します。
もしルートアカウント管理機能が有効化されている場合は、全メンバーアカウントのルートユーザーの情報をチェックし、その後全メンバーアカウントのルートユーザー認証情報を削除するか確認してから、削除します。
もしいずれかのアカウントで「アクセスキー」「デジタル署名証明書」がある場合は削除を行いません。事前にユーザーによって削除しておく必要があります。
上記スクリプトをファイルで保存し、管理アカウントの認証プロファイルを使って実行します。
% ./delete_root_credentials.sh --profile hogeadmin
ルート認証情報削除処理を開始します...
使用するAWSプロファイル: hogeadmin
ルートアクセス管理が有効化されているかを確認中...
:
動作確認のようす
では試してみましょう。
まずは、次のようにルートユーザー認証情報が混在している組織に対して使ってみます。
この時、ある一部のメンバーアカウントは認証情報に加えて、アクセスキーと MFA デバイス、デジタル署名証明書も作成済みとします。

試してみると次のように対象組織の全メンバーアカウントの情報を取得することができています。
冒頭のブログのとおり、各アカウントには AssumeRoot して一時認証情報を使う必要があるのですが、うまく使えていそうですね。
そして、Account6 にアクセスキーや署名証明書が含まれているため、後続の一括削除処理に進むことができないようになっています。

では Account6 のアクセスキーとデジタル署名証明書を削除した状態で使ってみるとどうなるでしょうか。
Account6 のルートユーザーでアクセスし、事前に上記を削除しておきました。

試してみると今度はルート認証情報の削除を実行するか聞かれましたね。
事前に定義した安全に削除できるであろう条件を満たしているので削除できるようになりました。

続行すると1アカウントずつ削除処理が進みます。

処理の終了後、マネジメントコンソールを確認してみると先程まで色々なアカウントにルートユーザー認証情報が存在していたのですが、一括で削除することが出来ていました。

さいごに
本日はルートアクセス管理を有効化している組織に対して全メンバーアカウントの認証情報をスクリプトで一括で無効化してみました。
私が個人的な仕様で作ったので実際の環境で使う際にはスクリプトの調整は必要だと思いますが、ルートアクセス管理機能を使って全メンバーアカウントに対して一括で何かするスクリプトを作成するときの参考にはなるのではないでしょうか。
AssumeRoot で認証情報を入れ替えながらメンバーアカウントを処理する必要があるのが注意点です。






