From f1f317a20cbcdd1386d90676eefecdf84bf1685c Mon Sep 17 00:00:00 2001 From: James Russo Date: Sat, 11 Jul 2026 21:36:17 -0700 Subject: [PATCH] fix(cli): grant s3 encryption actions in lambda policies user output (#2289) `hyperframes lambda deploy` runs `sam deploy --resolve-s3`, and SAM's managed artifacts bucket (aws-sam-cli-managed-default) is created with default SSE encryption. Setting that requires s3:PutEncryptionConfiguration, which the generated deploy policy did not grant, so a first deploy by a user provisioned exactly per `lambda policies user` 403s on the bucket and the managed stack rolls back. Add s3:GetEncryptionConfiguration and s3:PutEncryptionConfiguration to the s3Bucket action set (Get pairs with Put for CloudFormation update/drift reads, matching the existing Get/Put pairs in the list). Also add a hint to the sam-deploy failure path pointing at the ROLLBACK_COMPLETE recovery step, since first-time users hit the stuck-rollback error on their retry. Fixes #2137 Co-authored-by: Claude Opus 4.8 (1M context) --- packages/cli/src/commands/lambda/policies.test.ts | 4 ++++ packages/cli/src/commands/lambda/policies.ts | 6 ++++++ packages/cli/src/commands/lambda/sam.ts | 9 ++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/lambda/policies.test.ts b/packages/cli/src/commands/lambda/policies.test.ts index af99dd6a9..d67b1a2b0 100644 --- a/packages/cli/src/commands/lambda/policies.test.ts +++ b/packages/cli/src/commands/lambda/policies.test.ts @@ -32,6 +32,10 @@ describe("policies — required actions", () => { "lambda:CreateFunction", "states:StartExecution", "s3:PutObject", + // SAM's --resolve-s3 managed bucket is encrypted; the deploy user must + // be able to set/read that encryption or the first deploy rolls back. + "s3:PutEncryptionConfiguration", + "s3:GetEncryptionConfiguration", "iam:CreateRole", "logs:CreateLogGroup", "cloudwatch:PutMetricAlarm", diff --git a/packages/cli/src/commands/lambda/policies.ts b/packages/cli/src/commands/lambda/policies.ts index d2bbddb79..9514ff24d 100644 --- a/packages/cli/src/commands/lambda/policies.ts +++ b/packages/cli/src/commands/lambda/policies.ts @@ -123,12 +123,18 @@ const REQUIRED_ACTIONS = { "s3:GetBucketPolicy", "s3:GetBucketTagging", "s3:GetBucketVersioning", + // SAM's `--resolve-s3` managed bucket (aws-sam-cli-managed-default) sets + // default SSE encryption; CloudFormation reads it on update/drift. Without + // Get/PutEncryptionConfiguration the first `lambda deploy` 403s creating + // that bucket and the managed stack rolls back. + "s3:GetEncryptionConfiguration", "s3:GetLifecycleConfiguration", "s3:ListAllMyBuckets", "s3:ListBucket", "s3:PutBucketPolicy", "s3:PutBucketTagging", "s3:PutBucketVersioning", + "s3:PutEncryptionConfiguration", "s3:PutLifecycleConfiguration", "s3:PutPublicAccessBlock", ], diff --git a/packages/cli/src/commands/lambda/sam.ts b/packages/cli/src/commands/lambda/sam.ts index c31f18de4..e2b2af544 100644 --- a/packages/cli/src/commands/lambda/sam.ts +++ b/packages/cli/src/commands/lambda/sam.ts @@ -106,7 +106,14 @@ export function samDeploy(opts: DeployOptions): void { const samDir = join(opts.repoRoot, "examples", "aws-lambda"); const result = spawnSync("sam", args, { cwd: samDir, stdio: opts.stdio ?? "inherit" }); if (result.status !== 0) { - throw new Error(`[lambda] sam deploy exited with code ${result.status ?? "unknown"}`); + throw new Error( + `[lambda] sam deploy exited with code ${result.status ?? "unknown"}\n` + + `If a prior attempt left a stack in ROLLBACK_COMPLETE, CloudFormation can't reuse it. ` + + `Delete it before retrying:\n` + + ` aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default --region ${opts.region}\n` + + ` aws cloudformation delete-stack --stack-name ${opts.stackName} --region ${opts.region}\n` + + `(the first is SAM's managed artifacts stack from --resolve-s3; the second is the render stack).`, + ); } }