-
Notifications
You must be signed in to change notification settings - Fork 4.5k
[dynamodb] .grantWriteData() does not grant correct permissions to use PutItem on a KMS CMK encrypted DynamoDB Table. #10010
Description
Hi,
I was trying to give a Lambda PutItem access to a KMS CMK encrypted DynamoDB table, and it was throwing the following error:
[ERROR] ClientError: An error occurred (AccessDeniedException) when calling the PutItem operation: KMS key access denied error: com.amazonaws.services.kms.model.AWSKMSException: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access. (Service: AWSKMS; Status Code: 400; Error Code: AccessDeniedException; Request ID: xxxx-xxx-xxx-xxxxxx)
I had a look into the issue, and it seems, the KMS CMK resource policy requires the "kms:Decrypt" permission too, in order to call PutItem successfully. (Only added to the KMS Resource Policy, not added to the IAM Policy attached to the Lambda Execution Role. My fix below adds to both, for simplicity.)
I've attached an example Stack that reproduces the problem. I'd open a PR myself, but the only solution that jumps out to me would be to either just grant "kms:Decrypt" to the role, or split .grantWriteData() into multiple functions.
Thanks
Reproduction Steps
Relevant snippet from package.json
"@aws-cdk/aws-dynamodb": "^1.61.0",
"@aws-cdk/aws-iam": "^1.61.0",
"@aws-cdk/aws-kms": "^1.61.0",
"@aws-cdk/aws-lambda": "^1.61.0",
"@aws-cdk/core": "1.61.0",Example Stack:
import { AttributeType, BillingMode, Table, TableEncryption } from '@aws-cdk/aws-dynamodb'
import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'
import * as cdk from '@aws-cdk/core'
import * as path from 'path'
export class VanillaCdkBugStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const table = new Table(this, 'Table', {
partitionKey: {
name: 'pk',
type: AttributeType.STRING,
},
billingMode: BillingMode.PAY_PER_REQUEST,
encryption: TableEncryption.CUSTOMER_MANAGED,
})
const lambda = new Function(this, 'Function', {
runtime: Runtime.PYTHON_3_8,
code: Code.fromAsset(path.join(__dirname, 'lambda')),
handler: 'index.main',
environment: {
TABLE_NAME: table.tableName,
},
})
table.grantWriteData(lambda)
// Without this, is throws an error; with this, it succeeds.
// if (table.encryptionKey) {
// table.encryptionKey.grantDecrypt(lambda)
// }
}
}Lambda code:
import datetime
import os
import boto3
def main(event, context):
region = os.environ.get("AWS_REGION", "us-west-2")
table = boto3.resource("dynamodb", region_name=region).Table(os.environ["TABLE_NAME"])
table.put_item(Item={
"pk": datetime.datetime.now().isoformat(),
})
return {
"message": "Success!"
}What did you expect to happen?
The Lambda should be able to write to the DynamoDB Table.
What actually happened?
Running the Lambda receives the following error:
[ERROR] ClientError: An error occurred (AccessDeniedException) when calling the PutItem operation: KMS key access denied error: com.amazonaws.services.kms.model.AWSKMSException: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access. (Service: AWSKMS; Status Code: 400; Error Code: AccessDeniedException; Request ID: xxxx-xxx-xxx-xxxxxx)
Environment
- CLI Version : 1.61.0 (build 72e6727)
- Framework Version: 1.61.0
- Node.js Version: v12.17.0
- OS : OSX
- Language (Version): "typescript": "~3.9.7"
This is 🐛 Bug Report