こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 2022年12月始めに AWS CDK の GitHub リポジトリ の wiki に公開された Security And Safety Dev Guide を読んでみました。CDKアプリをデプロイする時の権限と、CDKアプリ内で作成する権限の両方について、管理方法と推奨事項が書かれています。 この記事ではざっくりとした要約と感想を筆者の観点で書いていきます(分かりやすさのために、幾分か内容の再構築や意訳が入っています)。翻訳記事ではないので、正確で詳細な内容は元のドキュメントをご確認ください。 https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide イントロダクション CDKのデプロイではどのような権限を使うべきか 権限昇格することなくIAMロールの作成を許可する CDKデプロイで使われる権限の管理 DefaultStackSynthesizer デプロイのフロー Bootstrap CliCredentialsStackSynthesizer CDK内のIAMアイデンティティとポリシーを取り扱う CDKにIAMロールとポリシーの管理を任せる 事前に手動で作成したロールを利用する Permissions boundary と SCP ざっくり要約をさらに要約 イントロダクション <ざっくり要約> CDKはあらゆる AWS サービスを構築・設定できる強力なツールで、効率的な開発に寄与する一方、組織は コンプライアンス や コストコ ン トロール の目的で開発者の権限を制限したい場合もあり、セキュリティと生産性はしばしば競合する。このドキュメントでは開発者の生産性を損なうことなく、大半のセキュリティに関する ユースケース を満たすだろう推奨事項を紹介する。 <感想> CDKに限らず、組織内のセキュリティ施策が開発を阻害するものとして捉えられる傾向があるのは、セキュリティ担当と開発担当で見ている景色が違うからだろうと思います。セキュリティ担当は最悪の事態を想像して対策を提示するのに対して、開発担当は普段の日々の開発を楽にすることにより価値を置きます。セキュリティ担当の立場としては、開発担当の気持ちを理解し、 原理主義 にならずに対話型でセキュリティ対策を考える状態を目指したいと考えています。ちなみに筆者が所属するセキュリティを担当するグループでは、開発側の気持ちを理解する方法の一つとして自ら社内向けに システム開発 も行っています。 CDKのデプロイではどのような権限を使うべきか <ざっくり要約> 権限付与の方法は一般的に、許可リスト方式と拒否リスト方式の2種類ある。 CDK/CloudFormationにおいては ロールバック もあり、許可リストでは最小権限を網羅するのは難しい。 AWS Config, AWS CloudTrail, AWS Security Hub, AWS CloudFormation Hooksなどによってガードレール、 コンプライアンス ルール、監査、モニタリングを実施した上で、それらを変更する以外の全ての権限を付与する拒否リスト方式の方が推奨される。 コンプライアンス に適合する範囲で開発者に自由を与えることができる。 <感想> IaCの経験がある方であれば、許可リスト方式での権限付与は厳しいことはよく分かると思います。特に CDK/CloudFormation における ロールバック は Terraform にはない特徴の一つなので、権限付与に限らず、CDKを使うときはそれを意識するのが良いでしょう。 権限昇格することなくIAMロールの作成を許可する <ざっくり要約> CDKでリソースをデプロイするときは、デフォルトで最小権限になるように必要なIAMロールを自動作成する。手動でロールを作らずに、このデフォルトの挙動に任せるのが推奨である。 アプリ開発 者にロールを作成させない運用ルールがある場合、その目的は主に開発者の権限昇格を防ぐことである。権限昇格を防ぐ代替手段としてPermissions boundary(アクセス許可境界)とSCP(サー ビスコ ン トロール ポリシー)がある。開発者のロールにPermissions boundaryをアタッチすることで、開発者自身の権限と、開発者のロールが作成するロールの権限両方を制限できる。 <感想> IAMロールやポリシーはIaCで作る他のリソースを参照するため、手動で管理せずにIaCの開発サイクルに含めた方が管理しやすいのは納得できる説明です。 CDKでインフラをデプロイすると、いつの間にかたくさんのIAMロールが作られていて把握しきれないと感じていましたが、そもそも人間が把握できる程度のシンプルさでは最小権限は実現できていないのだろうと説明を読んで思いました。 CDKデプロイで使われる権限の管理 <ざっくり要約> CDKはCloudFormationを使って変更をデプロイする。 デプロイを開始するアクター(ユーザーか自動化システム)はIAM アイデンティティ (ユーザーかロール)を持ち、それとは別にデプロイを実行する CloudFormation にもロールが与えられる。 スタックデプロイ時にCDKが使用するロールとアセットコンテナ(後述)は、Stack Synthesizer が決定する。 <感想> cdk deploy を実行する時の権限と、実際に CloudFormation がリソースをデプロイする時の権限は分けられることが書いてあります。これも Terraform と比較した時の CDK の特徴ですね。意識的に2つの権限を区別して考えるとこの後の説明が理解しやすいと思います。 Stack Synthesizer については DefaultStackSynthesizer と CliCredentialsStackSynthesizer の2種類の説明がこの後に続きます。 DefaultStackSynthesizer デプロイのフロー <ざっくり要約> DefaultStackSynthesizer は CDK v2 のデフォルトのStack Synthesizerであり、基本的にはこれを使うことになる。 CDKによるデプロイは次のフローの通りである CDKデプロイを開始するアクターの AWS 権限を認可する CDKデプロイを開始するアクターは「CDKデプロイロール」を引き受け(AssumeRole)、「CDKデプロイロール」は「CFN実行ロール」をCloudFormationに渡す(PassRole)。 「CFN実行ロール」は CloudFormationサービスロール で、スタックをデプロイする時にCloudFormationに利用される これ以降、デプロイに使われるのはデプロイを開始したアクターの権限ではなく、「CFN実行ロール」の権限になる 必要に応じてCDKデプロイを開始したアクターは「file-publishing-role」「image-publishing-role」「lookup-role」をそれぞれ引き受ける(AssumeRole) 「file-publishing-role」と「image-publishing-role」は、デプロイで使われるファイルとDockerイメージをS3 バケット とECR リポジトリ にプッシュする時に使われる。ここのS3 バケット とECR リポジトリ はbootstrap時に作られたものである。「CFN実行ロール」にはこれらのアセットを参照する権限が必要である 「lookup-role」はCDKのsynth時に既存のリソースを参照する場合に使われる。デフォルトではReadOnly管理ポリシーが付与されている (図は https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide より) <感想> 「CDKデプロイを開始するアクターの権限」「CDKデプロイロール」「CFN実行ロール」「file-publishing-role」「image-publishing-role」「lookup-role」と権限が6種類登場しています。 「CDKデプロイを開始するアクターの権限」はCDKを利用する前に手動で作成するもので、「CDKデプロイロール」「file-publishing-role」「image-publishing-role」「lookup-role」の4つをAssumeRoleできれば良いです。すなわち、 cdk deploy を実行する時の権限は AdministratorAccess である必要はありません。 「CDKデプロイロール」「CFN実行ロール」「file-publishing-role」「image-publishing-role」「lookup-role」の5つは cdk bootstrap するときに自動で作成されるもので、手で管理する必要はありません。bootstrapの説明はこの後続きます。 Bootstrap <ざっくり要約> DefaultStackSynthesizer を使うためにはまず AWS 環境を bootstrap する必要がある。bootstrap は各リージョンに一度だけ必要な操作で、Synthesizerに必要なIAMロール群とアセットコンテナ(S3 バケット とECR リポジトリ )が作成される。 cdk boostrap することでCloudFormationスタックがデプロイされる。デフォルトのテンプレートを使うこともできるし、 カスタマイズ することもできる。 コンプライアンス 管理をする場合、bootstrap時のテンプレートをカスタマイズする必要がある。例えばカスタマイズによって「CFN実行ロール」の権限を制限できる(ポリシーの変更、Permission boundaryの追加などによって権限昇格を防ぐ)。デフォルトでは「CFN実行ロール」はAdministratorAccessとなる。 また「CDKデプロイロール」「file-publishing-role」「image-publishing-role」を誰が AssumeRole できるのかを制限することもできる。デフォルトでは同じ AWS アカウントの全ての アイデンティティ が AssumeRole できる。 CDKがbootstrapしたロールを引き受けるためには、以下のポリシー ステートメント を アイデンティティ に追加する(=CDKデプロイを開始するアクターは以下の権限を持てば良い) { " Version ": " 2012-10-17 ", " Statement ": [{ " Sid ": " AssumeCDKRoles ", " Effect ": " Allow ", " Action ": " sts:AssumeRole ", " Resource ": " * ", " Condition ": { " ForAnyValue:StringEquals ": { " iam:ResourceTag/aws-cdk:bootstrap-role ": [ " image-publishing ", " file-publishing ", " deploy ", " lookup " ] } } }] } cdk bootstrap の実行はAdministratorAccessの権限を使うことを推奨する。bootstrapスタックの内容は変わりうるので、将来的に必要な権限は予測できない。またbootstrapによって任意の権限を持った新しいロールが作られるので、bootstrapを実行する権限を制限する実メリットがない。 <感想> cdk bootstrap は CDKToolkit という名前のCloudFormationスタックを作る程度の認識でしたが、実際に何をやっているのかの説明がされていました。デフォルト設定で作成された CDKToolkit スタックを実際に見てみると、確かに説明されていた通りのリソースが作られています。 ECR リポジトリ 「ContainerAssetsRepository」 S3 バケット 「StagingBucket」 CDKデプロイロール「DeploymentActionRole」 CloudFormation、S3、KMSの権限や、CFN実行ロールをPassRoleする権限が付いている aws-cdk:bootstrap-role : deploy リソースタグが付いている CFN実行ロール「CloudFormationExecutionRole」 AdministratorAccess ポリシーが付いている リソースタグは付いていない file-publishing-role「FilePublishingRole」 S3とKMSへの権限が付いている aws-cdk:bootstrap-role : file-publishing リソースタグが付いている image-publishing-role「ImagePublishingRole」 ECRへの権限が付いている aws-cdk:bootstrap-role : image-publishing リソースタグが付いている lookup-role「LookupRole」 AWS 管理の ReadOnlyAccess ポリシーが付いている aws-cdk:bootstrap-role : lookup リソースタグが付いている 注目すべきは、紹介されているポリシー ステートメント ではリソースタグで「CDKデプロイロール」「file-publishing-role」「image-publishing-role」「lookup-role」のAssumeRoleを許可していて、AdministratorAccess を持つ「CFN実行ロール」をAssumeRoleできないようになっている点です。つまりこれを使うことで、「CDKデプロイを開始するアクター」はAdministratorAccess権限をAssumeRoleできません。 (しかし厳密に考えると、「CDKデプロイロール」は CloudFormationスタックを作ることができて、「CFN実行ロール」をPassRoleできるため、CloudFormationに強権限のIAMロールを作らせて然るべきリソースタグを付けることで、「CDKデプロイを開始するアクター」から強権限のロールに最終的にAssumeRoleできるように思います。AssumeRoleを許可する対象リソースはリソースタグではなくロール名で指定した方がより安全でしょう) bootstrapで作成されるリソースは将来的に変わる可能性もあるとのことですが、「CDKデプロイを開始するアクター」がAssumeすべきロールの種類が増えていかないかは気になるところです。 CliCredentialsStackSynthesizer <ざっくり要約> DefaultStackSynthesizer の代わりに CliCredentialsStackSynthesizer を使うことができる。IAMロールをAssumeするのではなく、「CDKデプロイを開始するアクター」の権限を利用する。CloudFormationコールは直接実行され、サービスロールは渡されない。つまり「CDKデプロイを開始するアクター」は bootstrap が本来作成するロールの全ての権限を持つ必要がある。なお --role-arn パラメータを利用することで「CFN実行ロール」を別のロールとすることはできる。 <感想> CliCredentialsStackSynthesizer を使いたいケースがどのような場合があるのか気になります。「CDKデプロイを開始するアクター」(ローカルやデプロイパイプラインに保存するアクセスキー)が強い権限を持つ必要があるので、基本的には DefaultStackSynthesizer の方を使うのが良いのではないでしょうか。 CDK内のIAM アイデンティティ とポリシーを取り扱う <ざっくり要約> CDKにIAMロールとポリシーの作成を任せるのが推奨。もしそれを許可できない場合、事前に人の手で作成し、CDKアプリから参照するようにする。 <感想> CDKデプロイ自体の権限についての説明は終わり、ここからはなじみやすいイン フラリ ソースの権限についてです。 CDKにIAMロールとポリシーの管理を任せる <ざっくり要約> CDKに権限の生成を任せる場合、 grant メソッドを活用するのが良い。例えば暗号化されたS3 バケット からの読み取りをLambda関数に許可する場合、 bucket.grantRead(handler) のように書ける。 import { aws_s3 as s3 , aws_lambda as lambda , aws_kms as kms , Stack , } from 'aws-cdk-lib' ; const stack = new Stack ( app , 'LambdaStack' ); const key = new kms.Key ( stack , 'BucketKey' ); const bucket = new s3.Bucket ( stack , 'Bucket' , { encryptionKey: key , } ); const handler = new lambda. Function ( stack , 'Handler' , { runtime: lambda.Runtime.NODEJS_16_X , handler: 'index.handler' , code: lambda.Code.fromAsset ( path.join ( __dirname , 'lambda-handler' )), } ); bucket.grantRead ( handler ); handler.addToRolePolicy() でLambda関数に直接権限を与えることもできるが、 grant メソッドが使える場合はそれが推奨される。 <感想> CDKには明示的に権限を付与しなくても、(なるべく)最小権限を自動で付与される方法が多く用意されています。例のS3 バケット の grantRead では、以下の権限がLambda関数の実行ロールに付与されます。 対象 バケット への s3:GetObject* 対象 バケット への s3:GetBucket* 対象 バケット への s3:List* 対象 バケット が利用するKMSキーへの kms:Decrypt 対象 バケット が利用するKMSキーへの kms:DescribeKey もしLambda関数が GetObject しか実行しない場合、 GetBucket* や List* の権限も付与されているので厳密には最小権限ではありませんが、読み取りしか許可されていない意味では気になる状況は少ないでしょう。 grand メソッドの名前だけでは具体的に付与される権限が分からなかったりするので、メソッドの説明などで確認すると良いでしょう。例えば現状、DynamoDBテーブルのコンスト ラク トには grantReadWriteData メソッドが用意されており、実際に付与される権限がメソッドの説明で分かります。 アイテムをPutするだけで、Deleteする権限を与えたくない場合もあるかと思いますので、そのような時は addToRolePolicy などを使うことになります。 事前に手動で作成したロールを利用する <ざっくり要約> CDKでIAMロールとポリシーの作成が許可されていない場合、事前に手動で作成し、 Role.fromRoleName() で参照する。 あるいは Role.customizeRoles() を利用して、CDKアプリに必要となる全てのロールとポリシーを実際に作成せずに、レポートとして書き出してIAMロールを管理する担当者に連携することができる。 const stack = new Stack(app, 'LambdaStack'); // ロールのsynthをしないようにこれを追加する iam.Role.customizeRoles(stack); // 以下のコードは同じ const key = new kms.Key(stack, 'BucketKey'); const bucket = new s3.Bucket(stack, 'Bucket', { encryptionKey: key, }); const handler = new lambda.Function(stack, 'Handler', { runtime: lambda.Runtime.NODEJS_16_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); // 権限が必要であることをCDKに教えるため、通常通り grantRead() などは必要 bucket.grantRead(handler); 人の手によってロールが作成されたら、そのロール名を customizeRoles に追加することでCDKで利用できるようになる。 const stack = new Stack ( app , 'LambdaStack' ); iam.Role.customizeRoles ( stack , { usePrecreatedRoles: { 'LambdaStack/Handler/ServiceRole' : 'lambda-service-role' , } , } ); <感想> customizeRoles はCDK v2.51.0でリリースされた機能です。IAM権限を開発者が自由に作成できないような環境では役に立ちそうです。 Permissions boundary と SCP <ざっくり要約> セキュリティガードレールのためにPermissions boundaryを使うことができる。 権限昇格を防ぐために、同じPermissions boundaryを含まないようなユーザーやロールを作ることを禁止できる。 cdk bootstrap で --custom-permissions-boundary フラグを追加することで、「CFN実行ロール」にPermissions boundaryをアタッチできる。 CDKアプリ内で作るロールにPermissions boundaryをアタッチする場合は以下のとおりになる。 const stack = new Stack ( app , 'MyStack' ); new iam.Role ( stack , 'Role' , { assumeRolePolicy: new iam.ServicePrincipal ( 'lambda.amazonaws.com' , permissionsBoundary: iam.ManagedPolicy.fromManagedPolicyName ( `cdk- ${ stack.synthesizer.bootstrapQualifier } -permissions-boundary- ${ stack.account } - ${ stack.region } ` ); } ); cdk.json にてグローバルで設定もできる。 { " context ": { " @aws-cdk/core:permissionsBoundary ": { " name ": " cdk-permissions-boundary " } } } App や Stage レベルで設定もできる。 <感想> Permissions boundary をCDKで利用する方法について詳細に書かれていました。 --custom-permissions-boundary フラグは CDK v2.54.0 に追加された機能 です。 ざっくり要約をさらに要約 CDKのデプロイに与える権限は許可リストではなく、拒否リストが推奨である IAMロールは手動で作るのではなく、CDKの自動作成に任せるのが推奨である 開発者の権限昇格を防ぎたい場合、Permissions boundary(アクセス許可境界)とSCP(サー ビスコ ン トロール ポリシー)を利用する bootstrap 時のCloudFormationテンプレートをカスタマイズする cdk bootstrap はAdministratorAccessの権限で行う 基本的に cdk deploy にAdministratorAccessは必要なく、bootstrapで作成された4種類のIAMロールをAssumeRoleできれば良い CDKに権限の生成を任せる場合、なるべく grant メソッドを利用する Role.customizeRoles() を利用することで、CDKデプロイせずに必要となる権限一覧をレポート出力できる お読みいただいてありがとうございました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )