fix(cdk): model EB deployment capability

This commit is contained in:
Alexandre Brandizzi 2026-07-28 14:03:10 -03:00
parent 487684e3c9
commit fa99240af5
2 changed files with 80 additions and 16 deletions

View file

@ -77,9 +77,11 @@ The role grants only:
`githubdeploy-shoc-backend-dev/GitHubActions` and was denied. It is scoped to `githubdeploy-shoc-backend-dev/GitHubActions` and was denied. It is scoped to
the exact bucket-level ARN and grants no object access. the exact bucket-level ARN and grants no object access.
- `cloudformation:GetTemplate`, `cloudformation:DescribeStackResources`, and - The six CloudFormation discovery calls observed across the failed OIDC and
`cloudformation:ListStackResources` on the Elastic Beanstalk-managed successful administrator deployments (`DescribeStackEvents`,
CloudFormation stack `awseb-e-hehnrqjjrt-stack`, scoped to `DescribeStackResource`, `DescribeStackResources`, `DescribeStacks`,
`GetTemplate`, and `ListStackResources`) on the Elastic Beanstalk-managed
stack `awseb-e-hehnrqjjrt-stack`, scoped to
`arn:aws:cloudformation:us-east-1:396287094661:stack/awseb-e-hehnrqjjrt-stack/*`. `arn:aws:cloudformation:us-east-1:396287094661:stack/awseb-e-hehnrqjjrt-stack/*`.
These read-only calls are emitted by Elastic Beanstalk's These read-only calls are emitted by Elastic Beanstalk's
`UpdateEnvironment` path under the GitHub deploy role. `GetTemplate` was `UpdateEnvironment` path under the GitHub deploy role. `GetTemplate` was
@ -95,20 +97,39 @@ The role grants only:
mutate that stack; it is owned by Elastic Beanstalk and referenced by mutate that stack; it is owned by Elastic Beanstalk and referenced by
identifier only. identifier only.
- `ec2:DescribeSubnets` as a read-only account-level query. Elastic Beanstalk - `ec2:DescribeAvailabilityZones`, `ec2:DescribeImages`, and
emitted this call during run 30375409934 attempt 3 after the environment `ec2:DescribeSubnets` as read-only account-level discovery queries.
update started. EC2 does not support resource-level constraints for this CloudTrail identified the GitHub deploy role as the caller during run
Describe action, so IAM requires `Resource: "*"`. No EC2 mutation action is `30375409934`; attempt 5 confirmed the first two denials after
granted. `DescribeSubnets` was allowed. EC2 does not support resource-level
constraints for these Describe actions, so IAM requires `Resource: "*"`.
No EC2 mutation action is granted.
- The Auto Scaling discovery calls `DescribeAutoScalingGroups` and
`DescribeScalingActivities` on `Resource: "*"` plus
`PutNotificationConfiguration`, `ResumeProcesses`, and `SuspendProcesses`
on only Auto Scaling groups whose name starts with
`awseb-e-hehnrqjjrt-stack-`. These are the exact calls recorded during the
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 - `s3:GetObjectAcl` under the existing
`elasticbeanstalk-us-east-1-396287094661/shoc-backend/*` object prefix. `elasticbeanstalk-us-east-1-396287094661/shoc-backend/*` object prefix.
Elastic Beanstalk emitted this read during the same attempt while validating Elastic Beanstalk emitted this read during the same attempt while validating
the uploaded application bundle. It grants no bucket-wide or cross-prefix the uploaded application bundle. It grants no bucket-wide or cross-prefix
object access. object access.
- `s3:GetObjectAcl` on only
`resources/environments/e-hehnrqjjrt/_runtime/*` in the same bucket. A
successful break-glass deployment showed that EB copies each immutable
application version, its embedded extensions, and its manifest under that
exact environment runtime prefix. The failed OIDC path already allowed the
source object, so the remaining ACL validation is limited to that proven
runtime boundary. This grant exposes ACL metadata only; it does not grant
runtime content reads or writes.
It grants **no** IAM mutation or `PassRole`, **no** RDS / Secrets Manager It grants **no** IAM mutation or `PassRole`, **no** RDS / Secrets Manager
access, no EC2 mutation, and **no** administrator policy. There are no wildcard access, no EC2 mutation, and **no** administrator policy. The only non-EB/S3
mutation surfaces. mutations are the three deployment-process Auto Scaling calls, restricted to
this environment's ASG name pattern. There are no wildcard mutation surfaces.
## Prerequisites ## Prerequisites
@ -139,10 +160,11 @@ All commands run from `infra/cdk/`.
- Trust policy `StringEquals` matches the exact audience and subject above. - Trust policy `StringEquals` matches the exact audience and subject above.
- The inline policy contains no `Resource: "*"` mutation action and no service - The inline policy contains no `Resource: "*"` mutation action and no service
outside `elasticbeanstalk` / `s3` / `cloudformation` / `ec2`. The outside `elasticbeanstalk` / `s3` / `cloudformation` / `ec2` /
`cloudformation` grants are the three observed read-only actions on the `autoscaling`. CloudFormation discovery is limited to the single EB-managed
single EB-managed stack prefix; the only EC2 grant is the read-only stack prefix. EC2 and Auto Scaling discovery use `Resource: "*"` only where
`DescribeSubnets` action whose IAM resource model requires `Resource: "*"`. the IAM resource model requires it; Auto Scaling mutations are limited to
this environment's ASG name pattern.
The workflow's `AWS_DEPLOY_ROLE_ARN` repository secret (environment `dev`) must The workflow's `AWS_DEPLOY_ROLE_ARN` repository secret (environment `dev`) must
hold the ARN output by this stack (`GithubDeployRoleArn`). hold the ARN output by this stack (`GithubDeployRoleArn`).

View file

@ -6,6 +6,8 @@ const ACCOUNT_ID = '396287094661';
const REGION = 'us-east-1'; const REGION = 'us-east-1';
const APPLICATION_NAME = 'shoc-backend'; const APPLICATION_NAME = 'shoc-backend';
const ENVIRONMENT_NAME = 'shoc-backend-dev'; const ENVIRONMENT_NAME = 'shoc-backend-dev';
const ENVIRONMENT_ID = 'e-hehnrqjjrt';
const ENVIRONMENT_STACK_NAME = `awseb-${ENVIRONMENT_ID}-stack`;
const REPO = 'Sea-Haven-Industries/shoc-backend'; const REPO = 'Sea-Haven-Industries/shoc-backend';
const BUCKET_NAME = `elasticbeanstalk-${REGION}-${ACCOUNT_ID}`; const BUCKET_NAME = `elasticbeanstalk-${REGION}-${ACCOUNT_ID}`;
@ -74,12 +76,15 @@ export class DeployDevStack extends cdk.Stack {
new iam.PolicyStatement({ new iam.PolicyStatement({
effect: iam.Effect.ALLOW, effect: iam.Effect.ALLOW,
actions: [ actions: [
'cloudformation:DescribeStackEvents',
'cloudformation:DescribeStackResource',
'cloudformation:GetTemplate', 'cloudformation:GetTemplate',
'cloudformation:DescribeStackResources', 'cloudformation:DescribeStackResources',
'cloudformation:DescribeStacks',
'cloudformation:ListStackResources', 'cloudformation:ListStackResources',
], ],
resources: [ resources: [
`arn:aws:cloudformation:${REGION}:${ACCOUNT_ID}:stack/awseb-e-hehnrqjjrt-stack/*`, `arn:aws:cloudformation:${REGION}:${ACCOUNT_ID}:stack/${ENVIRONMENT_STACK_NAME}/*`,
], ],
}), }),
); );
@ -87,11 +92,40 @@ export class DeployDevStack extends cdk.Stack {
deployRole.addToPolicy( deployRole.addToPolicy(
new iam.PolicyStatement({ new iam.PolicyStatement({
effect: iam.Effect.ALLOW, effect: iam.Effect.ALLOW,
actions: ['ec2:DescribeSubnets'], actions: [
'ec2:DescribeAvailabilityZones',
'ec2:DescribeImages',
'ec2:DescribeSubnets',
],
resources: ['*'], resources: ['*'],
}), }),
); );
deployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'autoscaling:DescribeAutoScalingGroups',
'autoscaling:DescribeScalingActivities',
],
resources: ['*'],
}),
);
deployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'autoscaling:PutNotificationConfiguration',
'autoscaling:ResumeProcesses',
'autoscaling:SuspendProcesses',
],
resources: [
`arn:aws:autoscaling:${REGION}:${ACCOUNT_ID}:autoScalingGroup:*:autoScalingGroupName/${ENVIRONMENT_STACK_NAME}-*`,
],
}),
);
deployRole.addToPolicy( deployRole.addToPolicy(
new iam.PolicyStatement({ new iam.PolicyStatement({
effect: iam.Effect.ALLOW, effect: iam.Effect.ALLOW,
@ -113,6 +147,14 @@ export class DeployDevStack extends cdk.Stack {
}), }),
); );
deployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:GetObjectAcl'],
resources: [`${bucketArn}/resources/environments/${ENVIRONMENT_ID}/_runtime/*`],
}),
);
new cdk.CfnOutput(this, 'GithubDeployRoleArn', { new cdk.CfnOutput(this, 'GithubDeployRoleArn', {
value: deployRole.roleArn, value: deployRole.roleArn,
description: 'ARN of the GitHub OIDC deploy role for shoc-backend dev.', description: 'ARN of the GitHub OIDC deploy role for shoc-backend dev.',