はじめに
CDKではgrantメソッドを利用することで、リソースに必要なアクセス許可を簡単に定義することができます。
個人的にCloudFormationでリソースを作成する際は、リソースの権限周りの設定にかなり時間を要していました。
grantメソッドを利用することで、必要なポリシーが自動的に生成されて対象のIAMロールへ付与されるため、権限設定を簡単に定義することができます。
実際に使用してみる
今回はLambdaとS3を作成して、検証します。
S3の読み書きを行うLambdaをデプロイし、grantメソッドの有無でどのような挙動となるか確認します。
grantメソッドなし
まずは、grantメソッドなしでLambdaとS3を作成します。
CDKのコードは以下のように作成しました。
import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as lambda from "aws-cdk-lib/aws-lambda";
export class CdkServerlessSiteStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bucket = new s3.Bucket(this, "Bucket", {
autoDeleteObjects: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const handler = new lambda.Function(this, "Handler", {
runtime: lambda.Runtime.PYTHON_3_14,
handler: "index.lambda_handler",
code: lambda.Code.fromAsset("lambda/"),
environment: {
BUCKET_NAME: bucket.bucketName,
},
});
}
}
Lambdaのコードは下記です。
eventで渡されるパラメータによって、リスト or アップロードを行うようにしています。
import boto3
import json
import os
s3_client = boto3.client('s3')
def lambda_handler(event, context):
bucket_name = os.environ.get("BUCKET_NAME")
action = event.get("action")
if action == "List":
response = s3_client.list_objects_v2(Bucket=bucket_name)
objects = []
for obj in response.get('Contents', []):
objects.append(obj['Key'])
return {
'statusCode': 200,
'body': json.dumps(objects)
}
if action == "Upload":
file_name = event.get("file_name")
file_content = event.get("file_content")
s3_client.put_object(Bucket=bucket_name, Key=file_name, Body=file_content)
return {
'statusCode': 200,
'body': json.dumps(f"File {file_name} uploaded successfully.")
}
コードの準備ができたら、CDKをデプロイし、Lambda関数を実行してみます。
$ npx cdk deploy
作成されたS3バケットに適当なファイルを格納し、Lambda関数でListのテストをします。

CDKで権限未設定のため、期待通りエラーとなりました。
AccessDenied: An error occurred (AccessDenied) when calling the ListObjectsV2 operation
コンソール上でも、LambdaにはCloudWatch Logsへの権限しか設定されていないことが確認できます。

grantメソッドあり(read)
CDKのコードでgrantメソッドを追加して検証します。
下記のように1行追加しただけで権限の設定が完了します。
CloudFormationで同じような設定を入れようとするとまあまあの記述量が必要なので、とても楽です。
// ...省略
const handler = new lambda.Function(this, "Handler", {
runtime: lambda.Runtime.PYTHON_3_14,
handler: "index.lambda_handler",
code: lambda.Code.fromAsset("lambda/"),
environment: {
BUCKET_NAME: bucket.bucketName,
},
});
bucket.grants.read(handler); //追加
}
}
デプロイする前にcdk diffコマンドで差分を確認してみます。
readを設定したため、参照関連の権限が設定されることがわかります。
リソースも対象のS3バケットに限定したりとCDKの方でよしなに権限を設定してくれます。
$ npx cdk diff
...
Stack CdkServerlessSiteStack
IAM Statement Changes
┌───┬──────────────────────┬────────┬──────────────────────┬────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼──────────────────────┼────────┼──────────────────────┼────────────────────────┼───────────┤
│ + │ ${Bucket.Arn} │ Allow │ s3:GetBucket* │ AWS:${Handler/ServiceR │ │
│ │ ${Bucket.Arn}/* │ │ s3:GetObject* │ ole} │ │
│ │ │ │ s3:List* │ │ │
└───┴──────────────────────┴────────┴──────────────────────┴────────────────────────┴───────────┘
それでは、再度デプロイを行い、Lambdaのテストを実施します。
$ npx cdk deploy
Lambda関数で先ほどと同様にテストを行うと成功しました。
事前に格納した work.txt が一覧として表示されているので、期待通りの動作です。

続いて、アップロードのテストを行います。
イベントとして、action: Upload, file_name: <ファイル名>, file_content: <ファイルの中身>を指定した上で、テストを実行します。

今回はCDKでreadのみ設定したため、uploadは期待通りエラーとなりました。
AccessDenied: An error occurred (AccessDenied) when calling the PutObject operation
grantメソッドあり(read/write)
書き込み権限を追加で付与する場合は、grantメソッドを変更するのみで対応できます。
// ...省略
bucket.grants.readWrite(handler); // readWriteに変更
}
}
この状態で、cdk diffを実行すると差分は以下の通りとなります。
参照系に加えて、オブジェクトの書き込みや削除も可能となります。
$ npx cdk diff
...
Stack CdkServerlessSiteStack
IAM Statement Changes
┌───┬──────────────────────┬────────┬──────────────────────┬────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼──────────────────────┼────────┼──────────────────────┼────────────────────────┼───────────┤
│ - │ ${Bucket.Arn} │ Allow │ s3:GetBucket* │ AWS:${Handler/ServiceR │ │
│ │ ${Bucket.Arn}/* │ │ s3:GetObject* │ ole} │ │
│ │ │ │ s3:List* │ │ │
├───┼──────────────────────┼────────┼──────────────────────┼────────────────────────┼───────────┤
│ + │ ${Bucket.Arn} │ Allow │ s3:Abort* │ AWS:${Handler/ServiceR │ │
│ │ ${Bucket.Arn}/* │ │ s3:DeleteObject* │ ole} │ │
│ │ │ │ s3:GetBucket* │ │ │
│ │ │ │ s3:GetObject* │ │ │
│ │ │ │ s3:List* │ │ │
│ │ │ │ s3:PutObject │ │ │
│ │ │ │ s3:PutObjectLegalHol │ │ │
│ │ │ │ d │ │ │
│ │ │ │ s3:PutObjectRetentio │ │ │
│ │ │ │ n │ │ │
│ │ │ │ s3:PutObjectTagging │ │ │
│ │ │ │ s3:PutObjectVersionT │ │ │
│ │ │ │ agging │ │ │
└───┴──────────────────────┴────────┴──────────────────────┴────────────────────────┴───────────┘
...
再度cdk deployを行い、Lambda関数のテストを実施します。
$ npx cdk deploy
Lambda関数で先ほどと同様にアップロードのテストを行うと成功しました。

S3バケットを確認すると、指定したファイル名でオブジェクトが作成されていることが確認できます。

お片付け
今回作成したリソースはcdk destroyで削除しておきます。
即座に環境の作成/削除ができる点も便利です。(この点は他のIaCサービスも同様ですが…)
$ npx cdk destroy
参考