CDK bootstrap の --qualifier だけ変えたら CDKToolkit が更新されて事故った話
この記事の結論
CDK の bootstrap 環境を複数作りたい場合、--qualifier だけでは足りません。
# 危険: 既存の CDKToolkit スタックを更新してしまう
cdk bootstrap aws://123456789012/ap-northeast-1 \
--qualifier dev001
別の bootstrap 環境として分けたいなら、--toolkit-stack-name と --qualifier の両方を指定します。
cdk bootstrap aws://123456789012/ap-northeast-1 \
--toolkit-stack-name CDKToolkit-dev001 \
--qualifier dev001 \
--cloudformation-execution-policies arn:aws:iam::123456789012:policy/Dev001CdkDeployPolicy
そして cdk deploy 時には、CDK app 側がその qualifier の bootstrap resources を使うようにします。
cdk deploy -c '@aws-cdk/core:bootstrapQualifier=dev001'
何をしたかったか
CDK の deploy / destroy に使う権限を絞りたかった。
CDK v2 の modern bootstrap では、ざっくり以下のような role が作られます。
cdk-hnb659fds-deploy-role-<ACCOUNT_ID>-<REGION>
cdk-hnb659fds-cfn-exec-role-<ACCOUNT_ID>-<REGION>
cdk-hnb659fds-file-publishing-role-<ACCOUNT_ID>-<REGION>
cdk-hnb659fds-image-publishing-role-<ACCOUNT_ID>-<REGION>
cdk-hnb659fds-lookup-role-<ACCOUNT_ID>-<REGION>
CDK CLI は deployment role を assume し、CloudFormation execution role は CloudFormation に渡されて実際のデプロイ権限になります。AWS 公式ドキュメントでも、DefaultStackSynthesizer が必要とする role として deployment role、lookup role、file/image publishing role、CloudFormation execution role が説明されています。(AWS ドキュメント)
つまり、cfn-exec-role に AdministratorAccess が付いていると、CDK deploy できる人は CloudFormation 経由でかなり強い権限を行使できます。
そこで、開発用の bootstrap 環境を別 qualifier で作り、cfn-exec-role に独自の最小権限ポリシーを付けたい、というのが今回の目的です。
やらかしたコマンド
最初に以下を実行しました。
cdk bootstrap aws://123456789012/ap-northeast-1 \
--qualifier dev001
すると、新しい bootstrap stack が作られるのではなく、既存の CDKToolkit スタックが更新されました。
これが怖い。まるでキーボードの上にねこちゃんが乗ったかのようだ😺
--qualifier は bootstrap resource の物理名に入る値です。デフォルトは hnb659fds です。AWS 公式ドキュメントでも、qualifier は bootstrap stack 内の resource physical ID に追加される値であり、複数 bootstrap stack の名前衝突を避けるためのものだと説明されています。(AWS ドキュメント)
一方で、bootstrap stack 名そのものはデフォルトで CDKToolkit です。別名にしたい場合は --toolkit-stack-name を指定する必要があります。(AWS ドキュメント)
つまり、以下は CDKToolkit という同じ CloudFormation stack を dev001 qualifier 用に更新する操作になります。
cdk bootstrap aws://123456789012/ap-northeast-1 \
--qualifier dev001
復旧
既存の標準 bootstrap を戻すには、標準 qualifier で再 bootstrap しました。
cdk bootstrap aws://123456789012/ap-northeast-1 \
--qualifier hnb659fds
これで既存の CDKToolkit は標準 qualifier に戻りました。
正しい作り方
別 bootstrap 環境を作るなら、--toolkit-stack-name と --qualifier を両方指定します。
cdk bootstrap aws://123456789012/ap-northeast-1 \
--toolkit-stack-name CDKToolkit-dev001 \
--qualifier dev001 \
--cloudformation-execution-policies arn:aws:iam::123456789012:policy/Dev001CdkDeployPolicy
これで以下のような分離になります。
標準 bootstrap
CloudFormation stack:
CDKToolkit
qualifier:
hnb659fds
resources:
cdk-hnb659fds-deploy-role-...
cdk-hnb659fds-cfn-exec-role-...
cdk-hnb659fds-assets-...
dev001 bootstrap
CloudFormation stack:
CDKToolkit-dev001
qualifier:
dev001
resources:
cdk-dev001-deploy-role-...
cdk-dev001-cfn-exec-role-...
cdk-dev001-assets-...
--cloudformation-execution-policies は、CloudFormation deployment role にアタッチする managed policy ARN を指定するオプションです。デフォルトでは AdministratorAccess による full administrator permissions でデプロイされるため、最小権限化したい場合はここに独自 managed policy を指定します。(AWS ドキュメント)
deploy 側
bootstrap 側で dev001 qualifier を使っただけでは、CDK app 側が自動で dev001 を見に行くわけではありません。
deploy 時に context を渡します。
cdk deploy -c '@aws-cdk/core:bootstrapQualifier=dev001'
CDK 公式ドキュメントでも、bootstrap 時に qualifier を変更した場合は、stack synthesis 側でも同じ qualifier を使う必要があると説明されています。DefaultStackSynthesizer の qualifier property、または cdk.json の context key で指定できます。(AWS ドキュメント)
DefaultStackSynthesizerProps の API リファレンスでも、qualifier のデフォルト値は @aws-cdk/core:bootstrapQualifier context key があればその値、なければ DefaultStackSynthesizer.DEFAULT_QUALIFIER と説明されています。(AWS ドキュメント)
なので、上記のように -c オプションで qualifier を指定することで、利用する bootstrap 環境をコードを変更せずに指定することが出来ます。
コード変更なしで使う場合の条件
今回の方針は以下です。
cdk bootstrap:
--toolkit-stack-name と --qualifier の両方を付ける
cdk deploy:
-c '@aws-cdk/core:bootstrapQualifier=dev001' を付ける
CDKコード:
変更しない
ただし、以下のケースでは CLI の -c が期待どおり効かないことがあります。
1. CDK コード側で qualifier を固定している
たとえばコードに以下がある場合です。
new DefaultStackSynthesizer({
qualifier: 'hnb659fds'
})
この場合、CLI の context よりコード側の明示指定が優先されます。
2. すでに synth 済みの cloud assembly を deploy している
たとえば以下のような場合です。
cdk deploy --app cdk.out -c '@aws-cdk/core:bootstrapQualifier=dev001'
cdk.out がすでに synth 済みなら、deploy 時に context を渡しても bootstrap resource 名は変わりません。
3. dev001 側の bootstrap resources が存在しない
先に bootstrap が必要です。
cdk bootstrap aws://123456789012/ap-northeast-1 \
--toolkit-stack-name CDKToolkit-dev001 \
--qualifier dev001 \
--cloudformation-execution-policies arn:aws:iam::123456789012:policy/Dev001CdkDeployPolicy
確認コマンド
作られた role を確認します。
aws iam list-roles \
--query "Roles[?contains(RoleName, 'cdk-dev001')].RoleName"
期待値は以下のような role です。
cdk-dev001-deploy-role-123456789012-ap-northeast-1
cdk-dev001-cfn-exec-role-123456789012-ap-northeast-1
cdk-dev001-file-publishing-role-123456789012-ap-northeast-1
cdk-dev001-image-publishing-role-123456789012-ap-northeast-1
cdk-dev001-lookup-role-123456789012-ap-northeast-1
bootstrap stack も確認します。
aws cloudformation describe-stacks \
--stack-name CDKToolkit-dev001 \
--query "Stacks[0].StackStatus"
CloudFormation execution role に意図した policy が付いているかも確認します。
aws iam list-attached-role-policies \
--role-name cdk-dev001-cfn-exec-role-123456789012-ap-northeast-1
--role-arn では deploy-role は変えられない
ここも混乱しやすいポイントです。
cdk deploy --role-arn ... は、CDK CLI が assume する deploy-role を指定するものではありません。
標準の DefaultStackSynthesizer では、CDK CLI は bootstrap の deploy-role を assume し、その deploy-role が CloudFormation execution role を CloudFormation に渡します。DefaultStackSynthesizer は conventionally named roles と asset storage locations を使う synthesizer で、modern bootstrap stack を要求します。(AWS ドキュメント)
したがって、今回やりたいことが「別の deploy-role / cfn-exec-role / asset bucket 一式を使いたい」であれば、--role-arn ではなく qualifier を分けるのが自然です。
cdk deploy -c '@aws-cdk/core:bootstrapQualifier=dev001'
今回の運用ルール
事故防止のため、運用ルールとしては以下にしました。
既存 bootstrap を触るとき
cdk bootstrap aws://123456789012/ap-northeast-1 \
--qualifier hnb659fds
開発用 bootstrap を作るとき
cdk bootstrap aws://123456789012/ap-northeast-1 \
--toolkit-stack-name CDKToolkit-dev001 \
--qualifier dev001 \
--cloudformation-execution-policies arn:aws:iam::123456789012:policy/Dev001CdkDeployPolicy
開発用 bootstrap で deploy するとき
cdk deploy -c '@aws-cdk/core:bootstrapQualifier=dev001'
開発用 bootstrap で destroy するとき
cdk destroy -c '@aws-cdk/core:bootstrapQualifier=dev001'
まとめ
CDK の bootstrap を複数作る場合のポイントは以下です。
--qualifier:
bootstrap resources の物理名に入る値
例: cdk-dev001-cfn-exec-role-...
--toolkit-stack-name:
bootstrap stack の CloudFormation stack 名
例: CDKToolkit-dev001
-c '@aws-cdk/core:bootstrapQualifier=dev001':
CDK app 側に dev001 qualifier の bootstrap resources を使わせる指定
一番重要なのはこれです。
# NG: 既存 CDKToolkit を更新する可能性がある
cdk bootstrap aws://123456789012/ap-northeast-1 \
--qualifier dev001
# OK: 別 bootstrap stack として作る
cdk bootstrap aws://123456789012/ap-northeast-1 \
--toolkit-stack-name CDKToolkit-dev001 \
--qualifier dev001 \
--cloudformation-execution-policies arn:aws:iam::123456789012:policy/Dev001CdkDeployPolicy
--qualifier だけ見て「別 bootstrap 環境になる」と思うと危険です。
bootstrap stack を分けるなら --toolkit-stack-name もセット。
deploy / destroy では @aws-cdk/core:bootstrapQualifier を渡す。
これで、CDK コードを変更せずに、デフォルト bootstrap とは別の最小権限 bootstrap 環境を使えます。
真なる教訓
本記事の内容へたどり着くに際して検証時にスタック名を適当に入力するのはやめたほうがいい
うっかりnekoなんて入力して cdk deploy とかしないように
これほど命名を後悔したことはない・・・😿
