管理アカウントからSSM Automationを使用して、複数のメンバーアカウントのEC2インスタンスに特定のコマンドを実行してみた

管理アカウントからSSM Automationを使用して、複数のメンバーアカウントのEC2インスタンスに特定のコマンドを実行してみた

Clock Icon2025.01.20

はじめに

自作のコマンドをオートメーションランブックとして作成し、管理アカウントからSSMオートメーションを使用して複数のメンバーアカウントのEC2インスタンスで実行する方法をまとめました。

automation-multi-region-and-multi-account (1)
引用元

SSMドキュメントにはいくつかのタイプがあり、その中でSSMオートメーションで利用するドキュメントは「オートメーションランブック」として分類されています。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/documents.html

そのため、本記事では、SSMオートメーションで利用するSSMドキュメントを「オートメーションランブック」と呼びます。

前提条件

  • 対象のEC2インスタンスがマネージドインスタンス化されていること
    • 今回は、OSがLinuxのEC2インスタンスを用意しています。

IAMロール作成

管理アカウントからメンバーアカウントに対して、SSMオートメーションを実行するには、管理アカウントとメンバーアカウントの両方にIAMロールを作成する必要があります。

以下のドキュメントには、2つのCloudFormationテンプレートが用意されています。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/running-automations-multiple-accounts-regions.html#setup-management-account-iam-roles

AWS-SystemsManager-AutomationAdministrationRole (org).yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Configure the AWS-SystemsManager-AutomationAdministrationRole to allow multi-region and multi-account automations for your organization."
Resources:
  MasterAccountRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ssm.amazonaws.com
          Action: sts:AssumeRole
          Condition:
            ArnLike:
              aws:SourceArn: 
                Fn::Sub: arn:${AWS::Partition}:ssm:*:*:automation-execution/*
      Path: "/"
      RoleName: AWS-SystemsManager-AutomationAdministrationRole
      Policies:
        - PolicyName: 'AssumeRole-AWSSystemsManagerAutomationExecutionRole'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action: sts:AssumeRole
              Resource: 
                Fn::Sub: arn:${AWS::Partition}:iam::*:role/AWS-SystemsManager-AutomationExecutionRole
            - Effect: Allow
              Action: 
              - organizations:ListAccountsForParent
              - organizations:ListChildren
              Resource: '*'
AWS-SystemsManager-AutomationExecutionRole (org).yml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Configure the AWS-SystemsManager-AutomationExecutionRole.'
Parameters:
  AdminAccountId:
    Type: String
    Description: "The ID of the primary account from which automations will be initiated for your organization."
    MaxLength: 12
    MinLength: 12
  OrganizationID:
    Type: String
    Description: AWS Organizations ID.
    AllowedPattern: "^o-[a-z0-9]{10,32}$"
Resources:
  AWSSystemsManagerAutomationExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AWS-SystemsManager-AutomationExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            AWS:
              Fn::Sub:
              - arn:${AWS::Partition}:iam::${AdminAccountId}:role/AWS-SystemsManager-AutomationAdministrationRole
              - AdminAccountId:
                  Ref: AdminAccountId
          Action: sts:AssumeRole
          Condition:
            StringEquals:
              aws:PrincipalOrgID:
                Ref: OrganizationID
        - Effect: Allow
          Principal:
            Service: ssm.amazonaws.com
          Action:
          - sts:AssumeRole
          Condition:
            StringEquals:
              aws:SourceAccount: 
                Fn::Sub: ${AWS::AccountId}
            ArnLike:
              aws:SourceArn: 
                Fn::Sub: arn:${AWS::Partition}:ssm:*:${AWS::AccountId}:automation-execution/*
      ManagedPolicyArns:
      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole
      Path: "/"
      Policies:
      - PolicyName: ExecutionPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - resource-groups:ListGroupResources
            - tag:GetResources
            - ec2:DescribeInstances
            Resource: "*"
          - Effect: Allow
            Action:
            - iam:PassRole
            Resource:
              Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:role/AWS-SystemsManager-AutomationExecutionRole

CloudFormationでIAMロール作成

管理アカウントで作成するIAMロールは、以下の設定を使用してCloudFormationスタックを作成します。
作成されるIAMロール名は、AWS-SystemsManager-AutomationAdministrationRoleです。

  • スタック名:AWS-SystemsManager-AutomationAdministrationRole
  • テンプレートの指定:AWS-SystemsManager-AutomationAdministrationRole (org).yml

メンバーアカウントで作成が必要なIAMロールは、以下の設定を使用してCloudFormation StackSetsを作成します。
作成されるIAMロール名は、AWS-SystemsManager-AutomationExecutionRoleです。

  • アクセス許可モデル:サービスマネージドアクセス許可(SERVICE_MANAGED)
  • テンプレートの指定:AWS-SystemsManager-AutomationExecutionRole (org).yml
  • StackSet 名:AWS-SystemsManager-AutomationExecutionRole
  • パラメータ
    • AdminAccountId:管理アカウントのAWSアカウントID
    • OrganizationID :管理アカウントのAWS Organizationsページで確認できる「組織 ID」
  • デプロイターゲット:組織単位 (OU) へのデプロイ
    • AWS OU ID:対象アカウントを含むOU ID
  • リージョンの指定:東京リージョン

オートメーションランブックを作成

管理アカウントで、SSMドキュメントに遷移し、[ドキュメントの作成]から[オートメーション]を選択します。

cm-hirai-screenshot 2025-01-17 17.16.16

以下のコードを貼り付け、check-osという名前でオートメーションランブックを作成します。

check-os
schemaVersion: '0.3'
description: Check OS settings using SSM Automation.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  AutomationAssumeRole:
    type: String
    description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
  InstanceId:
    type: String
    description: The ID of the EC2 instance to check.
  CheckHostname:
    type: String
    default: check
    description: Check hostname or skip.
    allowedValues:
      - check
      - skip
  CheckTimezone:
    type: String
    default: check
    description: Check timezone or skip.
    allowedValues:
      - check
      - skip
  CheckLocale:
    type: String
    default: check
    description: Check locale or skip.
    allowedValues:
      - check
      - skip
  CheckUpdate:
    type: String
    default: check
    description: Check yum updates or skip.
    allowedValues:
      - check
      - skip
  CheckNTP:
    type: String
    default: check
    description: Check NTP status or skip.
    allowedValues:
      - check
      - skip
mainSteps:
  - name: CheckHostname
    action: aws:runCommand
    maxAttempts: 1
    timeoutSeconds: 10
    nextStep: CheckTimezone
    isEnd: false
    onFailure: Continue
    inputs:
      DocumentName: AWS-RunShellScript
      Parameters:
        commands:
          - if [[ "{{ CheckHostname }}" == "check" ]]; then hostname; else echo "Skipping hostname check"; fi
      InstanceIds:
        - '{{ InstanceId }}'
    outputs:
      - Name: HostnameOutput
        Selector: $.CommandPlugins[0].Output
        Type: String
  - name: CheckTimezone
    action: aws:runCommand
    maxAttempts: 1
    timeoutSeconds: 10
    nextStep: CheckLocale
    isEnd: false
    onFailure: Continue
    inputs:
      DocumentName: AWS-RunShellScript
      Parameters:
        commands:
          - if [[ "{{ CheckTimezone }}" == "check" ]]; then timedatectl status; else echo "Skipping timezone check"; fi
      InstanceIds:
        - '{{ InstanceId }}'
    outputs:
      - Name: TimezoneOutput
        Selector: $.CommandPlugins[0].Output
        Type: String
  - name: CheckLocale
    action: aws:runCommand
    maxAttempts: 1
    timeoutSeconds: 10
    nextStep: CheckUpdate
    isEnd: false
    onFailure: Continue
    inputs:
      DocumentName: AWS-RunShellScript
      Parameters:
        commands:
          - if [[ "{{ CheckLocale }}" == "check" ]]; then localectl status; else echo "Skipping locale check"; fi
      InstanceIds:
        - '{{ InstanceId }}'
    outputs:
      - Name: LocaleOutput
        Selector: $.CommandPlugins[0].Output
        Type: String
  - name: CheckUpdate
    action: aws:runCommand
    maxAttempts: 1
    timeoutSeconds: 10
    nextStep: CheckNTP
    isEnd: false
    onFailure: Continue
    inputs:
      DocumentName: AWS-RunShellScript
      Parameters:
        commands:
          - if [[ "{{ CheckUpdate }}" == "check" ]]; then yum check-update; else echo "Skipping update check"; fi
      InstanceIds:
        - '{{ InstanceId }}'
    outputs:
      - Name: UpdateOutput
        Selector: $.CommandPlugins[0].Output
        Type: String
  - name: CheckNTP
    action: aws:runCommand
    maxAttempts: 1
    timeoutSeconds: 10
    isEnd: true
    onFailure: Continue
    inputs:
      DocumentName: AWS-RunShellScript
      Parameters:
        commands:
          - if [[ "{{ CheckNTP }}" == "check" ]]; then chronyc sources; else echo "Skipping NTP check"; fi
      InstanceIds:
        - '{{ InstanceId }}'
    outputs:
      - Name: NTPOutput
        Selector: $.CommandPlugins[0].Output
        Type: String

cm-hirai-screenshot 2025-01-17 17.18.25

以下の記事のSSMドキュメント(コマンドドキュメント)を参考にしました。
https://dev.classmethod.jp/articles/check-os-setting-ssm-doc-al2/

タイムアウト

今回作成したオートメーションランブックでは、各ステップごとのコマンド実行に対して、以下のようにタイムアウトを設定しました。

mainSteps:
  - name: CheckHostname
    action: aws:runCommand
    maxAttempts: 1
    timeoutSeconds: 10

cm-hirai-screenshot 2025-01-20 8.59.34

SSMオートメーションの対象EC2インスタンスは、起動中のマネージドインスタンス以外にもアンマネージドインスタンスや停止中のEC2インスタンスも実行対象となります。
タイムアウトを設定しないと、長時間オートメーション実行のステータスが進行中のままになってしまうため、基本的にはタイムアウトは設定した方がよいと思います。

SSM Automationを実行

マネジメントコンソール上ではなく、AWS CLIでSSM Automationを実行します。

理由は、ターゲットアカウントを指定する際、OUで指定すると、OU直下のAWSアカウントしか対象にならず、指定したOUの子OU配下に存在するアカウントは対象にならないためです。

cm-hirai-screenshot 2025-01-17 17.27.43
Accounts and organizational units (OUs)

AWS CLIであれば、--target-locations"IncludeChildOrganizationUnits": true,を設定すると子や孫OUも対象になりますが、マネジメントコンソールでは設定できません。

IncludeChildOrganizationUnits
Indicates whether to include child organizational units (OUs) that are children of the targeted OUs. The default is false.
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/APIReference/API_TargetLocation.html

その点が問題なければ、マネジメントコンソール上で実行しても構いません。

実行するEC2インスタンスをタグで指定して実行する場合、AWS CloudShellから以下のコマンドで実行します。

aws ssm start-automation-execution \
    --document-name "check-os" \
    --parameters AutomationAssumeRole=arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
    --target-parameter-name InstanceId \
    --targets Key=tag:ssm-automation,Values=true \
    --target-locations '[{
        "Accounts": ["ou-o2pk-xxxxxxxx"],
        "IncludeChildOrganizationUnits": true,
        "Regions": ["ap-northeast-1"],
        "ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
    }]'
  • --parameters
    • AutomationAssumeRoleは管理アカウントで作成したIAMロールのARNの指定が必須です。
    • 以下はオートメーションランブックに設定されているパラメータです。
      • CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check
  • --target-locations
    • Regions:対象リージョンを指定
    • Accounts:アカウントIDかOU IDを指定する
    • ExecutionRoleName:リソースアカウントに存在するIAMロール名を指定
  • --targets
    • タグキー名:ssm-automation
    • タグ値:true

実行後、AutomationExecutionIdが出力されます。

出力結果
{
    "AutomationExecutionId": "xxxxxxxx"
}

オートメーションランブックはデフォルトバージョンで実行されます。
明示的にオートメーションランブックのバージョンを指定したい場合、--document-version "6"のように追加するとよいです。

aws ssm start-automation-execution \
    --document-name "check-os" \
    --document-version "6" \
    --parameters AutomationAssumeRole=arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
    --target-parameter-name InstanceId \
    --targets Key=tag:ssm-automation,Values=true \
    --target-locations '[{
        "Accounts": ["ou-o2pk-xxxxxxxx"],
        "IncludeChildOrganizationUnits": true,
        "Regions": ["ap-northeast-1"],
        "ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
    }]'

タグを指定せず、対象アカウントの全てのEC2インスタンスを対象にする場合、--targets Key=AWS::EC2::Instance,Values=*と指定します。

aws ssm start-automation-execution \
    --document-name "check-os" \
    --parameters AutomationAssumeRole=arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
    --target-parameter-name InstanceId \
    --targets Key=AWS::EC2::Instance,Values=* \
    --target-locations '[{
        "Accounts": ["ou-o2pk-xxxxxxxx"],
        "IncludeChildOrganizationUnits": true,
        "Regions": ["ap-northeast-1"],
        "ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
    }]'

マネジメントコンソール上の場合、以下のように設定します。
cm-hirai-screenshot 2025-01-16 14.45.51

実行結果の内容を確認

実行結果(Execution ID)は以下のように作成されます

  • 管理アカウント
    • 親Execution IDは1つ
      • 各メンバーアカウントの実行結果をまとめたもの
  • メンバーアカウント
    • 子Execution IDは2つ以上作成される
      • 1つは、全EC2インスタンスの実行結果をまとめたもの
      • 残りは、各EC2インスタンスごとの実行結果詳細

管理アカウントでSSMオートメーションを実行していますので、メンバーアカウントに親Execution IDは存在しません

管理アカウントでは、実行した対象メンバーアカウントごとにExecuted stepsが作成されます。Step IDを選択します。

cm-hirai-screenshot 2025-01-20 8.21.24
実行詳細ページに遷移しますが、ExecutionIdは、メンバーアカウントのExecutionIdに紐づいているため、以下のスクリーンショットのExecutionIdである0c521866-d0b7-4ffa-9b42-e720c59cdffdを選択しても実行結果内容(コマンド実行結果)は確認できません。

cm-hirai-screenshot 2025-01-20 8.22.07

Automation自体の成功可否は管理アカウントからステータスで確認できますが、コマンド実行がエラーであるかどうかや、コマンド実行結果のOutputsについては管理アカウントから直接確認することはできません。
これらの詳細な情報を確認するには、メンバーアカウントにログインして確認する必要があります

メンバーアカウントにログインし、ExecutionId(例: 0c521866-d0b7-4ffa-9b42-e720c59cdffd)を選択して実行詳細ページに移動します。
このページでは、各EC2インスタンスごとに異なるStep execution IDが表示されます。
cm-hirai-screenshot 2025-01-20 8.26.50
次に、確認したいStep execution IDを選択します。
cm-hirai-screenshot 2025-01-20 8.26.56
これにより、オートメーションランブックの各Stepごとに実行されたコマンドの結果を確認できます。
cm-hirai-screenshot 2025-01-20 8.27.27

さらに、特定のStep execution IDを選択すると、そのStepで実行されたコマンドの詳細な結果を確認することができます。
cm-hirai-screenshot 2025-01-20 8.35.55

Windowsでの文字化け対策

OSがWindowsの場合、コマンド実行結果のOutputに文字化けが発生することがあります。これを解決するために、実行するコマンドの前にchcp 65001を追加します。

このコマンドは、コンソールの文字コードをUTF-8に設定します。
UTF-8は、日本語を含む多言語の文字を正しく表示できる文字エンコーディングです。

以下は、オートメーションランブックでの実装例です

  - name: xxxx
    action: aws:runCommand
    inputs:
      DocumentName: AWS-RunPowerShellScript
      Parameters:
        commands:
          - chcp 65001
          - # ここに特定のWindowsコマンドを記述

https://dev.classmethod.jp/articles/tsnote-ssm-runcommand-garbled-char/

管理アカウントのEC2インスタンスもAutomation対象にしたい場合

SSM Automation実行時に管理アカウントのEC2インスタンスも対象に含めたい場合、Root OUを指定すると以下のようなエラーが発生します。

aws ssm start-automation-execution \
    --document-name "check-os" \
    --parameters AutomationAssumeRole=arn:aws:iam::111111111111:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
    --target-parameter-name InstanceId \
    --targets Key=tag:ssm-automation,Values=true \
    --target-locations '[{
        "Accounts": ["r-o2pk"],
        "IncludeChildOrganizationUnits": true,
        "Regions": ["ap-northeast-1"],
        "ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
    }]'

Failure message
The provided role: arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationExecutionRole can't be assumed. (Service: null; Status Code: 0; Error Code: null; Request ID: null; Proxy: null)

このエラーは、管理アカウントにAWS-SystemsManager-AutomationExecutionRoleが存在しないことが原因です。

管理アカウントにAWS-SystemsManager-AutomationExecutionRoleを作成するため、以下の設定でCloudFormationスタックを作成します。

  • スタック名:AWS-SystemsManager-AutomationExecutionRole-org
  • テンプレートの指定:AWS-SystemsManager-AutomationExecutionRole (org).yml
  • パラメータ
    • AdminAccountId:管理アカウントのAWSアカウントID
    • OrganizationID:管理アカウントのAWS Organizationsページで確認できる「組織 ID」

CloudFormationスタックを作成後、再度以下のコマンドを実行すると成功します。

aws ssm start-automation-execution \
    --document-name "check-os" \
    --parameters AutomationAssumeRole=arn:aws:iam::111111111111:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
    --target-parameter-name InstanceId \
    --targets Key=tag:ssm-automation,Values=true \
    --target-locations '[{
        "Accounts": ["r-o2pk"],
        "IncludeChildOrganizationUnits": true,
        "Regions": ["ap-northeast-1"],
        "ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
    }]'

実行後、以下のようにExecution IDが作成されます。

  • 管理アカウント
    • 親Execution ID:1つ
      • 管理アカウントと各メンバーアカウントの実行結果をまとめたもの
    • 子Execution ID:2つ以上
      • 管理アカウント内の全EC2インスタンスの実行結果をまとめたもの
      • 管理アカウント内の各EC2インスタンスごとの実行結果詳細
  • メンバーアカウント
    • 子Execution ID:2つ以上
      • 1つは、メンバーアカウント内の全EC2インスタンスの実行結果をまとめたもの
      • 残りは、各EC2インスタンスごとの実行結果詳細

これにより、管理アカウントのEC2インスタンスもAutomationの対象に含めることが可能になります。

参考

https://docs.aws.amazon.com/cli/latest/reference/ssm/start-automation-execution.html

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-documents.html

https://dev.classmethod.jp/articles/ssm-automation-cross-account-access/

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.