aws-lambda: Adding an encrypted SQS queue as a Lambda DLQ does not grant Lambda role necessary KMS permissions
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the bug
When creating a Lambda, you have the option to [specify a DLQ](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.Function.html#deadletterqueue). Doing so correctly automatically grants the Lambda's role `sqs:SendMessage` permissions on that SQS queue resource. However, if that SQS queue is encrypted with a customer-managed KMS key in the same stack, the Lambda's role is not automatically granted the required permissions on the KMS key resource. This leads to the Lambda being unable to write to the DLQ and messages being lost.
### Regression Issue
- [ ] Select this option if this issue appears to be a regression.
### Last Known Working CDK Version
_No response_
### Expected Behavior
The Lambda role to be automatically granted the required permissions on the KMS key
### Current Behavior
The Lambda role did not have any permissions on the KMS key
### Reproduction Steps
```TypeScript
import { Capture, Template } from "aws-cdk-lib/assertions";
import { Key } from "aws-cdk-lib/aws-kms";
import { Function, InlineCode, Runtime } from "aws-cdk-lib/aws-lambda";
import { Queue } from "aws-cdk-lib/aws-sqs";
import { App, Stack } from "aws-cdk-lib/core";
import { Construct } from "constructs";
class TestStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
const lambdaDlqKey = new Key(this, "LambdaDlqKey", {
enableKeyRotation: true,
});
const lambdaDlq = new Queue(this, "LambdaDlq", {
queueName: "LambdaDlq",
encryptionMasterKey: lambdaDlqKey,
});
const lambda = new Function(this, "Lambda", {
code: new InlineCode("throw new Error()"),
handler: "someHandler",
runtime: Runtime.NODEJS_18_X,
deadLetterQueue: lambdaDlq,
});
}
}
describe("Function with customer managed KMS encrypted DLQ", () => {
it("does not grant KMS permissions", () => {
const mockApp = new App();
const testInfraStack = new TestStack(mockApp, "testStack");
const template = Template.fromStack(testInfraStack);
const statementCapture = new Capture();
template.hasResourceProperties("AWS::IAM::Policy", {
PolicyDocument: {
Statement: statementCapture,
},
});
// Succeeds (correctly grants required sqs:SendMessage permission)
expect(statementCapture.asArray().filter((statement) => hasActionMatching(statement, /sqs:SendMessage/))).not.toHaveLength(0);
// Fails: (does not grant required kms permission)
expect(statementCapture.asArray().filter((statement) => hasActionMatching(statement, /kms:.*/))).not.toHaveLength(0);
});
});
function hasActionMatching(statement: { Action: string | string[] }, action: RegExp) {
if (typeof statement.Action === "string") {
return action.test(statement.Action);
}
return statement.Action.filter((statementAction) => action.test(statementAction)).length > 0;
}
```
This can also be confirmed by deploying the TestStack, sending an async message to the Lambda, and validating that it is unable to write the failed message to its DLQ (and a dead letter queue failure metric is published).
### Possible Solution
This can of course be worked around by the user manually granting KMS permissions (e.g. `lambdaDlqKey.grantEncrypt(lambda.role!)`, or even `lambdaDlq.grantSendMessages(lambda)`), however the fact that this is not done automatically by CDK seems like a bug to me, or at very least a poor customer experience that can lead to customer data loss due to accidental misconfiguration.
A fix would be to check if the DLQ is encrypted when the Lambda is initialised in CDK, and grant the necessary KMS permissions if so.
### Additional Information/Context
_No response_
### CDK CLI Version
2.175.1
### Framework Version
_No response_
### Node.js Version
18.20.5
### OS
Ubuntu 24.04.1
### Language
TypeScript
### Language Version
TypeScript (5.7.3)
### Other information
_No response_
Contributor guide
Research direction
Start with the Lambda Function deadLetterQueue handling and reproduce the behavior using the TestStack and assertions shown in the issue. Synthesize the IAM policy for a Lambda using a customer-managed KMS-encrypted SQS queue; done means the Lambda role includes the required KMS permissions in addition to sqs:SendMessage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100