diff --git a/README.md b/README.md index 4a96da3..b32729c 100644 --- a/README.md +++ b/README.md @@ -406,19 +406,20 @@ external-dev IAM guardrail SCP is 5,095 compact characters against its 5,120-character Organizations limit; keep size assertions in every change. **External-dev SHOC frontend adoption uses separate gates and creates its -boundaries first.** The three retained boundaries are -`shoc-frontend-new-{tf-poc,dev,staging}-deploy-boundary`. Each permits only +boundaries first.** The two live retained boundaries are +`shoc-frontend-new-{dev,staging}-deploy-boundary`. Each permits only bucket location/list/version reads, object get/put/current and version delete, `GetDistribution`/`GetDistributionConfig`, and invalidation create/read for one exact distribution. Dev is pinned to `E2CWLM1AFB964P`; staging is pinned -to `E2JDVEZ6EGD49J`. The tf-poc distribution, OAC, function, hosted-zone, and -certificate identifiers are intentionally empty in `cdk.json`. They must come -from the frontend shared creator outputs; this substrate does not reuse the -backend tf-poc zone or certificate. Its site name is -`frontend-tf-poc.seahaven.com`. While the identifier set is empty, its -boundary omits distribution read and invalidation access and -`ShouldManageShocFrontendPocRoles` remains false even if its role gate is -mistakenly enabled. +to `E2JDVEZ6EGD49J`. The frontend tf-poc rehearsal is retired: its site, +HCP workspace, and GitHub deploy role are gone. `enableShocFrontendPocRoles` +is false and the five `shocFrontendPoc*` identifiers are empty, so +`ShouldManageShocFrontendPocRoles` stays false. After the stack update, +delete the retained orphans `hcptf-shoc-frontend-new-tf-poc`, +`hcptf-shoc-frontend-new-tf-poc-plan`, and +`shoc-frontend-new-tf-poc-deploy-boundary` with +`OrganizationAccountAccessRole`. A later rehearsal must +inventory new identifiers before that gate is turned on. The frontend role transition is manual and is not part of the external-dev CD job: @@ -457,14 +458,9 @@ 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 -(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. +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. `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/cdk.json b/cdk.json index a7bae96..3f3b008 100644 --- a/cdk.json +++ b/cdk.json @@ -20,12 +20,12 @@ "@aws-cdk/core:target-partitions": ["aws"], "enableShocBackendPocRoles": true, "enableShocBackendLiveRoles": true, - "enableShocFrontendPocRoles": true, + "enableShocFrontendPocRoles": false, "enableShocFrontendLiveRoles": true, - "shocFrontendPocDistributionId": "E73KH1SPNFL00", - "shocFrontendPocOriginAccessControlId": "E14MP8Z5YRWO93", - "shocFrontendPocFunctionName": "us-east-1shocfrontendtfpocSpaRewrite4B1A4F5F", - "shocFrontendPocHostedZoneId": "Z10433621DH3UOWM8663D", - "shocFrontendPocCertificateArn": "arn:aws:acm:us-east-1:396287094661:certificate/3dbc8c23-3467-47db-9f6c-39236ca11682" + "shocFrontendPocDistributionId": "", + "shocFrontendPocOriginAccessControlId": "", + "shocFrontendPocFunctionName": "", + "shocFrontendPocHostedZoneId": "", + "shocFrontendPocCertificateArn": "" } } diff --git a/lib/scp/external-dev-iam-guardrails.json b/lib/scp/external-dev-iam-guardrails.json index 542bafe..a0cbc83 100644 --- a/lib/scp/external-dev-iam-guardrails.json +++ b/lib/scp/external-dev-iam-guardrails.json @@ -66,7 +66,7 @@ "Effect": "Deny", "Action": ["iam:PutRolePolicy", "iam:TagRole", "iam:UntagRole"], "Resource": "arn:aws:iam::396287094661:role/githubdeploy-*", - "Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*", "arn:aws:iam::396287094661:role/hcptf-shoc-backend-tf-poc", "arn:aws:iam::396287094661:role/hcptf-shoc-backend-dev", "arn:aws:iam::396287094661:role/hcptf-shoc-backend-staging", "arn:aws:iam::396287094661:role/hcptf-shoc-frontend-new-tf-poc", "arn:aws:iam::396287094661:role/hcptf-shoc-frontend-new-dev", "arn:aws:iam::396287094661:role/hcptf-shoc-frontend-new-staging"] } } + "Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*", "arn:aws:iam::396287094661:role/hcptf-shoc-backend-tf-poc", "arn:aws:iam::396287094661:role/hcptf-shoc-backend-dev", "arn:aws:iam::396287094661:role/hcptf-shoc-backend-staging", "arn:aws:iam::396287094661:role/hcptf-shoc-frontend-new-dev", "arn:aws:iam::396287094661:role/hcptf-shoc-frontend-new-staging"] } } }, { "Sid": "DenyUnmanagedGithubRole", diff --git a/lib/terraform-substrate/shoc-frontend-resources.ts b/lib/terraform-substrate/shoc-frontend-resources.ts index 342e633..098998b 100644 --- a/lib/terraform-substrate/shoc-frontend-resources.ts +++ b/lib/terraform-substrate/shoc-frontend-resources.ts @@ -230,93 +230,6 @@ 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 => ({ @@ -356,7 +269,25 @@ const frontendApplyPolicy = ( { Sid: "DenyInfrastructureReplacement", Effect: "Deny", - Action: infrastructureReplacementDenyActions(environment), + 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", + ], Resource: "*", }, { @@ -472,7 +403,6 @@ const frontendApplyPolicy = ( }, }, }, - ...frontendPocDestroyStatements(environment), ], });