はじめに
こんにちは!CDKが好きな社会人二年目のエンジニアです。
皆さん AWS CDK (以下 CDK と呼びます)を普段使っていますでしょうか?
CDK は CloudFormation の難しい部分を開発者向けに抽象化してくれている Developer Tool ですが、その「抽象化」ゆえに、開発者が意識しないところで社内規則に反するリソースが裏側で作られてしまうことがあります。
その一例が、環境構築の際に実行する cdk bootstrap です。
環境構築時、cdk bootstrap を行うと裏で CDK がさまざまなリソースを作成しますが、その中の一つに AdministratorAccess 権限を持つロールが作成されます。
ただし、エンタープライズ環境や厳格な社内ルールが存在する環境では、この AdministratorAccess の利用自体が禁止・非推奨とされているケースも少なくありません。
- セキュリティポリシーで
AdministratorAccessの付与が禁止されている - 強い権限を持つ IAM Role に対して Permission Boundary のアタッチが必須化されている
- SCP で特定のアクションがそもそも禁止されている
このような制約下でも CDK を導入したいというケースに向けて、本記事では AdministratorAccess の作成ができない、もしくは非推奨となっている環境で cdk bootstrap を成功させるための 3 つのアプローチ(実行ロールのポリシー指定、Permission Boundary の活用、テンプレートのカスタマイズ)について解説していきます。
対象読者と前提知識
本記事は以下のような方を対象としています。
- CDK の基本的な使い方を理解している
- IAM の Role、Policy、Permission Boundary の概念を把握している
- 社内の権限制約のなかで CDK を導入しようとしている
当記事の内容は、2026年4月末の情報に基づいて記載しています。
最新情報は公式ドキュメントをご参照ください。
cdk bootstrap で何が作成されるのか
cdk bootstrap は、CDK アプリケーションをデプロイするために必要なリソースを、対象の AWS アカウント・リージョンにあらかじめ用意するコマンドです。
公式ドキュメントでも触れられている通り、cdk bootstrap を実行すると実際に以下のリソースが作成されます。
- S3 バケット
Lambda の関数コードやアセットなどの CDK プロジェクトファイルを保存するために使用されます。 - ECR リポジトリ
主に Docker イメージの保存に使用されます。 - 5 つの IAM Role
CDK のデプロイに利用されます。
このうち、特に重要なのが 5 つの IAM Role です。これらの Role についても公式ドキュメントで解説されており、実際に以下の IAM Role が作成されます。
-
CloudFormationExecutionRole
CloudFormation が assume する Role。
cdk deployでリソースを実際に作成・更新・削除するときに使われます。CDK アプリで作るあらゆるリソース(Lambda、DynamoDB、IAM Role など)の操作権限が必要なため、デフォルトではAdministratorAccessがアタッチされます。本記事の主役です。 -
DeploymentActionRole
CDK CLI が assume する Role。
cdk deployの起点となる Role で、CloudFormation スタックの作成・更新や、CloudFormationExecutionRole を CloudFormation に渡す(PassRole)役割を担います。 -
FilePublishingRole
CDK CLI が assume する Role。
Lambda のコード ZIP やアセットファイルを、Bootstrap で作られた S3 バケットにアップロードする際に使われます。権限は対象 S3 バケットへの操作に絞られています。 -
ImagePublishingRole
CDK CLI が assume する Role。
コンテナイメージアセット(Docker イメージ)を、Bootstrap で作られた ECR リポジトリに Push する際に使われます。権限は対象 ECR リポジトリへの操作に絞られています。 -
LookupRole
CDK CLI が assume する Role。
Vpc.fromLookup()などで既存リソースの情報を取得するときや、cdk diffなどの読み取り専用のスタック操作に使われます。読み取り専用の権限のみを持ちます。
上記の通り、5 つの IAM Role のうち、AdministratorAccess がアタッチされるのは CloudFormationExecutionRole だけです。残り 4 つは、それぞれの責務に応じた絞られた権限を持っています。
ではなぜ CloudFormationExecutionRole にだけ AdministratorAccess が必要なのでしょうか。答えはシンプルで、CDK アプリがどのような AWS リソースを作るか事前に分からないからです。
CDK で書かれたアプリは、Lambda、DynamoDB、VPC、IAM Role、KMS Key など、あらゆる AWS リソースを作成しうる存在です。CloudFormationExecutionRole は実際にそれらのリソースを作成する Role なので、「将来作られるかもしれない、すべてのリソース」に対する権限を持っている必要があります。
逆に言えば、CDK アプリで作るリソースの種類が限定されているなら、CloudFormationExecutionRole の権限はそれに合わせて絞れるということになります。これが本記事で話したいことの出発点になります。
CDK の開発において CloudFormationExecutionRole から AdministratorAccess を外し、権限を絞ることが常にベストプラクティスというわけではありません。
- CDK アプリで作成しうるリソースは多岐にわたり、事前にすべてを予測することが難しい
- 権限不足によるデプロイ失敗が頻発すると、開発者体験が著しく損なわれる
- Bootstrap で作成される Role は CloudFormation や CDK CLI からのみ assume されるため、人間が直接悪用するリスクは比較的低い
これらの理由から、本記事で紹介するアプローチは「開発体験の最大化」を目的としたものではなく、あくまで社内ルールに従う必要がある場合の現実的な選択肢としてお読みください。
よくある社内ルールでの制約
実際の現場で cdk bootstrap がブロックされるパターンはさまざまですが、概ね以下のいずれかに分類できるのではないでしょうか。
-
AdministratorAccessの付与自体が禁止されている
社内ルールとして、SCPやIAMガードレールにて「"*"を許可するポリシーは作成、アタッチ不可」と設定されているケースです。 - 強い権限を持つ IAM Role にPermission Boundary のアタッチが必須化されている
AdministratorAccessなどの強い権限の IAM Role に対して、特定の Permission Boundary をアタッチすることが SCP で強制されているケースです。このルールが設定されている場合、Bootstrap が作る IAM Role にも当然これが適用されます。 - 特定 Action や特定リソースの作成自体が禁止されている
例えば「iam:CreateUser は組織全体で禁止」「特定のリージョンでの操作は禁止」などが設定されているケースです。
cdk bootstrapにはこれらの制約に対応するための仕組みが用意されています。
以下に、代表的な3つのアプローチを示します。
| アプローチ | 概要 | 主な用途 |
|---|---|---|
--cloudformation-execution-policies |
CloudFormationExecutionRole にアタッチするマネージドポリシーを指定 |
AdministratorAccess の付与禁止対応 |
--custom-permissions-boundary |
Bootstrap が作る CloudFormationExecutionRole に Permission Boundary を付与 | Permission Boundary 必須対応 |
| Bootstrap テンプレートのカスタマイズ | テンプレート自体を取得・修正してデプロイ | 上記で解決できない複雑な制約 |
以下それぞれ詳しく見ていきます。
対策1 - --cloudformation-execution-policies で権限を絞る
最もシンプルで、AdministratorAccess の付与禁止というシナリオに直接対応する方法です。
$ cdk bootstrap --cloudformation-execution-policies "arn:aws:iam::aws:policy/AWSLambda_FullAccess,arn:aws:iam::aws:policy/AWSCodeDeployFullAccess"
--cloudformation-execution-policies に、CloudFormationExecutionRole へアタッチしたいポリシーの ARN をカンマ区切りで指定します。
これにより、デフォルトの AdministratorAccess の代わりに、指定したポリシーがアタッチされた状態で Bootstrap されます。
ポリシーの選び方には大きく 2 つの方向性があります。
ポリシーの選び方
カスタマー管理ポリシーの自作
最小権限を目指す場合、カスタマー管理ポリシーを自作し必要な権限を追加する必要があります。
S3バケットを作成する場合を例に、作成する管理ポリシーを具体的に見ていきましょう。
例1 - シンプルな S3 バケットを作成する
まず、最小構成の S3 バケットを CDK で作成してみます。
const bucket = new s3.Bucket(this, 'TestBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY
});
直感的には s3:CreateBucket だけあれば足りそうですが、この権限のみをCloudFormationExecutionRoleが使用するポリシーに指定して実際にデプロイすると以下のエラーが返ってきます。
is not authorized to perform: ssm:GetParameters on resource
これは、CloudFormation が CDK Bootstrap のバージョンを SSM Parameter Store から確認するために発生するエラーです。S3 の権限だけでなく、SSM の ssm:GetParameters も必要になります。
最終的に、シンプルなバケットの作成には以下のポリシーが必要でした。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"ssm:GetParameters"
],
"Resource": "*"
}
]
}
例2 - S3 バケットの設定を増やす
次に、実運用で一般的な設定(バージョニング、暗号化、パブリックアクセスブロック)を追加してみます。
const bucket = new s3.Bucket(this, 'TestBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});
たった 3 行追加しただけですが、それぞれの設定に対応する Action が新たに必要になります。
| 追加した設定 | 不足していた権限 |
|---|---|
encryption: S3_MANAGED |
s3:PutEncryptionConfiguration |
blockPublicAccess: BLOCK_ALL |
s3:PutBucketPublicAccessBlock |
versioned: true |
s3:PutBucketVersioning |
S3 の場合、設定項目ごとに独立した Action が対応しているため、設定する項目の数だけ権限が増える構造になっています。
例3 - Lambda と S3 イベント通知を組み合わせる
さらに実践的な構成として、S3 へのオブジェクト Put をトリガーに Lambda を起動する構成を追加してみます。すると、必要な権限はさらに多くなります。
| 不足していた権限 | 用途 |
|---|---|
iam:CreateRole |
Lambda 実行ロールの作成 |
iam:AttachRolePolicy |
実行ロールへのマネージドポリシーアタッチ |
iam:GetRole, iam:GetRolePolicy
|
ロール情報の取得(Lambda 関数作成時に参照) |
lambda:CreateFunction, iam:PutRolePolicy
|
Lambda 関数の作成、インラインポリシーの設定 |
iam:PassRole |
Lambda に実行ロールを渡す |
s3:GetObject |
Lambda コード ZIP を Bootstrap バケットから取得 |
lambda:GetFunction |
関数作成後の状態確認 |
lambda:AddPermission |
S3 から Lambda を呼び出す権限の付与 |
logs:CreateLogGroup |
CloudWatch Logs グループの作成 |
logs:DeleteLogGroup |
既存ロググループの削除(CDK による採用処理) |
lambda:InvokeFunction |
S3 バケット通知設定用カスタムリソースの実行 |
logs:DescribeLogGroups, logs:PutRetentionPolicy
|
ロググループの存在確認・保持期間設定 |
ロールバック時にも権限が必要
権限不足でデプロイが失敗すると、CloudFormation は自動的にロールバックを試みます。このロールバック処理でも CloudFormationExecutionRole が使われるため、作成時とは別の Action が必要になる場合があります。
例えば iam:CreateRole の権限だけでは、ロールバック時の iam:DeleteRole や iam:DetachRolePolicy が不足してロールバック自体も失敗します(ROLLBACK_FAILED 状態)。権限を追加する際は、ロールバック時に必要な Action も合わせて考慮する必要があります。
ロールバック時に必要な権限も含めて、最終的に以下のポリシーでデプロイが完了しました。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:AttachRolePolicy",
"iam:CreateRole",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:PassRole",
"iam:PutRolePolicy",
"lambda:AddPermission",
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:GetFunction",
"lambda:InvokeFunction",
"lambda:RemovePermission",
"logs:CreateLogGroup",
"logs:DeleteLogGroup",
"logs:DescribeLogGroups",
"logs:PutRetentionPolicy",
"s3:CreateBucket",
"s3:GetObject",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketVersioning",
"s3:PutEncryptionConfiguration",
"ssm:GetParameters"
],
"Resource": "*"
}
]
}
本記事では簡略化のため Resource: "*" としていますが、本来はリソース ARN を特定のバケット・関数・ロールに絞ることで、より厳密な最小権限を実現できます。
なお、上記は筆者の検証構成でデプロイが完了した例であり、CDK のバージョンやスタックの状態によっては追加の Action が必要になる場合があります。
ここまで見てきたとおり、「シンプルな S3 バケット 1 つ」から「Lambda + S3 イベント通知」に拡張するだけで、必要な Action は 2 つから 25 個以上に膨れ上がります。実際のアーキテクチャでは、VPC、ECS、RDS、API Gateway など、より多くのサービスが絡みます。すべてを最小権限で管理しようとすると、ポリシーのメンテナンスが次第に難しくなっていきます。
そこで現実的な落としどころとして、次のアプローチが選択肢に入ってきます。
サービス単位のマネージドポリシーを組み合わせ、絞れるところは絞る
厳密な最小権限の維持が難しい場合、より現実的なアプローチとして「マネージドポリシーを組み合わせて土台を作り、明確に絞り込める対象だけ個別に絞る」という方針が有効です。
このアプローチの基本方針は次の通りです。
- CDK アプリで使う各サービスは、AWS 管理のマネージドポリシー(AmazonS3FullAccess 等)を組み合わせて土台にする
- ARN や Condition で明確に絞り込める対象については、カスタムポリシーで個別に置き換える
まず、CDK アプリで使う各サービスに対応するマネージドポリシーを --cloudformation-execution-policies に列挙します。
cdk bootstrap \
--cloudformation-execution-policies \
"arn:aws:iam::aws:policy/AmazonS3FullAccess,\
arn:aws:iam::aws:policy/AWSLambda_FullAccess,\
arn:aws:iam::aws:policy/CloudWatchLogsFullAccess,\
arn:aws:iam::<account-id>:policy/MyCdkCustomPolicy"
ここにおける MyCdkCustomPolicy は、後述する絞り込みが可能な対象(SSM、IAM)をまとめたカスタマー管理ポリシーです。IAMFullAccess や AmazonSSMFullAccess をそのまま使う代わりに、これらは個別に絞ったポリシーで置き換えます。
SSM はこの方針における最も絞りやすい対象です。CDK Bootstrap が必要とする SSM 操作は「/cdk-bootstrap/<qualifier>/* 配下のパラメータを読み取る」だけであり、Action・Resource ともに明確に特定できます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:GetParameters",
"Resource": "arn:aws:ssm:<region>:<account-id>:parameter/cdk-bootstrap/<qualifier>/*"
}
]
}
IAMは他の権限を作成することができる権限であるため、この方針において最も絞る価値が高い対象です。
絞り込みの基本は、CDK アプリが作る Role の ARN パターンに限定することです。
CloudFormation 経由で作られる Role の名前にはスタック名がプレフィックスとして含まれるため、組織でスタック名の命名規則を統一していれば、これを利用して絞り込めます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ManageRolesForCdkStack",
"Effect": "Allow",
"Action": [
"iam:CreateRole",
"iam:DeleteRole",
"iam:GetRole",
"iam:UpdateAssumeRolePolicy",
"iam:CreatePolicy",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"iam:GetRolePolicy",
"iam:TagRole",
"iam:UntagRole"
],
"Resource": "arn:aws:iam::<account-id>:role/MyApp-*"
},
{
"Sid": "AttachOnlyAllowedManagedPolicies",
"Effect": "Allow",
"Action": [
"iam:AttachRolePolicy",
"iam:DetachRolePolicy"
],
"Resource": "arn:aws:iam::<account-id>:role/MyApp-*",
"Condition": {
"ArnLike": {
"iam:PolicyARN": [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
}
}
},
{
"Sid": "PassRoleToLambdaOnly",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::<account-id>:role/MyApp-*",
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
]
}
完全な最小権限ではないものの、運用可能な範囲で絞れるところまで絞るという現実的な落としどころとして、社内ルールが厳しい多くの現場で採用しやすい構成です。
対策2 - --custom-permissions-boundary で CloudFormationExecutionRole に境界を設ける
AdministratorAccess をアタッチするが、Permission Boundaryを用いてDenyガードレールを引くアプローチです。
--custom-permissions-boundary オプションに、あらかじめ作成しておいた IAM Managed Policy の名前を渡します。
# 事前に Permission Boundary 用ポリシーを作成しておく
aws iam create-policy \
--policy-name CdkBootstrapPermissionBoundary \
--policy-document file://permission-boundary.json
# Boundary を指定して bootstrap
cdk bootstrap --custom-permissions-boundary CdkBootstrapPermissionBoundary
対策1(--cloudformation-execution-policies)は ARN を指定するのに対し、こちらは ポリシー名 を指定します。
Permission Boundary ポリシーの選び方
対策1(--cloudformation-execution-policies)との違いは、権限の指定方向です。
| 対策1 | 対策2 | |
|---|---|---|
| 方向 | 必要なものを Allow | 禁じるものを Deny |
CloudFormationExecutionRole の identity policy |
絞り込んだポリシー |
AdministratorAccess のまま |
| メンテコスト | CDK アプリの変更のたびに修正 | 組織のガードレールとして機能 |
Permission Boundary を最小権限の Allow リストで書こうとすると対策1と同じ課題が生じるため、Denyベースのガードレールを作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAll",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
},
{
"Sid": "DenyDangerousActions",
"Effect": "Deny",
"Action": [
"iam:CreateUser",
"iam:CreateVirtualMFADevice",
"organizations:LeaveOrganization",
"account:CloseAccount",
"cloudtrail:DeleteTrail",
"cloudtrail:StopLogging",
"guardduty:DeleteDetector",
"config:DeleteConfigurationRecorder",
"config:DeleteDeliveryChannel"
],
"Resource": "*"
}
]
}
CloudFormationExecutionRole の identity policy は AdministratorAccess のままで、実効権限を絞ることができます。
Boundary が適用される範囲
実際に bootstrap を実行してみると、CLI には以下のようなメッセージが表示されます。
Adding new permissions boundary CdkBootstrapPermissionBoundary
bootstrap 後、作成された 5 つの IAM Role に対して PermissionsBoundary の有無を確認すると、--custom-permissions-boundary が Boundary をアタッチするのは CloudFormationExecutionRole のみであることだけがわかります。
Bootstrap テンプレート(後述)を確認すると、PermissionsBoundary プロパティが記述されているのは CloudFormationExecutionRole のみとなっているためです。
CloudFormationExecutionRole:
Type: AWS::IAM::Role
Properties:
# ...
PermissionsBoundary:
Fn::If:
- PermissionsBoundarySet # --custom-permissions-boundary が指定されているか
- Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary}
- Ref: AWS::NoValue
他の 4 ロールには同等の記述がないため、Boundary は付与されません。
他の IAM Role にも Boundary を付与したい場合、次に説明する対策3のアプローチを行う必要があります。
対策3 - Bootstrap テンプレートのカスタマイズ
これまで述べてきた対策1・2は cdk bootstrap のフラグでテンプレートのパラメータを外部から注入するアプローチでした。
対策3としては、 Bootstrap テンプレートについて解説しつつ、リソース定義そのものを書き換えていく方針を見ていきます。
Bootstrap テンプレートのカスタマイズが必要になる代表的なケースは以下のとおりです。
-
CloudFormationExecutionRoleだけでなく、5 つすべての IAM ロールに Permission Boundary を付けたい(対策2の応用) - Bootstrap リソースの設計そのものを組織のポリシーに合わせて管理したい
まず、テンプレートのカスタマイズを理解するのに必要な3つの概念(Bootstrap テンプレート、Synthesizer、Bootstrap Contract)を見ていきます。
Bootstrap テンプレートとは
cdk bootstrap が AWS 環境にデプロイする CloudFormation テンプレートです(スタック名: CDKToolkit)。
以下のコマンドでその内容を YAML ファイルとして取り出せます。
npx cdk bootstrap --show-template > bootstrap-template.yaml
テンプレートでは、以下のリソースを定義されています。
| リソース論理 ID | タイプ | 説明 |
|---|---|---|
FileAssetsBucketEncryptionKey |
AWS::KMS::Key |
S3 バケット暗号化用 KMS キー(CreateNewKey 条件が true のときのみ) |
FileAssetsBucketEncryptionKeyAlias |
AWS::KMS::Alias |
上記 KMS キーのエイリアス |
StagingBucket |
AWS::S3::Bucket |
Lambda コードなどのファイルアセット保存用 S3 バケット |
StagingBucketPolicy |
AWS::S3::BucketPolicy |
バケットポリシー |
ContainerAssetsRepository |
AWS::ECR::Repository |
Docker イメージ保存用 ECR リポジトリ |
FilePublishingRole |
AWS::IAM::Role |
CDK CLI がファイルアセットを S3 にアップロードするときに AssumeRole するロール |
ImagePublishingRole |
AWS::IAM::Role |
CDK CLI が Docker イメージを ECR にプッシュするときに AssumeRole するロール |
LookupRole |
AWS::IAM::Role |
cdk synth 時のコンテキストルックアップ(Availability Zone 一覧取得など)に使うロール |
FilePublishingRoleDefaultPolicy |
AWS::IAM::Policy |
FilePublishingRole にアタッチするポリシー |
ImagePublishingRoleDefaultPolicy |
AWS::IAM::Policy |
ImagePublishingRole にアタッチするポリシー |
DeploymentActionRole |
AWS::IAM::Role |
CDK CLI が cloudformation:CreateChangeSet などを呼ぶときに AssumeRole するロール |
CloudFormationExecutionRole |
AWS::IAM::Role |
CloudFormation がスタックリソースを実際に作成・変更するときに使うロール |
CdkBoostrapPermissionsBoundaryPolicy |
AWS::IAM::ManagedPolicy |
--example-permissions-boundary 指定時に作成されるサンプル用 Permission Boundary ポリシー |
CdkBootstrapVersion |
AWS::SSM::Parameter |
Bootstrap テンプレートのバージョン番号を記録する SSM パラメータ |
対策1・2との関係
対策1(--cloudformation-execution-policies)と対策2(--custom-permissions-boundary)は、このテンプレートの Parameters ブロックにある値をフラグ経由で注入するアプローチであり、テンプレートのリソース定義自体は変えていません。対策3では Resources 定義を直接編集します。
Synthesizer とは
CDK アプリを cdk deploy で動かすには、大きく 3 つの処理が必要です。
- CDK のコードを CloudFormation テンプレート(JSON)に変換する
- Lambda のコードなどのファイルアセットを S3 にアップロードする
- CloudFormation テンプレートをデプロイする
Synthesizer は 1 と 2 の仕様を決定するコンポーネントです。
どの S3 バケットを使うか、どの IAM ロールを AssumeRole するか、生成される CloudFormation テンプレートに何を埋め込むかを制御します。
Synthesizerには、レガシーなものを除いて2つの種類があります。
DefaultStackSynthesizer(デフォルト)
cdk synth を実行すると、Synthesizer は cdk.out/ 以下に 2 種類のファイルを生成します。
1 CloudFormation テンプレート(cdk.out/CdkBootstrapTestStack.template.json)
テンプレートには以下が埋め込まれます。
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/<qualifier>/version"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [{
"Assert": {
"Fn::Not": [{ "Fn::Contains": [["1","2","3","4","5"], {"Ref": "BootstrapVersion"}] }]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}]
}
}
Parameters.BootstrapVersion の型が AWS::SSM::Parameter::Value<String> になっていますが、これはCloudFormation がスタックのデプロイ時に自動的に SSM パラメータの値を解決する型となります。
CloudFormation は /cdk-bootstrap/<qualifier>/version に記録されているバージョン番号を読み取り、その値に対して Rules.CheckBootstrapVersion のアサーションを実行します。バージョンが 1〜5( CDK v1 時代の Bootstrapで利用されていた値)であればデプロイを拒否します。
Lambda コードが格納される S3 バケットの参照もテンプレートに埋め込まれます。
"Code": {
"S3Bucket": {
"Fn::Sub": "cdk-<qualifier>-assets-${AWS::AccountId}-${AWS::Region}"
}
}
2 アセットマニフェスト(cdk.out/CdkBootstrapTestStack.assets.json)
ファイルアセットのアップロード方法を記述するマニフェストです。
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-<qualifier>-file-publishing-role-${AWS::AccountId}-${AWS::Region}",
"bucketName": "cdk-<qualifier>-assets-${AWS::AccountId}-${AWS::Region}"
cdk deploy を実行すると、CDK CLI はこのマニフェストを読んで cdk-<qualifier>-file-publishing-role-... を AssumeRole し、Lambda コードを cdk-<qualifier>-assets-... バケットにアップロードします。アップロード完了後、今度は cdk-<qualifier>-deploy-role-... を AssumeRole して CloudFormation の API を呼び出します。
qualifier がバケット名・ロール名・SSM パラメータパスの全てに埋め込まれています。これが後述する Bootstrap Contract の重要な要素になります。
CliCredentialsStackSynthesizer
デフォルトで使用される DefaultStackSynthesizer の他に、CliCredentialsStackSynthesizer という Synthesizer があります。
同じプロジェクトに対して Synthesizer を切り替えると、生成物が変わります。
new CdkBootstrapTestStack(app, 'CdkBootstrapTestStack', {
synthesizer: new cdk.CliCredentialsStackSynthesizer({ qualifier: '<qualifier>' })
});
このまま cdk synth を実行すると、生成された CloudFormation テンプレートから Parameters.BootstrapVersion と Rules.CheckBootstrapVersion が消えます。
-"Parameters": {
- "BootstrapVersion": {
- "Type": "AWS::SSM::Parameter::Value<String>",
- "Default": "/cdk-bootstrap/<qualifier>/version"
- }
-},
-"Rules": {
- "CheckBootstrapVersion": {
- "Assertions": [{ "AssertDescription": "CDK bootstrap stack version 6 required..." }]
- }
-}
また assets.json の assumeRoleArn も変わります。
| DefaultStackSynthesizer | CliCredentialsStackSynthesizer | |
|---|---|---|
assumeRoleArn(アップロード用) |
cdk-<qualifier>-file-publishing-role-... |
null |
bucketName |
cdk-<qualifier>-assets-... |
cdk-<qualifier>-assets-...(同じ) |
assumeRoleArn が null になったため、CDK CLI はアセットのアップロード時に IAM ロールを AssumeRole しません。aws login などで取得した現在のセッション認証情報をそのまま使って S3 に書き込みます。CloudFormation の呼び出しについても同様で、deploy-role を AssumeRole せず、現在の認証情報でそのまま呼び出します。
S3 バケット名には引き続き qualifier が使われるため、バケット自体は Bootstrap が作る必要がありますが、5つのIAM Roleが不要になります。
IAM Role を完全に制御したい場合、CliCredentialsStackSynthesizerを使用することも方法の一つですが、デフォルトの DefaultStackSynthesizer と比較した際の制約を理解しておく必要があります。
CliCredentialsStackSynthesizer の制約
- IAM ロールの AssumeRole を行わないため、cross-account デプロイには対応できません
- CDK Pipelines は内部で
deploy-roleへの AssumeRole を前提としているため、CDK Pipelines とは一緒に使えません - 実行者の IAM 権限が S3 書き込み・CloudFormation 操作を含むすべての操作をカバーする必要があります
Bootstrap Contract とは
Bootstrap Contract は、Synthesizer が生成する成果物(テンプレート・assets.json)に埋め込まれた Bootstrap リソースへの参照が、実際に AWS 上に存在し、かつアクセス可能でなければならないという前提条件を指します。
公式ドキュメントには、以下のように書かれています。
CDK アプリケーションを適切にデプロイするには、合成中に生成される CloudFormation テンプレートが、ブートストラップ中に作成されるリソースを正しく指定している必要があります。これらのリソースは、一般的にブートストラップリソースと呼ばれます。ブートストラップは、デプロイの実行とアプリケーションアセットの管理に AWS CDK が使用するリソースを AWS 環境に作成します。合成は、アプリケーション内の各 CDK スタックから CloudFormation テンプレートを生成します。これらのテンプレートは、アプリケーションからプロビジョニングされる AWS リソースを定義するだけではありません。これらは、デプロイ中に使用するブートストラップリソースも指定します。
cdk deploy の実行フローに沿って、何が要求されているかを順番に確認します。
1.アセットアップロード時の要求
CDK CLI は assets.json の assumeRoleArn に記載されたロール(cdk-<qualifier>-file-publishing-role-...)を AssumeRole して S3 にアップロードします。
-
要求:
cdk-<qualifier>-file-publishing-role-{accountId}-{region}という名前の IAM ロールが存在すること -
Bootstrap が満たす方法:
FilePublishingRoleリソースをこの命名規則で作成する
2.CloudFormation 呼び出し時の要求
アップロード完了後、CDK CLI は cdk-<qualifier>-deploy-role-... を AssumeRole して cloudformation:CreateChangeSet などを呼び出します。
-
要求:
cdk-<qualifier>-deploy-role-{accountId}-{region}という名前の IAM ロールが存在すること -
Bootstrap が満たす方法:
DeploymentActionRoleリソースをこの命名規則で作成する
3.Lambda コード参照の解決
CloudFormation テンプレートの Code.S3Bucket には cdk-<qualifier>-assets-{accountId}-{region} が書かれています。CloudFormation がスタックを作成するとき、Lambda リソースのコードを読みに行くバケットとして使います。
-
要求:
cdk-<qualifier>-assets-{accountId}-{region}という名前の S3 バケットが存在すること(かつその中に対象の zip ファイルが存在すること) -
Bootstrap が満たす方法:
StagingBucketリソースをこの命名規則で作成する。ファイル自体は CDK CLI が 1 のアップロードで事前に配置する
4.SSM パラメータの解決と権限
CloudFormation テンプレートには Parameters.BootstrapVersion が型 AWS::SSM::Parameter::Value<String> として定義されています。この型を CloudFormation が処理するとき、スタックのデプロイ処理の中で CloudFormation 自身が SSM の GetParameters API を呼んでパラメータの値を取得します。この API 呼び出しは CloudFormationExecutionRole の権限で行われます。
-
要求:
/cdk-bootstrap/<qualifier>/versionという SSM パラメータが存在すること -
Bootstrap が満たす方法:
CdkBootstrapVersionリソースをこのパス・qualifier で作成する
qualifier が Bootstrap Contract の橋渡しになっている
qualifier とは、同一アカウント・リージョン内に複数の Bootstrap 環境を共存させるための識別子です。デフォルト値は hnb659fds(CDK が自動生成した固定文字列)ですが、cdk bootstrap --qualifier myqualifier で任意の値に変更できます。また、cdk.json の @aws-cdk/core:bootstrapQualifier に設定するとアプリ全体に適用されます。
qualifier の主な役割は Bootstrap リソースの命名の衝突を防ぐことです。同じアカウント内で複数チームが独立した Bootstrap 環境を持つ場合、それぞれ異なる qualifier を使うことでリソース名が重複しません。
Synthesizer が生成する成果物に埋め込まれるリソース名は、すべて qualifier を軸に組み立てられます。Bootstrap テンプレートもまったく同じ qualifier を使って同名のリソースを作ります。この命名が一致することが、Bootstrap Contract が成立するということとイコールになります。
カスタムテンプレートの作成
では実際に、カスタムテンプレートを作成していきます。
テンプレートの取得
以下のコマンドで、実際のテンプレートをyamlファイルで落としてくることができます。
npx cdk bootstrap --show-template > bootstrap-template.yaml
このファイルをベースに変更を加えていきます。
まずBootstrapVariantを修正する
Bootstrap テンプレートには BootstrapVariant というパラメータがあります。
BootstrapVariant:
Type: String
Default: "AWS CDK: Default Resources"
Description: Describe the provenance of the resources in this bootstrap stack.
Change this when you customize the template. To prevent accidents, the CDK CLI
will not overwrite bootstrap stacks with a different variant.
cdk bootstrap が CDKToolkit スタックをデプロイするとき、この BootstrapVariant の Default 値が CloudFormation スタックの Parameters として記録されます。
cdk bootstrap --template custom.yaml を実行するとき、CDK CLI は以下の手順で処理します。
- 現在 AWS にデプロイされている CDKToolkit スタックの
BootstrapVariantパラメータ値を読む - 新しいテンプレート(
custom.yaml)のParameters.BootstrapVariant.Default値を読む - 両者が異なり、かつ新しいテンプレートの値が
"AWS CDK: Default Resources"以外であれば、デプロイをブロックして--forceを求める
カスタムテンプレートの BootstrapVariant.Default を "AWS CDK: Default Resources" のままにしてデプロイすると、チームの誰かが cdk bootstrap(--template なし)を実行したとき、両者の BootstrapVariant が一致してしまうため CDK CLI は上書きを許可してしまい、カスタマイズがデフォルトのテンプレートで上書きされます。
BootstrapVariant を独自の値に変更しておくことで、少なくとも意図しないカスタムテンプレートへの上書きを --force オプションなしではできなくなります。
-Default: "AWS CDK: Default Resources"
+Default: "AWS CDK: Custom Secured Bootstrap"
cdk bootstrapで作成される、全 5 つ IAM Role への Permission Boundary 追加
対策2で確認したとおり、デフォルトの Bootstrap テンプレートには CloudFormationExecutionRole にしか PermissionsBoundary プロパティが記述されていません。
CloudFormationExecutionRole:
Type: AWS::IAM::Role
Properties:
...
PermissionsBoundary: # これは CloudFormationExecutionRole のみ
Fn::If:
- PermissionsBoundarySet
- Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary}
- Ref: AWS::NoValue
FilePublishingRole・ImagePublishingRole・LookupRole・DeploymentActionRole の 4 ロールには同じ記述がないため、--custom-permissions-boundary を指定しても Boundary が付きません。
テンプレートをカスタマイズして、4 ロールの Properties ブロックにも同じ記述を追加します。
FilePublishingRole:
Type: AWS::IAM::Role
Properties:
...
RoleName:
Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region}
PermissionsBoundary: # 追加
Fn::If:
- PermissionsBoundarySet
- Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary}
- Ref: AWS::NoValue
上記 2 つの変更を加えたテンプレートを custom-bootstrap.yaml として保存してデプロイします。
npx cdk bootstrap \
--template custom-bootstrap.yaml \
--qualifier <qualifier> \
--custom-permissions-boundary CdkBootstrapPermissionBoundary \
--force
デプロイが完了し、実際に 5 つの IAM Role すべてに CdkBootstrapPermissionBoundary が Permission Boundary として付いていることが確認できます。
まとめ
CDK には多くの選択肢が用意されており、その組織ごとのルールに準拠した環境構築を行うことができます。
当記事の設定を行わずに開発できることが一番ですが、社内ルールで CDK の利用を諦める前に是非試していただけると幸いです。
最後に、ここまで読んでくださった皆様、誠にありがとうございました!