この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
中山(順)です
「AWS Organizationsをコードで管理したい・・・」
そんなことを思ったことはありませんか?
今日はAwsOrganizationFormationというOSSのご紹介です。
READMEには以下のように記載されています。
AWS Organization Formation is an Infrastructure as Code (IaC) tool for AWS Organizations.
OlafConijn/AwsOrganizationFormation
AWS Organizationをコードで管理するツールのようです。 これは俺得。
AwsOrganizationFormationの機能
主要な機能として、以下の3つが挙げられています。
- Infrastructure as Code for AWS Organizations(AWS Organizations自体をコード化)
- CloudFormation annotations to provision resources cross account(アカウントを横断してリソースをプロビジョニングするためのアノテーション)
- Automation of account creation and resource provisioning(アカウントの作成とリソースのプロビジョニングを自動化)
なんと、Organizationsのコード化だけでなくアカウント横断のリソースのプロビジョニングまでできるようです。 ますます俺得!
やってみた
- 事前準備
- 既存のOrganizationをコード化
- 子アカウントの作成
- OU/SCP/Password Policyの管理
- 子アカウントにプロビジョニングするための準備(テンプレートの作成)
- 各アカウントでプロビジョニングされるスタックのテンプレートを確認
- 子アカウントへのリソースのプロビジョニング
事前準備
まず、AwsOrganizationFormationをインストールします。
sudo npm install -g aws-organization-formation
提供されているコマンドは以下の通りです。
$ org-formation --help
Usage: org-formation [options] [command]
aws organization formation
Options:
-v, --version output the version number
-h, --help output usage information
Commands:
create-change-set [options] <templateFile> create change set that can be reviewed and executed later
delete-stacks [options] removes all stacks deployed to accounts using org-formation
describe-stacks [options] list all stacks deployed to accounts using org-formation
execute-change-set [options] <change-set-name> execute previously created change set
init-pipeline [options] initializes organization and created codecommit repo, codebuild and codepipeline
init [options] <file> generate template & initialize organization
perform-tasks [options] <tasks-file> performs all tasks from either a file or directory structure
print-stacks [options] <templateFile> outputs cloudformation templates generated by org-formation to the console
update [options] <templateFile> update organization resources
update-stacks [options] <templateFile> update cloudformation resources in accounts
validate-stacks [options] <templateFile> validates the cloudformation templates that will be generated
validate-tasks [options] <templateFile> Will validate the tasks file, including configured tasks
AwsOrganizationFormationを介してAWSにアクセスするため、マスターアカウントのアクセスキーを設定しておく必要があります。 これ以降、AwsOrganizationFormationのコマンドを実行する際には以下のプロファイルを指定します。
aws configure --profile organizations
既存のOrganizationをコード化
検証を開始する時点で、親アカウントとAWS Organizationsを通して作成した子アカウント1つが存在する状態です。
まず、initコマンドで現在の状態をコードとして出力します。
org-formation init organization.yml --region us-east-1 --profile organizations
INFO: Your organization template is written to organization.yml
INFO: Hope this will get you started!
INFO:
INFO: You can keep the organization.yml file on disk or even better, under source control.
INFO: If you work with code pipeline you might find init-pipeline an interesting command too.
INFO:
INFO: Dont worry about losing the organization.yml file, at any point you can recreate it.
INFO: Have fun!
INFO:
INFO: --OC
init-pipelineコマンドでパイプラインごと作成することもできるようですが、それはまたの機会に・・・
出力されたコードを確認してみます。
cat organization.yml
AWSTemplateFormatVersion: '2010-09-09-OC'
Description: default template generated for organization with master account XXXXXXXXXXXX
Organization:
MasterAccount:
Type: OC::ORG::MasterAccount
Properties:
AccountName: Master
AccountId: 'XXXXXXXXXXXX'
OrganizationRoot:
Type: OC::ORG::OrganizationRoot
Properties:
NobuhiroNakayamaAccount:
Type: OC::ORG::Account
Properties:
AccountName: Nobuhiro Nakayama
AccountId: 'YYYYYYYYYYYY'
RootEmail: ********+001@gmail.com
なお、initコマンドを実行すると自動で状態ファイルの生成とS3バケットへの保存が行われます。 S3バケットがない場合にはバケットも自動で作成されます。 このS3バケットは"--region"で指定したリージョンに作成されます。
{
"masterAccountId": "XXXXXXXXXXXX",
"bindings": {
"OC::ORG::MasterAccount": {
"MasterAccount": {
"type": "OC::ORG::MasterAccount",
"logicalId": "MasterAccount",
"physicalId": "XXXXXXXXXXXX",
"lastCommittedHash": "d84c19fee925ab85000bb5c4012b4b85"
}
},
"OC::ORG::OrganizationRoot": {
"OrganizationRoot": {
"type": "OC::ORG::OrganizationRoot",
"logicalId": "OrganizationRoot",
"physicalId": "r-i18d",
"lastCommittedHash": "6be66ccf6b2a5417439fec93c294f165"
}
},
"OC::ORG::Account": {
"NobuhiroNakayamaAccount": {
"type": "OC::ORG::Account",
"logicalId": "NobuhiroNakayamaAccount",
"physicalId": "YYYYYYYYYYYY",
"lastCommittedHash": "dab47fd962b4aade7fc51a7b5afbb711"
}
}
},
"stacks": {},
"values": {},
"previousTemplate": "{\"AWSTemplateFormatVersion\":\"2010-09-09-OC\",\"Description\":\"default template generated for organization with master account XXXXXXXXXXXX\",\"Organization\":{\"MasterAccount\":{\"Type\":\"OC::ORG::MasterAccount\",\"Properties\":{\"AccountName\":\"Master\",\"AccountId\":\"XXXXXXXXXXXX\"}},\"OrganizationRoot\":{\"Type\":\"OC::ORG::OrganizationRoot\",\"Properties\":null},\"NobuhiroNakayamaAccount\":{\"Type\":\"OC::ORG::Account\",\"Properties\":{\"AccountName\":\"Nobuhiro Nakayama\",\"AccountId\":\"YYYYYYYYYYYY\",\"RootEmail\":\"********+001@gmail.com\"}}}}"
}
子アカウントの作成
テンプレートを修正します。
AWSTemplateFormatVersion: '2010-09-09-OC'
Description: default template generated for organization with master account XXXXXXXXXXXX
Organization:
MasterAccount:
Type: OC::ORG::MasterAccount
Properties:
AccountName: Master
AccountId: 'XXXXXXXXXXXX'
OrganizationRoot:
Type: OC::ORG::OrganizationRoot
Properties:
NobuhiroNakayamaAccount:
Type: OC::ORG::Account
Properties:
AccountName: Nobuhiro Nakayama
AccountId: 'YYYYYYYYYYYY'
RootEmail: ********+001@gmail.com
MemberAccount:
Type: OC::ORG::Account
Properties:
AccountName: OrgFormationTest
RootEmail: ********+orgformation@gmail.com
Change Setを作成します。
$ org-formation create-change-set organization.yml --profile organizations
{
"changeSetName": "5f0417cb-9fe4-4255-98ea-d30ae89992f5",
"changes": [
{
"logicalId": "MemberAccount",
"type": "OC::ORG::Account",
"action": "Create"
}
]
}
execute-change-setコマンドで変更を適用します。
org-formation execute-change-set 5f0417cb-9fe4-4255-98ea-d30ae89992f5 --profile organizations
OC::ORG::Account | MemberAccount | Create (ZZZZZZZZZZZZ)
OC::ORG::Account | MemberAccount | CommitHash
INFO: done
AWS CLIでアカウントが作成されたことを確認します。
$ aws organizations list-accounts --profile organizations --query "sort_by(Accounts, &JoinedTimestamp)[-1]"
$ aws organizations list-accounts --profile organizations --query "sort_by(Accounts, &JoinedTimestamp)[-1]"
{
"Status": "ACTIVE",
"Name": "OrgFormationTest",
"Email": "********+orgformation@gmail.com",
"JoinedMethod": "CREATED",
"JoinedTimestamp": 1583569462.768,
"Id": "ZZZZZZZZZZZZ",
"Arn": "arn:aws:organizations::XXXXXXXXXXXX:account/o-xxxxxxxxxx/ZZZZZZZZZZZZ"
}
OU/SCP/Password Policyの管理
そもそも何を管理できるかドキュメントで確認してみましょう。
https://github.com/OlafConijn/AwsOrganizationFormation/blob/master/docs/organization-resources.md
- MasterAccount
- Account
- OrganizationRoot
- OrganizationalUnit
- ServiceControlPolicy
- PasswordPolicy
最初に生成されたリソース以外に、"OrganizationalUnit", "ServiceControlPolicy", "PasswordPolicy" の3つのリソースをサポートしているようです。 以下のようにテンプレートを修正してみます。 ドキュメントの例をほぼそのまま拝借しました。 ちなみに、いろいろやり直したりしたため、アカウントのリソースIDやプロパティが少し変わっていますのでご了承くださいw
AWSTemplateFormatVersion: '2010-09-09-OC'
Description: default template generated for organization with master account XXXXXXXXXXXX
Organization:
MasterAccount:
Type: OC::ORG::MasterAccount
Properties:
AccountName: Master
AccountId: 'XXXXXXXXXXXX'
PasswordPolicy: !Ref PasswordPolicy
OrganizationRoot:
Type: OC::ORG::OrganizationRoot
Properties:
OrgFormationTestAccount:
Type: OC::ORG::Account
Properties:
AccountName: OrgFormationTest
AccountId: 'ZZZZZZZZZZZZ'
RootEmail: ********+orgformation@gmail.com
PasswordPolicy: !Ref PasswordPolicy
NobuhiroNakayamaAccount:
Type: OC::ORG::Account
Properties:
AccountName: Nobuhiro Nakayama
AccountId: 'YYYYYYYYYYYY'
RootEmail: ********+001@gmail.com
PasswordPolicy: !Ref PasswordPolicy
ProductionOU:
Type: OC::ORG::OrganizationalUnit
Properties:
OrganizationalUnitName: production
ServiceControlPolicies: !Ref RestrictUnusedRegionsSCP
Accounts: !Ref OrgFormationTestAccount
DevelopmentOU:
Type: OC::ORG::OrganizationalUnit
Properties:
OrganizationalUnitName: development
ServiceControlPolicies: !Ref RestrictUnusedRegionsSCP
Accounts: !Ref NobuhiroNakayamaAccount
RestrictUnusedRegionsSCP:
Type: OC::ORG::ServiceControlPolicy
Properties:
PolicyName: RestrictUnusedRegions
Description: Restrict Unused regions
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: DenyUnsupportedRegions
Effect: Deny
NotAction:
- 'cloudfront:*'
- 'iam:*'
- 'route53:*'
- 'support:*'
Resource: '*'
Condition:
StringNotEquals:
'aws:RequestedRegion':
- us-east-1
- us-weat-2
- ap-northeast-1
PasswordPolicy:
Type: OC::ORG::PasswordPolicy
Properties:
MaxPasswordAge: 30
MinimumPasswordLength: 12
RequireLowercaseCharacters: true
RequireNumbers: true
RequireSymbols: true
RequireUppercaseCharacters: true
PasswordReusePrevention: 5
AllowUsersToChangePassword: true
変更を適用します。 今度はChange Setを作成せずにorg-formation updateコマンドを利用しています。
org-formation update organization.yml --profile organizations
OC::ORG::ServiceControlPolicy | RestrictUnusedRegionsSCP | Create (p-8w6roxp1)
OC::ORG::Account | OrgFormationTestAccount | Update
OC::ORG::Account | OrgFormationTestAccount | CommitHash
OC::ORG::Account | NobuhiroNakayamaAccount | Update
OC::ORG::Account | NobuhiroNakayamaAccount | CommitHash
OC::ORG::OrganizationalUnit | ProductionOU | Create (ou-i18d-cmtwak1n)
OC::ORG::OrganizationalUnit | ProductionOU | Attach Policy (RestrictUnusedRegionsSCP)
OC::ORG::OrganizationalUnit | ProductionOU | Attach Account (OrgFormationTestAccount)
OC::ORG::OrganizationalUnit | ProductionOU | CommitHash
OC::ORG::OrganizationalUnit | DevelopmentOU | Create (ou-i18d-md5cpw1c)
OC::ORG::OrganizationalUnit | DevelopmentOU | Attach Policy (RestrictUnusedRegionsSCP)
OC::ORG::OrganizationalUnit | DevelopmentOU | Attach Account (NobuhiroNakayamaAccount)
OC::ORG::OrganizationalUnit | DevelopmentOU | CommitHash
OC::ORG::MasterAccount | MasterAccount | Update
OC::ORG::MasterAccount | MasterAccount | CommitHash
INFO: done
詳細は割愛しますが、設定が反映されていることを確認しました。
子アカウントにプロビジョニングするための準備(テンプレートの作成)
org-formationでは、組織内のアカウントに対してリソースをまとめて展開することができます。 例えば、CloudTrailの設定を全てのアカウントに設定したり、ログを特定のバケットに集約することが可能です。
以下のサンプルファイルを使って使い方を確認していきます。
https://github.com/OlafConijn/AwsOrganizationFormation/blob/master/examples/templates/cloudtrail.yml
サンプルテンプレートを現行のOrganizationに併せて修正
まず、修正したものは以下の通りです。
AWSTemplateFormatVersion: '2010-09-09-OC'
# Include file that contains Organization Section.
# The Organization Section describes Accounts, Organizational Units, etc.
Organization: !Include ../organization.yml
# Any Binding that does not explicitly specify a region will default to this.
# Value can be either string or list
DefaultOrganizationBindingRegion: ap-northeast-1
# Section that contains a named list of Bindings.
# Bindings determine what resources are deployed where
# These bindings can be !Ref'd from the Resources in the resource section
OrganizationBindings:
# Binding for: S3Bucket, S3BucketPolicy
CloudTrailBucketBinding:
Account: !Ref MasterAccount
# Binding for: CloudTrail, CloudTrailLogGroup, CloudTrailLogGroupRole
CloudTrailBinding:
Account: '*'
IncludeMasterAccount: true
Resources:
CloudTrailS3Bucket:
OrganizationBinding: !Ref CloudTrailBucketBinding
DeletionPolicy: Retain
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'cloudtrail-${MasterAccount}'
CloudTrailS3BucketPolicy:
OrganizationBinding: !Ref CloudTrailBucketBinding
Type: AWS::S3::BucketPolicy
DependsOn: CloudTrailS3Bucket
Properties:
Bucket: !Ref CloudTrailS3Bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: 'AWSCloudTrailAclCheck'
Effect: 'Allow'
Principal: { Service: 'cloudtrail.amazonaws.com' }
Action: 's3:GetBucketAcl'
Resource: !Sub 'arn:aws:s3:::${CloudTrailS3Bucket}'
- Sid: 'AWSCloudTrailWrite'
Effect: 'Allow'
Principal: { Service: 'cloudtrail.amazonaws.com' }
Action: 's3:PutObject'
Resource: !Sub 'arn:aws:s3:::${CloudTrailS3Bucket}/AWSLogs/*/*'
Condition:
StringEquals:
s3:x-amz-acl: 'bucket-owner-full-control'
CloudTrailLogGroup:
OrganizationBinding: !Ref CloudTrailBinding
Type: 'AWS::Logs::LogGroup'
Properties:
RetentionInDays: 14
LogGroupName: CloudTrail/audit-log
CloudTrailLogGroupRole:
OrganizationBinding: !Ref CloudTrailBinding
Type: 'AWS::IAM::Role'
Properties:
RoleName: AWSCloudTrailLogGroupRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AssumeRole1
Effect: Allow
Principal:
Service: 'cloudtrail.amazonaws.com'
Action: 'sts:AssumeRole'
Policies:
- PolicyName: 'cloudtrail-policy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AWSCloudTrailCreateLogStream
Effect: Allow
Action:
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: !GetAtt 'CloudTrailLogGroup.Arn'
CloudTrail:
OrganizationBinding: !Ref CloudTrailBinding
Type: AWS::CloudTrail::Trail
DependsOn:
- CloudTrailS3BucketPolicy
- CloudTrailLogGroup
- CloudTrailLogGroupRole
Properties:
S3BucketName: !Ref CloudTrailS3Bucket
IsLogging: false
IncludeGlobalServiceEvents: true
IsMultiRegionTrail: true
CloudWatchLogsLogGroupArn: !GetAtt 'CloudTrailLogGroup.Arn'
CloudWatchLogsRoleArn: !GetAtt 'CloudTrailLogGroupRole.Arn'
ここでポイントなのは、各リソースの属性として設定してある"OrganizationBinding"です。
CloudTrailをマルチアカウント環境で利用する場合、「あるアカウントにログを集約するためのS3バケットを作成」「全てのアカウントでCloudTrailを有効にしてログ集約用のS3バケットにログを保存」といった設計をよくやります。 上述のテンプレートでは、"AWS::S3::Bucket", "AWS::S3::BucketPolicy", "AWS::Logs::LogGroup", "AWS::IAM::Role", "AWS::CloudTrail::Trail"のリソースが1つのテンプレート上に記述されています。 このうち、"AWS::S3::Bucket", "AWS::S3::BucketPolicy"は、どこか一つのアカウントに作成したいのですが、これをOrganizationBindingで制御しています。
前半のOrganizationBindings > CloudTrailBucketBindingでログ集約用のS3バケットを作成するアカウントを定義しています。 また、OrganizationBindings > CloudTrailBindingでCloudTrailを有効にするアカウントを定義しており、ここではマスターアカウントを含むOrganization内の全てのアカウントが指定されています。
(OrganizationおよびDefaultOrganizationBindingRegionの説明は割愛します)
テンプレートを管理するディレクトリを作成し、上記のテンプレートを配置します。
mkdir templates
cd templates/
各アカウントでプロビジョニングされるスタックのテンプレートを確認
print-stacksコマンドで各アカウントにプロビジョニングされるスタックのテンプレートを確認することができます。 意図した内容のテンプレートが生成されていることが確認できます。
org-formation print-stacks cloudtrail.yml --profile organizations
template for account XXXXXXXXXXXX and region ap-northeast-1
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {},
"Resources": {
"CloudTrailS3Bucket": {
"DeletionPolicy": "Retain",
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "cloudtrail-XXXXXXXXXXXX"
}
},
"CloudTrailS3BucketPolicy": {
"Type": "AWS::S3::BucketPolicy",
"DependsOn": "CloudTrailS3Bucket",
"Properties": {
"Bucket": {
"Ref": "CloudTrailS3Bucket"
},
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": {
"Fn::Sub": "arn:aws:s3:::${CloudTrailS3Bucket}"
}
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": {
"Fn::Sub": "arn:aws:s3:::${CloudTrailS3Bucket}/AWSLogs/*/*"
},
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
}
},
"CloudTrailLogGroup": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"RetentionInDays": 14,
"LogGroupName": "CloudTrail/audit-log"
}
},
"CloudTrailLogGroupRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "AWSCloudTrailLogGroupRole",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRole1",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "cloudtrail-policy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailCreateLogStream",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": {
"Fn::GetAtt": [
"CloudTrailLogGroup",
"Arn"
]
}
}
]
}
}
]
}
},
"CloudTrail": {
"Type": "AWS::CloudTrail::Trail",
"DependsOn": [
"CloudTrailS3BucketPolicy",
"CloudTrailLogGroup",
"CloudTrailLogGroupRole"
],
"Properties": {
"S3BucketName": {
"Ref": "CloudTrailS3Bucket"
},
"IsLogging": false,
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"CloudWatchLogsLogGroupArn": {
"Fn::GetAtt": [
"CloudTrailLogGroup",
"Arn"
]
},
"CloudWatchLogsRoleArn": {
"Fn::GetAtt": [
"CloudTrailLogGroupRole",
"Arn"
]
}
}
}
},
"Outputs": {
"printDashCloudTrailS3Bucket": {
"Value": {
"Ref": "CloudTrailS3Bucket"
},
"Description": "Cross Account dependency",
"Export": {
"Name": "print-CloudTrailS3Bucket"
}
}
}
}
template for account YYYYYYYYYYYY and region ap-northeast-1
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"CloudTrailS3Bucket": {
"Description": "Cross Account dependency",
"Type": "String",
"ExportAccountId": "XXXXXXXXXXXX",
"ExportRegion": "ap-northeast-1",
"ExportName": "print-CloudTrailS3Bucket"
}
},
"Resources": {
"CloudTrailLogGroup": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"RetentionInDays": 14,
"LogGroupName": "CloudTrail/audit-log"
}
},
"CloudTrailLogGroupRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "AWSCloudTrailLogGroupRole",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRole1",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "cloudtrail-policy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailCreateLogStream",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": {
"Fn::GetAtt": [
"CloudTrailLogGroup",
"Arn"
]
}
}
]
}
}
]
}
},
"CloudTrail": {
"Type": "AWS::CloudTrail::Trail",
"DependsOn": [
"CloudTrailLogGroup",
"CloudTrailLogGroupRole"
],
"Properties": {
"S3BucketName": {
"Ref": "CloudTrailS3Bucket"
},
"IsLogging": false,
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"CloudWatchLogsLogGroupArn": {
"Fn::GetAtt": [
"CloudTrailLogGroup",
"Arn"
]
},
"CloudWatchLogsRoleArn": {
"Fn::GetAtt": [
"CloudTrailLogGroupRole",
"Arn"
]
}
}
}
},
"Outputs": {}
}
template for account ZZZZZZZZZZZZ and region ap-northeast-1
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"CloudTrailS3Bucket": {
"Description": "Cross Account dependency",
"Type": "String",
"ExportAccountId": "XXXXXXXXXXXX",
"ExportRegion": "ap-northeast-1",
"ExportName": "print-CloudTrailS3Bucket"
}
},
"Resources": {
"CloudTrailLogGroup": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"RetentionInDays": 14,
"LogGroupName": "CloudTrail/audit-log"
}
},
"CloudTrailLogGroupRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "AWSCloudTrailLogGroupRole",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRole1",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "cloudtrail-policy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailCreateLogStream",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": {
"Fn::GetAtt": [
"CloudTrailLogGroup",
"Arn"
]
}
}
]
}
}
]
}
},
"CloudTrail": {
"Type": "AWS::CloudTrail::Trail",
"DependsOn": [
"CloudTrailLogGroup",
"CloudTrailLogGroupRole"
],
"Properties": {
"S3BucketName": {
"Ref": "CloudTrailS3Bucket"
},
"IsLogging": false,
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"CloudWatchLogsLogGroupArn": {
"Fn::GetAtt": [
"CloudTrailLogGroup",
"Arn"
]
},
"CloudWatchLogsRoleArn": {
"Fn::GetAtt": [
"CloudTrailLogGroupRole",
"Arn"
]
}
}
}
},
"Outputs": {}
}
子アカウントへのリソースのプロビジョニング
validate-stacksコマンドでテンプレートを検証します。
org-formation validate-stacks cloudtrail.yml --profile organizations
INFO: template for stack validation account XXXXXXXXXXXX/ap-northeast-1 valid.
INFO: template for stack validation account YYYYYYYYYYYY/ap-northeast-1 valid.
INFO: template for stack validation account ZZZZZZZZZZZZ/ap-northeast-1 valid.
INFO: done
update-stacksコマンドでテンプレートを利用してスタックをデプロイします。 初回作成時もこのコマンドです。
org-formation update-stacks cloudtrail.yml --stack-name cloudtrail --profile organizations
INFO: stack cloudtrail successfully updated in XXXXXXXXXXXX/ap-northeast-1.
INFO: stack cloudtrail successfully updated in YYYYYYYYYYYY/ap-northeast-1.
INFO: stack cloudtrail successfully updated in ZZZZZZZZZZZZ/ap-northeast-1.
INFO: done
describe-stacksコマンドで作成されたスタックを確認します。
org-formation describe-stacks --profile organizations
{
"cloudtrail": [
{
"accountId": "XXXXXXXXXXXX",
"region": "ap-northeast-1",
"stackName": "cloudtrail",
"lastCommittedHash": "99ca4f9d5e7c3c14b5b93625b3d4f838",
"logicalAccountId": "MasterAccount",
"terminationProtection": false
},
{
"accountId": "ZZZZZZZZZZZZ",
"region": "ap-northeast-1",
"stackName": "cloudtrail",
"lastCommittedHash": "99ca4f9d5e7c3c14b5b93625b3d4f838",
"logicalAccountId": "OrgFormationTestAccount",
"terminationProtection": false
},
{
"accountId": "YYYYYYYYYYYY",
"region": "ap-northeast-1",
"stackName": "cloudtrail",
"lastCommittedHash": "99ca4f9d5e7c3c14b5b93625b3d4f838",
"logicalAccountId": "NobuhiroNakayamaAccount",
"terminationProtection": false
}
]
}
AWS CLIで各アカウントのスタックに含まれるリソースを確認してみます。
まず、集約用のS3バケットを作成するとしたアカウントのスタックです。
aws cloudformation describe-stack-resources \
--stack-name cloudtrail
{
"StackResources": [
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::CloudTrail::Trail",
"Timestamp": "2020-03-08T13:58:03.042Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "cloudtrail-CloudTrail-3NCR3OF874M7",
"LogicalResourceId": "CloudTrail"
},
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::Logs::LogGroup",
"Timestamp": "2020-03-08T13:57:33.866Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "CloudTrail/audit-log",
"LogicalResourceId": "CloudTrailLogGroup"
},
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::IAM::Role",
"Timestamp": "2020-03-08T13:57:52.160Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "AWSCloudTrailLogGroupRole",
"LogicalResourceId": "CloudTrailLogGroupRole"
},
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2020-03-08T13:57:55.497Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "cloudtrail-XXXXXXXXXXXX",
"LogicalResourceId": "CloudTrailS3Bucket"
},
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::S3::BucketPolicy",
"Timestamp": "2020-03-08T13:57:58.925Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "cloudtrail-CloudTrailS3BucketPolicy-BK6YXBTJQJ8B",
"LogicalResourceId": "CloudTrailS3BucketPolicy"
}
]
}
他のアカウントのスタックは以下のようになっております。 S3バケットおよびバケットポリシーが含まれていないことを確認できます。
aws cloudformation describe-stack-resources \
--stack-name cloudtrail \
--profile organizations-member
{
"StackResources": [
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:YYYYYYYYYYYY:stack/cloudtrail/e8cd9900-6144-11ea-800c-0e8a9cd56576",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::CloudTrail::Trail",
"Timestamp": "2020-03-08T13:59:05.432Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "cloudtrail-CloudTrail-IATKD7YI5H6Y",
"LogicalResourceId": "CloudTrail"
},
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:YYYYYYYYYYYY:stack/cloudtrail/e8cd9900-6144-11ea-800c-0e8a9cd56576",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::Logs::LogGroup",
"Timestamp": "2020-03-08T13:58:42.563Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "CloudTrail/audit-log",
"LogicalResourceId": "CloudTrailLogGroup"
},
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:YYYYYYYYYYYY:stack/cloudtrail/e8cd9900-6144-11ea-800c-0e8a9cd56576",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"ResourceType": "AWS::IAM::Role",
"Timestamp": "2020-03-08T13:59:01.096Z",
"StackName": "cloudtrail",
"PhysicalResourceId": "AWSCloudTrailLogGroupRole",
"LogicalResourceId": "CloudTrailLogGroupRole"
}
]
}
CloudTrail以外を展開するためのサンプルテンプレートも用意されています。
https://github.com/OlafConijn/AwsOrganizationFormation/tree/master/examples
まとめ
これは神ツールですね。
アカウント数が2桁を超えるとOrganizationの管理に難儀する印象ですが、ここまでできるといろいろ捗りそうです。 いろいろ使ってガンガンフィードバックしていきたいと思った次第です。
早速、怪しい挙動を見つけたのでフィードバックしました。
Errors occur in the init command when member account names are duplicated? #41
現場からは以上です。