全メンバーアカウントの全リージョンを対象に、AWS Systems Managerドキュメントのパブリック共有のブロック設定を有効化してみた

全メンバーアカウントの全リージョンを対象に、AWS Systems Managerドキュメントのパブリック共有のブロック設定を有効化してみた

2025.09.09

はじめに

AWS Security Hubコントロール [SSM.7] では、AWS Systems Manager ドキュメントのパブリック共有ブロック設定を監視し、設定が無効化されている場合にコントロールが失敗となります。

このコントロールのアラートを解消するには、パブリック共有ブロック設定を有効化する必要があります。

今回は、管理アカウントから全メンバーアカウントの全リージョンを対象に、SSMドキュメントのパブリック共有のブロック設定を有効化してみます。

1つのアカウントの1つのリージョンでコンソール操作による有効化を行う場合は、以下のブログをご参照ください。

https://dev.classmethod.jp/articles/securityhub-fsbp-remediation-ssm-7/

管理アカウントでの設定

後述するAWS Lambdaを使った一括設定は、メンバーアカウントのみが対象となります。管理アカウント自体は対象に含まれないため、管理アカウントでは個別にコマンドを実行する必要があります。

全リージョンを対象とした設定

管理アカウントの全リージョンを対象に、SSMドキュメントのパブリック共有のブロック設定を有効化するには、以下のコマンドを実行します。

AWS CloudShellを開いて実行してください。

			
			bash -c '
echo "全リージョンの取得中..."
if ! REGIONS=($(aws ec2 describe-regions --query "Regions[].RegionName" --output text 2>/dev/null)); then
    echo "エラー: リージョン一覧の取得に失敗しました"
    exit 1
fi

SUCCESS_COUNT=0
ERROR_COUNT=0
TOTAL_COUNT=${#REGIONS[@]}

echo "SSMドキュメント パブリック共有ブロック処理開始"
echo "対象リージョン数: $TOTAL_COUNT"
echo ""

for region in "${REGIONS[@]}"; do
    if aws ssm update-service-setting \
        --setting-id /ssm/documents/console/public-sharing-permission \
        --setting-value Disable \
        --region $region > /dev/null 2>&1; then

        SETTING_VALUE=$(aws ssm get-service-setting \
            --setting-id /ssm/documents/console/public-sharing-permission \
            --region $region \
            --query "ServiceSetting.SettingValue" \
            --output text 2>/dev/null)

        if [ "$SETTING_VALUE" = "Disable" ]; then
            echo "リージョン $region : 成功"
            ((SUCCESS_COUNT++))
        else
            echo "リージョン $region : 失敗 (現在の値: $SETTING_VALUE)"
            ((ERROR_COUNT++))
        fi
    else
        echo "リージョン $region : エラー (SSMサービスが利用できない可能性があります)"
        ((ERROR_COUNT++))
    fi
done

echo ""
echo "処理完了: 成功 $SUCCESS_COUNT/$TOTAL_COUNT リージョン"
if [ $ERROR_COUNT -gt 0 ]; then
    echo "エラー: $ERROR_COUNT リージョンで失敗しました"
fi

if [ $SUCCESS_COUNT -eq $TOTAL_COUNT ]; then
    echo "すべてのリージョンで正常に完了しました"
else
    echo "一部のリージョンで失敗がありました"
fi
'

		
実行結果
			
			全リージョンの取得中...
SSMドキュメント パブリック共有ブロック処理開始
対象リージョン数: 20

リージョン af-south-1 : 成功
リージョン ap-south-1 : 成功
リージョン eu-north-1 : 成功
リージョン eu-west-3 : 成功
リージョン eu-west-2 : 成功
リージョン eu-west-1 : 成功
リージョン ap-northeast-3 : 成功
リージョン ap-northeast-2 : 成功
リージョン ap-northeast-1 : 成功
リージョン ca-central-1 : 成功
リージョン sa-east-1 : 成功
リージョン mx-central-1 : 成功
リージョン ap-southeast-1 : 成功
リージョン ap-southeast-2 : 成功
リージョン eu-central-1 : 成功
リージョン us-east-1 : 成功
リージョン us-east-2 : 成功
リージョン us-west-1 : 成功
リージョン ap-southeast-7 : 成功
リージョン us-west-2 : 成功

処理完了: 成功 20/20 リージョン
すべてのリージョンで正常に完了しました

		

特定のリージョンでの設定

管理アカウントで、一部のリージョンのみを対象にSSMドキュメントのパブリック共有のブロックを有効化する場合は、以下のコマンドを使用してください。

東京と大阪リージョンのみを対象にする場合
			
			bash -c '
REGIONS=(
    "ap-northeast-1"  # 東京
    "ap-northeast-3"  # 大阪
)

SUCCESS_COUNT=0
TOTAL_COUNT=${#REGIONS[@]}

echo "SSMドキュメント パブリック共有ブロック処理開始"
echo "対象リージョン数: $TOTAL_COUNT"
echo ""

for region in "${REGIONS[@]}"; do
    if aws ssm update-service-setting \
        --setting-id /ssm/documents/console/public-sharing-permission \
        --setting-value Disable \
        --region $region > /dev/null 2>&1; then

        SETTING_VALUE=$(aws ssm get-service-setting \
            --setting-id /ssm/documents/console/public-sharing-permission \
            --region $region \
            --query "ServiceSetting.SettingValue" \
            --output text)

        if [ "$SETTING_VALUE" = "Disable" ]; then
            echo "リージョン $region : 成功"
            ((SUCCESS_COUNT++))
        else
            echo "リージョン $region : 失敗 (現在の値: $SETTING_VALUE)"
        fi
    else
        echo "リージョン $region : エラー"
    fi
done

echo ""
echo "処理完了: 成功 $SUCCESS_COUNT/$TOTAL_COUNT リージョン"
if [ $SUCCESS_COUNT -eq $TOTAL_COUNT ]; then
    echo "すべてのリージョンで正常に完了しました"
else
    echo "一部のリージョンで失敗がありました"
fi
'

		
実行結果
			
			SSMドキュメント パブリック共有ブロック処理開始
対象リージョン数: 2

リージョン ap-northeast-1 : 成功
リージョン ap-northeast-3 : 成功

処理完了: 成功 2/2 リージョン
すべてのリージョンで正常に完了し

		

全メンバーアカウントでの設定

この手順では、管理アカウントで作成したLambdaから全メンバーアカウントを対象に一括で有効化を実施します。管理アカウント自体は対象に含まれないため、前述の手順で個別に対応してください。

事前準備:各メンバーアカウントにIAMロール作成

以下の記事を参考に、管理アカウントのLambdaから各メンバーアカウントのリソースを操作するために必要なクロスアカウント用IAMロールを各メンバーアカウントに作成しておいてください。IAMロール名は「CrossAccountAdminRole」とします。

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

Lambdaの設定

管理アカウントでAWS CloudShellを開き、以下のコマンドを実行してLambda用のIAMポリシーを作成します。

			
			aws iam create-policy \
    --policy-name BlockSSMDocumentPublicSharingLambdaPolicy \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "organizations:ListAccountsForParent",
                    "organizations:ListChildren",
                    "ec2:DescribeRegions"
                ],
                "Resource": "*"
            },
            {
                "Effect": "Allow",
                "Action": "sts:AssumeRole",
                "Resource": "arn:aws:iam::*:role/CrossAccountAdminRole"
            }
        ]
    }' \

		

次に、管理アカウントで以下の設定でLambdaを作成します。

Lambda設定

  • ランタイム:Python 3.13
  • タイムアウト:15分
  • メモリ:2048MB
  • IAMポリシー
    • AWSLambdaBasicExecutionRole
    • 先ほど作成したIAMポリシーを適用

設定時の注意点

  • OU_IDには、対象のOUのIDを指定してください(例:ou-xxxx-xxxx
  • リージョン制限がある場合、TARGET_REGIONSにはリージョン制限されていないリージョンを指定してください
  • 一部のアカウントでオプトインリージョンを有効化している場合は、OUとオプトインリージョンのみを指定して実行してください
  • OU指定が困難な場合は、Lambdaは利用せず、管理アカウントで実行したコマンドを対象のメンバーアカウントにログインして個別に実行することをお勧めします
			
			import boto3
import logging
from boto3.session import Session
from concurrent.futures import ThreadPoolExecutor, as_completed
from botocore.exceptions import ClientError
import threading

OU_ID = 'ou-xxxx'
ROLE_NAME = 'CrossAccountAdminRole'

# 全リージョンを対象にする場合
TARGET_REGIONS = [
    'af-south-1', 'ap-east-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3',
    'ap-south-1', 'ap-south-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3',
    'ap-southeast-4', 'ca-central-1', 'eu-central-1', 'eu-central-2', 'eu-north-1',
    'eu-south-1', 'eu-south-2', 'eu-west-1', 'eu-west-2', 'eu-west-3',
    'me-central-1', 'me-south-1', 'sa-east-1', 'us-east-1', 'us-east-2',
    'us-west-1', 'us-west-2'
]

# 対象リージョンを指定する場合
# TARGET_REGIONS = [
#     'ap-northeast-1',
#     'us-east-1'
# ]

MAX_WORKERS_REGIONS = 5
MAX_WORKERS_ACCOUNTS = 10

logger = logging.getLogger()
logger.setLevel(logging.INFO)
logging.getLogger('boto3').setLevel(logging.WARNING)
logging.getLogger('botocore').setLevel(logging.WARNING)

account_results = {}
account_results_lock = threading.Lock()

def get_all_account_ids(org_client, ou_id):
    """指定されたOU配下の全アクティブアカウントIDを取得"""
    accounts = []
    queue = [ou_id]

    while queue:
        current_ou = queue.pop(0)
        try:
            # 現在のOUに属するアカウントを取得
            response = org_client.list_accounts_for_parent(ParentId=current_ou)
            for account in response['Accounts']:
                if account['Status'] == 'ACTIVE':
                    accounts.append(account['Id'])

            # 子OUを取得してキューに追加
            child_ous = org_client.list_children(ParentId=current_ou, ChildType='ORGANIZATIONAL_UNIT')
            queue.extend([child['Id'] for child in child_ous['Children']])

        except Exception as e:
            logger.error(f"Error retrieving accounts for OU {current_ou}: {e}")

    logger.info(f"All active accounts retrieved: {', '.join(accounts)}")
    return accounts

def sts_assume_role(account_id, role_name, region):
    """指定されたアカウントのIAMロールを引き受けてセッションを作成"""
    role_arn = f"arn:aws:iam::{account_id}:role/{role_name}"
    try:
        response = boto3.client('sts').assume_role(
            RoleArn=role_arn,
            RoleSessionName="SSMDocumentPublicSharingUpdate"
        )
        return Session(
            aws_access_key_id=response['Credentials']['AccessKeyId'],
            aws_secret_access_key=response['Credentials']['SecretAccessKey'],
            aws_session_token=response['Credentials']['SessionToken'],
            region_name=region
        )
    except Exception as e:
        logger.error(f"Error assuming role for account {account_id}: {e}")
        return None

def update_ssm_document_sharing(ssm_client, account_id, region):
    """SSMドキュメントのパブリック共有をブロック"""
    try:
        # パブリック共有をDisableに設定
        ssm_client.update_service_setting(
            SettingId='/ssm/documents/console/public-sharing-permission',
            SettingValue='Disable'
        )

        # 設定の確認
        response = ssm_client.get_service_setting(
            SettingId='/ssm/documents/console/public-sharing-permission'
        )

        current_value = response['ServiceSetting']['SettingValue']

        # スレッドセーフに結果を記録
        with account_results_lock:
            account_results.setdefault(account_id, {'successful_regions': set(), 'failed_regions': set()})
            if current_value == 'Disable':
                account_results[account_id]['successful_regions'].add(region)
                logger.info(f"Successfully blocked public sharing in account {account_id} region {region}")
            else:
                account_results[account_id]['failed_regions'].add(region)
                logger.error(f"Failed to block public sharing in account {account_id} region {region}: Current value is {current_value}")

    except Exception as e:
        logger.error(f"Error updating SSM document sharing in account {account_id} region {region}: {e}")
        # スレッドセーフに失敗した結果を記録
        with account_results_lock:
            account_results.setdefault(account_id, {'successful_regions': set(), 'failed_regions': set()})
            account_results[account_id]['failed_regions'].add(region)

def execute_in_regions(session, account_id, regions):
    """複数のリージョンで並列にSSM設定を更新"""
    with ThreadPoolExecutor(max_workers=MAX_WORKERS_REGIONS) as executor:
        futures = []
        for region in regions:
            futures.append(
                executor.submit(update_ssm_document_sharing, session.client('ssm', region_name=region), account_id, region)
            )

        # 全てのタスクが完了するまで待機
        for future in as_completed(futures):
            try:
                future.result()
            except Exception as e:
                logger.error(f"Error processing account {account_id}: {e}")

def process_account(account_id, regions, role_name):
    """1つのアカウントに対してSSM設定を更新"""
    session = sts_assume_role(account_id, role_name, 'us-east-1')

    # セッションの作成に失敗した場合は処理を中断
    if not session:
        logger.error(f"Failed to assume role for account {account_id}")
        return

    execute_in_regions(session, account_id, regions)

def generate_execution_summary():
    """実行結果のサマリーを生成"""
    summary_lines = ["=== SSM Document Public Sharing Block Summary ==="]
    total_success = total_failure = 0

    # スレッドセーフに結果を参照
    with account_results_lock:
        for account_id, result in sorted(account_results.items()):
            success_count = len(result['successful_regions'])
            failure_count = len(result['failed_regions'])
            total_success += success_count
            total_failure += failure_count

            # アカウントごとの結果をサマリーに追加
            summary_lines.extend([
                f"\nAccount: {account_id}",
                f"Successfully blocked regions ({success_count}): {', '.join(sorted(result['successful_regions']))}"
            ])
            if failure_count > 0:
                summary_lines.append(f"Failed regions ({failure_count}): {', '.join(sorted(result['failed_regions']))}")

    total_updates = total_success + total_failure
    success_rate = (total_success / total_updates * 100) if total_updates > 0 else 0

    summary_lines.extend([
        "\n=== Overall Statistics ===",
        f"Total successful updates: {total_success}",
        f"Total failed updates: {total_failure}",
        f"Success rate: {success_rate:.2f}%"
    ])

    return "\n".join(summary_lines), total_success, total_failure, success_rate

def lambda_handler(event, context):
    """Lambda関数のメインハンドラー"""
    try:
        # Organizationsクライアントを作成
        org_client = boto3.client('organizations')

        # メンバーアカウントを取得
        member_accounts = get_all_account_ids(org_client, OU_ID)

        if not member_accounts:
            logger.warning("No member accounts found in the specified OU")
            return {
                'statusCode': 200,
                'body': 'No member accounts found',
                'summary': {
                    'total_success': 0,
                    'total_failure': 0,
                    'success_rate': '0.00%'
                }
            }

        logger.info(f"Starting SSM document public sharing block for {len(member_accounts)} accounts in {len(TARGET_REGIONS)} regions")
        logger.info(f"Target regions: {', '.join(TARGET_REGIONS)}")

        # 各メンバーアカウントのSSM設定を並列で更新
        with ThreadPoolExecutor(max_workers=MAX_WORKERS_ACCOUNTS) as executor:
            futures = [
                executor.submit(process_account, account_id, TARGET_REGIONS, ROLE_NAME)
                for account_id in member_accounts
            ]

            for future in as_completed(futures):
                try:
                    future.result()
                except Exception as e:
                    logger.error(f"Error in account processing: {e}")

        # 実行結果のサマリーを生成
        summary, total_success, total_failure, success_rate = generate_execution_summary()
        logger.info(summary)

        return {
            'statusCode': 200,
            'body': 'SSM Document Public Sharing Block completed',
            'summary': {
                'total_success': total_success,
                'total_failure': total_failure,
                'success_rate': f"{success_rate:.2f}%",
                'processed_accounts': len(member_accounts),
                'processed_regions': len(TARGET_REGIONS)
            }
        }

    except Exception as e:
        logger.error(f"Lambda execution failed: {e}")
        return {
            'statusCode': 500,
            'body': f'Lambda execution failed: {str(e)}'
        }

		

処理の流れは以下のとおりです。

  1. 指定されたOU配下のすべてのアクティブアカウントを取得
  2. 各メンバーアカウントに対してクロスアカウントロールでアクセス
  3. 各リージョンでSSMドキュメントのパブリック共有設定を無効化
  4. 設定結果の確認と集計

動作確認

Lambdaを実行します。テストイベントには特に値を設定する必要がないため、空のイベントで実行してください。

cm-hirai-screenshot 2025-07-31 16.41.58

以下は、東京とバージニアリージョンのみを対象とした場合の実行例です。

出力結果例
			
			{
  "statusCode": 200,
  "body": "SSM Document Public Sharing Block completed",
  "summary": {
    "total_success": 4,
    "total_failure": 0,
    "success_rate": "100.00%",
    "processed_accounts": 2,
    "processed_regions": 2
  }
}
START RequestId: 522cd5ed-a9c2-49c3-b58a-eab60e562a78 Version: $LATEST
[INFO]	2025-07-31T07:42:49.705Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	All active accounts retrieved: 111111111111, 2222222222222
[INFO]	2025-07-31T07:42:49.705Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	Starting SSM document public sharing block for 2 accounts in 2 regions
[INFO]	2025-07-31T07:42:49.705Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	Target regions: ap-northeast-1, us-east-1
[INFO]	2025-07-31T07:42:54.512Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	Successfully blocked public sharing in account 111111111111 region us-east-1
[INFO]	2025-07-31T07:42:54.892Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	Successfully blocked public sharing in account 2222222222222 region us-east-1
[INFO]	2025-07-31T07:42:55.173Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	Successfully blocked public sharing in account 111111111111 region ap-northeast-1
[INFO]	2025-07-31T07:42:55.380Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	Successfully blocked public sharing in account 2222222222222 region ap-northeast-1
[INFO]	2025-07-31T07:42:55.411Z	522cd5ed-a9c2-49c3-b58a-eab60e562a78	=== SSM Document Public Sharing Block Summary ===

Account: 2222222222222
Successfully blocked regions (2): ap-northeast-1, us-east-1

Account: 111111111111
Successfully blocked regions (2): ap-northeast-1, us-east-1

=== Overall Statistics ===
Total successful updates: 4
Total failed updates: 0
Success rate: 100.00%
END RequestId: 522cd5ed-a9c2-49c3-b58a-eab60e562a78
REPORT RequestId: 522cd5ed-a9c2-49c3-b58a-eab60e562a78	Duration: 9207.44 ms	Billed Duration: 9208 ms	Memory Size: 128 MB	Max Memory Used: 105 MB	Init Duration: 321.03 ms	

		

最後に

今回は、AWS Security Hub コントロール [SSM.7] のアラートを解消するため、AWS Systems Manager ドキュメントのパブリック共有のブロック設定を組織全体で有効化しました。

実施した内容は以下のとおりです。

  • 管理アカウント: CloudShellでコマンドを実行して個別に設定
  • メンバーアカウント: 管理アカウントからLambdaを使用して一括設定

複数のアカウントと複数のリージョンを対象とする場合、手動での設定は非常に手間がかかりますが、今回のようにLambdaを活用することで効率的に実施できます。

参考

https://dev.classmethod.jp/articles/aws-config-all-accounts-service-role-update/

この記事をシェアする

FacebookHatena blogX

関連記事