🔒
Security Hub CSPMのコントロール "SSM.7" を解消するための設定を組織内全アカウント全リージョンで行いたい
AWSのセキュリティサービスの一つである「Security Hub CSPM」にて「SSM.7」という新規のコントロールが追加され、その影響で大量にノイジーなアラートが発生しました。
内容としては「SSMドキュメントの『パブリック共有をブロック』設定は有効でなければならない」というもので、デフォルトではこれが無効でした。
設定自体はAWSコンソールから行うことができるのですが、組織内全アカウント全リージョンでポチポチするのは非常に面倒なので、上記記事を参考にCLI経由で一括設定を試みました。
CloudFormation StackSetsでIAMロールを作成
今回、CLIの実行は組織のルートアカウントのCloudShellから、各アカウントに対してAssumeRoleをした上で行う形としました。
そのため、組織内の全アカウントにIAMロールを作成する必要があります。
CloudFormation StackSetsにて、以下のCFnテンプレートを用いてサービスマネージドなStackSetsを作成することで完了です。
ManagementAccountId
には組織のルートアカウントのIDを入力してください。
AWSTemplateFormatVersion: "2010-09-09"
Description: "IAM Role for managing SSM public sharing settings across accounts via StackSets"
Parameters:
ManagementAccountId:
Type: String
Description: AWS Account ID of the management account that will assume this role
AllowedPattern: "[0-9]{12}"
ConstraintDescription: Must be a valid 12-digit AWS Account ID
RoleName:
Type: String
Default: SSMPublicSharingAdminRole
Description: Name of the IAM role to create
AllowedPattern: "[a-zA-Z0-9+=,.@_-]+"
MinLength: 1
MaxLength: 64
Resources:
SSMPublicSharingAdminRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref RoleName
Description: Role for managing SSM public sharing settings across AWS accounts
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${ManagementAccountId}:root"
Action: sts:AssumeRole
Policies:
- PolicyName: SSMPublicSharingManagement
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: ManageSSMServiceSettings
Effect: Allow
Action:
- ssm:UpdateServiceSetting
- ssm:GetServiceSetting
Resource: "*"
- Sid: DescribeRegions
Effect: Allow
Action:
- ec2:DescribeRegions
Resource: "*"
Tags:
- Key: Purpose
Value: SSMPublicSharingManagement
- Key: ManagedBy
Value: StackSets
- Key: CreatedBy
Value: CloudFormation
Outputs:
RoleArn:
Description: ARN of the created IAM role
Value: !GetAtt SSMPublicSharingAdminRole.Arn
Export:
Name: !Sub "${AWS::StackName}-SSMPublicSharingAdminRole-Arn"
RoleName:
Description: Name of the created IAM role
Value: !Ref SSMPublicSharingAdminRole
Export:
Name: !Sub "${AWS::StackName}-SSMPublicSharingAdminRole-Name"
AssumeRoleCommand:
Description: AWS CLI command to assume this role
Value: !Sub |
aws sts assume-role \
--role-arn "${SSMPublicSharingAdminRole}" \
--role-session-name "SSMPublicSharingSession"
CloudShellでスクリプトを実行
組織のルートアカウントでCloudShellを立ち上げ、以下のシェルスクリプトを実行します。
順次実行なので、完了までには結構時間がかかります。
#!/bin/bash
set -euo pipefail
# ---------------------------
# 設定
# ---------------------------
ROLE_NAME="SSMPublicSharingAdminRole"
SESSION_NAME="ManageSSMPublicSharingSession"
DRY_RUN=false
SETTING_VALUE="Disable" # デフォルト設定値
# 色付き出力
GREEN="\e[32m"
YELLOW="\e[33m"
RED="\e[31m"
RESET="\e[0m"
export AWS_MAX_ATTEMPTS=5 # スロットリング緩和
# ---------------------------
# 関数
# ---------------------------
log_info() { echo -e "${GREEN}[INFO]${RESET} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${RESET} $1"; }
log_error() { echo -e "${RED}[ERROR]${RESET} $1"; }
assume_role() {
local account_id="$1"
aws sts assume-role \
--role-arn "arn:aws:iam::$account_id:role/$ROLE_NAME" \
--role-session-name "$SESSION_NAME" \
--output json 2>/dev/null
}
usage() {
echo "Usage: $0 [--setting VALUE] [--role ROLE_NAME] [--dry-run]"
echo " --setting SSM パブリック共有設定: Enable または Disable (デフォルト: Disable)"
echo " --role クロスアカウントアクセス用のIAMロール名 (デフォルト: $ROLE_NAME)"
echo " --dry-run 実際に更新せずに実行内容を表示"
}
# ---------------------------
# 引数処理
# ---------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--setting)
if [[ "$2" == "Enable" || "$2" == "Disable" ]]; then
SETTING_VALUE="$2"
shift 2
else
log_error "不正な設定値: $2. 'Enable' または 'Disable' を指定してください"
usage
exit 1
fi
;;
--role)
ROLE_NAME="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
log_error "不明なオプション: $1"
usage
exit 1
;;
esac
done
$DRY_RUN && log_info "ドライランモード: 有効"
log_info "設定値: $SETTING_VALUE"
log_info "ロール名: $ROLE_NAME"
# ---------------------------
# メイン処理
# ---------------------------
log_info "SSM パブリック共有権限の更新処理を開始します..."
log_info "対象設定: $SETTING_VALUE"
# ---------------------------
# 管理アカウントを特定
# ---------------------------
CURRENT_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
log_info "管理アカウントID: $CURRENT_ACCOUNT_ID"
# ---------------------------
# アカウント一覧取得
# ---------------------------
log_info "AWS アカウントIDを取得中..."
ACCOUNT_IDS=$(aws organizations list-accounts \
--query "Accounts[?Status=='ACTIVE'].Id" \
--output text 2>/dev/null)
if [ -z "$ACCOUNT_IDS" ]; then
log_error "アクティブなアカウントが見つからないか、権限が不足しています。"
exit 1
fi
log_info "アクティブなアカウントを $(echo $ACCOUNT_IDS | wc -w) 件発見しました"
# ---------------------------
# 各アカウント処理
# ---------------------------
for ACCOUNT_ID in $ACCOUNT_IDS; do
log_info "🔄 アカウント処理中: $ACCOUNT_ID"
# 管理アカウントの場合は直接実行
if [[ "$ACCOUNT_ID" == "$CURRENT_ACCOUNT_ID" ]]; then
log_info "📋 管理アカウントを直接処理"
# 全リージョンを取得
REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text 2>/dev/null)
if [ -z "$REGIONS" ]; then
log_error "管理アカウントでリージョンが見つかりません。"
continue
fi
# 各リージョンで処理
for region in $REGIONS; do
log_info " 🌍 リージョン処理中: $region"
if $DRY_RUN; then
log_info " 🧪 ドライラン: $region で SSM パブリック共有を $SETTING_VALUE に設定予定"
log_info " ✅ 処理完了: ${region} (ドライラン - $SETTING_VALUE に設定予定)"
else
log_info " 🔧 $region で SSM 設定を更新中..."
if aws ssm update-service-setting \
--setting-id /ssm/documents/console/public-sharing-permission \
--setting-value $SETTING_VALUE \
--region ${region}; then
log_info " ✅ 処理完了: ${region} ($SETTING_VALUE に設定)"
else
log_warn " ⚠️ スキップ: ${region} (エラー詳細は上記を確認)"
fi
fi
sleep 0.5 # スロットリング緩和
done
else
# 他のアカウントの場合はAssume Role
log_info "🔐 アカウントにロール切り替え中: $ACCOUNT_ID"
CREDS_JSON=$(assume_role "$ACCOUNT_ID")
if [ -z "$CREDS_JSON" ]; then
log_warn "❌ アカウント $ACCOUNT_ID にロール切り替えできませんでした、スキップします..."
continue
fi
# 一時的に環境変数を設定
export AWS_ACCESS_KEY_ID=$(echo "$CREDS_JSON" | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS_JSON" | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo "$CREDS_JSON" | jq -r '.Credentials.SessionToken')
# 全リージョンを取得
REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text 2>/dev/null)
if [ -z "$REGIONS" ]; then
log_error "アカウント $ACCOUNT_ID でリージョンが見つかりません。"
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
continue
fi
# 各リージョンで処理
for region in $REGIONS; do
log_info " 🌍 リージョン処理中: $region"
if $DRY_RUN; then
log_info " 🧪 ドライラン: $region で SSM パブリック共有を $SETTING_VALUE に設定予定"
log_info " ✅ 処理完了: ${region} (ドライラン - $SETTING_VALUE に設定予定)"
else
log_info " 🔧 $region で SSM 設定を更新中..."
if aws ssm update-service-setting \
--setting-id /ssm/documents/console/public-sharing-permission \
--setting-value $SETTING_VALUE \
--region ${region}; then
log_info " ✅ 処理完了: ${region} ($SETTING_VALUE に設定)"
else
log_warn " ⚠️ スキップ: ${region} (エラー詳細は上記を確認)"
fi
fi
sleep 0.5 # スロットリング緩和
done
# 環境変数をクリア
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
fi
log_info "✅ アカウント完了: $ACCOUNT_ID"
sleep 2 # アカウント間のスロットリング緩和
echo ""
done
log_info "🎉 全アカウント処理完了"
余談
今回、シェルスクリプトはClaude Sonnet 4に作ってもらいました。
「組織内の全アカウント全リージョンで特定のCLIコマンドを実行したい」というパターンには結構遭遇するのですが、AssumeRoleを活用することで結構何でも出来ます。
上記シェルスクリプトを基に各自いじってみてください。
Discussion