diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index fd6eb5d..d7a9598 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -58,10 +58,10 @@ jobs: uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@e5691d8a7f96ac4d5a841a82975ff0a4354d53ac # v1.0.7 with: node-version: "24" - # seahaven-site-hcptf was imported after the roles left terraform-substrate. - # seahaven-hcptf was imported. The deploy creates the three managed - # policies and removes the inline policies. Do not create the roles. - stacks: "prod-baseline dynamodb-cmk-prod alarm-topic-prod deploy-substrate-prod app-web-acl-prod seahaven-site-hcptf seahaven-hcptf" + # seahaven-hcptf was imported. It owns payments-dashboard and the + # seahaven-site exec roles. Do not create the roles. Deleting the + # retired seahaven-site-hcptf stack is a separate prod step. + stacks: "prod-baseline dynamodb-cmk-prod alarm-topic-prod deploy-substrate-prod app-web-acl-prod seahaven-hcptf" stack-name: "seahaven-prod-baseline" secrets: deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN_PROD }} diff --git a/README.md b/README.md index 05fe885..2d6a985 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,7 @@ are noted): | `seahaven-engineering-access` | 328440206208 | us-east-1 | Identity Center group `engineering`. `EngineeringProd` reads payments-dashboard configuration and seahaven-site in 011934824531. `EngineeringDev` has no allow (PLAT-235, PLAT-236). | | `seahaven-external-dev-baseline` | 396287094661 | us-east-1 | Member-account baseline: Config, GuardDuty, Security Hub (FSBP + CIS v3.0), Access Analyzer, flow logs, budget | | `seahaven-terraform-substrate` | 011934824531, 710827005802 | us-east-1 | Removed from the CDK app and from CD (PLAT-147). Live stacks remain until `scripts/delete-terraform-substrate-prod-dev.sh`. The six imported prod pairs are already forgotten. | -| `seahaven-site-hcptf` | 011934824531 | us-east-1 | Imported `hcptf-seahaven-site` apply and plan roles (PLAT-225). Policies are managed at `/tf-managed/`. On the prod deploy job. Do not create the roles. | -| `seahaven-hcptf` | 011934824531, 710827005802 | us-east-1 | payments-dashboard HCP apply and plan roles, scoped policies, and the Lambda boundary. Trust is pinned to `payments-dashboard-prod` in project `seahaven-prod`, and to `payments-dashboard-dev` in project `seahaven-dev`. Roles and boundary are imported. On the dev and prod deploy jobs. | +| `seahaven-hcptf` | 011934824531, 710827005802 | us-east-1 | payments-dashboard HCP apply and plan roles, scoped policies, and the Lambda boundary in prod and dev. Prod also owns the imported `hcptf-seahaven-site` apply and plan roles (PLAT-225), with policies at `/tf-managed/`. Trust is pinned per workspace. On the dev and prod deploy jobs. Do not create the roles. | | `seahaven-terraform-substrate` | 396287094661 | us-east-1 | Staged manually for SHOC backend/frontend adoption; exact HCP roles and deploy boundaries referencing the existing OIDC provider. Stays (PLAT-148). | | `seahaven-security-baseline` | 001520130573 | us-east-1 | Member-account baseline for the delegated security-admin account (same construct set) | | `seahaven-dev-baseline` | 710827005802 | us-east-1 | Member-account baseline for internal dev/staging (org-managed detection — no local GuardDuty/SecurityHub) | @@ -78,7 +77,6 @@ the TypeScript source — no separate compile step needed for `cdk synth` / | `dynamodb-cmk-prod` | `seahaven-dynamodb-cmk` | 011934824531 | us-east-1 | `lib/dynamodb-cmk-stack.ts` | | `alarm-topic-prod` | `seahaven-alarm-topic` | 011934824531 | us-east-1 | `lib/alarm-topic-stack.ts` | | `app-web-acl-prod` | `seahaven-app-web-acl` | 011934824531 | us-east-1 | `lib/app-web-acl-stack.ts` | -| `seahaven-site-hcptf` | `seahaven-site-hcptf` | 011934824531 | us-east-1 | `lib/seahaven-site-hcptf-stack.ts` | | `seahaven-hcptf` | `seahaven-hcptf` | 011934824531 | us-east-1 | `lib/seahaven-hcptf-stack.ts` | | `seahaven-hcptf-dev` | `seahaven-hcptf` | 710827005802 | us-east-1 | `lib/seahaven-hcptf-stack.ts` | @@ -240,12 +238,13 @@ Console / one-shot CLI owns only: Do not manage prod/dev workload IAM (`hcptf-` pairs, Lambda exec roles, `policy/tf-managed/` ceilings) in the console. Do not append new prod/dev `hcptf-` pairs to this template. New prod/dev HCP stacks -do not need an org-baseline IAM PR. payments-dashboard HCP roles and -the Lambda boundary are stack `seahaven-hcptf` in prod and dev. Both copies -are imported and are on the deploy jobs. The deploy creates +do not need an org-baseline IAM PR. Prod HCP exec roles for +payments-dashboard and seahaven-site both live in `seahaven-hcptf`. +Dev `seahaven-hcptf` is payments-dashboard only. Both account copies +are imported and are on the deploy jobs. The payments deploy creates `payments-dashboard-hcptf-iam`, `payments-dashboard-hcptf-services`, and `payments-dashboard-hcptf-plan`, and removes the inline policies. Do not -create the roles or the boundary. +create the roles, the boundary, or the seahaven-site roles. **External-dev IAM stays in this repo (PLAT-148).** SHOC backend/frontend HCP roles, SHOC deploy/runtime boundaries, `shoc-frontend-resources.ts`, and @@ -267,7 +266,7 @@ Until the delete script runs, the live stacks still hold: `seahaven-lambda-execution-boundary-*` allow-list; frozen, do not append). The six imported prod pairs are Retain-removed. `seahaven-site` is -`seahaven-site-hcptf`. `sh-openswe-traces` is gone. +`seahaven-hcptf`. `sh-openswe-traces` is gone. External-dev still carries: @@ -314,7 +313,7 @@ and nonprod (PLAT-145), covering `hcptf-bootstrap*` plus the break-glass / CDK / `githubdeploy-*` set already on the security OU. The six live prod pairs are imported into the owning app, not recreated, then -Retain-removed from this template. `seahaven-site` is `seahaven-site-hcptf`. +Retain-removed from this template. `seahaven-site` is `seahaven-hcptf`. `sh-openswe-traces` is not imported. The prod/dev stacks are deleted after that forget. See the first-apply and import runbooks below. @@ -611,7 +610,7 @@ wildcards. Do not enumerate provider Get* APIs. state: `afi-backup-monitor`, `front-integrations`, `paychex-integrations`, `procurement-ingest`, `meal-order-manager`, `seahaven-door-unlock-api`. `sh-openswe-traces` was decommissioned (PLAT-196) and is not imported. -`seahaven-site` is stack `seahaven-site-hcptf` (PLAT-225), not this template. +`seahaven-site` is stack `seahaven-hcptf` (PLAT-225), not this template. Repos that do not change: SHOC (`shoc-backend`, `shoc-frontend-new`), remaining SAM / unmigrated stacks. diff --git a/bin/app.ts b/bin/app.ts index e5b8488..9ea80e5 100644 --- a/bin/app.ts +++ b/bin/app.ts @@ -11,7 +11,6 @@ import { TerraformSubstrateStack } from "../lib/terraform-substrate-stack"; import { DynamoDbCmkStack } from "../lib/dynamodb-cmk-stack"; import { AppWebAclStack } from "../lib/app-web-acl-stack"; import { SeahavenHcptfStack } from "../lib/seahaven-hcptf-stack"; -import { SeahavenSiteHcptfStack } from "../lib/seahaven-site-hcptf-stack"; import { MemberBaselineStack } from "../lib/member-baseline-stack"; import { OrgGovernanceStack } from "../lib/org-governance-stack"; import { PlatformAccessStack } from "../lib/platform-access-stack"; @@ -231,19 +230,12 @@ new AppWebAclStack(app, "app-web-acl-prod", { env: { account: PROD_ACCOUNT, region: "us-east-1" }, }); -// seahaven-site exec roles (PLAT-225). Not part of terraform-substrate. -// Imported. On the prod deploy job. A create fails because the roles already exist. -// Inline policies are managed policies at /tf-managed/. Do not recreate the roles. -new SeahavenSiteHcptfStack(app, "seahaven-site-hcptf", { - stackName: "seahaven-site-hcptf", - env: { account: PROD_ACCOUNT, region: "us-east-1" }, -}); - // payments-dashboard HCP roles, scoped policies, and the Lambda boundary. -// The same three names already exist in prod and dev. Import them with -// `-c hcptfPaymentsImport=true`. A create fails. Neither stack is on a -// deploy job until that import succeeds. Trust is pinned per workspace. +// Prod also includes the imported seahaven-site apply and plan roles +// (PLAT-225). A create fails. Import site with `-c hcptfSiteImport=true`. +// Trust is pinned per workspace. const hcptfPaymentsImport = contextBoolean("hcptfPaymentsImport"); +const hcptfSiteImport = contextBoolean("hcptfSiteImport"); const paymentsSecrets = ( account: string, suffixes: readonly string[], @@ -269,6 +261,8 @@ new SeahavenHcptfStack(app, "seahaven-hcptf", { "expense-slack-signing-secret-lbb78J", ]), importExisting: hcptfPaymentsImport, + includeSeahavenSite: true, + siteImportExisting: hcptfSiteImport, }); new SeahavenHcptfStack(app, "seahaven-hcptf-dev", { diff --git a/import-maps/seahaven-site-into-hcptf-prod.json b/import-maps/seahaven-site-into-hcptf-prod.json new file mode 100644 index 0000000..3ce7a53 --- /dev/null +++ b/import-maps/seahaven-site-into-hcptf-prod.json @@ -0,0 +1,17 @@ +{ + "SeahavenSiteApplyRoleE31E1F61": { + "RoleName": "hcptf-seahaven-site" + }, + "SeahavenSitePlanRoleF0DFA964": { + "RoleName": "hcptf-seahaven-site-plan" + }, + "SeahavenSiteIamPolicy1032B47C": { + "PolicyArn": "arn:aws:iam::011934824531:policy/tf-managed/seahaven-site-hcptf-iam" + }, + "SeahavenSiteServicesPolicy1E92BC24": { + "PolicyArn": "arn:aws:iam::011934824531:policy/tf-managed/seahaven-site-hcptf-services" + }, + "SeahavenSitePlanPolicyC4736E1F": { + "PolicyArn": "arn:aws:iam::011934824531:policy/tf-managed/seahaven-site-hcptf-plan" + } +} diff --git a/lib/hcptf-policy-aspect.ts b/lib/hcptf-policy-aspect.ts index 78e74d9..e2a4ca4 100644 --- a/lib/hcptf-policy-aspect.ts +++ b/lib/hcptf-policy-aspect.ts @@ -8,7 +8,8 @@ const MAX_POLICY_CHARS = 6144; /** * Fails synth when an HCP stack's managed policy document reaches the IAM * size quota, or when a role in that stack carries an inline policy. - * Register it on seahaven-hcptf and seahaven-site-hcptf only. + * Register it on seahaven-hcptf only. That stack owns payments-dashboard + * in prod and dev, and seahaven-site in prod. * * `allowInlinePolicies` is only for the one-time `cdk import` template. * That template has to name the live inline policies so the following diff --git a/lib/seahaven-hcptf-stack.ts b/lib/seahaven-hcptf-stack.ts index 38b6ee8..93121ee 100644 --- a/lib/seahaven-hcptf-stack.ts +++ b/lib/seahaven-hcptf-stack.ts @@ -2,11 +2,15 @@ import * as cdk from "aws-cdk-lib"; import * as iam from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; import { HcptfPolicyAspect } from "./hcptf-policy-aspect"; +import { SeahavenSiteRoles } from "./seahaven-site-hcptf-stack"; /** - * payments-dashboard HCP exec roles. One stack per account. Prod is - * 011934824531, workspace payments-dashboard-prod, project seahaven-prod. - * Dev is 710827005802, workspace payments-dashboard-dev, project seahaven-dev. + * HCP exec roles. One stack per account. Prod is 011934824531 and also + * hosts the seahaven-site apply and plan roles. Dev is 710827005802 and + * stays payments-dashboard only. + * + * payments-dashboard: workspace payments-dashboard-prod, project + * seahaven-prod, or workspace payments-dashboard-dev, project seahaven-dev. * * hcptf-payments-dashboard, hcptf-payments-dashboard-plan, and * payments-dashboard-lambda-boundary already existed in both accounts and @@ -37,12 +41,27 @@ export interface SeahavenHcptfStackProps extends cdk.StackProps { * Default is the managed-policy template. */ importExisting?: boolean; + /** Prod only. Fold seahaven-site exec roles into this stack. */ + includeSeahavenSite?: boolean; + /** + * Synthesize the seahaven-site import template. Set from + * `-c hcptfSiteImport=true`. Omits site role tags and site outputs. + */ + siteImportExisting?: boolean; } export class SeahavenHcptfStack extends cdk.Stack { constructor(scope: Construct, id: string, props: SeahavenHcptfStackProps) { super(scope, id, props); + if (props.importExisting && props.siteImportExisting) { + throw new Error("hcptfPaymentsImport and hcptfSiteImport cannot both be set"); + } paymentsDashboard(this, props); + if (props.includeSeahavenSite) { + new SeahavenSiteRoles(this, "SeahavenSite", { + importExisting: props.siteImportExisting === true, + }); + } cdk.Aspects.of(this).add(new HcptfPolicyAspect(props.importExisting === true)); } } diff --git a/lib/seahaven-site-hcptf-stack.ts b/lib/seahaven-site-hcptf-stack.ts index 9c4ff27..094d195 100644 --- a/lib/seahaven-site-hcptf-stack.ts +++ b/lib/seahaven-site-hcptf-stack.ts @@ -1,25 +1,43 @@ import * as cdk from "aws-cdk-lib"; import * as iam from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; -import { HcptfPolicyAspect } from "./hcptf-policy-aspect"; /** * Prod exec roles for the seahaven-site HCP workspace (PLAT-225). * - * These roles already exist and were imported into this stack. Do not - * create them. A plain create fails because the roles already exist. - * The stack is on the prod deploy job. + * Nested in the prod seahaven-hcptf stack. These roles already exist. + * Do not create them. A plain create fails because the roles already exist. * - * Import identifiers were the role names `hcptf-seahaven-site` and - * `hcptf-seahaven-site-plan`. Construct ids stay ApplyRole and PlanRole. + * Import identifiers are the role names `hcptf-seahaven-site` and + * `hcptf-seahaven-site-plan`, plus the three /tf-managed/ policy ARNs. + * Construct ids stay ApplyRole and PlanRole under SeahavenSite. * Inline policies are managed policies at /tf-managed/. Do not rename * the roles. + * + * `importExisting` is the `-c hcptfSiteImport=true` template. It omits + * role tags and the role ARN outputs. CloudFormation rejects both on an + * IAM role import. The steady-state template adds them back. */ -export class SeahavenSiteHcptfStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); +export interface SeahavenSiteRolesProps { + /** Omit role tags and outputs. Set from `-c hcptfSiteImport=true`. */ + importExisting?: boolean; +} - const account = this.account; +export class SeahavenSiteRoles extends Construct { + constructor(scope: Construct, id: string, props: SeahavenSiteRolesProps = {}) { + super(scope, id); + + const importing = props.importExisting === true; + // Overrides the parent stack's Project=payments-dashboard tag. + cdk.Tags.of(this).add("Project", "seahaven-site", { priority: 200 }); + if (importing) { + const roleOnly = { priority: 300, includeResourceTypes: ["AWS::IAM::Role"] }; + cdk.Tags.of(this).remove("Project", roleOnly); + cdk.Tags.of(this).remove("Owner", roleOnly); + cdk.Tags.of(this).remove("ManagedBy", roleOnly); + } + + const account = cdk.Stack.of(this).account; const deployRole = `arn:aws:iam::${account}:role/tf-managed/githubdeploy-seahaven-site`; const boundary = `arn:aws:iam::${account}:policy/tf-managed/seahaven-site-githubdeploy-boundary`; const bucket = "arn:aws:s3:::seahaven-site-prod"; @@ -62,7 +80,7 @@ export class SeahavenSiteHcptfStack extends cdk.Stack { maxSessionDuration: 3600, assumeRolePolicyDocument: trust(hcpOidc, "apply"), managedPolicyArns: [iamPolicy.ref, services.ref], - tags: roleTags(), + ...(importing ? {} : { tags: roleTags() }), }); retain(apply); @@ -74,17 +92,16 @@ export class SeahavenSiteHcptfStack extends cdk.Stack { "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess", planRefresh.ref, ], - tags: roleTags(), + ...(importing ? {} : { tags: roleTags() }), }); retain(plan); - new cdk.CfnOutput(this, "ApplyRoleArn", { value: apply.attrArn }); - new cdk.CfnOutput(this, "PlanRoleArn", { value: plan.attrArn }); - - cdk.Tags.of(this).add("Project", "seahaven-site"); - cdk.Tags.of(this).add("Owner", "adam@seahavenind.com"); - cdk.Tags.of(this).add("ManagedBy", "cdk"); - cdk.Aspects.of(this).add(new HcptfPolicyAspect()); + if (!importing) { + const applyArn = new cdk.CfnOutput(this, "ApplyRoleArn", { value: apply.attrArn }); + const planArn = new cdk.CfnOutput(this, "PlanRoleArn", { value: plan.attrArn }); + applyArn.overrideLogicalId("SeahavenSiteApplyRoleArn"); + planArn.overrideLogicalId("SeahavenSitePlanRoleArn"); + } } } diff --git a/lib/terraform-substrate/terraform-substrate.template.yaml b/lib/terraform-substrate/terraform-substrate.template.yaml index 2fecce8..e9effda 100644 --- a/lib/terraform-substrate/terraform-substrate.template.yaml +++ b/lib/terraform-substrate/terraform-substrate.template.yaml @@ -78,7 +78,7 @@ Description: >- # those roles (PLAT-144/PLAT-146). The six imported prod pairs are removed # here. Their previous DeletionPolicy is Retain, so CloudFormation forgets # them (PLAT-147). hcptf-sh-openswe-traces was already forgotten. -# seahaven-site lives in seahaven-site-hcptf. External-dev +# seahaven-site lives in seahaven-hcptf. External-dev # SHOC roles below remain in this template (PLAT-148). All remaining subs are # exact StringEquals (never StringLike, never a wildcarded run_phase). #