mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-07 16:19:07 +00:00
fix(cdk): apply supported Beanstalk S3 policy
This commit is contained in:
parent
9966c22240
commit
4a6632f025
2 changed files with 40 additions and 107 deletions
|
|
@ -203,12 +203,8 @@ The previous OIDC deployment remained fail-closed after Elastic Beanstalk
|
||||||
reported a generic `s3:GetObjectAcl` denial outside the account-owned source
|
reported a generic `s3:GetObjectAcl` denial outside the account-owned source
|
||||||
prefix. AWS Support case `178526484500047` subsequently confirmed that
|
prefix. AWS Support case `178526484500047` subsequently confirmed that
|
||||||
`UpdateEnvironment` checks objects in AWS-owned Elastic Beanstalk buckets
|
`UpdateEnvironment` checks objects in AWS-owned Elastic Beanstalk buckets
|
||||||
using the initiating role and requires the `elasticbeanstalk-*/*` resource
|
using the initiating role and requires the service-wide
|
||||||
namespace. This policy adds only the denied ACL-read action on that namespace;
|
`elasticbeanstalk-*` bucket/object namespaces.
|
||||||
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 July 30 deployment of backend PR #41 then reached `UpdateEnvironment` and
|
The July 30 deployment of backend PR #41 then reached `UpdateEnvironment` and
|
||||||
failed on `ec2:DescribeVpcs`. Elastic Beanstalk performs this read-only network
|
failed on `ec2:DescribeVpcs`. Elastic Beanstalk performs this read-only network
|
||||||
|
|
@ -216,22 +212,25 @@ discovery using the initiating role, so the CDK policy includes that action
|
||||||
alongside the existing EC2 describe permissions. It remains resource `*`
|
alongside the existing EC2 describe permissions. It remains resource `*`
|
||||||
because `DescribeVpcs` does not support resource-level permissions.
|
because `DescribeVpcs` does not support resource-level permissions.
|
||||||
|
|
||||||
After that permission was deployed, the next exact rerun reached the S3 cleanup
|
Successive exact reruns then reached S3 cleanup, the delegated CloudFormation
|
||||||
phase and failed because Elastic Beanstalk could not delete one or more
|
update, and the CloudFormation template fetch. The observed failures were
|
||||||
temporary objects. The service does not expose the AWS-owned bucket/key in its
|
`s3:DeleteObject`, `cloudformation:UpdateStack`, and finally an opaque
|
||||||
event, so `s3:DeleteObject` is scoped to the same AWS-confirmed
|
CloudFormation `S3 error: Access Denied` after narrower object reads had been
|
||||||
`arn:aws:s3:::elasticbeanstalk-*/*` object namespace as `GetObjectAcl`. The
|
added. Because AWS does not expose the AWS-owned bucket/key or exact internal
|
||||||
policy does not grant wildcard S3 actions or access to non-Beanstalk buckets.
|
S3 read in that final error, the CDK now uses AWS Support's authoritative
|
||||||
|
UpdateEnvironment S3 set:
|
||||||
|
|
||||||
The following rerun passed both denials and reached the delegated
|
- `s3:Delete*`, `s3:Get*`, and `s3:Put*` on
|
||||||
CloudFormation update, which required `cloudformation:UpdateStack`. That
|
`arn:aws:s3:::elasticbeanstalk-*/*`.
|
||||||
mutation is limited to the single existing Elastic Beanstalk-managed stack ARN
|
- `s3:GetBucket*`, `s3:ListBucket`, `s3:PutBucketPolicy`,
|
||||||
for `shoc-backend-dev`; it cannot create stacks or update any other stack.
|
`s3:PutBucketPublicAccessBlock`, and `s3:PutBucketOwnershipControls` on
|
||||||
|
`arn:aws:s3:::elasticbeanstalk-*`.
|
||||||
|
|
||||||
Once `UpdateStack` was allowed, CloudFormation returned `S3 error: Access
|
`s3:CreateBucket` remains excluded because this workflow targets an existing
|
||||||
Denied` while resolving the Elastic Beanstalk-managed template. The caller
|
application/environment and explicitly disables bucket creation. No S3 access
|
||||||
therefore receives `s3:GetObject` on the same Beanstalk-only object namespace;
|
is granted to non-Elastic-Beanstalk bucket names. The CloudFormation mutation
|
||||||
the policy still does not grant `s3:Get*` or access to other bucket names.
|
remains limited to the single existing `shoc-backend-dev` managed stack ARN; it
|
||||||
|
cannot create stacks or update another stack.
|
||||||
|
|
||||||
The pinned deployment action can return success after Elastic Beanstalk emits a
|
The pinned deployment action can return success after Elastic Beanstalk emits a
|
||||||
fatal deployment event. The following workflow step therefore verifies that
|
fatal deployment event. The following workflow step therefore verifies that
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ const ENVIRONMENT_NAME = 'shoc-backend-dev';
|
||||||
const ENVIRONMENT_ID = 'e-hehnrqjjrt';
|
const ENVIRONMENT_ID = 'e-hehnrqjjrt';
|
||||||
const ENVIRONMENT_STACK_NAME = `awseb-${ENVIRONMENT_ID}-stack`;
|
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}`;
|
|
||||||
|
|
||||||
export class DeployDevStack extends cdk.Stack {
|
export class DeployDevStack extends cdk.Stack {
|
||||||
constructor(scope: Construct, id: string, props: cdk.StackProps = {}) {
|
constructor(scope: Construct, id: string, props: cdk.StackProps = {}) {
|
||||||
|
|
@ -17,19 +16,6 @@ export class DeployDevStack extends cdk.Stack {
|
||||||
|
|
||||||
const applicationArn = `arn:aws:elasticbeanstalk:${REGION}:${ACCOUNT_ID}:application/${APPLICATION_NAME}`;
|
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 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 oidcProviderArn = `arn:aws:iam::${ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com`;
|
||||||
|
|
||||||
const deployRole = new iam.Role(this, 'GithubDeployRole', {
|
const deployRole = new iam.Role(this, 'GithubDeployRole', {
|
||||||
|
|
@ -143,83 +129,31 @@ 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: [
|
actions: ['s3:Delete*', 's3:Get*', 's3:Put*'],
|
||||||
's3:ListBucket',
|
// AWS Support case 178526484500047 confirmed that UpdateEnvironment
|
||||||
's3:CreateBucket',
|
// reads, writes, versions, ACL-checks, and removes objects in both the
|
||||||
's3:PutBucketOwnershipControls',
|
// account bucket and AWS-owned Elastic Beanstalk service buckets.
|
||||||
's3:GetBucketLocation',
|
|
||||||
's3:GetBucketPolicy',
|
|
||||||
],
|
|
||||||
resources: [bucketArn],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
deployRole.addToPolicy(
|
|
||||||
new iam.PolicyStatement({
|
|
||||||
effect: iam.Effect.ALLOW,
|
|
||||||
actions: ['s3:GetObject', 's3:GetObjectVersion', 's3:PutObject'],
|
|
||||||
resources: [`${bucketArn}/${APPLICATION_NAME}/*`],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
actions: ['s3:DeleteObject', 's3:GetObject', 's3:GetObjectAcl'],
|
|
||||||
// UpdateEnvironment also checks and removes temporary 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-*/*'],
|
resources: ['arn:aws:s3:::elasticbeanstalk-*/*'],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
deployRole.addToPolicy(
|
||||||
|
new iam.PolicyStatement({
|
||||||
|
effect: iam.Effect.ALLOW,
|
||||||
|
actions: [
|
||||||
|
's3:GetBucket*',
|
||||||
|
's3:ListBucket',
|
||||||
|
's3:PutBucketOwnershipControls',
|
||||||
|
's3:PutBucketPolicy',
|
||||||
|
's3:PutBucketPublicAccessBlock',
|
||||||
|
],
|
||||||
|
// This is AWS Support's bucket-level UpdateEnvironment set, excluding
|
||||||
|
// CreateBucket because the workflow deploys only to an existing
|
||||||
|
// application/environment and disables bucket creation.
|
||||||
|
resources: ['arn:aws:s3:::elasticbeanstalk-*'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
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.',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue