mirror of
https://github.com/Sea-Haven-Industries/seahaven-account-baseline.git
synced 2026-08-04 16:56:14 +00:00
feat(iac): add per-account HCP Terraform deploy substrate for prod and dev
New stack seahaven-terraform-substrate (instances terraform-substrate-prod + terraform-substrate-dev): app.terraform.io OIDC provider and the shared boundary-gated guardrail policy seahaven-hcptf-iam-management that per-workspace Terraform apply roles attach at migration time. No roles are pre-provisioned (accumulator pattern, parallel to githubdeploy-*). Guardrail statements mirror seahaven-cfn-exec-iam-management byte-identically except DenySelfMutation, whose scope extends to hcptf-* alongside the GitHub-substrate principals. Explicit stack dependency on the same-account deploy-substrate stack (boundary ARN appears only in Condition strings, so CFN infers no edge).
This commit is contained in:
parent
47a6ca2939
commit
ea27635ef2
5 changed files with 424 additions and 4 deletions
4
.github/workflows/deploy.yaml
vendored
4
.github/workflows/deploy.yaml
vendored
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@0170a57c0d99b542cfafd1f3e1d369c32643f486 # v1.0.2
|
||||
with:
|
||||
node-version: "24"
|
||||
stacks: "dev-baseline deploy-substrate-dev"
|
||||
stacks: "dev-baseline deploy-substrate-dev terraform-substrate-dev"
|
||||
stack-name: "seahaven-dev-baseline"
|
||||
secrets:
|
||||
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN_DEV }}
|
||||
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@0170a57c0d99b542cfafd1f3e1d369c32643f486 # v1.0.2
|
||||
with:
|
||||
node-version: "24"
|
||||
stacks: "prod-baseline dynamodb-cmk-prod alarm-topic-prod deploy-substrate-prod"
|
||||
stacks: "prod-baseline dynamodb-cmk-prod alarm-topic-prod deploy-substrate-prod terraform-substrate-prod"
|
||||
stack-name: "seahaven-prod-baseline"
|
||||
secrets:
|
||||
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN_PROD }}
|
||||
|
|
|
|||
78
README.md
78
README.md
|
|
@ -187,6 +187,84 @@ created with the boundary already attached.
|
|||
4. Merge; the substrate deploys via CD. Per-repo deploy roles and app stacks
|
||||
follow the cross-account migration playbook from there.
|
||||
|
||||
### Terraform deploy substrate (per account)
|
||||
|
||||
`lib/terraform-substrate-stack.ts` + `lib/terraform-substrate/terraform-substrate.template.yaml`
|
||||
deploy `seahaven-terraform-substrate` into each member account that hosts
|
||||
Terraform-managed workloads (currently seahaven-prod and seahaven-dev; never
|
||||
mgmt — mgmt stays SAM until its stacks migrate out). It contains only the
|
||||
shared account-level plumbing:
|
||||
|
||||
- the `app.terraform.io` OIDC identity provider (audience
|
||||
`aws.workload.identity`; Retain — it is the federation anchor for every
|
||||
future `hcptf-*` role),
|
||||
- the `seahaven-hcptf-iam-management` guardrail policy: the boundary-gated
|
||||
IAM role lifecycle (conditioned on `seahaven-lambda-execution-boundary`,
|
||||
owned by the deploy-substrate stack — hence the explicit stack dependency
|
||||
in `bin/app.ts`) plus the `DenyBoundaryTampering` / `DenyBoundaryPolicyEdit`
|
||||
/ `DenySelfMutation` backstops, mirroring `seahaven-cfn-exec-iam-management`.
|
||||
`DenySelfMutation` here covers `hcptf-*`, `github-cfn-execution-role` and
|
||||
`githubdeploy-*` — the Terraform path can never mutate either substrate's
|
||||
principals.
|
||||
|
||||
Per-workspace roles (`hcptf-<stack>` apply + `hcptf-<stack>-plan`) are
|
||||
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
|
||||
do not deploy to it.
|
||||
|
||||
**HCP Terraform layout (org-level setup, console):** one org `seahaven`
|
||||
(free tier: 500 managed resources, 1 concurrent run); one HCP **project per
|
||||
AWS account** (`seahaven-prod`, `seahaven-dev`); one **workspace per stack**
|
||||
(`<stack>-<env>`, one state file = one blast radius). Default execution mode
|
||||
Remote. Never use HCP's "Quick setup AWS dynamic credentials" button — it
|
||||
writes the single `TFC_AWS_RUN_ROLE_ARN`, which collapses the plan/apply role
|
||||
split this substrate exists to enforce.
|
||||
|
||||
**Migration checklist (per stack, in order):**
|
||||
|
||||
1. Create the workspace in the target account's HCP project
|
||||
(`<stack>-<env>`). Apply method **Manual**; automatic speculative plans on
|
||||
if VCS-connected (CLI `terraform plan` runs are inherently speculative).
|
||||
2. PR to this repo appending `hcptf-<stack>-plan` (read-only,
|
||||
ViewOnlyAccess-class, **no** IAM writes, **no** guardrail-policy attach)
|
||||
and `hcptf-<stack>` (attaches `seahaven-hcptf-iam-management` + stack-scoped
|
||||
service statements) to the substrate template. Trust: this account's
|
||||
`app.terraform.io` provider; `StringEquals` on
|
||||
`app.terraform.io:aud` = `aws.workload.identity` and on
|
||||
`app.terraform.io:sub` =
|
||||
`organization:seahaven:project:seahaven-<env>:workspace:<workspace>:run_phase:plan`
|
||||
(or `:apply`). Exact `StringEquals` only — never `StringLike`, never a
|
||||
wildcarded `run_phase` (a speculative PR plan must never hold write
|
||||
credentials). IAM roles = mandatory GPT-4.1 cross-review +
|
||||
`/sh-security-review` on the diff.
|
||||
3. After deploy, verify: both roles exist; `hcptf-<stack>` lists
|
||||
`seahaven-hcptf-iam-management` in `list-attached-role-policies`; trust
|
||||
subs match the live org/project/workspace names byte-for-byte; simulate
|
||||
the apply role against a `hcptf-*` ARN (expect `explicitDeny` from
|
||||
`DenySelfMutation`) and against a normal stack role name (expect
|
||||
`allowed`).
|
||||
4. Set **workspace-level** variables `TFC_AWS_PLAN_ROLE_ARN` +
|
||||
`TFC_AWS_APPLY_ROLE_ARN` (category env) to the verified role ARNs, plus
|
||||
`TFC_AWS_PROVIDER_AUTH=true`. Never project-scoped variable sets — the
|
||||
trust is pinned per workspace, so a shared set breaks every other
|
||||
workspace.
|
||||
5. Auto-apply stays OFF until the stack is sealed.
|
||||
|
||||
**Rollback (proven in mgmt 2026-07-30):** delete any `hcptf-*` roles first
|
||||
(they reference the provider), then the stack. The provider and guardrail
|
||||
policy are Retain — after a stack delete, remove the orphaned provider with
|
||||
`aws iam delete-open-id-connect-provider` and the policy with
|
||||
`aws iam delete-policy` once nothing references them. Workspaces with state
|
||||
must be migrated or destroyed HCP-side first; an OIDC provider deletion
|
||||
strands them mid-run, it does not clean them up.
|
||||
|
||||
**Verification of record for the guardrail policy** is mechanical
|
||||
reconciliation — tag-preserving YAML load of the template vs
|
||||
`get-policy-version` readback, sorted `json.dumps` compare per statement —
|
||||
same discipline as the deploy-substrate reconciliation (2026-07-27), not
|
||||
header-reading. The managed-policy document budget is 6,144 characters;
|
||||
measure before appending statements.
|
||||
|
||||
### CloudTrail (audit finding C-1)
|
||||
|
||||
| Resource | Logical ID | Notes |
|
||||
|
|
|
|||
38
bin/app.ts
38
bin/app.ts
|
|
@ -7,6 +7,7 @@ import { BackupOffsiteStack } from "../lib/backup-offsite-stack";
|
|||
import { BackupStack } from "../lib/backup-stack";
|
||||
import { RegionalBaselineStack } from "../lib/regional-baseline-stack";
|
||||
import { DeploySubstrateStack } from "../lib/deploy-substrate-stack";
|
||||
import { TerraformSubstrateStack } from "../lib/terraform-substrate-stack";
|
||||
import { DynamoDbCmkStack } from "../lib/dynamodb-cmk-stack";
|
||||
import { MemberBaselineStack } from "../lib/member-baseline-stack";
|
||||
import { OrgGovernanceStack } from "../lib/org-governance-stack";
|
||||
|
|
@ -171,18 +172,51 @@ new MemberBaselineStack(app, "prod-baseline", {
|
|||
// seahaven-lambda-execution-boundary policy both returned NoSuchEntity in
|
||||
// 011934824531 AND 710827005802, so the named creates cannot collide with
|
||||
// out-of-band copies.
|
||||
new DeploySubstrateStack(app, "deploy-substrate-prod", {
|
||||
const deploySubstrateProd = new DeploySubstrateStack(app, "deploy-substrate-prod", {
|
||||
stackName: "seahaven-deploy-substrate",
|
||||
env: { account: PROD_ACCOUNT, region: "us-east-1" },
|
||||
createOidcProvider: false,
|
||||
});
|
||||
|
||||
new DeploySubstrateStack(app, "deploy-substrate-dev", {
|
||||
const deploySubstrateDev = new DeploySubstrateStack(app, "deploy-substrate-dev", {
|
||||
stackName: "seahaven-deploy-substrate",
|
||||
env: { account: DEV_ACCOUNT, region: "us-east-1" },
|
||||
createOidcProvider: false,
|
||||
});
|
||||
|
||||
// ── Per-account HCP Terraform deploy substrate ───────────────────────────────
|
||||
// The Terraform analog of the GitHub Actions substrate above: app.terraform.io
|
||||
// OIDC provider + the shared boundary-gated guardrail policy
|
||||
// (seahaven-hcptf-iam-management) that per-workspace apply roles attach.
|
||||
// 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
|
||||
// decision 2026-07-30; the mgmt POC substrate was rolled back the same day).
|
||||
// The guardrail policy names the seahaven-lambda-execution-boundary ARN only
|
||||
// inside Condition strings, so CFN infers no creation edge — the explicit
|
||||
// dependency below guarantees the deploy-substrate stack (which owns the
|
||||
// boundary) lands first in any future account onboarding. First-create
|
||||
// precondition verified 2026-07-30: no app.terraform.io provider and no
|
||||
// hcptf-* roles in either account.
|
||||
const terraformSubstrateProd = new TerraformSubstrateStack(
|
||||
app,
|
||||
"terraform-substrate-prod",
|
||||
{
|
||||
stackName: "seahaven-terraform-substrate",
|
||||
env: { account: PROD_ACCOUNT, region: "us-east-1" },
|
||||
},
|
||||
);
|
||||
terraformSubstrateProd.addStackDependency(deploySubstrateProd);
|
||||
|
||||
const terraformSubstrateDev = new TerraformSubstrateStack(
|
||||
app,
|
||||
"terraform-substrate-dev",
|
||||
{
|
||||
stackName: "seahaven-terraform-substrate",
|
||||
env: { account: DEV_ACCOUNT, region: "us-east-1" },
|
||||
},
|
||||
);
|
||||
terraformSubstrateDev.addStackDependency(deploySubstrateDev);
|
||||
|
||||
// ── Shared DynamoDB CMK (INFRA-95 / M-3) ─────────────────────────────────────
|
||||
// Dedicated, standalone stack so the customer-managed key for sensitive
|
||||
// finance/PII DynamoDB tables is an independent shared dependency for the owning
|
||||
|
|
|
|||
42
lib/terraform-substrate-stack.ts
Normal file
42
lib/terraform-substrate-stack.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import * as cdk from "aws-cdk-lib";
|
||||
import * as cfninc from "aws-cdk-lib/cloudformation-include";
|
||||
import * as path from "path";
|
||||
import { Construct } from "constructs";
|
||||
|
||||
/**
|
||||
* Per-account HCP Terraform deploy substrate: the shared account-level
|
||||
* resources every Terraform workspace pipeline needs -
|
||||
* - app.terraform.io OIDC identity provider (always created; Phase-0
|
||||
* checks confirmed no account has one), and
|
||||
* - `seahaven-hcptf-iam-management`, the shared boundary-gated IAM
|
||||
* guardrail policy every per-workspace APPLY role attaches.
|
||||
*
|
||||
* Deliberately NOT here: per-workspace hcptf-<stack> / hcptf-<stack>-plan
|
||||
* roles. Those are appended to the template at each stack's migration time
|
||||
* (accumulator pattern, parallel to per-repo githubdeploy-* roles) so an
|
||||
* account never accumulates trust for workspaces that do not deploy to it.
|
||||
*
|
||||
* The IAM guardrail statements mirror seahaven-cfn-exec-iam-management in
|
||||
* lib/deploy-substrate/deploy-substrate.template.yaml - see the provenance
|
||||
* header in lib/terraform-substrate/terraform-substrate.template.yaml for
|
||||
* the reconciliation rule and the boundary-ARN coupling to the
|
||||
* seahaven-deploy-substrate stack (bin/app.ts carries the explicit
|
||||
* addStackDependency; the ARN reference alone creates no CFN edge).
|
||||
*/
|
||||
export class TerraformSubstrateStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
new cfninc.CfnInclude(this, "Substrate", {
|
||||
templateFile: path.join(
|
||||
__dirname,
|
||||
"terraform-substrate",
|
||||
"terraform-substrate.template.yaml",
|
||||
),
|
||||
});
|
||||
|
||||
cdk.Tags.of(this).add("Project", "account-baseline");
|
||||
cdk.Tags.of(this).add("Owner", "adam@seahavenind.com");
|
||||
cdk.Tags.of(this).add("ManagedBy", "cdk");
|
||||
}
|
||||
}
|
||||
266
lib/terraform-substrate/terraform-substrate.template.yaml
Normal file
266
lib/terraform-substrate/terraform-substrate.template.yaml
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
AWSTemplateFormatVersion: "2010-09-09"
|
||||
Description: >-
|
||||
Per-account HCP Terraform deploy substrate for Sea Haven Industries:
|
||||
the app.terraform.io OIDC identity provider and the shared boundary-gated
|
||||
IAM guardrail policy that every per-workspace Terraform APPLY role attaches.
|
||||
Per-workspace hcptf-* roles are NOT pre-provisioned — they are appended to
|
||||
this template at each stack's migration time.
|
||||
|
||||
# PROVENANCE / DESIGN SOURCE
|
||||
# Authored fresh 2026-07-30 (the mgmt Terraform POC's CLI-created provider and
|
||||
# hcptf-* roles were rolled back the same day, so there is no deployed source
|
||||
# to vendor). The IAM statement set in HcptfIamManagementPolicy MIRRORS the
|
||||
# reviewed seahaven-cfn-exec-iam-management pattern in
|
||||
# lib/deploy-substrate/deploy-substrate.template.yaml (boundary-gated
|
||||
# CreateRole/AttachRolePolicy/PutRolePolicy/PutRolePermissionsBoundary +
|
||||
# DenyBoundaryTampering / DenyBoundaryPolicyEdit / DenySelfMutation). If that
|
||||
# pattern changes in either file, reconcile BOTH in the same piece of work and
|
||||
# verify mechanically (tag-preserving YAML load + sorted JSON compare per
|
||||
# statement), never by reading headers.
|
||||
#
|
||||
# COUPLING: the boundary ARN referenced in the Conditions below is
|
||||
# seahaven-lambda-execution-boundary, created by the seahaven-deploy-substrate
|
||||
# stack in the same account. The reference is a literal !Sub string inside
|
||||
# Condition values, so CloudFormation infers NO ordering edge from it —
|
||||
# bin/app.ts carries an explicit addStackDependency on the same-account
|
||||
# deploy-substrate stack instead. The coupling is by NAME: if the boundary
|
||||
# policy is ever renamed or replaced, every Condition below (and the
|
||||
# deploy-substrate copy) must change in the same piece of work. INFRA-186
|
||||
# (per-workload boundary scoping) changes the boundary's CONTENT, not its ARN,
|
||||
# and does not touch this file.
|
||||
#
|
||||
# SIZE BUDGET: an attached managed policy document is capped at 6,144
|
||||
# characters (whitespace excluded). The statement set below is ~2.5 KB.
|
||||
# Measure before adding statements — len(json.dumps(doc,separators=(',',':')))
|
||||
# on the synthesized PolicyDocument — the same wall the role INLINE limit
|
||||
# (10,240 bytes) put the first deploy-substrate deploy into on 2026-07-27.
|
||||
#
|
||||
# PER-WORKSPACE ROLE ACCUMULATOR
|
||||
# At each stack's migration, a PR appends to this template:
|
||||
# - hcptf-<stack>-plan: read-only (ViewOnlyAccess-class), trust sub
|
||||
# organization:seahaven:project:seahaven-<env>:workspace:<workspace>:run_phase:plan
|
||||
# - hcptf-<stack>: apply role attaching HcptfIamManagementPolicy plus
|
||||
# stack-scoped service statements, trust sub ...run_phase:apply
|
||||
# All subs are exact StringEquals (never StringLike, never a wildcarded
|
||||
# run_phase — a speculative PR plan must never hold write credentials);
|
||||
# audience is aws.workload.identity. IAM role additions here are a mandatory
|
||||
# GPT-4.1 cross-review + /sh-security-review trigger. See the README
|
||||
# "Terraform substrate" section for the full migration checklist and the
|
||||
# rollback runbook.
|
||||
#
|
||||
# This template is deployed via lib/terraform-substrate-stack.ts
|
||||
# (cloudformation-include) as stack seahaven-terraform-substrate, once per
|
||||
# member account that hosts Terraform-managed workloads (currently
|
||||
# seahaven-prod 011934824531 and seahaven-dev 710827005802; NEVER mgmt —
|
||||
# mgmt stays SAM until its stacks migrate out).
|
||||
|
||||
Resources:
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HCP Terraform OIDC provider
|
||||
#
|
||||
# Always created: Phase-0 checks (2026-07-30) confirmed neither prod nor dev
|
||||
# has an app.terraform.io provider (the mgmt POC's copy was deleted in the
|
||||
# same-day rollback and never existed in the member accounts). An account
|
||||
# holds exactly ONE provider per URL.
|
||||
# ---------------------------------------------------------------------------
|
||||
TerraformCloudOIDCProvider:
|
||||
Type: AWS::IAM::OIDCProvider
|
||||
Properties:
|
||||
Url: https://app.terraform.io
|
||||
ClientIdList:
|
||||
# Default audience of HCP Terraform dynamic provider credentials
|
||||
# (TFC_AWS_WORKLOAD_IDENTITY_AUDIENCE). Trust policies pin this via
|
||||
# StringEquals on app.terraform.io:aud.
|
||||
- aws.workload.identity
|
||||
ThumbprintList:
|
||||
# AWS ignores thumbprints for issuers signed by a trusted root CA
|
||||
# (app.terraform.io qualifies) and secures trust via the CA bundle;
|
||||
# the property is populated because CloudFormation requires a value.
|
||||
# This is the thumbprint HashiCorp's own AWS setup documentation uses.
|
||||
- 9e99a48a9960b14926bb7f3b02e22da2b0ab7280
|
||||
# Every future hcptf-* role trusts this provider. Retain so deleting the
|
||||
# stack can never delete the account's Terraform federation anchor out
|
||||
# from under live workspaces.
|
||||
DeletionPolicy: Retain
|
||||
UpdateReplacePolicy: Retain
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared boundary-gated IAM guardrail policy (attached managed policy)
|
||||
#
|
||||
# Attached by every per-workspace Terraform APPLY role (hcptf-<stack>);
|
||||
# NEVER by plan roles (hcptf-<stack>-plan are read-only and hold no IAM
|
||||
# writes at all). Defined once here so all apply roles carry the identical
|
||||
# reviewed escalation control instead of per-role copies that can drift.
|
||||
#
|
||||
# PRIMARY ESCALATION CONTROL (same design as INFRA-97 on the SAM side):
|
||||
# every iam:CreateRole / AttachRolePolicy / PutRolePolicy is conditioned on
|
||||
# the target role carrying seahaven-lambda-execution-boundary, so a role
|
||||
# created by a Terraform apply can never exceed the boundary ceiling. The
|
||||
# POC security review confirmed the unconditioned alternative is critical:
|
||||
# iam:PutRolePolicy on Lambda exec roles + lambda:UpdateFunctionCode reads
|
||||
# every secret in the account.
|
||||
# ---------------------------------------------------------------------------
|
||||
HcptfIamManagementPolicy:
|
||||
Type: AWS::IAM::ManagedPolicy
|
||||
Properties:
|
||||
# Fixed name: future hcptf-* roles reference it by ARN, and a rename
|
||||
# would detach-and-replace mid-update. Treat a rename as a coordinated
|
||||
# migration, not an edit.
|
||||
ManagedPolicyName: seahaven-hcptf-iam-management
|
||||
Description: >-
|
||||
Boundary-gated IAM role lifecycle for per-workspace Terraform apply
|
||||
roles (hcptf-*), plus the explicit Deny backstops that keep the
|
||||
permissions boundary from being detached or rewritten and the deploy
|
||||
substrates' own principals from being mutated. Mirrors
|
||||
seahaven-cfn-exec-iam-management; reconcile changes across both.
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
# Create role — MUST attach boundary
|
||||
- Sid: IAMCreateRoleWithBoundary
|
||||
Effect: Allow
|
||||
Action:
|
||||
- iam:CreateRole
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
|
||||
Condition:
|
||||
StringEquals:
|
||||
"iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary"
|
||||
|
||||
# Attach managed policies — MUST have boundary already on role
|
||||
- Sid: IAMAttachPolicyWithBoundary
|
||||
Effect: Allow
|
||||
Action:
|
||||
- iam:AttachRolePolicy
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
|
||||
Condition:
|
||||
StringEquals:
|
||||
"iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary"
|
||||
|
||||
# Put inline policy — MUST have boundary already on role
|
||||
- Sid: IAMPutRolePolicyWithBoundary
|
||||
Effect: Allow
|
||||
Action:
|
||||
- iam:PutRolePolicy
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
|
||||
Condition:
|
||||
StringEquals:
|
||||
"iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary"
|
||||
|
||||
# Boundary management — SET only, never DELETE. For a delete, the
|
||||
# iam:PermissionsBoundary condition key resolves to the boundary
|
||||
# CURRENTLY on the target role, so a StringEquals grant would match
|
||||
# exactly the roles the gate protects and self-defeat it (verified
|
||||
# live against the mgmt SAM copy 2026-07-27). Terraform never needs
|
||||
# the delete: it SETS the boundary on roles it creates, and destroy
|
||||
# calls DeleteRole.
|
||||
- Sid: IAMPutPermissionsBoundary
|
||||
Effect: Allow
|
||||
Action:
|
||||
- iam:PutRolePermissionsBoundary
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
|
||||
Condition:
|
||||
StringEquals:
|
||||
"iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary"
|
||||
|
||||
# Explicit Deny backstop (AWS's NoBoundaryPolicyEdit/NoBoundaryDelete
|
||||
# delegation pattern). A Deny is required, not merely omitting the
|
||||
# Allow — any future Allow added to an apply role silently reopens
|
||||
# the escalation otherwise.
|
||||
- Sid: DenyBoundaryTampering
|
||||
Effect: Deny
|
||||
Action:
|
||||
- iam:DeleteRolePermissionsBoundary
|
||||
- iam:DeleteUserPermissionsBoundary
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:user/*"
|
||||
|
||||
# Whole seahaven-* policy family: this policy carries the Denies, so
|
||||
# it is a higher-value target than the boundary it protects. Safe to
|
||||
# scope broadly — no Terraform stack manages a seahaven-* managed
|
||||
# policy, and apply roles hold no iam:CreatePolicy.
|
||||
- Sid: DenyBoundaryPolicyEdit
|
||||
Effect: Deny
|
||||
Action:
|
||||
- iam:CreatePolicyVersion
|
||||
- iam:SetDefaultPolicyVersion
|
||||
- iam:DeletePolicyVersion
|
||||
- iam:DeletePolicy
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-*"
|
||||
|
||||
# Self-protection for BOTH deploy substrates' principals. Without
|
||||
# this the control is one API call from being undone —
|
||||
# IAMRoleReadAndDelete below grants iam:DetachRolePolicy on
|
||||
# Resource "*" unconditioned, so an apply role could detach this
|
||||
# very policy from itself. Scope covers the Terraform substrate's
|
||||
# own roles (hcptf-*) AND the GitHub Actions substrate's
|
||||
# (github-cfn-execution-role, githubdeploy-*): a Terraform apply
|
||||
# never legitimately manages any of them — hcptf-* roles are
|
||||
# managed by THIS stack via the CDK bootstrap execution role, the
|
||||
# GitHub-side roles by their own substrate/onboarding — so the Deny
|
||||
# costs nothing operationally and closes the same
|
||||
# UpdateAssumeRolePolicy-on-* repoint risk the SAM-side review
|
||||
# flagged, for every substrate principal reachable from this path.
|
||||
- Sid: DenySelfMutation
|
||||
Effect: Deny
|
||||
Action:
|
||||
- iam:AttachRolePolicy
|
||||
- iam:DeleteRole
|
||||
- iam:DeleteRolePolicy
|
||||
- iam:DeleteRolePermissionsBoundary
|
||||
- iam:DetachRolePolicy
|
||||
- iam:PutRolePolicy
|
||||
- iam:PutRolePermissionsBoundary
|
||||
- iam:UpdateAssumeRolePolicy
|
||||
- iam:UpdateRole
|
||||
- iam:UpdateRoleDescription
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/hcptf-*"
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/github-cfn-execution-role"
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/githubdeploy-*"
|
||||
|
||||
# Read / tag / delete role and policy — no boundary condition needed
|
||||
# (delete cannot be boundary-conditioned, see IAMPutPermissionsBoundary;
|
||||
# DenySelfMutation above is the backstop). Parity with the SAM copy.
|
||||
- Sid: IAMRoleReadAndDelete
|
||||
Effect: Allow
|
||||
Action:
|
||||
- iam:DeleteRole
|
||||
- iam:DeleteRolePolicy
|
||||
- iam:DetachRolePolicy
|
||||
- iam:GetRole
|
||||
- iam:GetRolePolicy
|
||||
- iam:ListAttachedRolePolicies
|
||||
- iam:ListRolePolicies
|
||||
- iam:ListRoles
|
||||
- iam:TagRole
|
||||
- iam:UntagRole
|
||||
- iam:UpdateRole
|
||||
- iam:UpdateRoleDescription
|
||||
- iam:UpdateAssumeRolePolicy
|
||||
- iam:GetPolicy
|
||||
- iam:GetPolicyVersion
|
||||
- iam:ListPolicies
|
||||
- iam:ListPolicyVersions
|
||||
Resource: "*"
|
||||
|
||||
# PassRole — Terraform passes stack-created execution roles to the
|
||||
# Lambda service. Other target services (e.g. scheduler.amazonaws.com,
|
||||
# apigateway.amazonaws.com) are NOT granted here: a stack that needs
|
||||
# one adds a scoped PassRole statement on its own apply role at
|
||||
# migration time.
|
||||
- Sid: IAMPassRole
|
||||
Effect: Allow
|
||||
Action:
|
||||
- iam:PassRole
|
||||
Resource:
|
||||
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
|
||||
Condition:
|
||||
StringEquals:
|
||||
"iam:PassedToService": "lambda.amazonaws.com"
|
||||
Loading…
Add table
Reference in a new issue