こんにちは。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サポートに依頼するしかない)ので、やろうとしたら検証に時間が掛かりそうです…。