最近は複数のAWSアカウント利用が増えてきました。 AWS Configのようなサービスを有効化する場合、「リージョン数 × アカウント数」の設定が必要になります。 CloudFormationを使ったとしても、これだけの作業をするのは一苦労です。 そこでCloudFormation StackSetsを利用し、複数のアカウント・リージョンに対して一気に設定することにしました。
要件
- 利用しているアカウント・リージョンが多くても手間をかけずに設定したい
- AWS Configのログ(設定スナップショット、履歴)を1つのS3バケットに集約したい
- OrganizationにAWSアカウントを追加した場合も自動的に設定されるようにしたい
前提
- AWS Organizationsで「すべての機能」が有効化されている
全体的な方針
- AWS Organizationsに統合されたCloudFormation StackSetsを使う
余談:アグリゲータについて
AWS Configにはアグリゲータという機能があります。 マルチアカウントマルチリージョンのデータ集約と説明されているので、「これを使えば解決?」と思うのですが、実際は上記要件のいずれも解決してくれませんでした。 単一のAWS Configのコンソール画面で、全環境のリソースを検索したりできるので、これはこれで有用なものです。 しかし、AWS Configの有効化、ログファイルのS3バケットへのエクスポート、などはアグリゲータでは設定できません。
手順
1. S3バケットの作成
ログ保存用のS3バケットを作成するために、任意のアカウントの任意のリージョンでCloudFormationを実行します。 これはStackSetsではなく、普通のCloudFormation実行です。
AWSTemplateFormatVersion: 2010-09-09
Description: Create S3 bucket for AWS Config
Parameters:
ConfigBucket:
Type: String
OrgID:
Type: String
Resources:
S3Bucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Ref ConfigBucket
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
S3Policy:
Type: "AWS::S3::BucketPolicy"
Properties:
Bucket:
Ref: "S3Bucket"
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: "AWSConfigPrincipalCheck"
Action: ['*']
Effect: "Deny"
Resource:
- !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket']]
- !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket','/*']]
NotPrincipal:
Service: [config.amazonaws.com]
- Sid: "AWSConfigBucketPermissionsCheck"
Action: ['s3:GetBucketAcl']
Effect: "Allow"
Resource:
- !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket']]
Principal: "*"
Condition:
StringEquals:
aws:PrincipalOrgID: [ !Ref 'OrgID' ]
- Sid: "AWSConfigBucketExistenceCheck"
Action: ['s3:ListBucket']
Effect: "Allow"
Resource:
- !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket']]
Principal: "*"
Condition:
StringEquals:
aws:PrincipalOrgID: [ !Ref 'OrgID' ]
- Sid: "AWSConfigBucketDelivery"
Action: ['s3:PutObject']
Effect: "Allow"
Resource:
- !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket','/AWSLogs/*']]
Principal: "*"
Condition:
StringEquals:
s3:x-amz-acl: "bucket-owner-full-control"
aws:PrincipalOrgID: [ !Ref 'OrgID' ]
以下を考慮し、バケットポリシーを作成しました。
- アカウントが増えてもメンテナンスフリーにするため、アカウントIDを埋め込まない
- config.amazonaws.com以外のアクセスはDenyする
- aws:PrincipalOrgIDでOrganization内からのみAllowする
2. AWS Configの作成
2-1.マスターアカウントでAWS OrganizationsのStackSetsの有効化
2-2.CloudFormation StackSetsを実行
マスターアカウントの任意のリージョンで実行します。
テンプレートファイルの内容は下記です。
AWSTemplateFormatVersion: 2010-09-09
Description: Enable AWS Config
Parameters:
ConfigBucket:
Type: String
IncludeGlobalResourceTypeRegion:
Type: String
Default: ap-northeast-1
Conditions:
IsIncludeGlobalResourceTypeRegion: !Equals [ !Ref IncludeGlobalResourceTypeRegion, !Ref "AWS::Region" ]
Resources:
ConfigRecorderRole:
Type: AWS::IAM::Role
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- config.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSConfigRole
RoleName: !Sub "config-role-${AWS::Region}"
ConfigRecorder:
Type: AWS::Config::ConfigurationRecorder
DeletionPolicy: Delete
Properties:
Name: !Sub "configuration-recorder-${AWS::Region}"
RoleARN: !GetAtt ConfigRecorderRole.Arn
RecordingGroup:
AllSupported: true
IncludeGlobalResourceTypes: !If [IsIncludeGlobalResourceTypeRegion, true, false]
DependsOn: ConfigRecorderRole
ConfigDeliveryChannel:
Type: AWS::Config::DeliveryChannel
DeletionPolicy: Delete
Properties:
Name: !Sub "delivery-channel-${AWS::Region}"
ConfigSnapshotDeliveryProperties:
DeliveryFrequency: One_Hour
# 1hour : One_Hour
# 3hours : Three_Hours
# 6hours : Six_Hours
# 12hours : Twelve_Hours
# 24hours : TwentyFour_Hours
S3BucketName: !Ref ConfigBucket
DependsOn: ConfigRecorderRole
パラメータにIncludeGlobalResourceTypeRegionというのを作成しました。 基本的に AWS Config はリージョンごとにリソースを記録します。 しかし、リージョン外のグローバルリソース(IAM等)というものもあります。 これを各リージョンで記録対象にすることも可能ですが、検索時などに重複してしまう可能性を避けるため、1つのリージョンを指定するようにしました。
「組織へのデプロイ」または「組織単位(OU)へのデプロイ」を選択します。 今回は「組織単位(OU)へのデプロイ」を選択したので、OU IDを追加で入力しました。
また、「自動デプロイ」もデフォルトで有効になっています。 アカウント追加時に自動的にデプロイされることになります。
今回は「すべてのリージョンを追加」を選択しました。 個別にリージョンを選択することも可能です。
実行開始すると、オペレーションステータスが RUNNIING になります。
スタックインスタンスのステータスが OUTDATED になっています。
1リージョンずつ実行され、徐々に CURRENT に変更していきます。
最終的には全てのスタックインスタンスが CURRENT になり、オペレーションステータスが SUCCEEDED になります。
余談:なぜ、IAM Roleをリージョンごとに作っているか
AWS Configに割り当てるIAM Roleは、全リージョンで共用できます。 しかし、今回はわざわざ複数作成しています。
ConfigurationRecorderを作成するには、IAM Roleを先に作成する必要があります。 CloudFormation StackSetsを実行すると、デプロイは1リージョンずつ実行されます。 したがって、最初のリージョンでIAM Role作成すればいいのですが、CloudFormationの仕様上、「IAM Roleが存在しなければ作成する」といった書き方はできません。
私が試した限りでは、デプロイ対象として全リージョンを指定すると、必ずus-east-1から実行されました。 しかし、必ずしも全リージョンにデプロイするとも限りません。
なお、IAM Roleだけ別スタックに切り出して事前に作成してもいいと思いますが、今回はスタックを増やしたくなかったのでこのような実装にしました。
感想
StackSetsが使えることで、CloudFormationの価値が上がり、学習するメリットも上がったと思いました。











