ルートアクセス管理を有効化している組織に対して全メンバーアカウントの認証情報をスクリプトで一括で無効化する

ルートアクセス管理を有効化している組織に対して全メンバーアカウントの認証情報をスクリプトで一括で無効化する

2026.01.12

いわさです。

ルートアクセス管理機能を有効化すると、メンバーアカウントのルートユーザーの認証情報を削除することができます。
先日まで、API でどのように操作できるかいくつかの記事で確認していました。

今回いくつかの 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 デバイス、デジタル署名証明書も作成済みとします。

196C5C18-2A6B-4F95-B0AB-4BA8FD4F3713_1_105_c.jpeg

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

D5C22327-0FC3-43E2-A995-D983A0800693_1_105_c.jpeg

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

7DECB240-5600-4CF6-B6EF-88FF5A34988F_1_105_c.jpeg

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

12255CB8-E5F6-49E5-81FD-62B6A33B0AC0_1_105_c.jpeg

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

1F085C81-BE86-4D6E-84E9-4354E239A046_1_105_c.jpeg

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

9F8D04BA-415E-498B-A905-8C8B5B82D674_1_105_c.jpeg

さいごに

本日はルートアクセス管理を有効化している組織に対して全メンバーアカウントの認証情報をスクリプトで一括で無効化してみました。

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

この記事をシェアする

FacebookHatena blogX

関連記事