From 061a084fdaf817a3e59031a9fb62c4f61ba0ba1d Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 08:01:01 -0300 Subject: [PATCH 01/11] fix(cdk): grant AWS-confirmed EB ACL read --- infra/cdk/README.md | 40 ++++++++++++++++++++--------------- infra/cdk/deploy-dev-stack.ts | 13 +++++++++++- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 93273f3..37446c4 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -45,10 +45,17 @@ The role grants only: ownership-safe bucket checks), plus `s3:CreateBucket` and `s3:PutBucketOwnershipControls` on the same bucket-level ARN. Under the `shoc-backend/` object prefix only: - `s3:PutObject`, `s3:GetObject`, `s3:GetObjectAcl`, and - `s3:GetObjectVersion`, which the pinned official deployment action and - Elastic Beanstalk require to validate the `CreateApplicationVersion` source - bundle after upload. + `s3:PutObject`, `s3:GetObject`, and `s3:GetObjectVersion`, which the pinned + official deployment action requires to validate the + `CreateApplicationVersion` source bundle after upload. +- `s3:GetObjectAcl` on objects under the service-wide + `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case + `178526484500047` confirmed that `UpdateEnvironment` uses the initiating + role to inspect objects in AWS-owned Elastic Beanstalk buckets, not only the + account-owned source-bundle bucket. The wildcard is limited to one read-only + ACL action and the Elastic Beanstalk bucket namespace; it grants no object + content read, write, delete, bucket-management, IAM, or `PassRole` + capability. `s3:CreateBucket` is part of the pinned `aws-actions/aws-elastic-beanstalk-deploy@cfad3e5e...` (v1.0.6) IAM @@ -112,15 +119,11 @@ The role grants only: successful administrator deployment. AWS supports resource-level constraints for all three mutations, so replacement ASGs remain covered without granting access to another environment. -- `s3:GetObjectAcl` under the existing - `elasticbeanstalk-us-east-1-396287094661/shoc-backend/*` object prefix. - Elastic Beanstalk emitted this read during the same attempt while validating - the uploaded application bundle. It grants no bucket-wide or cross-prefix - object access. It grants **no** IAM mutation or `PassRole`, **no** RDS / Secrets Manager access, no EC2 mutation, and **no** administrator policy. The only non-EB/S3 mutations are the three deployment-process Auto Scaling calls, restricted to -this environment's ASG name pattern. There are no wildcard mutation surfaces. +this environment's ASG name pattern. There are no wildcard mutation surfaces; +the only service-wide object grant is read-only ACL metadata. ## Prerequisites @@ -160,13 +163,16 @@ All commands run from `infra/cdk/`. The workflow's `AWS_DEPLOY_ROLE_ARN` repository secret (environment `dev`) must hold the ARN output by this stack (`GithubDeployRoleArn`). -The OIDC deployment is currently fail-closed, not repaired. Elastic Beanstalk -still reports a generic `s3:GetObjectAcl` denial after the account-owned source -prefix and every exact cross-account object referenced by the sanitized live -stack were tested independently. The runtime-prefix, platform-assets, and -launch-control hypotheses were disproved and are intentionally absent from the -policy. Do not broaden `GetObjectAcl` without an exact principal/action/resource -record from AWS Support or the AWS-owned bucket's diagnostic owner. +The previous OIDC deployment remained fail-closed after Elastic Beanstalk +reported a generic `s3:GetObjectAcl` denial outside the account-owned source +prefix. AWS Support case `178526484500047` subsequently confirmed that +`UpdateEnvironment` checks objects in AWS-owned Elastic Beanstalk buckets +using the initiating role and requires the `elasticbeanstalk-*/*` resource +namespace. This policy adds only the denied ACL-read action on that namespace; +it intentionally does not copy the managed +`AdministratorAccess-AWSElasticBeanstalk` policy's broad `s3:Get*`, +`s3:Put*`, or `s3:Delete*` grants. Any later denial must be evaluated and +granted independently. The pinned deployment action can return success after Elastic Beanstalk emits a fatal deployment event. The following workflow step therefore verifies that diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index 3575452..36addaf 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -142,11 +142,22 @@ export class DeployDevStack extends cdk.Stack { deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, - actions: ['s3:GetObject', 's3:GetObjectAcl', 's3:GetObjectVersion', 's3:PutObject'], + actions: ['s3:GetObject', 's3:GetObjectVersion', 's3:PutObject'], resources: [`${bucketArn}/${APPLICATION_NAME}/*`], }), ); + deployRole.addToPolicy( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['s3:GetObjectAcl'], + // UpdateEnvironment also checks objects in AWS-owned Elastic Beanstalk + // buckets. AWS Support case 178526484500047 confirmed that the caller's + // identity policy must cover the service-wide bucket namespace. + resources: ['arn:aws:s3:::elasticbeanstalk-*/*'], + }), + ); + new cdk.CfnOutput(this, 'GithubDeployRoleArn', { value: deployRole.roleArn, description: 'ARN of the GitHub OIDC deploy role for shoc-backend dev.', From b02787087eb1aacc48329a6a65f3bd671d049027 Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 08:56:19 -0300 Subject: [PATCH 02/11] fix(cdk): allow EB runtime bundle copy --- infra/cdk/README.md | 7 +++++++ infra/cdk/deploy-dev-stack.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 37446c4..4a61dc1 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -48,6 +48,13 @@ The role grants only: `s3:PutObject`, `s3:GetObject`, and `s3:GetObjectVersion`, which the pinned official deployment action requires to validate the `CreateApplicationVersion` source bundle after upload. +- `s3:PutObject` on only + `elasticbeanstalk-us-east-1-396287094661/resources/environments/e-hehnrqjjrt/_runtime/_versions/shoc-backend/*`. + Elastic Beanstalk copies each uploaded source bundle into this + environment-specific runtime prefix during `UpdateEnvironment`. Run + `30448885838` exposed the exact source and destination after the earlier ACL + denial was resolved. The grant does not cover another environment, another + application, bucket ACLs, object ACLs, tags, retention, deletion, or reads. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index 36addaf..3047287 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -18,6 +18,9 @@ export class DeployDevStack extends cdk.Stack { const applicationArn = `arn:aws:elasticbeanstalk:${REGION}:${ACCOUNT_ID}:application/${APPLICATION_NAME}`; const environmentArn = `arn:aws:elasticbeanstalk:${REGION}:${ACCOUNT_ID}:environment/${APPLICATION_NAME}/${ENVIRONMENT_NAME}`; const bucketArn = `arn:aws:s3:::${BUCKET_NAME}`; + const runtimeVersionArn = + `${bucketArn}/resources/environments/${ENVIRONMENT_ID}` + + `/_runtime/_versions/${APPLICATION_NAME}/*`; const oidcProviderArn = `arn:aws:iam::${ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com`; const deployRole = new iam.Role(this, 'GithubDeployRole', { @@ -147,6 +150,16 @@ export class DeployDevStack extends cdk.Stack { }), ); + deployRole.addToPolicy( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['s3:PutObject'], + // UpdateEnvironment copies the uploaded source bundle into this + // environment-specific runtime version prefix before deployment. + resources: [runtimeVersionArn], + }), + ); + deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, From 42d877295108645ea051b779b61f2e2e219fbd6e Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 09:05:44 -0300 Subject: [PATCH 03/11] fix(cdk): allow EB runtime cleanup --- infra/cdk/README.md | 12 +++++++----- infra/cdk/deploy-dev-stack.ts | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 4a61dc1..1102e63 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -48,13 +48,15 @@ The role grants only: `s3:PutObject`, `s3:GetObject`, and `s3:GetObjectVersion`, which the pinned official deployment action requires to validate the `CreateApplicationVersion` source bundle after upload. -- `s3:PutObject` on only +- `s3:PutObject` and `s3:DeleteObject` on only `elasticbeanstalk-us-east-1-396287094661/resources/environments/e-hehnrqjjrt/_runtime/_versions/shoc-backend/*`. Elastic Beanstalk copies each uploaded source bundle into this - environment-specific runtime prefix during `UpdateEnvironment`. Run - `30448885838` exposed the exact source and destination after the earlier ACL - denial was resolved. The grant does not cover another environment, another - application, bucket ACLs, object ACLs, tags, retention, deletion, or reads. + environment-specific runtime prefix during `UpdateEnvironment` and removes + that temporary copy after the version is registered. Attempts 1 and 2 of run + `30448885838` exposed the exact source, destination, and cleanup denial after + the earlier ACL denial was resolved. The grant does not cover another + environment, another application, source bundles, object versions, bucket + ACLs, object ACLs, tags, retention, or reads. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index 3047287..ee5d6e4 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -153,9 +153,10 @@ export class DeployDevStack extends cdk.Stack { deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, - actions: ['s3:PutObject'], + actions: ['s3:DeleteObject', 's3:PutObject'], // UpdateEnvironment copies the uploaded source bundle into this - // environment-specific runtime version prefix before deployment. + // environment-specific runtime prefix and removes that temporary copy + // after the version is registered. resources: [runtimeVersionArn], }), ); From 424bf20f54092864737412bba780053e1b86bdcc Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 09:21:28 -0300 Subject: [PATCH 04/11] fix(cdk): allow EB runtime verification --- infra/cdk/README.md | 14 ++++++++------ infra/cdk/deploy-dev-stack.ts | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 1102e63..e84694b 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -48,15 +48,17 @@ The role grants only: `s3:PutObject`, `s3:GetObject`, and `s3:GetObjectVersion`, which the pinned official deployment action requires to validate the `CreateApplicationVersion` source bundle after upload. -- `s3:PutObject` and `s3:DeleteObject` on only +- `s3:PutObject`, `s3:GetObject`, and `s3:DeleteObject` on only `elasticbeanstalk-us-east-1-396287094661/resources/environments/e-hehnrqjjrt/_runtime/_versions/shoc-backend/*`. Elastic Beanstalk copies each uploaded source bundle into this - environment-specific runtime prefix during `UpdateEnvironment` and removes - that temporary copy after the version is registered. Attempts 1 and 2 of run - `30448885838` exposed the exact source, destination, and cleanup denial after - the earlier ACL denial was resolved. The grant does not cover another + environment-specific runtime prefix during `UpdateEnvironment`, verifies it + with `HeadObject` (authorized by `s3:GetObject`), and removes the temporary + copy after the version is registered. Attempts 1 through 4 of run + `30448885838` exposed the exact source, destination, cleanup, and verification + operations after the earlier ACL denial was resolved. CloudTrail recorded + the exact `s3:GetObject` denial on attempt 4. The grant does not cover another environment, another application, source bundles, object versions, bucket - ACLs, object ACLs, tags, retention, or reads. + ACLs, object ACLs, tags, or retention. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index ee5d6e4..88c6009 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -153,10 +153,10 @@ export class DeployDevStack extends cdk.Stack { deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, - actions: ['s3:DeleteObject', 's3:PutObject'], + actions: ['s3:DeleteObject', 's3:GetObject', 's3:PutObject'], // UpdateEnvironment copies the uploaded source bundle into this - // environment-specific runtime prefix and removes that temporary copy - // after the version is registered. + // environment-specific runtime prefix, verifies the temporary copy, + // and removes it after the version is registered. resources: [runtimeVersionArn], }), ); From 9f54399e63e044c49e0dc9ac5cf0689760531d70 Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 09:26:12 -0300 Subject: [PATCH 05/11] fix(cdk): allow EB runtime version ACL read --- infra/cdk/README.md | 10 ++++++---- infra/cdk/deploy-dev-stack.ts | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index e84694b..7588086 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -48,7 +48,8 @@ The role grants only: `s3:PutObject`, `s3:GetObject`, and `s3:GetObjectVersion`, which the pinned official deployment action requires to validate the `CreateApplicationVersion` source bundle after upload. -- `s3:PutObject`, `s3:GetObject`, and `s3:DeleteObject` on only +- `s3:PutObject`, `s3:GetObject`, `s3:GetObjectVersionAcl`, and + `s3:DeleteObject` on only `elasticbeanstalk-us-east-1-396287094661/resources/environments/e-hehnrqjjrt/_runtime/_versions/shoc-backend/*`. Elastic Beanstalk copies each uploaded source bundle into this environment-specific runtime prefix during `UpdateEnvironment`, verifies it @@ -56,9 +57,10 @@ The role grants only: copy after the version is registered. Attempts 1 through 4 of run `30448885838` exposed the exact source, destination, cleanup, and verification operations after the earlier ACL denial was resolved. CloudTrail recorded - the exact `s3:GetObject` denial on attempt 4. The grant does not cover another - environment, another application, source bundles, object versions, bucket - ACLs, object ACLs, tags, or retention. + the exact `s3:GetObject` denial on attempt 4; attempt 6 then exposed the + version-specific ACL read performed on the copied object. The grant does not + cover another environment, another application, source bundles, object + content versions, ACL mutation, tags, or retention. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index 88c6009..97350e7 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -153,7 +153,12 @@ export class DeployDevStack extends cdk.Stack { deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, - actions: ['s3:DeleteObject', 's3:GetObject', 's3:PutObject'], + actions: [ + 's3:DeleteObject', + 's3:GetObject', + 's3:GetObjectVersionAcl', + 's3:PutObject', + ], // UpdateEnvironment copies the uploaded source bundle into this // environment-specific runtime prefix, verifies the temporary copy, // and removes it after the version is registered. From 156b7bbed6cbdab228f7ac7b31895ddb34e600f0 Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 09:30:48 -0300 Subject: [PATCH 06/11] fix(cdk): allow EB runtime version ACL write --- infra/cdk/README.md | 11 ++++++----- infra/cdk/deploy-dev-stack.ts | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 7588086..5036031 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -48,8 +48,8 @@ The role grants only: `s3:PutObject`, `s3:GetObject`, and `s3:GetObjectVersion`, which the pinned official deployment action requires to validate the `CreateApplicationVersion` source bundle after upload. -- `s3:PutObject`, `s3:GetObject`, `s3:GetObjectVersionAcl`, and - `s3:DeleteObject` on only +- `s3:PutObject`, `s3:GetObject`, `s3:GetObjectVersionAcl`, + `s3:PutObjectVersionAcl`, and `s3:DeleteObject` on only `elasticbeanstalk-us-east-1-396287094661/resources/environments/e-hehnrqjjrt/_runtime/_versions/shoc-backend/*`. Elastic Beanstalk copies each uploaded source bundle into this environment-specific runtime prefix during `UpdateEnvironment`, verifies it @@ -58,9 +58,10 @@ The role grants only: `30448885838` exposed the exact source, destination, cleanup, and verification operations after the earlier ACL denial was resolved. CloudTrail recorded the exact `s3:GetObject` denial on attempt 4; attempt 6 then exposed the - version-specific ACL read performed on the copied object. The grant does not - cover another environment, another application, source bundles, object - content versions, ACL mutation, tags, or retention. + version-specific ACL read performed on the copied object; attempt 7 exposed + the matching version-ACL write. The grant does not cover another + environment, another application, source bundles, object content versions, + non-version ACL mutation, tags, or retention. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index 97350e7..df96f05 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -158,10 +158,11 @@ export class DeployDevStack extends cdk.Stack { 's3:GetObject', 's3:GetObjectVersionAcl', 's3:PutObject', + 's3:PutObjectVersionAcl', ], // UpdateEnvironment copies the uploaded source bundle into this // environment-specific runtime prefix, verifies the temporary copy, - // and removes it after the version is registered. + // preserves its version ACL, and removes it after registration. resources: [runtimeVersionArn], }), ); From 3f607304643084bd82bfdbacbdc36eece5d8a978 Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 09:45:45 -0300 Subject: [PATCH 07/11] fix(cdk): allow EB embedded extension write --- infra/cdk/README.md | 7 +++++++ infra/cdk/deploy-dev-stack.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 5036031..492db5b 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -62,6 +62,13 @@ The role grants only: the matching version-ACL write. The grant does not cover another environment, another application, source bundles, object content versions, non-version ACL mutation, tags, or retention. +- `s3:PutObject` on only + `elasticbeanstalk-us-east-1-396287094661/resources/_runtime/_embedded_extensions/shoc-backend/*`. + After the runtime bundle copy and version-ACL operations succeeded, attempt 8 + of run `30448885838` showed Elastic Beanstalk materializing the application's + embedded-extension manifest at this application-specific prefix. CloudTrail + recorded the exact denied action and object ARN. The grant does not include + reads, deletes, ACL mutation, another application, or another bucket. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index df96f05..433d3b2 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -21,6 +21,9 @@ export class DeployDevStack extends cdk.Stack { const runtimeVersionArn = `${bucketArn}/resources/environments/${ENVIRONMENT_ID}` + `/_runtime/_versions/${APPLICATION_NAME}/*`; + const embeddedExtensionArn = + `${bucketArn}/resources/_runtime/_embedded_extensions/` + + `${APPLICATION_NAME}/*`; const oidcProviderArn = `arn:aws:iam::${ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com`; const deployRole = new iam.Role(this, 'GithubDeployRole', { @@ -167,6 +170,16 @@ export class DeployDevStack extends cdk.Stack { }), ); + deployRole.addToPolicy( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['s3:PutObject'], + // UpdateEnvironment materializes the application's embedded-extension + // manifest under this application-specific runtime prefix. + resources: [embeddedExtensionArn], + }), + ); + deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, From 5b719a660e67a87e4052794903789e291416b640 Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 09:55:58 -0300 Subject: [PATCH 08/11] fix(cdk): allow EB environment extension write --- infra/cdk/README.md | 14 +++++++++----- infra/cdk/deploy-dev-stack.ts | 7 +++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 492db5b..0b9da58 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -62,13 +62,17 @@ The role grants only: the matching version-ACL write. The grant does not cover another environment, another application, source bundles, object content versions, non-version ACL mutation, tags, or retention. -- `s3:PutObject` on only - `elasticbeanstalk-us-east-1-396287094661/resources/_runtime/_embedded_extensions/shoc-backend/*`. +- `s3:PutObject` on only the two embedded-extension prefixes + `elasticbeanstalk-us-east-1-396287094661/resources/_runtime/_embedded_extensions/shoc-backend/*` + and + `elasticbeanstalk-us-east-1-396287094661/resources/environments/e-hehnrqjjrt/_runtime/_embedded_extensions/shoc-backend/*`. After the runtime bundle copy and version-ACL operations succeeded, attempt 8 of run `30448885838` showed Elastic Beanstalk materializing the application's - embedded-extension manifest at this application-specific prefix. CloudTrail - recorded the exact denied action and object ARN. The grant does not include - reads, deletes, ACL mutation, another application, or another bucket. + embedded-extension manifest at the application-specific shared prefix. + Attempt 9 then showed the matching write into the exact dev-environment + prefix. CloudTrail recorded both denied actions and object ARNs. The grant + does not include reads, deletes, ACL mutation, another application, + another environment, or another bucket. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index 433d3b2..78c1fdf 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -24,6 +24,9 @@ export class DeployDevStack extends cdk.Stack { const embeddedExtensionArn = `${bucketArn}/resources/_runtime/_embedded_extensions/` + `${APPLICATION_NAME}/*`; + const environmentEmbeddedExtensionArn = + `${bucketArn}/resources/environments/${ENVIRONMENT_ID}` + + `/_runtime/_embedded_extensions/${APPLICATION_NAME}/*`; const oidcProviderArn = `arn:aws:iam::${ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com`; const deployRole = new iam.Role(this, 'GithubDeployRole', { @@ -175,8 +178,8 @@ export class DeployDevStack extends cdk.Stack { effect: iam.Effect.ALLOW, actions: ['s3:PutObject'], // UpdateEnvironment materializes the application's embedded-extension - // manifest under this application-specific runtime prefix. - resources: [embeddedExtensionArn], + // manifest under the shared and environment-specific runtime prefixes. + resources: [embeddedExtensionArn, environmentEmbeddedExtensionArn], }), ); From c2f2c411d8927455a6d990dbaf5b50e4ff3daab3 Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 10:33:31 -0300 Subject: [PATCH 09/11] fix(cdk): allow EB extension verification --- infra/cdk/README.md | 4 ++++ infra/cdk/deploy-dev-stack.ts | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 0b9da58..c790ad8 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -73,6 +73,10 @@ The role grants only: prefix. CloudTrail recorded both denied actions and object ARNs. The grant does not include reads, deletes, ACL mutation, another application, another environment, or another bucket. +- `s3:GetObject` on only the environment-specific embedded-extension prefix + above. Attempt 10 showed that Elastic Beanstalk verifies the materialized + environment copy with `HeadObject`, which S3 authorizes through + `s3:GetObject`. The shared embedded-extension prefix remains write-only. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index 78c1fdf..e0f3853 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -183,6 +183,15 @@ export class DeployDevStack extends cdk.Stack { }), ); + deployRole.addToPolicy( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['s3:GetObject'], + // Elastic Beanstalk verifies the environment copy with HeadObject. + resources: [environmentEmbeddedExtensionArn], + }), + ); + deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, From b8db4a7e61e838c239752a8d506feadeea2a9ece Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 11:02:30 -0300 Subject: [PATCH 10/11] fix(cdk): allow EB bucket policy read --- infra/cdk/README.md | 3 ++- infra/cdk/deploy-dev-stack.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index c790ad8..9485c85 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -42,7 +42,8 @@ The role grants only: - `elasticbeanstalk:UpdateEnvironment` on environment `shoc-backend-dev` only. - `s3:ListBucket` and `s3:GetBucketLocation` on `elasticbeanstalk-us-east-1-396287094661` (the official action's - ownership-safe bucket checks), plus `s3:CreateBucket` and + ownership-safe bucket checks), `s3:GetBucketPolicy` for the policy inspection + observed in attempt 11 of run `30448885838`, plus `s3:CreateBucket` and `s3:PutBucketOwnershipControls` on the same bucket-level ARN. Under the `shoc-backend/` object prefix only: `s3:PutObject`, `s3:GetObject`, and `s3:GetObjectVersion`, which the pinned diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index e0f3853..d501786 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -143,6 +143,7 @@ export class DeployDevStack extends cdk.Stack { 's3:CreateBucket', 's3:PutBucketOwnershipControls', 's3:GetBucketLocation', + 's3:GetBucketPolicy', ], resources: [bucketArn], }), From 76cdc70c18045dbb151ea483ad35e2b5f684518c Mon Sep 17 00:00:00 2001 From: brandizzi Date: Wed, 29 Jul 2026 11:07:53 -0300 Subject: [PATCH 11/11] fix(cdk): allow EB runtime manifest updates --- infra/cdk/README.md | 6 ++++++ infra/cdk/deploy-dev-stack.ts | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 9485c85..e32d030 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -78,6 +78,12 @@ The role grants only: above. Attempt 10 showed that Elastic Beanstalk verifies the materialized environment copy with `HeadObject`, which S3 authorizes through `s3:GetObject`. The shared embedded-extension prefix remains write-only. +- `s3:GetObject` and `s3:PutObject` on only + `elasticbeanstalk-us-east-1-396287094661/resources/environments/e-hehnrqjjrt/_runtime/versions/*`. + Attempt 12 showed Elastic Beanstalk reading the previous environment version + manifest and writing its replacement under this exact dev-environment + runtime prefix. The grant excludes deletes, ACL mutation, other environments, + and application bundle content. - `s3:GetObjectAcl` on objects under the service-wide `arn:aws:s3:::elasticbeanstalk-*/*` namespace. AWS Support case `178526484500047` confirmed that `UpdateEnvironment` uses the initiating diff --git a/infra/cdk/deploy-dev-stack.ts b/infra/cdk/deploy-dev-stack.ts index d501786..0cdef0c 100644 --- a/infra/cdk/deploy-dev-stack.ts +++ b/infra/cdk/deploy-dev-stack.ts @@ -27,6 +27,9 @@ export class DeployDevStack extends cdk.Stack { const environmentEmbeddedExtensionArn = `${bucketArn}/resources/environments/${ENVIRONMENT_ID}` + `/_runtime/_embedded_extensions/${APPLICATION_NAME}/*`; + const runtimeManifestArn = + `${bucketArn}/resources/environments/${ENVIRONMENT_ID}` + + '/_runtime/versions/*'; const oidcProviderArn = `arn:aws:iam::${ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com`; const deployRole = new iam.Role(this, 'GithubDeployRole', { @@ -193,6 +196,16 @@ export class DeployDevStack extends cdk.Stack { }), ); + deployRole.addToPolicy( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['s3:GetObject', 's3:PutObject'], + // UpdateEnvironment reads the prior environment version manifest and + // writes its replacement under this environment-only runtime prefix. + resources: [runtimeManifestArn], + }), + ); + deployRole.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW,