diff --git a/README.md b/README.md index 50e1bde..4a96da3 100644 --- a/README.md +++ b/README.md @@ -457,9 +457,14 @@ job: a false gate as rollback after CloudFormation owns a role. The apply roles explicitly deny role lifecycle/trust/boundary/managed-policy -changes, `PassRole`, secret and parameter reads, CloudFront/S3 create and -delete (including OAC mutation), and deletion of inline role or bucket -policies. `UpdateDistribution` is allowed on the exact pinned distribution ARN. +changes, `PassRole`, secret and parameter reads, CloudFront/S3 create, and +(for dev and staging) CloudFront/S3 delete including OAC mutation and bucket +policy deletion. The tf-poc apply role is the exception: it keeps create +denied and allows destroy of the exact rehearsal bucket (including object +versions), distribution `E73KH1SPNFL00`, OAC `E14MP8Z5YRWO93`, function +`us-east-1shocfrontendtfpocSpaRewrite4B1A4F5F`, and A/AAAA in +`Z10433621DH3UOWM8663D`. It still does not delete `githubdeploy-*`; SCP +`ProtectDeploymentPrincipalLifecycle` denies that for HCP principals. `UpdateDistribution` is allowed on the exact pinned distribution ARN. `CreateInvalidation` and `GetInvalidation` are allowed on that same ARN so the Terraform invalidation action can run. `GetObject`/`GetObjectTagging`/`PutObject`/`PutObjectTagging` on `.release/current` lets Terraform own the release pointer, including the diff --git a/lib/terraform-substrate/shoc-frontend-resources.ts b/lib/terraform-substrate/shoc-frontend-resources.ts index 098998b..342e633 100644 --- a/lib/terraform-substrate/shoc-frontend-resources.ts +++ b/lib/terraform-substrate/shoc-frontend-resources.ts @@ -230,6 +230,93 @@ const frontendReadPolicy = ( }; }; +const infrastructureReplacementDenyActions = ( + environment: FrontendEnvironment, +): string[] => { + const createOnly = [ + "cloudfront:CreateDistribution", + "cloudfront:CreateFunction", + "cloudfront:CreateOriginAccessControl", + "s3:CreateBucket", + ]; + if (environment.key === "tf-poc") { + // Destroy needs Delete* and the Put/Delete calls Terraform uses to + // remove versioning, encryption, ownership, and public-access configs. + return createOnly; + } + return [ + ...createOnly, + "cloudfront:DeleteDistribution", + "cloudfront:DeleteFunction", + "cloudfront:DeleteOriginAccessControl", + "cloudfront:UpdateOriginAccessControl", + "s3:DeleteBucket", + "s3:DeleteBucketEncryption", + "s3:DeleteBucketOwnershipControls", + "s3:DeleteBucketPolicy", + "s3:DeleteBucketPublicAccessBlock", + "s3:PutBucketOwnershipControls", + "s3:PutBucketPublicAccessBlock", + "s3:PutBucketVersioning", + "s3:PutEncryptionConfiguration", + ]; +}; + +const frontendPocDestroyStatements = ( + environment: FrontendEnvironment, +): Record[] => { + if (environment.key !== "tf-poc") { + return []; + } + const siteBucketArn = bucketArn(environment.bucketName); + return [ + { + Sid: "DestroyExactPocBucket", + Effect: "Allow", + Action: [ + "s3:DeleteBucket", + "s3:DeleteBucketEncryption", + "s3:DeleteBucketOwnershipControls", + "s3:DeleteBucketPolicy", + "s3:DeleteBucketPublicAccessBlock", + "s3:GetBucketVersioning", + "s3:ListBucket", + "s3:ListBucketVersions", + "s3:PutBucketOwnershipControls", + "s3:PutBucketPublicAccessBlock", + "s3:PutBucketVersioning", + "s3:PutEncryptionConfiguration", + ], + Resource: siteBucketArn, + }, + { + Sid: "DestroyExactPocBucketObjects", + Effect: "Allow", + Action: [ + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:GetObject", + "s3:GetObjectVersion", + ], + Resource: `${siteBucketArn}/*`, + }, + { + Sid: "DestroyExactPocCloudFront", + Effect: "Allow", + Action: [ + "cloudfront:DeleteDistribution", + "cloudfront:DeleteFunction", + "cloudfront:DeleteOriginAccessControl", + ], + Resource: [ + distributionArn(environment.distributionId), + functionArn(environment.functionName), + originAccessControlArn(environment.originAccessControlId), + ], + }, + ]; +}; + const frontendApplyPolicy = ( environment: FrontendEnvironment, ): Record => ({ @@ -269,25 +356,7 @@ const frontendApplyPolicy = ( { Sid: "DenyInfrastructureReplacement", Effect: "Deny", - Action: [ - "cloudfront:CreateDistribution", - "cloudfront:CreateFunction", - "cloudfront:CreateOriginAccessControl", - "cloudfront:DeleteDistribution", - "cloudfront:DeleteFunction", - "cloudfront:DeleteOriginAccessControl", - "cloudfront:UpdateOriginAccessControl", - "s3:CreateBucket", - "s3:DeleteBucket", - "s3:DeleteBucketEncryption", - "s3:DeleteBucketOwnershipControls", - "s3:DeleteBucketPolicy", - "s3:DeleteBucketPublicAccessBlock", - "s3:PutBucketOwnershipControls", - "s3:PutBucketPublicAccessBlock", - "s3:PutBucketVersioning", - "s3:PutEncryptionConfiguration", - ], + Action: infrastructureReplacementDenyActions(environment), Resource: "*", }, { @@ -403,6 +472,7 @@ const frontendApplyPolicy = ( }, }, }, + ...frontendPocDestroyStatements(environment), ], });