Merge pull request #43 from Sea-Haven-Industries/fix/eb-runtime-copy-permission
Some checks are pending
Validate and deploy dev / Validate deployable source bundle (push) Waiting to run
Validate and deploy dev / Deploy shoc-backend to Elastic Beanstalk dev (push) Blocked by required conditions

fix(cdk): allow Beanstalk runtime bundle copy
This commit is contained in:
Alexandre Brandizzi 2026-07-29 21:59:04 -03:00 • committed by GitHub
commit 1a3d9ac3e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 96 additions and 1 deletions

View file

@ -42,12 +42,48 @@ 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
official deployment action requires to validate the
`CreateApplicationVersion` source bundle after upload.
- `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
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; attempt 6 then exposed the
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: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 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: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: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

View file

@ -18,6 +18,18 @@ 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 embeddedExtensionArn =
`${bucketArn}/resources/_runtime/_embedded_extensions/` +
`${APPLICATION_NAME}/*`;
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', {
@ -134,6 +146,7 @@ export class DeployDevStack extends cdk.Stack {
's3:CreateBucket',
's3:PutBucketOwnershipControls',
's3:GetBucketLocation',
's3:GetBucketPolicy',
],
resources: [bucketArn],
}),
@ -147,6 +160,52 @@ export class DeployDevStack extends cdk.Stack {
}),
);
deployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:DeleteObject',
's3:GetObject',
's3:GetObjectVersionAcl',
's3:PutObject',
's3:PutObjectVersionAcl',
],
// UpdateEnvironment copies the uploaded source bundle into this
// environment-specific runtime prefix, verifies the temporary copy,
// preserves its version ACL, and removes it after registration.
resources: [runtimeVersionArn],
}),
);
deployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:PutObject'],
// UpdateEnvironment materializes the application's embedded-extension
// manifest under the shared and environment-specific runtime prefixes.
resources: [embeddedExtensionArn, environmentEmbeddedExtensionArn],
}),
);
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,
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,