From 35461d9267fb424286483872692105216431a44a Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:09:57 -0400 Subject: [PATCH] feat(iam): add hcptf-seahaven-site plan/apply roles (PLAT-91) (#89) * feat(iam): add hcptf-seahaven-site plan/apply roles Static-site HCP substrate for seahaven-site-prod plus boundary widen for the TF-managed content-deploy role (S3 origin + CloudFront invalidate). * fix(iam): allow seahaven-site HCP roles to read GitHub OIDC provider Plan refresh needs iam:GetOpenIDConnectProvider for the content-deploy role trust data source (PLAT-91 first-plan AccessDenied). --- .../deploy-substrate.template.yaml | 39 ++++- .../terraform-substrate.template.yaml | 145 ++++++++++++++++++ 2 files changed, 180 insertions(+), 4 deletions(-) diff --git a/lib/deploy-substrate/deploy-substrate.template.yaml b/lib/deploy-substrate/deploy-substrate.template.yaml index a50b5eb..bc1f8dc 100644 --- a/lib/deploy-substrate/deploy-substrate.template.yaml +++ b/lib/deploy-substrate/deploy-substrate.template.yaml @@ -661,12 +661,43 @@ Resources: - !Sub "arn:aws:sns:us-east-1:${AWS::AccountId}:site-alerts" - !Ref AWS::NoValue + # ── seahaven-site (PLAT-91) — content-deploy role data plane ──────── + # TF creates githubdeploy-seahaven-site under /tf-managed/ with this + # boundary as ceiling. Role policy is S3 sync + CloudFront invalidate + # only; no Lambda. Exact origin bucket + distribution-scoped invalidate. + - !If + - IsProdAccount + - Sid: SeahavenSiteOriginS3 + Effect: Allow + Action: + - s3:GetObject + - s3:PutObject + - s3:DeleteObject + - s3:GetObjectTagging + - s3:PutObjectTagging + - s3:ListBucket + - s3:GetBucketLocation + Resource: + - arn:aws:s3:::seahaven-site-prod + - arn:aws:s3:::seahaven-site-prod/* + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: SeahavenSiteCloudFrontInvalidate + Effect: Allow + Action: + - cloudfront:CreateInvalidation + - cloudfront:GetInvalidation + Resource: + - !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/*" + - !Ref AWS::NoValue + # ── FURTHER PER-WORKLOAD DATA-PLANE STATEMENTS ────────────────────── # Floor above + afi-backup-monitor (PLAT-56) + front-integrations - # (PLAT-72) + procurement-ingest (PLAT-86). Additional stacks add - # their own statements here, derived from THEIR OWN template, in - # their own PR, deployed to UPDATE_COMPLETE before first workload - # deploy. WIDENING PATH in the header still governs. + # (PLAT-72) + procurement-ingest (PLAT-86) + seahaven-site (PLAT-91). + # Additional stacks add their own statements here, derived from THEIR + # OWN template, in their own PR, deployed to UPDATE_COMPLETE before + # first workload deploy. WIDENING PATH in the header still governs. # # WHY the floor stayed empty of data-plane (decided 2026-07-30, Adam): # The security win of INFRA-186 comes from DELETION, not enumeration. diff --git a/lib/terraform-substrate/terraform-substrate.template.yaml b/lib/terraform-substrate/terraform-substrate.template.yaml index 2cbd0c6..dbe482d 100644 --- a/lib/terraform-substrate/terraform-substrate.template.yaml +++ b/lib/terraform-substrate/terraform-substrate.template.yaml @@ -1409,3 +1409,148 @@ Resources: - secretsmanager:GetResourcePolicy Resource: - !Sub "arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:procurement-ingest/web-ui-auth-token-*" + + # --------------------------------------------------------------------------- + # seahaven-site-prod (PLAT-91) — static site S3 + CloudFront + ACM + GHA + # content-deploy role. No Lambda → no boundary widen. No Route53 (apex DNS + # stays OOB in mgmt). Plan role: ViewOnly + plan-refresh sidecar. + # --------------------------------------------------------------------------- + HcptfSeahavenSitePlanRole: + Type: AWS::IAM::Role + Condition: IsProdAccount + Properties: + RoleName: hcptf-seahaven-site-plan + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/app.terraform.io" + Action: sts:AssumeRoleWithWebIdentity + Condition: + StringEquals: + "app.terraform.io:aud": aws.workload.identity + "app.terraform.io:sub": organization:seahaven:project:seahaven-prod:workspace:seahaven-site-prod:run_phase:plan + ManagedPolicyArns: + - arn:aws:iam::aws:policy/job-function/ViewOnlyAccess + Policies: + - PolicyName: seahaven-site-plan-refresh + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: RefreshDeployRole + Effect: Allow + Action: + - iam:GetRole + - iam:GetRolePolicy + - iam:ListRolePolicies + - iam:ListAttachedRolePolicies + - iam:ListRoleTags + Resource: + - !Sub "arn:aws:iam::${AWS::AccountId}:role/tf-managed/githubdeploy-seahaven-site" + - Sid: RefreshGithubOidcProvider + Effect: Allow + Action: + - iam:GetOpenIDConnectProvider + Resource: + - !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com" + - Sid: RefreshManagedPolicies + Effect: Allow + Action: + - iam:GetPolicy + - iam:GetPolicyVersion + Resource: "*" + - Sid: RefreshOriginBucket + Effect: Allow + Action: + - s3:Get* + - s3:ListBucket + Resource: + - arn:aws:s3:::seahaven-site-prod + - arn:aws:s3:::seahaven-site-prod/* + - Sid: RefreshCloudFront + Effect: Allow + Action: + - cloudfront:Get* + - cloudfront:List* + Resource: "*" + - Sid: RefreshAcm + Effect: Allow + Action: + - acm:DescribeCertificate + - acm:ListCertificates + - acm:ListTagsForCertificate + - acm:GetCertificate + Resource: "*" + + HcptfSeahavenSiteApplyRole: + Type: AWS::IAM::Role + Condition: IsProdAccount + Properties: + RoleName: hcptf-seahaven-site + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/app.terraform.io" + Action: sts:AssumeRoleWithWebIdentity + Condition: + StringEquals: + "app.terraform.io:aud": aws.workload.identity + "app.terraform.io:sub": organization:seahaven:project:seahaven-prod:workspace:seahaven-site-prod:run_phase:apply + ManagedPolicyArns: + - !Ref HcptfIamManagementPolicy + Policies: + - PolicyName: seahaven-site-services + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: OriginBucket + Effect: Allow + Action: + - s3:* + Resource: + - arn:aws:s3:::seahaven-site-prod + - arn:aws:s3:::seahaven-site-prod/* + - Sid: ReadGithubOidcProvider + Effect: Allow + Action: + - iam:GetOpenIDConnectProvider + Resource: + - !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com" + - Sid: CloudFrontManage + Effect: Allow + Action: + - cloudfront:* + Resource: "*" + # RequestCertificate is account-level; pin via RequestTag matching + # provider default_tags (Project=seahaven-site). Post-create manage + # requires the same ResourceTag. + - Sid: AcmCreate + Effect: Allow + Action: + - acm:RequestCertificate + Resource: "*" + Condition: + StringEquals: + "aws:RequestTag/Project": seahaven-site + - Sid: AcmList + Effect: Allow + Action: + - acm:ListCertificates + - acm:ListTagsForCertificate + Resource: "*" + - Sid: AcmManageTagged + Effect: Allow + Action: + - acm:DescribeCertificate + - acm:GetCertificate + - acm:DeleteCertificate + - acm:AddTagsToCertificate + - acm:RemoveTagsFromCertificate + - acm:RenewCertificate + Resource: "*" + Condition: + StringEquals: + "aws:ResourceTag/Project": seahaven-site