From 0c6f307b61b9da8d72d2993c1049229c020739dc Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:08:21 +0000 Subject: [PATCH] feat(iam): allow frontend HCP apply to update exact CloudFront resources (PLAT-187) (#142) * feat(iam): allow frontend HCP apply to update exact CloudFront resources (PLAT-187) Phase 2 ownership tags cannot apply while UpdateDistribution and UpdateFunction are denied on *. Allow those two actions only on the pinned distribution and function ARNs. * fix(iam): allow PublishFunction on exact frontend CloudFront functions (PLAT-187) The AWS provider publishes after UpdateFunction, including tag-only applies, so denying PublishFunction on * still blocked Phase 2 function updates. --- README.md | 25 +++++++++++-------- .../shoc-frontend-resources.ts | 18 ++++++++++--- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6652dc3..b6eef7d 100644 --- a/README.md +++ b/README.md @@ -438,8 +438,10 @@ job: ownership handoff for HCP roles/boundaries where applicable. Import the existing site resources only after a no-replacement plan. Apply-role writes are limited to ordinary tags, `PutRolePolicy` on the exact deploy role, - `PutBucketPolicy` on the exact bucket, and A/AAAA changes for the exact site - name with CREATE/DELETE/UPSERT conditions. + `PutBucketPolicy` on the exact bucket, `UpdateDistribution` on the exact + distribution, `UpdateFunction` and `PublishFunction` on the exact CloudFront + function, and A/AAAA changes for the exact site name with + CREATE/DELETE/UPSERT conditions. 5. For a future tf-poc, first provision and inventory the site outside these adoption roles. Set all five `shocFrontendPoc*` identifiers from the frontend shared creator outputs while its role gate remains false, deploy @@ -451,14 +453,17 @@ 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 infrastructure -mutation, and deletion of inline role or bucket policies. IAM does not expose a -condition key for an inline policy name, so `PutRolePolicy` is constrained to -the exact target-role ARN and requires the exact dedicated deploy boundary to -already be attached. The boundary limits effective permissions, and the SCP -requires the target role's locked `HcpTerraformWorkspace` tag to equal the -apply role's principal tag. The Terraform resource must retain the inventoried -inline policy name. +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. +`UpdateFunction` and `PublishFunction` are allowed on the exact pinned function +ARN so Phase 2 ownership tags can apply; create, delete, and OAC updates stay +denied. IAM does not expose a condition key for an inline policy name, so +`PutRolePolicy` is constrained to the exact target-role ARN and requires the +exact dedicated deploy boundary to already be attached. The boundary limits +effective permissions, and the SCP requires the target role's locked +`HcpTerraformWorkspace` tag to equal the apply role's principal tag. The +Terraform resource must retain the inventoried inline policy name. **HCP Terraform layout (org-level setup, console):** one org `seahaven` (free tier: 500 managed resources, 1 concurrent run); one HCP **project per diff --git a/lib/terraform-substrate/shoc-frontend-resources.ts b/lib/terraform-substrate/shoc-frontend-resources.ts index daf6721..3c8ee7b 100644 --- a/lib/terraform-substrate/shoc-frontend-resources.ts +++ b/lib/terraform-substrate/shoc-frontend-resources.ts @@ -270,9 +270,6 @@ const frontendApplyPolicy = ( "cloudfront:DeleteDistribution", "cloudfront:DeleteFunction", "cloudfront:DeleteOriginAccessControl", - "cloudfront:PublishFunction", - "cloudfront:UpdateDistribution", - "cloudfront:UpdateFunction", "cloudfront:UpdateOriginAccessControl", "s3:CreateBucket", "s3:DeleteBucket", @@ -331,6 +328,21 @@ const frontendApplyPolicy = ( functionArn(environment.functionName), ], }, + // TagResource is already allowed. Update* and PublishFunction were denied + // on * so Phase 2 in-place CloudFront updates could not apply. Scope them + // to exact ARNs. The AWS provider publishes after UpdateFunction. + { + Sid: "UpdateExactDistribution", + Effect: "Allow", + Action: "cloudfront:UpdateDistribution", + Resource: distributionArn(environment.distributionId), + }, + { + Sid: "UpdateExactFunction", + Effect: "Allow", + Action: ["cloudfront:UpdateFunction", "cloudfront:PublishFunction"], + Resource: functionArn(environment.functionName), + }, { Sid: "ReplaceExactDeployInlinePolicy", Effect: "Allow",