From db8faa134d3061958e65cb0251d7dba817f083ca Mon Sep 17 00:00:00 2001 From: bgagent Date: Mon, 22 Jun 2026 21:08:56 -0400 Subject: [PATCH] fix(cdk): grant lambda Put/DeleteProvisionedConcurrencyConfig (#409) The Slack-events function pins provisioned concurrency on its `live` alias (AWS::Lambda::Alias with ProvisionedConcurrencyConfig), so CloudFormation issues lambda:PutProvisionedConcurrencyConfig at create. The Lambda statement in the bootstrap policy granted only lambda:GetProvisionedConcurrencyConfig, so a fresh `mise //cdk:bootstrap` + deploy of current main rolls back with: cdk-hnb659fds-cfn-exec-role is not authorized to perform lambda:PutProvisionedConcurrencyConfig Add Put/DeleteProvisionedConcurrencyConfig next to the existing Get verb (resource ARN function:backgroundagent-dev-* is already covered), regenerate bootstrap artifacts, update the DEPLOYMENT_ROLES.md golden (+ mirror), and add a regression guard. Fourth instance of bootstrap allow-list drift (after #403, #405, #408). Fixes #409 --- cdk/bootstrap/bootstrap-template.yaml | 2 ++ cdk/bootstrap/policies/application.json | 2 ++ cdk/src/bootstrap/policies/application.ts | 2 ++ cdk/test/bootstrap/policies.test.ts | 14 ++++++++++++++ docs/design/DEPLOYMENT_ROLES.md | 2 ++ .../content/docs/architecture/Deployment-roles.md | 2 ++ 6 files changed, 24 insertions(+) diff --git a/cdk/bootstrap/bootstrap-template.yaml b/cdk/bootstrap/bootstrap-template.yaml index edaa4bbe..62963cc3 100644 --- a/cdk/bootstrap/bootstrap-template.yaml +++ b/cdk/bootstrap/bootstrap-template.yaml @@ -1002,6 +1002,8 @@ Resources: - lambda:GetFunctionCodeSigningConfig - lambda:GetFunctionRecursionConfig - lambda:GetProvisionedConcurrencyConfig + - lambda:PutProvisionedConcurrencyConfig + - lambda:DeleteProvisionedConcurrencyConfig - lambda:GetRuntimeManagementConfig - lambda:ListVersionsByFunction - lambda:InvokeFunction diff --git a/cdk/bootstrap/policies/application.json b/cdk/bootstrap/policies/application.json index 1a84ebe4..45315b7c 100644 --- a/cdk/bootstrap/policies/application.json +++ b/cdk/bootstrap/policies/application.json @@ -50,6 +50,8 @@ "lambda:GetFunctionCodeSigningConfig", "lambda:GetFunctionRecursionConfig", "lambda:GetProvisionedConcurrencyConfig", + "lambda:PutProvisionedConcurrencyConfig", + "lambda:DeleteProvisionedConcurrencyConfig", "lambda:GetRuntimeManagementConfig", "lambda:ListVersionsByFunction", "lambda:InvokeFunction", diff --git a/cdk/src/bootstrap/policies/application.ts b/cdk/src/bootstrap/policies/application.ts index 2892c1de..c5036f1c 100644 --- a/cdk/src/bootstrap/policies/application.ts +++ b/cdk/src/bootstrap/policies/application.ts @@ -84,6 +84,8 @@ export function applicationPolicy(): iam.PolicyDocument { 'lambda:GetFunctionCodeSigningConfig', 'lambda:GetFunctionRecursionConfig', 'lambda:GetProvisionedConcurrencyConfig', + 'lambda:PutProvisionedConcurrencyConfig', + 'lambda:DeleteProvisionedConcurrencyConfig', 'lambda:GetRuntimeManagementConfig', 'lambda:ListVersionsByFunction', 'lambda:InvokeFunction', diff --git a/cdk/test/bootstrap/policies.test.ts b/cdk/test/bootstrap/policies.test.ts index 332beab5..e0eb7dbf 100644 --- a/cdk/test/bootstrap/policies.test.ts +++ b/cdk/test/bootstrap/policies.test.ts @@ -149,6 +149,20 @@ describe('IaCRole-ABCA-Application', () => { ); }); + it('grants Put/Delete provisioned-concurrency, not just Get (#409)', () => { + // The Slack-events function pins provisioned concurrency on its `live` + // alias, so the exec role needs Put (create/update) and Delete (removal), + // not only the Get verb. Get-without-Put rolled the deploy back with + // AccessDenied on lambda:PutProvisionedConcurrencyConfig. + const resolvedDoc = stack.resolve(doc); + const statements = resolvedDoc.Statement as Array<{ Action: string | string[] }>; + const allActions = statements.flatMap((s) => + Array.isArray(s.Action) ? s.Action : [s.Action], + ); + expect(allActions).toContain('lambda:PutProvisionedConcurrencyConfig'); + expect(allActions).toContain('lambda:DeleteProvisionedConcurrencyConfig'); + }); + it('SecretsManager statement allow-lists a secret pattern for every integration that creates a secret', () => { // Regression guard for #402: each integration construct that creates a // Secrets Manager secret (GitHub token, Slack, Linear, Jira, GitHub diff --git a/docs/design/DEPLOYMENT_ROLES.md b/docs/design/DEPLOYMENT_ROLES.md index 214e1a02..17e61f23 100644 --- a/docs/design/DEPLOYMENT_ROLES.md +++ b/docs/design/DEPLOYMENT_ROLES.md @@ -319,6 +319,8 @@ DynamoDB tables, Lambda functions, API Gateway, Cognito, WAFv2, EventBridge, SQS "lambda:GetFunctionCodeSigningConfig", "lambda:GetFunctionRecursionConfig", "lambda:GetProvisionedConcurrencyConfig", + "lambda:PutProvisionedConcurrencyConfig", + "lambda:DeleteProvisionedConcurrencyConfig", "lambda:GetRuntimeManagementConfig", "lambda:ListVersionsByFunction", "lambda:InvokeFunction", diff --git a/docs/src/content/docs/architecture/Deployment-roles.md b/docs/src/content/docs/architecture/Deployment-roles.md index e03ae00b..91e32ce1 100644 --- a/docs/src/content/docs/architecture/Deployment-roles.md +++ b/docs/src/content/docs/architecture/Deployment-roles.md @@ -323,6 +323,8 @@ DynamoDB tables, Lambda functions, API Gateway, Cognito, WAFv2, EventBridge, SQS "lambda:GetFunctionCodeSigningConfig", "lambda:GetFunctionRecursionConfig", "lambda:GetProvisionedConcurrencyConfig", + "lambda:PutProvisionedConcurrencyConfig", + "lambda:DeleteProvisionedConcurrencyConfig", "lambda:GetRuntimeManagementConfig", "lambda:ListVersionsByFunction", "lambda:InvokeFunction",