こんにちは。SCSK渡辺(大)です。
今月から家庭菜園をはじめてみました。
ミニトマトを苗から、大葉を種から、育てています。
毎日食べれるようになることを夢見ています。
今回は、
AWS Organizationsを使用できない場合にマルチアカウント環境でAWS Health イベントの通知を集約する方法
を考えてみました。
背景
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 IAM ロール
- AWS CloudFormation StackSets セルフマネージド型の管理者用のロール
- AWS CloudFormation StackSets セルフマネージド型のターゲット用のロール
- 以下のAmazon EventBridge ルール用のロール
- Amazon EventBridge ルール
- ソースアカウントのヘルスイベントをアグリゲータアカウントのイベントバスへ送信するためのルール
- AWS IAM ロール
構成図
上記の設計を図にすると以下になります。
黒線は構築時、オレンジ線は構築後のデータの動きです。
事前作業
ゴール
ゴールは下図のように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 IAM ロール
- 補足
- 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 ルール用のロール
- AWS IAM ロール
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 イベントバス
- ソースアカウントのヘルスイベントを受信するためのポリシー
- Amazon EventBridge イベントバス
- 補足
- aws:PrincipalOrgIDを使っている理由は、ソースアカウントが増えるたびにAmazon EventBridge イベントバスのポリシーを変更したくないからです。「危険だ!」という場合にはアカウント単位での指定方法(aws:PrincipalAccount)も用意されているので、以下リンク先の内容を参考にカスタマイズしてご利用ください。
Amazon global condition context keys
- aws:PrincipalOrgIDを使っている理由は、ソースアカウントが増えるたびにAmazon EventBridge イベントバスのポリシーを変更したくないからです。「危険だ!」という場合にはアカウント単位での指定方法(aws:PrincipalAccount)も用意されているので、以下リンク先の内容を参考にカスタマイズしてご利用ください。
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 ルール
- ソースアカウントのヘルスイベントをアグリゲータアカウントのイベントバスへ送信するためのルール
- 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 CloudFormation スタック
パラメーターについて補足
- CloudFormationAdministratorAccountIdにはソースアカウントのAWSアカウントIDを入力してください。
Cfnテンプレート: PreHealthNotificationSource
作業実施アカウント
- ソースアカウント
使用サービス
- AWS CloudFormation スタック
パラメーターについて補足
- AdministratorAccountIdにはアグリゲータアカウントのAWSアカウントIDを入力してください。
Cfnテンプレート: EventBusPolicy
作業実施アカウント
- ソースアカウント
使用サービス
- AWS CloudFormation StackSets
- 画面イメージ等は前回の記事を参考にしてください。
パラメーターについて補足
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サポートに依頼するしかない)ので、やろうとしたら検証に時間が掛かりそうです…。






