feat(iam): add external-dev backend Terraform substrate (#131)
Some checks are pending
Deploy / deploy-management (push) Waiting to run
Deploy / deploy-external-dev (push) Waiting to run
Deploy / deploy-security (push) Waiting to run
Deploy / deploy-dev (push) Waiting to run
Deploy / deploy-prod (push) Waiting to run

* feat(iam): add external-dev backend terraform substrate

* fix(iam): require boundaries for SHOC policy writes
This commit is contained in:
Adam Moussa 2026-08-29 21:04:40 +00:00 • committed by GitHub
parent ee233379dd
commit dba0871587
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1232 additions and 23 deletions

2
.gitignore vendored
View file

@ -1,5 +1,5 @@
node_modules/ node_modules/
cdk.out/ cdk.out*/
*.js *.js
*.d.ts *.d.ts
*.js.map *.js.map

131
README.md
View file

@ -18,7 +18,8 @@ like any other stack.
> only) until 2026-07-14, when the external-dev member baseline was merged in > only) until 2026-07-14, when the external-dev member baseline was merged in
> and the repo renamed. Deployed CloudFormation stack names are unchanged. > and the repo renamed. Deployed CloudFormation stack names are unchanged.
Stacks (deployed by the CD workflow — one job per target account): Stacks (normally deployed by one CD job per target account; staged exceptions
are noted):
| Stack | Account | Region | Purpose | | Stack | Account | Region | Purpose |
|---|---|---|---| |---|---|---|---|
@ -30,6 +31,7 @@ Stacks (deployed by the CD workflow — one job per target account):
| `seahaven-backup-offsite` | 328440206208 | us-west-2 | Governance-locked offsite copy vault (C-7) | | `seahaven-backup-offsite` | 328440206208 | us-west-2 | Governance-locked offsite copy vault (C-7) |
| `seahaven-org-governance` | 328440206208 | us-east-1 | AWS Organizations OU tree + SCPs (incl. the cdk-imported external-dev guardrails) | | `seahaven-org-governance` | 328440206208 | us-east-1 | AWS Organizations OU tree + SCPs (incl. the cdk-imported external-dev guardrails) |
| `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-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` | 396287094661 | us-east-1 | Staged manually until SHOC role imports complete; HCP roles/deploy boundaries for the backend rehearsal, referencing the existing OIDC provider |
| `seahaven-security-baseline` | 001520130573 | us-east-1 | Member-account baseline for the delegated security-admin account (same construct set) | | `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) | | `seahaven-dev-baseline` | 710827005802 | us-east-1 | Member-account baseline for internal dev/staging (org-managed detection — no local GuardDuty/SecurityHub) |
| `seahaven-prod-baseline` | 011934824531 | us-east-1 | Member-account baseline for production workloads (org-managed detection; mgmt account frozen for new workloads) | | `seahaven-prod-baseline` | 011934824531 | us-east-1 | Member-account baseline for production workloads (org-managed detection; mgmt account frozen for new workloads) |
@ -67,6 +69,7 @@ the TypeScript source — no separate compile step needed for `cdk synth` /
| `prod-baseline` | `seahaven-prod-baseline` | 011934824531 | us-east-1 | `lib/member-baseline-stack.ts` (orgManagedDetection) | | `prod-baseline` | `seahaven-prod-baseline` | 011934824531 | us-east-1 | `lib/member-baseline-stack.ts` (orgManagedDetection) |
| `deploy-substrate-prod` | `seahaven-deploy-substrate` | 011934824531 | us-east-1 | `lib/deploy-substrate-stack.ts` | | `deploy-substrate-prod` | `seahaven-deploy-substrate` | 011934824531 | us-east-1 | `lib/deploy-substrate-stack.ts` |
| `deploy-substrate-dev` | `seahaven-deploy-substrate` | 710827005802 | us-east-1 | `lib/deploy-substrate-stack.ts` | | `deploy-substrate-dev` | `seahaven-deploy-substrate` | 710827005802 | us-east-1 | `lib/deploy-substrate-stack.ts` |
| `terraform-substrate-external-dev` | `seahaven-terraform-substrate` | 396287094661 | us-east-1 | `lib/terraform-substrate-stack.ts` |
| `dynamodb-cmk-prod` | `seahaven-dynamodb-cmk` | 011934824531 | us-east-1 | `lib/dynamodb-cmk-stack.ts` | | `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` | | `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` | | `app-web-acl-prod` | `seahaven-app-web-acl` | 011934824531 | us-east-1 | `lib/app-web-acl-stack.ts` |
@ -214,9 +217,11 @@ created with the boundary already attached.
`lib/terraform-substrate-stack.ts` + `lib/terraform-substrate/terraform-substrate.template.yaml` `lib/terraform-substrate-stack.ts` + `lib/terraform-substrate/terraform-substrate.template.yaml`
deploy `seahaven-terraform-substrate` into each member account that hosts deploy `seahaven-terraform-substrate` into each member account that hosts
Terraform-managed workloads (currently seahaven-prod and seahaven-dev; never Terraform-managed workloads (currently seahaven-prod, seahaven-dev, and
mgmt — mgmt stays SAM until its stacks migrate out). It contains only the external-dev; never mgmt — mgmt stays SAM until its stacks migrate out).
shared account-level plumbing: Prod/dev use the shared IAM-management policy. External-dev references its
existing `app.terraform.io` provider and carries only exact SHOC
import/adoption roles:
- the `app.terraform.io` OIDC identity provider (audience - the `app.terraform.io` OIDC identity provider (audience
`aws.workload.identity`; Retain — it is the federation anchor for every `aws.workload.identity`; Retain — it is the federation anchor for every
@ -226,7 +231,19 @@ shared account-level plumbing:
`seahaven-lambda-execution-boundary` allow-list owned by the `seahaven-lambda-execution-boundary` allow-list owned by the
deploy-substrate stack — hence the explicit stack dependency in deploy-substrate stack — hence the explicit stack dependency in
`bin/app.ts`) plus the `DenyBoundaryTampering` / `DenyBoundaryPolicyEdit` `bin/app.ts`) plus the `DenyBoundaryTampering` / `DenyBoundaryPolicyEdit`
/ `DenySelfMutation` backstops. / `DenySelfMutation` backstops,
- external-dev-only deploy boundaries
`shoc-backend-{tf-poc,dev,staging}-deploy-boundary`. Each is the maximum
current policy for one exact `githubdeploy-shoc-backend-*` role. Dev
temporarily retains its live broad `elasticbeanstalk-*` S3 grants so
boundary attachment cannot regress deployment before the separately
reviewed policy narrowing,
- external-dev-only runtime boundaries
`shoc-backend-{tf-poc,dev,staging}-runtime-boundary`. These retain only the
account-scoped S3, environment health/log, and X-Ray portions of
`AWSElasticBeanstalkWebTier`, plus each environment's exact secrets/KMS/STS
data plane. They deliberately exclude the managed policy's 2026
Bedrock/Marketplace additions.
**This policy derives from `seahaven-cfn-exec-iam-management` but is **This policy derives from `seahaven-cfn-exec-iam-management` but is
deliberately stricter — it is not a mirror.** The 2026-07-30 security review deliberately stricter — it is not a mirror.** The 2026-07-30 security review
@ -255,6 +272,110 @@ deliberately NOT pre-provisioned — they are appended to the template at each
stack's migration time so an account never carries trust for workspaces that stack's migration time so an account never carries trust for workspaces that
do not deploy to it. do not deploy to it.
**External-dev SHOC role adoption is a staged CloudFormation import, not a
normal first deploy.** Exactly four roles exist today:
`hcptf-shoc-backend-{dev,staging}` and their `-plan` partners. The tf-poc pair
does not exist. Two independent CDK contexts make each transition explicit:
`enableShocBackendPocRoles` and `enableShocBackendLiveRoles`. Both are
version-controlled as `false` in `cdk.json` for the initial rollout.
`terraform-substrate-external-dev` is deliberately absent from the automatic
external-dev deploy job during this sequence; `external-dev-baseline` remains
automatic and unchanged.
1. Create the role-free base stack:
```bash
npx cdk deploy terraform-substrate-external-dev \
-c enableShocBackendPocRoles=false \
-c enableShocBackendLiveRoles=false
```
`CreateOIDCProvider=false` is fixed in `bin/app.ts`; the external-dev
account therefore creates neither the existing provider, SHOC roles, nor
the prod/dev-only shared IAM policy. The base stack does create all six
retained external-dev deploy/runtime boundary policies.
2. Set `enableShocBackendPocRoles` to `true` in `cdk.json`, leave the live gate
`false`, review the synthesized two-role addition, then run the normal
external-dev stack update. This creates only the new tf-poc HCP plan/apply
pair. The retained POC CDK stack references
`shoc-backend-tf-poc-deploy-boundary` when it creates
`githubdeploy-shoc-backend-tf-poc` and
`shoc-backend-tf-poc-runtime-boundary` when it creates the POC runtime
role; do not attach the generic account execution boundary to either role.
3. Prove both tf-poc HCP assumptions and the retained POC import rehearsal
before touching the live-role ownership boundary.
4. In a separately approved administrator/CDK migration, tag the existing HCP
apply roles first:
`hcptf-shoc-backend-dev` gets
`HcpTerraformWorkspace=shoc-backend-dev`, and
`hcptf-shoc-backend-staging` gets
`HcpTerraformWorkspace=shoc-backend-staging`. Next attach
`shoc-backend-dev-deploy-boundary` and
`shoc-backend-staging-deploy-boundary` to the exact `githubdeploy-*` roles,
and attach `shoc-backend-dev-runtime-boundary` /
`shoc-backend-staging-runtime-boundary` to the exact runtime roles. Verify
the boundary ceilings before adding the matching manager tag to either
target `githubdeploy-*` role. The POC CDK
creates its deploy role with `HcpTerraformWorkspace=shoc-backend-tf-poc`;
the substrate-created POC apply role already carries the same principal
tag. Verify each effective deployment action before continuing. HCP remains
blocked while a target tag is missing/different or the target lacks its
exact dedicated boundary, so a partial migration cannot authorize policy
writes. Complete both runtime/deploy boundary attachments before workload
imports. HCP apply roles deliberately have no
`iam:PutRolePermissionsBoundary` or boundary-policy mutation permissions.
5. In the backend bootstrap, add Terraform `removed` blocks with
`destroy = false` for only the four dev/staging HCP roles and their inline
policies. Apply and verify Terraform state no longer owns them while all
four physical roles and ARNs remain unchanged.
6. Set both contexts to `true`, synthesize with
`npx cdk synth terraform-substrate-external-dev`, and create a
CloudFormation **IMPORT** change set for the four existing
`AWS::IAM::Role` resources by exact role name. Do not run a normal
CREATE/UPDATE change set for this ownership transition. The POC gate must
remain true so the already-managed pair stays in the template. Import
records ownership; it does not update existing role properties or inline
policies.
7. Run a separate, reviewed CloudFormation reconcile update after import and
before switching workspace credentials. Each current live role has one
inline policy: `shoc-backend-dev-import-plan`,
`shoc-backend-dev-import-apply`,
`shoc-backend-staging-import-plan`, or
`shoc-backend-staging-import-apply`. Existing descriptions and tags are
inventoried in the backend handoff. Reconcile those explicit differences
to the final baseline shape without replacing a role.
8. After reconcile and HCP assumption proof, keep both context values committed
as `true`. Every subsequent normal deployment must synthesize all six
roles. Never return either gate to false as a rollback mechanism; Retain
protects the physical role but removing it from the stack abandons
CloudFormation ownership.
9. Only after all imports/reconciliation complete and both context defaults
are permanently `true`, add `terraform-substrate-external-dev` back to the
external-dev workflow stack selector. Until then all substrate operations
are deliberate manual deploy/import actions.
10. Retire the backend bootstrap only after the POC pair and all four imported
live roles are proven under this stack. Role deletion/recreation is never a
migration step.
The external-dev apply roles intentionally omit role create/delete,
managed-policy attach/detach, trust or boundary mutation, `iam:PassRole`, and
secret-value APIs. IAM writes are limited to exact-role inline-policy and
ordinary tag updates plus exact-profile tags; role descriptions remain stable
and HCP receives no `UpdateRole` or `UpdateRoleDescription`. The SCP permits
only the three enumerated HCP apply roles to mutate a `githubdeploy-*` role
whose locked `HcpTerraformWorkspace` resource tag equals the caller's immutable
principal tag. Adding or changing that manager tag remains administrator/CDK
only. POC DNS and certificate access is tag/name constrained because their
physical IDs are allocated by the temporary retained CDK stack before
Terraform imports them. Dev and staging DNS writes are pinned to their existing
hosted-zone IDs and API record names.
Current compact policy-document sizes are 1,387 / 1,873 / 1,844 characters for
the POC/dev/staging deploy boundaries and 1,176 / 1,779 / 1,656 for their
runtime boundaries, each below IAM's 6,144-character managed-policy limit. The
external-dev IAM guardrail SCP is 4,922 compact characters against its
5,120-character Organizations limit; keep size assertions in every change.
**HCP Terraform layout (org-level setup, console):** one org `seahaven` **HCP Terraform layout (org-level setup, console):** one org `seahaven`
(free tier: 500 managed resources, 1 concurrent run); one HCP **project per (free tier: 500 managed resources, 1 concurrent run); one HCP **project per
AWS account** (`seahaven-prod`, `seahaven-dev`); one **workspace per stack** AWS account** (`seahaven-prod`, `seahaven-dev`); one **workspace per stack**

View file

@ -31,6 +31,13 @@ const PROD_VPC_IDS = [
const app = new cdk.App(); const app = new cdk.App();
const contextBoolean = (key: string): boolean => {
const value = app.node.tryGetContext(key);
if (value === true || value === "true") return true;
if (value === false || value === "false" || value === undefined) return false;
throw new Error(`${key} must be true or false`);
};
new AccountBaselineStack(app, "account-baseline", { new AccountBaselineStack(app, "account-baseline", {
stackName: "seahaven-account-baseline", stackName: "seahaven-account-baseline",
env: { account: ACCOUNT, region: "us-east-1" }, env: { account: ACCOUNT, region: "us-east-1" },
@ -190,8 +197,10 @@ const deploySubstrateDev = new DeploySubstrateStack(app, "deploy-substrate-dev",
// OIDC provider + the shared boundary-gated guardrail policy // OIDC provider + the shared boundary-gated guardrail policy
// (seahaven-hcptf-iam-management) that per-workspace apply roles attach. // (seahaven-hcptf-iam-management) that per-workspace apply roles attach.
// Per-workspace hcptf-* roles are appended to the template at each stack's // Per-workspace hcptf-* roles are appended to the template at each stack's
// migration time, never here. prod/dev ONLY — mgmt stays SAM (Terraform POC // migration time, never here. prod/dev/external-dev ONLY — mgmt stays SAM
// decision 2026-07-30; the mgmt POC substrate was rolled back the same day). // (Terraform POC decision 2026-07-30; the mgmt POC substrate was rolled back
// the same day). External-dev references its existing provider and uses
// workload-specific inline policies instead of the shared IAM manager.
// The guardrail policy names the seahaven-lambda-execution-boundary ARN only // The guardrail policy names the seahaven-lambda-execution-boundary ARN only
// inside Condition strings, so CFN infers no creation edge — the explicit // inside Condition strings, so CFN infers no creation edge — the explicit
// dependency below guarantees the deploy-substrate stack (which owns the // dependency below guarantees the deploy-substrate stack (which owns the
@ -227,6 +236,21 @@ const terraformSubstrateDev = new TerraformSubstrateStack(
); );
terraformSubstrateDev.addStackDependency(deploySubstrateDev); terraformSubstrateDev.addStackDependency(deploySubstrateDev);
// External-dev already has app.terraform.io federation. Both role gates start
// false in cdk.json: POC is enabled by a normal update; dev/staging only by
// CloudFormation import after Terraform relinquishes those four live roles.
const terraformSubstrateExternalDev = new TerraformSubstrateStack(
app,
"terraform-substrate-external-dev",
{
stackName: "seahaven-terraform-substrate",
env: { account: EXTERNAL_DEV_ACCOUNT, region: "us-east-1" },
createOidcProvider: false,
enableShocBackendPocRoles: contextBoolean("enableShocBackendPocRoles"),
enableShocBackendLiveRoles: contextBoolean("enableShocBackendLiveRoles"),
},
);
// ── Shared DynamoDB CMK (INFRA-95 / M-3) ───────────────────────────────────── // ── Shared DynamoDB CMK (INFRA-95 / M-3) ─────────────────────────────────────
// Dedicated, standalone stack so the customer-managed key for sensitive // Dedicated, standalone stack so the customer-managed key for sensitive
// finance/PII DynamoDB tables is an independent shared dependency for the owning // finance/PII DynamoDB tables is an independent shared dependency for the owning

View file

@ -17,6 +17,8 @@
"context": { "context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true, "@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": ["aws"] "@aws-cdk/core:target-partitions": ["aws"],
"enableShocBackendPocRoles": false,
"enableShocBackendLiveRoles": false
} }
} }

View file

@ -12,11 +12,18 @@
} }
}, },
{ {
"Sid": "ProtectBoundaryPolicyFromEdits", "Sid": "ProtectExecutionBoundary",
"Effect": "Deny", "Effect": "Deny",
"Action": ["iam:CreatePolicyVersion", "iam:SetDefaultPolicyVersion", "iam:DeletePolicy", "iam:DeletePolicyVersion"], "Action": ["iam:CreatePolicyVersion", "iam:SetDefaultPolicyVersion", "iam:DeletePolicy", "iam:DeletePolicyVersion"],
"Resource": "arn:aws:iam::396287094661:policy/external-dev-execution-boundary" "Resource": "arn:aws:iam::396287094661:policy/external-dev-execution-boundary"
}, },
{
"Sid": "ProtectShocBoundaries",
"Effect": "Deny",
"Action": ["iam:CreatePolicyVersion", "iam:SetDefaultPolicyVersion", "iam:DeletePolicy", "iam:DeletePolicyVersion"],
"Resource": "arn:aws:iam::396287094661:policy/shoc-backend-*-boundary",
"Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*"] } }
},
{ {
"Sid": "DenyAlteringPermissionsBoundaries", "Sid": "DenyAlteringPermissionsBoundaries",
"Effect": "Deny", "Effect": "Deny",
@ -32,18 +39,61 @@
"Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole"] } } "Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole"] } }
}, },
{ {
"Sid": "ProtectPrivilegedRoles", "Sid": "ProtectNonGithubPrivilegedRoles",
"Effect": "Deny", "Effect": "Deny",
"Action": ["iam:UpdateAssumeRolePolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:DeleteRole", "iam:UpdateRole", "iam:TagRole", "iam:UntagRole"], "Action": ["iam:UpdateAssumeRolePolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:DeleteRole", "iam:UpdateRole", "iam:TagRole", "iam:UntagRole"],
"Resource": [ "Resource": [
"arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/OrganizationAccountAccessRole",
"arn:aws:iam::396287094661:role/cdk-hnb659fds-*", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*",
"arn:aws:iam::396287094661:role/githubdeploy-*",
"arn:aws:iam::396287094661:role/seahaven-extdev-config-recorder-role", "arn:aws:iam::396287094661:role/seahaven-extdev-config-recorder-role",
"arn:aws:iam::396287094661:role/seahaven-extdev-config-custom-resource-role", "arn:aws:iam::396287094661:role/seahaven-extdev-config-custom-resource-role",
"arn:aws:iam::396287094661:role/aws-service-role/*" "arn:aws:iam::396287094661:role/aws-service-role/*"
], ],
"Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*"] } } "Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*"] } }
},
{
"Sid": "ProtectDeploymentPrincipalLifecycle",
"Effect": "Deny",
"Action": ["iam:UpdateAssumeRolePolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:DeleteRolePolicy", "iam:DeleteRole", "iam:UpdateRole", "iam:UpdateRoleDescription", "iam:PutRolePermissionsBoundary", "iam:DeleteRolePermissionsBoundary"],
"Resource": ["arn:aws:iam::396287094661:role/githubdeploy-*", "arn:aws:iam::396287094661:role/hcptf-*"],
"Condition": { "ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*"] } }
},
{
"Sid": "ProtectGithubRoleMetadata",
"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"] } }
},
{
"Sid": "DenyUnmanagedGithubRole",
"Effect": "Deny",
"Action": ["iam:PutRolePolicy", "iam:TagRole", "iam:UntagRole"],
"Resource": "arn:aws:iam::396287094661:role/githubdeploy-*",
"Condition": {
"ArnLike": { "aws:PrincipalArn": ["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"] },
"Null": { "aws:ResourceTag/HcpTerraformWorkspace": "true" }
}
},
{
"Sid": "EnforceGithubRoleManagerTag",
"Effect": "Deny",
"Action": ["iam:PutRolePolicy", "iam:TagRole", "iam:UntagRole"],
"Resource": "arn:aws:iam::396287094661:role/githubdeploy-*",
"Condition": {
"ArnLike": { "aws:PrincipalArn": ["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"] },
"StringNotEquals": { "aws:ResourceTag/HcpTerraformWorkspace": "${aws:PrincipalTag/HcpTerraformWorkspace}" }
}
},
{
"Sid": "LockHcpTerraformWorkspaceTag",
"Effect": "Deny",
"Action": ["iam:TagRole", "iam:UntagRole"],
"Resource": "arn:aws:iam::396287094661:role/*",
"Condition": {
"ArnNotLike": { "aws:PrincipalArn": ["arn:aws:iam::396287094661:role/OrganizationAccountAccessRole", "arn:aws:iam::396287094661:role/cdk-hnb659fds-*"] },
"ForAnyValue:StringEquals": { "aws:TagKeys": "HcpTerraformWorkspace" }
}
} }
] ]
} }

View file

@ -17,14 +17,26 @@ export interface TerraformSubstrateStackProps extends cdk.StackProps {
* remove the orphaned provider or redeploy with this false. * remove the orphaned provider or redeploy with this false.
*/ */
createOidcProvider?: boolean; createOidcProvider?: boolean;
/**
* Enable the two SHOC backend tf-poc HCP roles. Defaults false so the
* external-dev base-stack create is role-free.
*/
enableShocBackendPocRoles?: boolean;
/**
* Enable the four existing SHOC backend dev/staging HCP roles. Defaults
* false because these names must enter the stack through CloudFormation
* resource import, never a normal create/update.
*/
enableShocBackendLiveRoles?: boolean;
} }
/** /**
* Per-account HCP Terraform deploy substrate: the shared account-level * Per-account HCP Terraform deploy substrate: the conditional
* resources every Terraform workspace pipeline needs - * app.terraform.io OIDC provider, the prod/dev shared boundary-gated IAM
* - app.terraform.io OIDC identity provider (conditional, see props), and * manager, and reviewed per-workspace role pairs. External-dev conditions out
* - `seahaven-hcptf-iam-management`, the shared boundary-gated IAM * the shared manager and uses exact inline policies for its SHOC import roles.
* guardrail policy every per-workspace APPLY role attaches.
* *
* Deliberately NOT here: per-workspace hcptf-<stack> / hcptf-<stack>-plan * Deliberately NOT here: per-workspace hcptf-<stack> / hcptf-<stack>-plan
* roles. Those are appended to the template at each stack's migration time * roles. Those are appended to the template at each stack's migration time
@ -58,6 +70,10 @@ export class TerraformSubstrateStack extends cdk.Stack {
), ),
parameters: { parameters: {
CreateOIDCProvider: props?.createOidcProvider === false ? "false" : "true", CreateOIDCProvider: props?.createOidcProvider === false ? "false" : "true",
EnableShocBackendPocRoles:
props?.enableShocBackendPocRoles === true ? "true" : "false",
EnableShocBackendLiveRoles:
props?.enableShocBackendLiveRoles === true ? "true" : "false",
}, },
}); });

File diff suppressed because it is too large Load diff