AWS Organizationsを使用できない場合にマルチアカウント環境でAWS Health イベントの通知を集約する方法 [AWS CloudFormationテンプレート付き]

こんにちは。SCSK渡辺(大)です。

今月から家庭菜園をはじめてみました。
ミニトマトを苗から、大葉を種から、育てています。
毎日食べれるようになることを夢見ています。

今回は、
AWS Organizationsを使用できない場合にマルチアカウント環境でAWS Health イベントの通知を集約する方法
を考えてみました。

AWS障害やメンテナンスの情報を得るための手段としてAWSが用意しているものはPHDとSHDの2種類あります。本記事ではPHDを扱います。
AW Personal Health DashboardPHD ・・・特定AWSアカウントに公開される情報
AWS Service Health DashboardSHD ・・・一般に公開される情報

背景

AWSマネジメントコンソールの操作が面倒

マルチアカウント環境の場合、AWSアカウントの数にもよりますが、AWSマネジメントコンソールで1アカウントづつリソース作成や各種設定を行うことには個人的に抵抗があります。
理由は、単純に面倒であることと、作業ミスが発生しやすくなるためです。
また、管理対象のAWSアカウントが増えるたびにAWSマネジメントコンソールで設定することにも抵抗があります。
理由は、手順書を用意しておいたとしても、AWSのUIが変わった場合に手順書を更新しないといけなくなるためです。

AWSが用意している方法はAWS Organizationsを使用できることが前提

PHDの通知(AWS Health イベント(以降、ヘルスイベント))をマルチアカウント環境で1つのアカウントへ集約する方法をAWSが用意してくれてはいるものの、AWS Organizationsを使用できることが前提になっています。

AWS Health ユーザーガイド – アカウント間の AWS Health イベントの集約
—抜粋—————————————————————
If you use AWS Organizations, you can also view AWS Health events centrally across your organization. This feature provides access to the same information as single account operations. You can use filters to view events in specific AWS Regions, accounts, and services.
———————————————————————-

AWS Health Awareも同様に、集約するためにはAWS Organizationsを使用できることが前提になっています。

AWS Health Aware – REDUME
—抜粋—————————————————————
By enabling an account as a delegated administrator, you can use AHA in Organization Mode without the need to create and assume the management account IAM role.
———————————————————————-

 

アーキテクチャ

前回の記事と同様、AWS CloudFormation StackSets セルフマネージド型を用いる方法で考えました。
(「そんなにAWSマネジメントコンソールを触りたくないならAWS CDKを使えば?」と思われるかもしれませんが、AWS CDKだとソースコードの管理を考えないといけないので今回は選択しませんでした)

AWSアカウント

今回は2つのアカウントを用意しました。

  • 集約するイベントを受信するアカウント(以降、アグリゲータアカウント)
  • 集約するイベントを送信するアカウント(以降、ソースアカウント)

導入のために使用するサービス

以下のサービスを使用します。

  • Amazon S3 汎用バケット
    • AWS CloudFormation テンプレート(以降、Cfnテンプレート)をアップロードします。
  • AWS CloudFormation スタック
    • 後述の「作成/設定するリソース」を作成/設定します。
  • AWS CloudFormation StackSets
    • 後述の「作成/設定するリソース」を作成/設定します。

作成/設定するリソース

Cfnテンプレートでは以下のリソースを作成/設定します。

  • アグリゲータアカウント
    • AWS IAM ロール
      • AWS CloudFormation StackSets セルフマネージド型の管理者用のロール
      • AWS CloudFormation StackSets セルフマネージド型のターゲット用のロール
    • AWS User Notifications 通知ハブ
      • 通知データが保存・処理されるリージョンを設定
    • AWS User Notifications 配信チャンネル
      • ヘルスイベントの通知先(Eメール)を設定
    • AWS User Notifications 通知設定
      • アグリゲータアカウントとソースアカウントのヘルスイベントをEメールで送信するための通知設定
      • 裏で自動的にAmazon EventBridge ルールが作成されます
    • Amazon EventBridge イベントバス
      • ソースアカウントのヘルスイベントを受信するためのポリシー
  • ソースアカウント
    • AWS IAM ロール
      • AWS CloudFormation StackSets セルフマネージド型の管理者用のロール
      • AWS CloudFormation StackSets セルフマネージド型のターゲット用のロール
      • 以下のAmazon EventBridge ルール用のロール
    • Amazon EventBridge ルール
      • ソースアカウントのヘルスイベントをアグリゲータアカウントのイベントバスへ送信するためのルール

構成図

上記の設計を図にすると以下になります。
黒線は構築時、オレンジ線は構築後のデータの動きです。

 

事前作業

ゴール

ゴールは下図のようにAmazon S3バケットにCfnテンプレートがアップロードされていることです。

Amazon S3バケット作成

作業実施アカウント

  • アグリゲータアカウント

推奨設定

  • ブロックパブリックアクセスを有効化
  • ソースアカウントからもGetObjectできるようにバケットポリシーを設定する
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${ソースアカウントのAWSID}:root"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::${S3バケット名}/*"
        }
    ]
}

 

配置するCfnテンプレートは次の通りです。

Cfnテンプレート:PreHealthNotificationAggregator

  • アグリゲータアカウントから自身に対して実行し以下を作成する。
    • AWS IAM ロール
      • AWS CloudFormation StackSets セルフマネージド型の管理者用のロール
      • AWS CloudFormation StackSets セルフマネージド型のターゲット用のロール
    • AWS User Notifications 通知ハブ
      • 通知データが保存・処理されるリージョンを設定
    • AWS User Notifications 配信チャンネル
      • ヘルスイベントの通知先(Eメール)を設定
    • AWS User Notifications 通知設定
      • アグリゲータアカウントとソースアカウントのヘルスイベントをEメールで送信するための通知設定
      • 裏で自動的にAmazon EventBridge ルールが作成されます
  • 補足
    • AWS User Notifications 通知ハブはバージニア北部のみ設定されている状態を想定しています。もし、既に東京リージョンも設定されている状態の場合にはエラーになってしまいます。その場合には、東京リージョンを対象から外すか、Cfnテンプレートを修正してください。
    • AWS User Notifications 配信チャンネルは最大3つ設定できるようにしました。つまり、1~2つだけの設定でもエラーになりません。4つ以上設定したい場合にはCfnテンプレートを修正するか、メーリングリストにメールアドレスをまとめてしまうようにしてください。
    • AWS User Notifications 通知設定ではリージョンはデフォルトリージョンをすべて指定しています。
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  CloudFormationAdministrationRoleName:
    Type: String
    Default: AWSCloudFormationStackSetAdministrationRole
  CloudFormationExecutionRoleName:
    Type: String
    Default: AWSCloudFormationStackSetExecutionRole
  CloudFormationAdministratorAccountId:
    Type: String
  NotificationName:
    Type: String
    Default: HealthEventsNotification
  NotificationDescription:
    Type: String
    Default: HealthEventsNotification
  EmailContactName1:
    Type: String
    Default: ""
  EmailAddress1:
    Type: String
    Default: ""
  EmailContactName2:
    Type: String
    Default: ""
  EmailAddress2:
    Type: String
    Default: ""
  EmailContactName3:
    Type: String
    Default: ""
  EmailAddress3:
    Type: String
    Default: ""

Conditions:
  HasEmailContact1: !And [!Not [!Equals [!Ref EmailContactName1, ""]], !Not [!Equals [!Ref EmailAddress1, ""]]]
  HasEmailContact2: !And [!Not [!Equals [!Ref EmailContactName2, ""]], !Not [!Equals [!Ref EmailAddress2, ""]]]
  HasEmailContact3: !And [!Not [!Equals [!Ref EmailContactName3, ""]], !Not [!Equals [!Ref EmailAddress3, ""]]]

Resources:
  AdministrationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref CloudFormationAdministrationRoleName
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: AssumeRole-AWSCloudFormationStackSetExecutionRole
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource:
                  - !Sub 'arn:*:iam::*:role/${CloudFormationExecutionRoleName}'
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref CloudFormationExecutionRoleName
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref CloudFormationAdministratorAccountId
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
  NotificationHub:
    Type: AWS::Notifications::NotificationHub
    Properties:
      Region: ap-northeast-1
  EmailContact1Resource:
    Type: AWS::NotificationsContacts::EmailContact
    Condition: HasEmailContact1
    Properties:
      Name: !Ref EmailContactName1
      EmailAddress: !Ref EmailAddress1
  EmailContact2Resource:
    Type: AWS::NotificationsContacts::EmailContact
    Condition: HasEmailContact2
    Properties:
      Name: !Ref EmailContactName2
      EmailAddress: !Ref EmailAddress2
  EmailContact3Resource:
    Type: AWS::NotificationsContacts::EmailContact
    Condition: HasEmailContact3
    Properties:
      Name: !Ref EmailContactName3
      EmailAddress: !Ref EmailAddress3
  NotificationConfiguration:
    Type: AWS::Notifications::NotificationConfiguration
    Properties:
      Name: !Ref NotificationName
      Description: !Ref NotificationDescription
      AggregationDuration: NONE
      Tags:
        - Key: Environment
          Value: Production
  HealthEventRule:
    Type: AWS::Notifications::EventRule
    Properties:
      EventPattern: '{"source": ["aws.health"]}'
      EventType: 'AWS Health Event'
      NotificationConfigurationArn: !Ref NotificationConfiguration
      Regions:
        - ap-northeast-1
        - ap-northeast-2
        - ap-northeast-3
        - ap-south-1
        - ap-southeast-1
        - ap-southeast-2
        - ca-central-1
        - eu-central-1
        - eu-north-1
        - eu-west-1
        - eu-west-2
        - eu-west-3
        - sa-east-1
        - us-east-1
        - us-east-2
        - us-west-1
        - us-west-2
      Source: 'aws.health'
  EmailChannel1:
    Type: AWS::Notifications::ChannelAssociation
    Condition: HasEmailContact1
    Properties:
      Arn: !GetAtt EmailContact1Resource.Arn
      NotificationConfigurationArn: !Ref NotificationConfiguration
  EmailChannel2:
    Type: AWS::Notifications::ChannelAssociation
    Condition: HasEmailContact2
    Properties:
      Arn: !GetAtt EmailContact2Resource.Arn
      NotificationConfigurationArn: !Ref NotificationConfiguration
  EmailChannel3:
    Type: AWS::Notifications::ChannelAssociation
    Condition: HasEmailContact3
    Properties:
      Arn: !GetAtt EmailContact3Resource.Arn
      NotificationConfigurationArn: !Ref NotificationConfiguration

Outputs:
  NotificationHubId:
    Description: ID of the Notification Hub
    Value: !Ref NotificationHub
  NotificationConfigurationArn:
    Description: ARN of the Notification Configuration
    Value: !Ref NotificationConfiguration
  HealthEventRuleArn:
    Description: ARN of the Health Event Rule
    Value: !Ref HealthEventRule
  EmailContact1Arn:
    Description: ARN of Email Contact 1
    Condition: HasEmailContact1
    Value: !GetAtt EmailContact1Resource.Arn
  EmailContact2Arn:
    Description: ARN of Email Contact 2
    Condition: HasEmailContact2
    Value: !GetAtt EmailContact2Resource.Arn
  EmailContact3Arn:
    Description: ARN of Email Contact 3
    Condition: HasEmailContact3
    Value: !GetAtt EmailContact3Resource.Arn

Cfnテンプレート:PreHealthNotificationSource

  • ソースアカウントから自身に対して実行し以下を作成する。
    • AWS IAM ロール
      • AWS CloudFormation StackSets セルフマネージド型の管理者用のロール
      • AWS CloudFormation StackSets セルフマネージド型のターゲット用のロール
      • 以下のAmazon EventBridge ルール用のロール
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  CloudFormationAdministrationRoleName:
    Type: String
    Default: AWSCloudFormationStackSetAdministrationRole
  CloudFormationExecutionRoleName:
    Type: String
    Default: AWSCloudFormationStackSetExecutionRole
  EventBridgeRuleRoleName:
    Type: String
    Default: AWSHealthEventBridgeRuleRole
  AdministratorAccountId:
    Type: String

Resources:
  AdministrationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref CloudFormationAdministrationRoleName
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: AssumeRole-AWSCloudFormationStackSetExecutionRole
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource:
                  - !Sub 'arn:*:iam::*:role/${CloudFormationExecutionRoleName}'
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref CloudFormationExecutionRoleName
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref AdministratorAccountId
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
  EventBridgeRuleRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref EventBridgeRuleRoleName
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: EventBridgeRulePolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "events:PutEvents"
                Resource:
                  - !Sub "arn:aws:events:*:${AdministratorAccountId}:event-bus/default"

Outputs:
  EventBridgeRuleRoleArn:
    Description: ARN of the IAM role for AWS EventBridgeRule
    Value: !GetAtt EventBridgeRuleRole.Arn

Cfnテンプレート:EventBusPolicy

  • ソースアカウントからアグリゲータアカウントに対して実行し以下を作成する。
    • Amazon EventBridge イベントバス
      • ソースアカウントのヘルスイベントを受信するためのポリシー
  • 補足
    • aws:PrincipalOrgIDを使っている理由は、ソースアカウントが増えるたびにAmazon EventBridge イベントバスのポリシーを変更したくないからです。「危険だ!」という場合にはアカウント単位での指定方法(aws:PrincipalAccount)も用意されているので、以下リンク先の内容を参考にカスタマイズしてご利用ください。
      Amazon global condition context keys
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  OrganizationID:
    Type: String

Resources:
  EventBusPolicy:
    Type: AWS::Events::EventBusPolicy
    Properties:
      EventBusName: default
      StatementId: AllowPutEventsFromOrganization
      Statement:
        Effect: Allow
        Principal: "*"
        Action: events:PutEvents
        Resource: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default
        Condition:
          StringEquals:
            aws:PrincipalOrgID: !Ref OrganizationID

Outputs:
  EventBusArn:
    Description: ARN of the default event bus
    Value: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default

Cfnテンプレート:EventRule

  • アグリゲータアカウントからソースアカウントに対して実行し以下を作成する。
    • Amazon EventBridge ルール
      • ソースアカウントのヘルスイベントをアグリゲータアカウントのイベントバスへ送信するためのルール
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  HealthNotificationAggregatorAccountId:
    Type: String
  RuleName:
    Type: String
    Default: HealthEventForwardingRule
  EventBridgeRuleRoleName:
    Type: String
    Default: AWSHealthEventBridgeRuleRole

Resources:
  HealthEventRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Ref RuleName
      EventPattern:
        source:
          - "aws.health"
        detail-type:
          - "AWS Health Event"
      State: ENABLED
      Targets:
        - Arn: !Sub "arn:aws:events:${AWS::Region}:${HealthNotificationAggregatorAccountId}:event-bus/default"
          Id: 'CrossAccountEventBusTarget'
          RoleArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${EventBridgeRuleRoleName}'

Outputs:
  EventRuleArn:
    Description: 'ARN of the created EventBridge rule'
    Value: !GetAtt HealthEventRule.Arn

 

設定作業

ゴール

アグリゲータアカウントのAWS CloudFormationのスタック画面で下図のようになっていることです。
(バージニア北部のみPreHealthNotificationAggregatorあり)

 

また、ソースアカウントのAWS CloudFormationのスタック画面で下図のようになっていることです。
(PreHealthNotificationSourceを実行したリージョンでのみPreHealthNotificationSourceあり)

Cfnテンプレート: PreHealthNotificationAggregator

バージニア北部リージョンで実行してください。
AWS User Notificationsはグローバルサービスのため他のリージョンではエラーになります。

作業実施アカウント

  • アグリゲータアカウント

使用サービス

  • AWS CloudFormation スタック

パラメーターについて補足

  • CloudFormationAdministratorAccountIdにはソースアカウントのAWSアカウントIDを入力してください。

Cfnテンプレート: PreHealthNotificationSource

作業実施アカウント

  • ソースアカウント

使用サービス

  • AWS CloudFormation スタック

パラメーターについて補足

  • AdministratorAccountIdにはアグリゲータアカウントのAWSアカウントIDを入力してください。

Cfnテンプレート: EventBusPolicy

作業実施アカウント

  • ソースアカウント

使用サービス

  • AWS CloudFormation StackSets
  • 画面イメージ等は前回の記事を参考にしてください。

パラメーターについて補足

  • OrganizationID(組織ID)を指定します。組織IDの確認方法は、画面右上のログインユーザーをクリックした後に「組織」をクリックすると見れます

Cfnテンプレート: EventRule

作業実施アカウント

  • アグリゲータアカウント

使用サービス

  • AWS CloudFormation StackSets
  • 画面イメージ等は前回の記事を参考にしてください。

パラメーターについて補足

  • HealthNotificationAggregatorAccountIdにはアグリゲータアカウントのAWSアカウントIDを入力してください

 

以上です。

アカウントを追加したい時はどうしたら良いの?

追加したいソースアカウントでPreHealthNotificationSourceを実行した後、アグリゲータアカウントのAWS CloudFormation StackSetsからEventRuleを選択し、アクションから「StackSetにスタックを追加」をクリックすることで、追加したいソースアカウントに対してスタックを作成することができます。

なお、aws:PrincipalOrgIDを使わずにaws:PrincipalAccountを使う場合には、アグリゲータアカウントのAmazon EventBridge イベントバスにおいてポリシーの変更も必要になります。

 

まとめ

記事を書いてみたものの、アカウント数が少ない場合にはAWSマネジメントコンソールで設定したほうが早いと思います。また、通知先のメールアドレス変更やアカウント追加の頻度は多くないと思うので、本記事の内容は運用面でのメリットは殆ど無いように感じました。

しかしながら、マルチアカウント環境関連の設定はAWS Organizationsを使えることが前提のものが多いので、諸事情でAWS Organizationsを使用できないけどAWSマネジメントコンソールで設定するのは避けたいという方は少なからずいると思います。そのような方の参考になったら良いな、と思います。

アカウント数が多い場合には、Eメールで受信する通知を削減することで抜け漏れを防止するために、同じ内容のヘルスイベントは除外するような仕組みも考えたほうが良いかと思います。
試していませんが、AWS LambdaやAWS Step Functionsを活用することで実現できるかと思われます。
その内試したいですが、ヘルスイベントのテスト発行は自分で出来ない(AWSサポートに依頼するしかない)ので、やろうとしたら検証に時間が掛かりそうです…。

タイトルとURLをコピーしました