はじめに
少し古い記事ですが、下記AWSブログにて、Control Tower環境でConfigの設定をカスタマイズするソリューション「aws-control-tower-config-customization」が紹介されています。
具体的なカスタマイズ項目は、リソースごとの記録有無と記録頻度です。Configはリソースの変更記録回数が多ければ多いほどコストがかかるため、変更頻度が高いリソースがある場合は、このソリューションで記録有無や記録頻度を調整することでコストを抑えることができます。
今回は、このソリューションについて書いていきたいと思います。
Control Towerによって作成されたリソースの取り扱い
基本的には、Control Towerによって作成されたリソースを、ControlTowerを通さず変更してはいけません
(翻訳) AWS Control Towerによって作成されたリソース(管理アカウント、共有アカウント、メンバーアカウント内のリソースを含む)を変更または削除しないでください。これらのリソースを変更すると、ランディングゾーンの更新やOUの再登録が必要になる場合があり、変更によりコンプライアンス報告が不正確になる可能性があります。
(引用元:https://docs.aws.amazon.com/controltower/latest/userguide/getting-started-guidance.html)
(翻訳) SCPなどのAWS Control Towerリソースを変更した場合、またはConfig RecorderやAggregatorなどのAWS Configリソースを削除した場合、AWS Control Towerは制御機能が設計どおりに動作することを保証できなくなります。
(引用元:https://docs.aws.amazon.com/controltower/latest/userguide/control-limitations.html)
「aws-control-tower-config-customization」はControl Towerの機能ではありません。
では、なぜこのソリューションはControl Towerによって制御されているConfigの設定を変更していいのでしょうか?
下記は冒頭で引用したAWSブログから抜粋です。
- 各管理対象アカウントのAWSControlTowerExecutionロールを利用して、AWS Control Tower が管理するすべてのリージョンの AWS Config レコーダーリソースタイプを更新します。
- このソリューションは、Landing Zone の更新やアカウントの管理など、特定の管理操作のために AWS Control Tower が生成するライフサイクルイベントを活用します。
- この方法により、既存の AWS Control Tower の設定、将来の Landing Zone の更新、そしてアカウント管理機能への影響を回避できます。
つまり下記のような理由なのだと読み取りました。
- AWSControlTowerExecutionロールを利用することにより、Control Tower操作範囲から逸脱しない
- Control Towerのライフサイクルイベントに合わせて動作する
あとは、利用者視点からだと、AWS公式のソリューションであること、という点が非常に大きいです。
ソリューションの鮮度
AWS公式と言いましたが、aws-samplesはあくまでサンプルなので、AWSが動作を保証してくれるものではありません。リポジトリの更新が無い状態で何年も経過しているような場合は、現在も有効なソリューションであるかどうかを自身で確認する必要があります。
このソリューションがAWSブログで最初に紹介された日付は2022/8/5ですが、 2025/11/15にv1.1.0、2025/11/16にv2.0.0がリリースされており、現在もアクティブなソリューションとして更新されていることがわかります。
アーキテクチャー
下図はAWSブログからの引用です。サーバーレスで構成されているため、このソリューション自体のコストは低く抑えられると推測できます。
使ってみた
設計方針
ガバナンスの観点では、Configに対応しているリソースの変更は、基本的にはすべて記録するのが望ましいと思います。とは言え、あまりにも変更頻度が多いとコストが跳ね上がります。そこで、変更頻度が多くコスト影響が出るリソースのみ取得頻度を落とすことを考え、下記の設計で進めてみます。
- カスタマイズから除外するアカウントをリストアップする (*)
- Control Towerの共有アカウント(管理アカウント、ログアーカイブアカウント、監査アカウント)は除外する (*)
- IAMはControl Towerホームリージョンでのみ記録対象とし、頻度は日次とする (*)
- 変更頻度が高く、コスト影響が大きそうなリソースの取得頻度を日次とする
- いずれのリソースも取得対象外にはしない
(*) ソリューションのデフォルト値=推奨設定
できないこと
このソリューションでも下記のようなより細かいカスタマイズをすることはできません。
- アカウントごとのカスタマイズ
- (例) AアカウントだけSecurityGroupリソースの記録を除外する、ということはできない。
- リージョンごとのカスタマイズ
- (例) バージニアリージョンだけSecurityGroupリソースの記録を除外する、ということはできない。
できるのは、対象になったアカウントすべてに対して一律で、一部リソースの記録を除外したり、記録頻度を日次にするリソースを指定したりすることです。
デプロイ構成
運用を考えると、ソリューションのテンプレートファイルはリポジトリ管理が望ましいと思います。
今回は、CloudFormationの Git同期 を利用してソリューションを展開してみます。
GitプロバイダーとしてGitHub、GitLab、BitBucket等に対応していますが、今回はGitHubを利用します。
デプロイ先は管理アカウントのControl Towerホームリージョンです。
絵にすると以下のようなイメージです。
設定の流れ
- GitHub接続
- スタックデプロイメントファイル作成
- GitHubにコミット
- IAMロール作成
- CloudFormationスタック作成
1. GitHub接続
まず、AWSとGitHubの接続を作成します。
GitHubを選択します。
対象のGitHubアカウントを選択します。
新しいアプリをインストールをクリックします。(インストール済みの場合は左側から既存のアプリを選択)
対象のGitHubアカウントを選択します。
インストール先のリポジトリを選択します。
接続をクリックします。
選択したGitHubリポジトリとAWSの接続が作成できました。
2. スタックデプロイメントファイル作成
スタックデプロイメントファイルでは下記を指定します。
- CloudFormationテンプレートファイルのパス
- CloudFormationテンプレートに渡すパラメータ値
- CloudFormationスタックに適用するタグ
先述の設計方針を踏まえ、以下の記述としました。
template-file-path: template.yaml
parameters:
CloudFormationVersion: 1
# AccountSelectionMode: EXCLUSION ※デフォルト
ExcludedAccounts: "['111111111111', '222222222222', '333333333333']" # ダミー
# ConfigRecorderStrategy: EXCLUSION ※デフォルト
ConfigRecorderExcludedResourceTypes: ''
# ConfigRecorderDefaultRecordingFrequency: CONTINUOUS ※デフォルト
ConfigRecorderDailyResourceTypes: ''
tags:
Environment: 'test'
ExcludedAccountsに実際は管理アカウント、ログアーカイブアカウント、監査アカウントを設定しました。
デフォルトの部分は指定不要ですが、わかりやすいようにコメントアウトの形で載せました。
タグにはとりあえず適当な値を入れてみました。
なお、このスタックデプロイメントファイルは、Git同期の過程で自動生成することもできます。
今回は事前に作成してGitHubリポジトリに配置しておくことにしました。
3. GitHubにコミット
作成したデプロイメントファイルdeployment.yamlと、aws-samplesから取得したCloudFormationテンプレートtemplate.yamlをGitHubリポジトリにコミットします。
4. IAMロール作成
ロールは2種類作成します。
- GitHub同期サービスロール
- CloudFormationサービスロール
GitHub同期サービスロール
GitHubからCodeConnection経由でCloudFormationを操作するのに利用されるロールです。
ドキュメント を参考にして、下記のCloudFormationテンプレートでIAMロールを作成しました。
テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: IAM Role for CloudFormation GitHub Sync
Parameters:
SystemAlias:
Type: String
Description: System alias for the role name
Env:
Type: String
Description: Environment name
CodeConnectionId:
Type: String
Description: ID of the CodeConnections connection
Resources:
GitHubSyncRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${SystemAlias}-${Env}-github-sync-role'
Description: Role for CloudFormation GitHub Sync to deploy templates
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: CfnGitSyncTrustPolicy
Effect: Allow
Principal:
Service: cloudformation.sync.codeconnections.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
ArnLike:
aws:SourceArn: !Sub 'arn:${AWS::Partition}:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/${CodeConnectionId}'
Policies:
- PolicyName: CloudFormationGitHubSyncPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: SyncToCloudFormation
Effect: Allow
Action:
- cloudformation:CreateChangeSet
- cloudformation:DeleteChangeSet
- cloudformation:DescribeChangeSet
- cloudformation:DescribeStackEvents
- cloudformation:DescribeStacks
- cloudformation:ExecuteChangeSet
- cloudformation:GetTemplate
- cloudformation:ListChangeSets
- cloudformation:ListStacks
- cloudformation:ValidateTemplate
Resource: '*'
- Sid: PolicyForManagedRules
Effect: Allow
Action:
- events:PutRule
- events:PutTargets
Resource: '*'
Condition:
StringEquals:
events:ManagedBy:
- cloudformation.sync.codeconnections.amazonaws.com
- Sid: PolicyForDescribingRule
Effect: Allow
Action: events:DescribeRule
Resource: '*'
CloudFormationサービスロール
CloudFormationがリソースを作成・更新・削除するのに利用するロールです。
今回はAdministratorAccessをアタッチしたロールを作成して利用することにします。
本番環境では権限設計は慎重に行う必要があります。
サービスロールを指定すると、CloudFormation はスタックで実行されるすべてのオペレーションに対して、そのロールを常に使用します。スタックの作成後に、スタックにアタッチされたサービスロールを削除することはできません。このスタックで操作を実行する権限を持つ他のユーザーは、iam:PassRole 権限の有無にかかわらず、このロールを使用できます。ユーザーが持つべきではないアクセス権限がロールに含まれる場合、ユーザーのアクセス権限を非意図的にエスカレーションできてしまいます。
(引用元:https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html )
5. CloudFormationスタック作成
マネコンからスタックを作成していきます。
「Gitから同期」を選択します。
今回はデプロイメントファイルを事前に作成済みのため、「リポジトリに自分のファイルを提供しています」を選択します。
GitHubを選択し、先ほど作成したCodeConnectionを指定します。
GitHubにコミットしてあるデプロイメントファイル名を指定します。
GithHubがAWSへ接続するときに利用するロールを指定します。
CloudFormationがリソースを操作するときに利用するロールを指定します。
スタックを作成してしばらく待つと、各種ステータスが正常な状態になりました。
デプロイメントファイルで指定したタグはスタックに設定されています。
パラメータを変更してみる
変更内容
パラメータを下記のように変更してみます。
ExcludedAccounts: "['111111111111', '222222222222', '999999999999']" # 1~,2~は実在アカウント、9~はダミー
ConfigRecorderExcludedResourceTypes: 'AWS::EC2::NetworkInterface'
ConfigRecorderDailyResourceTypes: 'AWS::EC2::SecurityGroup'
除外リソースと、記録頻度を日次とするリソースを具体的に指定しました。また、わたしが試した環境では、除外アカウントに指定したアカウントしか存在しなかった(全アカウントがカスタマイズ対象外だった)ため、除外アカウントのうちの一つ(上記の例では999999999999)だけダミーにして、カスタマイズ対象としました。
プルリクエスト
プルリクエストを作成して少し待つと、コメントが書き込まれました。
画像のテーブル部分は下記のとおりです。
| Action | LogicalResourceId | PhysicalResourceId | ResourceType | Replacement | Scope | Attribute | Name | RequiresRecreation | BeforeValue | AfterValue | AttributeChangeType |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Modify | ConsumerLambda | config-custom-stack-ConsumerLambda-odXcWKbm28v7 | AWS::Lambda::Function | False | Properties | Properties | Environment | Never | AWS::EC2::SecurityGroup | Modify | |
| Properties | Environment | Never | AWS::EC2::NetworkInterface | Modify | |||||||
| Properties | Environment | Never | AWS::EC2::NetworkInterface | Modify | |||||||
| Properties | Environment | Never | AWS::EC2::SecurityGroup | Modify | |||||||
| Modify | ProducerLambda | config-custom-stack-ProducerLambda-1Hxh1Ii7WOep | AWS::Lambda::Function | False | Properties | Properties | Environment | Never | ['111111111111', '222222222222', '333333333333'] | ['111111111111', '222222222222', '999999999999'] | Modify |
| Properties | Environment | Never | ['111111111111', '222222222222', '333333333333'] | ['111111111111', '222222222222', '999999999999'] | Modify |
変更が入るリソースはLambda関数2つで、いずれも環境変数の値が変更されます。ConsumerLambdaでは除外リソースや日次記録対象リソース、ProducerLambdaではアカウントIDの値が変わることがわかりますが、どの環境変数名が変わるかまでは読み取れないようです。
プルリクエストをマージすると、スタックが更新されました。
変更状況を確認
具体的にどの環境変数が更新されたかをマネコン上で確認してみます。
ProducerLambda
ConsumerLambda
それぞれのLambda関数は構成図でいうとここです。
ProducerLambdaは、ControlTowerによって作成されたConfigをデプロイするStackSetsから、ConfigがデプロイされているアカウントIDとリージョンを読み取り、SQSに送信します。
ConsumerLambdaは、SQSをトリガーとして動作し、メンバーアカウントのAWSControlTowerExecutionロールを使って、環境変数に設定されたカスタマイズ値をもとにメンバーアカウントのConfig設定を更新します。
カスタマイズ対象アカウントの設定を確認したところ、なぜか変わっていません。
ProducerLambdaと、ProducerLambdaをキックするEventBridgeのメトリクスを確認したところ、動作した形跡がありませんでした。
Configカスタマイズのトリガー
ProducerLambdaをキックするEventBridgeのイベントパターンは下記のとおりで、ランディングゾーン変更やアカウント作成をハンドリングするようになっています。
{
"source": ["aws.controltower"],
"detail-type": ["AWS Service Event via CloudTrail"],
"detail": {
"eventName": ["UpdateLandingZone", "CreateManagedAccount", "UpdateManagedAccount", "ResetLandingZone"]
}
}
今回行った操作はConfigカスタマイズソリューションのパラメータ変更なので、イベントをハンドリングすることは当然できません。
パラメータ変更を反映させる方法
パラメータにCloudFormationVersionというものがあり、これを変更すればよさそうです。
CloudFormationVersion
説明: スタックの更新を強制し、ソリューションを再実行するためのバージョン番号
タイプ: 文字列
デフォルト:1
使用法: すべてのアカウントでソリューションを強制的に再実行する必要がある場合は、この値を増やします。
バージョンを2に変更してみます。
CloudFormationVersion: 2
今度は正しくConfigの設定が変更されました。
なお、IAMの記録頻度が変わっているのは、今回デプロイメントファイルで明示的に指定していなかった下記パラメータのデフォルト値が適用されたためです。(上記画面はホームリージョンのものです)
ConfigRecorderDailyGlobalResourceTypes
説明: コントロールタワーのホームリージョンで毎日記録するグローバルリソースタイプのコンマ区切りリスト
タイプ: 文字列
デフォルト:AWS::IAM::Policy,AWS::IAM::User,AWS::IAM::Role,AWS::IAM::Group
使用方法: 重複を避けるため、グローバルリソース(IAM、CloudFrontなど)はホームリージョンにのみ記録されます。
注: これらのリソースは、コントロールタワーのホームリージョンの毎日の記録にのみ自動的に追加されます。
なぜバージョンを変えるとLambdaが動作するのか
CloudFormationテンプレートの下記の部分でカスタムリソースが定義されています。
CloudFormationVersionを変更することで、ServiceTokenに指定されたProducerLambdaを動作させることができます。
なお、ProducerLambdaへのイベント入力として下記のようにFunctionNameやVersionが渡されますが、ProducerLambdaでこれらの値は使用されない作りになっています。
"ResourceProperties":
{
"ServiceToken": "arn:aws:lambda:ap-northeast-1:111111111111:function:config-custom-stack-ProducerLambda-1Hxh1Ii7WOep",
"FunctionName": "config-custom-stack-ProducerLambda-1Hxh1Ii7WOep",
"Version": "2",
}
CloudFormationVersionパラメータはString型なので数値以外も指定できますが、このソリューションを運用していく上では数字をインクリメントしていくのがよさそうです。
※FunctionNameはそもそも不要な気がします。
管理アカウントのConfigは対象?
管理アカウントは対象外です。
そもそもControl TowerはメンバーアカウントのConfigを有効化しますが、管理アカウントのConfigは管理対象外です。
そして、メンバーアカウントのConfig有効化はAWSControlTowerBP-BASELINE-CONFIGというControl Towerによって作成されるStackSetsによって行われます。
「aws-control-tower-config-customization」ではカスタマイズの対象アカウントまたは対象外アカウントを指定できますが、ProducerLambdaがAWSControlTowerBP-BASELINE-CONFIGに含まれるスタックインスタンスを読み取ることで、対象とするアカウントの全量を決めています。
さいごに
「aws-control-tower-config-customization」は、Control Towerを利用する場合、コスト最適の観点では必須の機能だと思います。AWSから公開されているので信頼できますし、容易に導入してコストを最適化できるので、良いソリューションだと感じました。
弊社では一緒に働く仲間を募集中です!
現在、様々な職種を募集しております。
カジュアル面談も可能ですので、ご連絡お待ちしております!
募集内容等詳細は、是非採用サイトをご確認ください。

























