whoAMI-scanner を AWS Organizations 全体で実行してみた(複数アカウント・複数リージョン対応)

whoAMI-scanner を AWS Organizations 全体で実行してみた(複数アカウント・複数リージョン対応)

AWS Organizations環境でwhoAMI-scannerを活用し、複数アカウント・複数リージョンのAMI安全性を一括チェックする方法をご紹介します。管理アカウントから全メンバーアカウントのEC2インスタンスが信頼できるAMIを使用しているか効率的に検証できます。
Clock Icon2025.03.04

こんにちは!クラウド事業本部のおつまみです。

先日、whoAMI攻撃への対策をまとめたブログを公開しました。

https://dev.classmethod.jp/articles/aws-security-whoami-attack-protection/

このブログで紹介しているwhoAMI-scannerは、現環境のEC2インスタンスが信頼できるAMIを利用しているか確認できる大変便利なツールです。しかし、標準の使用方法では以下の制約があります。

  • 単一アカウント内でのみ実行可能
  • 全リージョンまたは単一リージョンのみの実行に限定

しかし今回以下のような要件に直面しました。

  • AWS Organizations配下の全アカウントを一括調査したい
  • SCPによるリージョン制限があるため、特定の複数リージョンのみを対象に調査したい

そこで本記事では、whoAMI-scannerを管理アカウントから全メンバーアカウントに対して一括実行する方法を紹介します。

事前作業

管理アカウントから各リソースアカウントのリソースを操作するために必要なクロスアカウント用IAMロール(CrossAccountAdminRole)を作成してください。

手順はこちらのブログをご参考ください。

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

作業手順

1.スクリプト実行前準備

  1. 共通アカウントにて、CloudShellを起動します

  2. 以下コマンドを入力し、作業ディレクトリの作成およびwhoAMI-scanner をインストールする。

    # 作業ディレクトリを作成
    mkdir -p whoAMI-scanner-org
    cd whoAMI-scanner-org
    
    # バイナリをダウンロード
    wget https://github.com/DataDog/whoAMI-scanner/releases/download/v1.0.0/whoAMI-scanner_1.0.0_linux_amd64.tar.gz
    tar -xzf whoAMI-scanner_1.0.0_linux_amd64.tar.gz
    

    (実行結果例)
    299B7CEA-D068-4967-B29B-AA4FCF76389F

  3. 以下コマンドを入力し、実行するスクリプトの作成および実行権限を付与する。(トグルを開くと内容を確認できます。)

    実行コマンド
    cat > scan_by_region.py << 'EOF'
    #!/usr/bin/env python3
    import boto3
    import subprocess
    import json
    import os
    import sys
    import argparse
    from datetime import datetime
    import traceback
    import fcntl
    
    # JSTタイムゾーン対応(pytzがなくても動作するように修正)
    def get_jst_now():
        """現在の時刻を取得(JST表記)"""
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S') + " JST"
    
    def get_all_accounts():
        """Organization 内の全アカウントのIDとステータスを取得"""
        client = boto3.client('organizations')
        accounts = []
    
        try:
            paginator = client.get_paginator('list_accounts')
            for page in paginator.paginate():
                for account in page['Accounts']:
                    if account['Status'] == 'ACTIVE':
                        accounts.append(account['Id'])
        except Exception as e:
            print(f"Error fetching accounts: {str(e)}")
            traceback.print_exc()
    
        return accounts
    
    def assume_role(account_id, role_name):
        """指定したアカウントの IAM ロールを引き受ける"""
        sts_client = boto3.client('sts')
        try:
            response = sts_client.assume_role(
                RoleArn=f'arn:aws:iam::{account_id}:role/{role_name}',
                RoleSessionName='WhoAMIScannerSession'
            )
            return {
                'AWS_ACCESS_KEY_ID': response['Credentials']['AccessKeyId'],
                'AWS_SECRET_ACCESS_KEY': response['Credentials']['SecretAccessKey'],
                'AWS_SESSION_TOKEN': response['Credentials']['SessionToken']
            }
        except Exception as e:
            print(f"Error assuming role for account {account_id}: {str(e)}")
            return None
    
    def run_scanner(account_id, credentials, region, output_dir):
        """whoAMI-scanner を実行"""
        print(f"Scanning account: {account_id} in region: {region}")
    
        # 環境変数を設定して whoAMI-scanner を実行
        env = os.environ.copy()
        if credentials:
            env.update(credentials)
    
        # whoAMI-scanner の存在確認
        scanner_path = './whoAMI-scanner'
        if not os.path.exists(scanner_path):
            error_msg = f"Error: whoAMI-scanner not found at {os.path.abspath(scanner_path)}"
            print(error_msg)
            return False
    
        # 実行権限の確認
        if not os.access(scanner_path, os.X_OK):
            print(f"Adding execute permission to {scanner_path}")
            os.chmod(scanner_path, 0o755)
    
        # コマンドを構築
        command = [scanner_path, '-verbose', '-region', region]
    
        # スキャン実行
        try:
            result = subprocess.run(
                command,
                env=env,
                capture_output=True,
                text=True
            )
    
            # アカウント別の結果ファイル
            account_result_file = f"{output_dir}/account_{account_id}_results.txt"
    
            # ファイルロックを使用して安全に追記
            with open(account_result_file, 'a') as f:
                fcntl.flock(f, fcntl.LOCK_EX)
                f.write(f"\n\n{'='*80}\n")
                f.write(f"REGION: {region} | TIME: {get_jst_now()}\n")
                f.write(f"{'='*80}\n\n")
                f.write(result.stdout)
                fcntl.flock(f, fcntl.LOCK_UN)
    
            # エラーがある場合のみエラーファイルに追記
            if result.stderr:
                account_error_file = f"{output_dir}/account_{account_id}_errors.txt"
                with open(account_error_file, 'a') as f:
                    fcntl.flock(f, fcntl.LOCK_EX)
                    f.write(f"\n\n{'='*80}\n")
                    f.write(f"REGION: {region} | TIME: {get_jst_now()}\n")
                    f.write(f"{'='*80}\n\n")
                    f.write(result.stderr)
                    fcntl.flock(f, fcntl.LOCK_UN)
    
            print(f"Completed scanning account: {account_id} in region: {region}")
            return True
        except Exception as e:
            error_msg = f"Error scanning account {account_id} in region {region}: {str(e)}"
            print(error_msg)
    
            # エラーファイルを作成
            account_error_file = f"{output_dir}/account_{account_id}_errors.txt"
            with open(account_error_file, 'a') as f:
                fcntl.flock(f, fcntl.LOCK_EX)
                f.write(f"\n\n{'='*80}\n")
                f.write(f"REGION: {region} | TIME: {get_jst_now()}\n")
                f.write(f"{'='*80}\n\n")
                f.write(f"{error_msg}\n")
                f.write(traceback.format_exc())
                fcntl.flock(f, fcntl.LOCK_UN)
    
            return False
    
    def ensure_file_header(file_path, account_id, file_type):
        """ファイルが存在しない場合はヘッダーを作成"""
        if not os.path.exists(file_path):
            with open(file_path, 'w') as f:
                fcntl.flock(f, fcntl.LOCK_EX)
                f.write(f"whoAMI-scanner {file_type} - Account: {account_id}\n")
                f.write(f"Scan started at: {get_jst_now()}\n")
                f.write(f"{'='*80}\n")
                fcntl.flock(f, fcntl.LOCK_UN)
    
    def main():
        parser = argparse.ArgumentParser(description='Run whoAMI-scanner for a specific region across all accounts')
        parser.add_argument('--region', required=True, help='AWS region to scan')
        parser.add_argument('--role', default='CrossAccountAdminRole', help='IAM role name to assume (default: CrossAccountAdminRole)')
        parser.add_argument('--output-dir', default='scan_results', help='Directory to store scan results (default: scan_results)')
    
        args = parser.parse_args()
    
        # 出力ディレクトリを設定
        output_dir = args.output_dir
        os.makedirs(output_dir, exist_ok=True)
    
        # リージョン別の結果概要ファイル
        summary_file = f"{output_dir}/region_{args.region}_summary.txt"
    
        # 概要ファイルのヘッダーを作成
        with open(summary_file, 'w') as f:
            f.write(f"whoAMI-scanner Summary - Region: {args.region}\n")
            f.write(f"Scan started at: {get_jst_now()}\n")
            f.write(f"{'='*80}\n\n")
    
        # 管理アカウントのIDを取得
        sts_client = boto3.client('sts')
        try:
            management_account_id = sts_client.get_caller_identity()['Account']
            print(f"Management account ID: {management_account_id}")
    
            # 管理アカウント用の結果ファイルのヘッダーを確保
            account_result_file = f"{output_dir}/account_{management_account_id}_results.txt"
            ensure_file_header(account_result_file, management_account_id, "Results")
    
            # 管理アカウントは直接実行
            print(f"Scanning management account: {management_account_id} in region: {args.region}")
            run_scanner(management_account_id, None, args.region, output_dir)
    
            # 概要ファイルに追記
            with open(summary_file, 'a') as f:
                f.write(f"- Scanned management account: {management_account_id}\n")
    
            # Organization 内の全アカウントを取得
            accounts = get_all_accounts()
            print(f"Found {len(accounts)} accounts in the organization")
    
            # 他のアカウントはロールを引き受けて実行
            for account_id in accounts:
                if account_id != management_account_id:
                    # アカウント用の結果ファイルのヘッダーを確保
                    account_result_file = f"{output_dir}/account_{account_id}_results.txt"
                    ensure_file_header(account_result_file, account_id, "Results")
    
                    credentials = assume_role(account_id, args.role)
                    if credentials:
                        run_scanner(account_id, credentials, args.region, output_dir)
                        # 概要ファイルに追記
                        with open(summary_file, 'a') as f:
                            f.write(f"- Scanned account: {account_id}\n")
                    else:
                        # 概要ファイルに追記
                        with open(summary_file, 'a') as f:
                            f.write(f"- Failed to scan account: {account_id} (role assumption failed)\n")
                        print(f"Skipping account {account_id} due to role assumption failure")
    
            # スキャン完了時間を記録
            with open(summary_file, 'a') as f:
                f.write(f"\nScan completed at: {get_jst_now()}\n")
    
            print(f"Scan completed for region {args.region}")
            print(f"Summary stored in {summary_file}")
    
        except Exception as e:
            print(f"Error in main execution: {str(e)}")
            sys.exit(1)
    
    if __name__ == "__main__":
        main()
    EOF
    
    chmod +x scan_by_region.py
    
    cat > run_org_scan.sh << 'EOF'
    #!/bin/bash
    
    # JSTタイムゾーンを設定
    export TZ=Asia/Tokyo
    
    # 実行開始時間を記録 (JST)
    start_time=$(date +"%Y-%m-%d %H:%M:%S JST")
    echo "スキャン開始時間: $start_time"
    
    # スクリプトのディレクトリを取得
    SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    echo "スクリプトディレクトリ: $SCRIPT_DIR"
    
    # whoAMI-scanner の存在確認
    if [ ! -f "$SCRIPT_DIR/whoAMI-scanner" ]; then
        echo "whoAMI-scanner が見つかりません。ダウンロードします。"
        wget https://github.com/DataDog/whoAMI-scanner/releases/download/v1.0.0/whoAMI-scanner_1.0.0_linux_amd64.tar.gz
        tar -xzf whoAMI-scanner_1.0.0_linux_amd64.tar.gz
        chmod +x whoAMI-scanner
    fi
    
    # リージョンの配列を定義
    regions=("us-east-1" "ap-northeast-1" "ap-northeast-3")
    
    # タイムスタンプを生成(すべてのリージョンで共通のディレクトリを使用)
    timestamp=$(date +"%Y%m%d_%H%M%S")
    output_dir="$SCRIPT_DIR/scan_results/$timestamp"
    mkdir -p "$output_dir"
    echo "出力ディレクトリ: $output_dir"
    
    # IAMロール名(必要に応じて変更)
    role_name="CrossAccountAdminRole"
    
    # 各リージョンを別々のプロセスで実行
    pids=()
    for region in "${regions[@]}"; do
        echo "Starting scan for region: $region"
        python3 "$SCRIPT_DIR/scan_by_region.py" --region "$region" --role "$role_name" --output-dir "$output_dir" > "${output_dir}/${region}_execution.log" 2>&1 &
        pids+=($!)
    done
    
    # すべてのバックグラウンドプロセスが終了するまで待機
    echo "すべてのリージョンのスキャンを実行中..."
    for pid in "${pids[@]}"; do
        wait $pid
        status=$?
        if [ $status -ne 0 ]; then
            echo "プロセス $pid が異常終了しました(終了コード: $status)"
        fi
    done
    
    # 実行終了時間を記録 (JST)
    end_time=$(date +"%Y-%m-%d %H:%M:%S JST")
    echo "スキャン終了時間: $end_time"
    
    # 結果の概要を作成
    echo "すべてのリージョンのスキャンが完了しました"
    echo "結果は以下のディレクトリに保存されています: $output_dir"
    
    # 全リージョンの結果を1つのファイルにまとめる
    consolidated_file="${output_dir}/all_regions_summary.txt"
    
    echo "全リージョンの概要を1つのファイルにまとめています..."
    
    # ヘッダーを作成
    echo "whoAMI-scanner Consolidated Summary - All Regions" > "$consolidated_file"
    echo "Scan period: $start_time to $end_time" >> "$consolidated_file"
    echo "$(printf '=%.0s' {1..80})" >> "$consolidated_file"
    echo "" >> "$consolidated_file"
    
    # 各リージョンの概要を統合
    for region in "${regions[@]}"; do
        region_summary="${output_dir}/region_${region}_summary.txt"
    
        if [ -f "$region_summary" ]; then
            echo "" >> "$consolidated_file"
            echo "$(printf '#%.0s' {1..80})" >> "$consolidated_file"
            echo "# REGION: $region" >> "$consolidated_file"
            echo "$(printf '#%.0s' {1..80})" >> "$consolidated_file"
            echo "" >> "$consolidated_file"
            cat "$region_summary" >> "$consolidated_file"
        else
            echo "Warning: Summary file for $region not found at $region_summary"
        fi
    done
    
    # 各アカウントの結果ファイルに完了時間を追記
    for account_file in "${output_dir}"/account_*_results.txt; do
        if [ -f "$account_file" ]; then
            echo -e "\n\nScan completed at: $end_time" >> "$account_file"
            echo "$(printf '=%.0s' {1..80})" >> "$account_file"
        fi
    done
    
    # 結果の概要を表示
    echo "アカウント別スキャン結果概要:"
    # アカウント結果ファイルを検索
    account_files=$(find "$output_dir" -name "account_*_results.txt" | sort)
    for file in $account_files; do
        account_id=$(basename "$file" | cut -d'_' -f2)
        error_file="${output_dir}/account_${account_id}_errors.txt"
    
        if [ -f "$error_file" ]; then
            echo "- アカウント $account_id: スキャン完了 (エラーあり)"
        else
            echo "- アカウント $account_id: スキャン完了"
        fi
    done
    
    # 結果をZIPファイルにまとめる(階層を変更)
    zip_file="${SCRIPT_DIR}/whoAMI_scan_${timestamp}.zip"
    echo "結果をZIPファイルにまとめています: $zip_file"
    
    # 現在のディレクトリを保存
    current_dir=$(pwd)
    
    # 出力ディレクトリに移動してZIPを作成(相対パスを使用)
    cd "$output_dir"
    zip -r "$zip_file" . > /dev/null
    cd "$current_dir"
    
    echo "スキャン完了!"
    echo "開始時間: $start_time"
    echo "終了時間: $end_time"
    echo "統合概要ファイル: $consolidated_file"
    echo "結果ZIP: $zip_file"
    EOF
    
    chmod +x run_org_scan.sh
    
  4. 以下のコマンドを入力し、run_org_scan.sh,scan_by_region.pyが作成されていることを確認する。

    # スクリプトファイルが作成されていることを確認
    ls -la
    

    (実行結果例)

    15B6DA9A-EF2E-473F-8C27-2736BAD03D52_4_5005_c

2.スクリプト実行・結果ファイルのダウンロード

  1. 以下のコマンドを入力し、スクリプトを実行する。

    # スクリプト実行
    ./run_org_scan.sh
    

    (実行結果例)

    9F2CF568-A0D7-475F-B218-978F4E2AA7F5

  2. [結果ZIP]に出力されたzipファイルをCloudShellからダウンロードする。[アクション]→[ファイルのダウンロード]を選択する。

    411BE5A6-E9E0-48F4-8016-7AC4889BD3A0

  3. [個別のファイルパス]に[結果ZIPに出力されたzipファイル名を入力し、[ダウンロード]を選択する。

    FDFEC9DB-E8C7-4014-86A1-B2A84CA3F950

3.結果ファイルの確認

  1. ローカルにダウンロードしたzipファイルを解凍する。以下のパスでファイルが出力される。

    /
    ├── account_[アカウントID]_results.txt  # 各アカウントの結果(選択した全リージョンをまとめたもの)
    ├── account_[アカウントID]_errors.txt   # エラーがある場合のみ(選択した全リージョンをまとめたもの)
    ├── region_[リージョン]_summary.txt     # リージョン別の概要
    ├── all_regions_summary.txt             # 全リージョンの概要
    └── [リージョン]_execution.log          # 実行ログ(デバッグ用)
    
  2. account_[アカウントID]_results.txt を開き、Public, unverified, & unknown にカウントされているインスタンスおよびAMIがないか確認する。
    (出力結果例)

    whoAMI-scanner Results - Account: 123456789012
    Scan started at: 2025-03-04 10:11:31 JST
    ================================================================================
    
    ================================================================================
    REGION: ap-northeast-3 | TIME: 2025-03-04 10:11:31 JST
    ================================================================================
    
    [ 👀 whoAMI-scanner v1.0.0 👀 ] AWS Caller Identity: arn:aws:sts::123456789012:assumed-role/cm-matsunami.kana/cm-matsunami.kana
    [*] Verbose mode enabled.
    [*] Starting AMI analysis...
    [*] [ap-northeast-3] Allowed AMI Accounts status: Disabled
    
    Summary Key:
    +-------------------------------+-----------------------------------------------------------+
    | Term                          | Definition                                                |
    +-------------------------------+-----------------------------------------------------------+
    | Self hosted                   | AMIs from this account                                    |
    | Allowed AMIs                  | AMIs from an allowed account per the AWS Allowed AMIs API |
    | Trusted AMIs                  | AMIs from an trusted account per user input to this tool  |
    | Verified AMIs                 | AMIs from Verified Accounts (Verified by Amazon)          |
    | Shared with me (Private)      | AMIs shared privately with this account but NOT from a    |
    |                               | verified, trusted or allowed account. If you trust this   |
    |                               | account, add it to your Allowed AMIs API or specify it as |
    |                               | trusted in the whoAMI-scanner command line.               |
    | Public, unverified, but known | AMIs from unverified accounts, but we found the account   |
    |                               | ID in fwdcloudsec's known_aws_accounts mapping:           |
    |                               |   https://github.com/fwdcloudsec/known_aws_accounts.      |
    |                               | These are likely safe to use but worth investigating.     |
    | Public, unverified, & unknown | AMIs from unverified accounts. Be cautious with these     |
    |                               | unless they are from accounts you control. If not from    |
    |                               | your accounts, look to replace these with AMIs from       |
    |                               | verified accounts                                         |
    +-------------------------------+-----------------------------------------------------------+
    
    Summary:
     AWS's "Allowed AMI" config status by region
                     Enabled/Audit-mode/Disabled: 0/0/1
                                 Total Instances: 0
                                      Total AMIs: 0
                                Self hosted AMIs: 0
                                    Allowed AMIs: 0
                                    Trusted AMIs: 0
                                   Verified AMIs: 0
                   Shared with me (Private) AMIs: 0
                   Public, unverified, but known: 0
              Public, unverified, & unknown AMIs: 0
    
    [!] No regions have AWS's "Allowed AMIs" feature enabled or in audit mode.
    	Enabling Allowed AMIs protects you against the whoAMI attack.
    	Visit https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-allowed-amis.html for more information.
    
    ================================================================================
    REGION: ap-northeast-1 | TIME: 2025-03-04 10:11:31 JST
    ================================================================================
    
    [ 👀 whoAMI-scanner v1.0.0 👀 ] AWS Caller Identity: arn:aws:sts::123456789012:assumed-role/cm-matsunami.kana/cm-matsunami.kana
    [*] Verbose mode enabled.
    [*] Starting AMI analysis...
    [*] [ap-northeast-1] Allowed AMI Accounts status: Disabled
    [1/1][ap-northeast-1] ami-072298436ce5cb0c4 being analyzed (Instance: i-04ca1a803f1315e6e)
    [1/1][ap-northeast-1] ami-072298436ce5cb0c4 is a community AMI from an AWS verified account.
    
    Summary Key:
    +-------------------------------+-----------------------------------------------------------+
    | Term                          | Definition                                                |
    +-------------------------------+-----------------------------------------------------------+
    | Self hosted                   | AMIs from this account                                    |
    | Allowed AMIs                  | AMIs from an allowed account per the AWS Allowed AMIs API |
    | Trusted AMIs                  | AMIs from an trusted account per user input to this tool  |
    | Verified AMIs                 | AMIs from Verified Accounts (Verified by Amazon)          |
    | Shared with me (Private)      | AMIs shared privately with this account but NOT from a    |
    |                               | verified, trusted or allowed account. If you trust this   |
    |                               | account, add it to your Allowed AMIs API or specify it as |
    |                               | trusted in the whoAMI-scanner command line.               |
    | Public, unverified, but known | AMIs from unverified accounts, but we found the account   |
    |                               | ID in fwdcloudsec's known_aws_accounts mapping:           |
    |                               |   https://github.com/fwdcloudsec/known_aws_accounts.      |
    |                               | These are likely safe to use but worth investigating.     |
    | Public, unverified, & unknown | AMIs from unverified accounts. Be cautious with these     |
    |                               | unless they are from accounts you control. If not from    |
    |                               | your accounts, look to replace these with AMIs from       |
    |                               | verified accounts                                         |
    +-------------------------------+-----------------------------------------------------------+
    
    Summary:
     AWS's "Allowed AMI" config status by region
                     Enabled/Audit-mode/Disabled: 0/0/1
                                 Total Instances: 1
                                      Total AMIs: 1
                                Self hosted AMIs: 0
                                    Allowed AMIs: 0
                                    Trusted AMIs: 0
                                   Verified AMIs: 1
                   Shared with me (Private) AMIs: 0
                   Public, unverified, but known: 0
              Public, unverified, & unknown AMIs: 0
    
    [!] No regions have AWS's "Allowed AMIs" feature enabled or in audit mode.
    	Enabling Allowed AMIs protects you against the whoAMI attack.
    	Visit https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-allowed-amis.html for more information.
    
    ================================================================================
    REGION: us-east-1 | TIME: 2025-03-04 10:11:32 JST
    ================================================================================
    
    [ 👀 whoAMI-scanner v1.0.0 👀 ] AWS Caller Identity: arn:aws:sts::123456789012:assumed-role/cm-matsunami.kana/cm-matsunami.kana
    [*] Verbose mode enabled.
    [*] Starting AMI analysis...
    [*] [us-east-1] Allowed AMI Accounts status: Disabled
    
    Summary Key:
    +-------------------------------+-----------------------------------------------------------+
    | Term                          | Definition                                                |
    +-------------------------------+-----------------------------------------------------------+
    | Self hosted                   | AMIs from this account                                    |
    | Allowed AMIs                  | AMIs from an allowed account per the AWS Allowed AMIs API |
    | Trusted AMIs                  | AMIs from an trusted account per user input to this tool  |
    | Verified AMIs                 | AMIs from Verified Accounts (Verified by Amazon)          |
    | Shared with me (Private)      | AMIs shared privately with this account but NOT from a    |
    |                               | verified, trusted or allowed account. If you trust this   |
    |                               | account, add it to your Allowed AMIs API or specify it as |
    |                               | trusted in the whoAMI-scanner command line.               |
    | Public, unverified, but known | AMIs from unverified accounts, but we found the account   |
    |                               | ID in fwdcloudsec's known_aws_accounts mapping:           |
    |                               |   https://github.com/fwdcloudsec/known_aws_accounts.      |
    |                               | These are likely safe to use but worth investigating.     |
    | Public, unverified, & unknown | AMIs from unverified accounts. Be cautious with these     |
    |                               | unless they are from accounts you control. If not from    |
    |                               | your accounts, look to replace these with AMIs from       |
    |                               | verified accounts                                         |
    +-------------------------------+-----------------------------------------------------------+
    
    Summary:
     AWS's "Allowed AMI" config status by region
                     Enabled/Audit-mode/Disabled: 0/0/1
                                 Total Instances: 0
                                      Total AMIs: 0
                                Self hosted AMIs: 0
                                    Allowed AMIs: 0
                                    Trusted AMIs: 0
                                   Verified AMIs: 0
                   Shared with me (Private) AMIs: 0
                   Public, unverified, but known: 0
              Public, unverified, & unknown AMIs: 0
    
    [!] No regions have AWS's "Allowed AMIs" feature enabled or in audit mode.
    	Enabling Allowed AMIs protects you against the whoAMI attack.
    	Visit https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-allowed-amis.html for more information.
    
    Scan completed at: 2025-03-04 10:11:35 JST
    ================================================================================
    
    

    結果の確認方法については、以下もご参考ください。

4.お片付け

  1. 共通アカウントのCloudShellにて以下のコマンドを実行し、作業ディレクトリを削除します。

    #  ディレクトリを移動
    cd ..
    
    #  作業ディレクト全体を削除
    rm -rf whoAMI-scanner-org/
    

    (実行結果)

    5EBE3718-0B8C-4530-8CD0-5B69861CF9F2_4_5005_c

  2. 共通アカウントにて、以下手順の「スタックを削除する」のみ実施する

おまけ:スクリプトのカスタマイズ方法

今回作成したスクリプトをカスタマイズする方法をご紹介します。

スキャン対象リージョンの変更

run_org_scan.sh ファイル内の以下の部分を編集して、スキャン対象のリージョンを変更できます。

# リージョンの配列を定義
regions=("us-east-1" "ap-northeast-1" "ap-northeast-3")

例えば、すべての主要リージョンをスキャンする場合は、以下のように設定します。

regions=("us-east-1" "us-east-2" "us-west-1" "us-west-2" "ap-northeast-1" "ap-northeast-2" "ap-northeast-3" "ap-southeast-1" "ap-southeast-2" "eu-central-1" "eu-west-1" "eu-west-2" "eu-west-3" "sa-east-1")

IAM ロール名の変更

クロスアカウントアクセスに使用するIAMロール名を変更する場合は、run_org_scan.sh ファイル内の以下の部分を編集します

# IAMロール名(必要に応じて変更)
role_name="CrossAccountAdminRole"

特定のアカウントのみスキャン

特定のアカウントのみをスキャンする場合は、scan_by_region.py を修正して、アカウントIDのフィルタリングを追加します。

# 特定のアカウントのみをスキャン
target_accounts = ["123456789012", "098765432109"]
for account_id in accounts:
    if account_id in target_accounts and account_id != management_account_id:
        credentials = assume_role(account_id, args.role)
        if credentials:
            run_scanner(account_id, credentials, args.region, output_file, error_file)

さいごに

今回は、whoAMI-scannerを管理アカウントから全メンバーアカウントに対して一括実行する方法をご紹介しました。

AMIの安全性確認は、whoAMI攻撃対策として非常に重要ですが、複数アカウント・複数リージョンにまたがる環境では実施が煩雑になりがちです。今回ご紹介したスクリプトを活用することで、AWS Organizations全体の安全性を効率的に確認できるため、ぜひご活用ください!

最後までお読みいただきありがとうございました!

以上、おつまみ(@AWS11077)でした!

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.