# Example: import an existing prod/dev hcptf- pair into app Terraform # (PLAT-146). Copy into the consumer repo's terraform/ directory. Replace # locals, then `terraform import` (or keep the import blocks) on a Manual # apply. Do not recreate the role. Role names stay `hcptf-STACK` / # `hcptf-STACK-plan`. # # Live `seahaven-hcptf-iam-management` DenySelfMutation blocks DetachRolePolicy # and PutRolePolicy on hcptf-* (including this role). The stack workspace # cannot apply this file while TFC_AWS_* still points at hcptf-STACK, and # hcptf-bootstrap trust is exact StringEquals for workspace # iam-bootstrap- only (HCP names are org-unique). Import apply sequence: # 1. scripts/create-hcptf-bootstrap-roles.sh --account prod|dev \ # --allow-workspace STACK-prod # 2. Point this workspace's TFC_AWS_* at hcptf-bootstrap / # hcptf-bootstrap-plan (workspace vars, never a project set). # 3. One Manual apply (import + detach seahaven-hcptf-iam-management + # put scoped inline). # 4. Point TFC_AWS_* back at hcptf-STACK / hcptf-STACK-plan. # 5. Re-run the script without --allow-workspace to pin trust back to # iam-bootstrap- only. # Later apply-role IAM edits use the same window. Do not add StringLike # on bootstrap trust. # # SAM-only repos and SHOC/external-dev do not use this file. locals { account_id = "011934824531" # seahaven-prod; use 710827005802 for seahaven-dev hcp_project = "seahaven-prod" hcp_workspace = "STACK-prod" apply_role = "hcptf-STACK" plan_role = "hcptf-STACK-plan" stack_name = "STACK" stack_prefix = "STACK-" } data "aws_iam_policy_document" "hcptf_apply_trust" { statement { effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"] principals { type = "Federated" identifiers = ["arn:aws:iam::${local.account_id}:oidc-provider/app.terraform.io"] } condition { test = "StringEquals" variable = "app.terraform.io:aud" values = ["aws.workload.identity"] } condition { test = "StringEquals" variable = "app.terraform.io:sub" values = [ "organization:seahaven:project:${local.hcp_project}:workspace:${local.hcp_workspace}:run_phase:apply", ] } } } data "aws_iam_policy_document" "hcptf_plan_trust" { statement { effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"] principals { type = "Federated" identifiers = ["arn:aws:iam::${local.account_id}:oidc-provider/app.terraform.io"] } condition { test = "StringEquals" variable = "app.terraform.io:aud" values = ["aws.workload.identity"] } condition { test = "StringEquals" variable = "app.terraform.io:sub" values = [ "organization:seahaven:project:${local.hcp_project}:workspace:${local.hcp_workspace}:run_phase:plan", ] } } } # Rendered from lib/hcptf-bootstrap/scoped-apply-iam.json.tmpl. CreatePolicy # stays on hcptf-bootstrap only. Exec-role writes are prefix-scoped. Boundary # ARNs are StringLike-pinned (not Null); AdministratorAccess is not accepted. # The role cannot PutRolePolicy on hcptf-* (including itself). data "aws_iam_policy_document" "hcptf_scoped_iam" { statement { sid = "DenyCreatePolicy" effect = "Deny" actions = ["iam:CreatePolicy", "iam:CreatePolicyVersion"] resources = ["*"] } statement { sid = "CreateExecRoleWithBoundary" effect = "Allow" actions = ["iam:CreateRole"] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*"] condition { test = "StringLike" variable = "iam:PermissionsBoundary" values = [ "arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-${local.stack_name}", ] } } statement { sid = "MutateExecRoleWithBoundary" effect = "Allow" actions = [ "iam:AttachRolePolicy", "iam:PutRolePolicy", "iam:PutRolePermissionsBoundary", ] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*"] condition { test = "StringLike" variable = "iam:PermissionsBoundary" values = [ "arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-${local.stack_name}", ] } } statement { sid = "WriteExecRoles" effect = "Allow" actions = [ "iam:DeleteRole", "iam:DeleteRolePolicy", "iam:DetachRolePolicy", "iam:TagRole", "iam:UntagRole", "iam:UpdateAssumeRolePolicy", "iam:UpdateRole", "iam:UpdateRoleDescription", ] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*"] } statement { sid = "PassExecRolesToCompute" effect = "Allow" actions = ["iam:PassRole"] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*"] condition { test = "StringEquals" variable = "iam:PassedToService" values = ["lambda.amazonaws.com", "ecs-tasks.amazonaws.com", "scheduler.amazonaws.com"] } } statement { sid = "IamReadOnly" effect = "Allow" actions = [ "iam:GetPolicy", "iam:GetPolicyVersion", "iam:GetRole", "iam:GetRolePolicy", "iam:ListAttachedRolePolicies", "iam:ListPolicies", "iam:ListPolicyVersions", "iam:ListRolePolicies", "iam:ListRoles", ] resources = ["*"] } statement { sid = "DenySelfMutation" effect = "Deny" actions = [ "iam:AttachRolePolicy", "iam:DeleteRole", "iam:DeleteRolePolicy", "iam:DeleteRolePermissionsBoundary", "iam:DetachRolePolicy", "iam:PutRolePolicy", "iam:PutRolePermissionsBoundary", "iam:UpdateAssumeRolePolicy", "iam:UpdateRole", "iam:UpdateRoleDescription", ] resources = [ "arn:aws:iam::${local.account_id}:role/hcptf-*", "arn:aws:iam::${local.account_id}:role/github-cfn-execution-role", "arn:aws:iam::${local.account_id}:role/githubdeploy-*", "arn:aws:iam::${local.account_id}:role/cdk-hnb659fds-*", "arn:aws:iam::${local.account_id}:role/OrganizationAccountAccessRole", "arn:aws:iam::${local.account_id}:role/seahaven-*", ] } statement { sid = "DenyBoundaryTampering" effect = "Deny" actions = ["iam:DeleteRolePermissionsBoundary", "iam:DeleteUserPermissionsBoundary"] resources = [ "arn:aws:iam::${local.account_id}:role/*", "arn:aws:iam::${local.account_id}:user/*", ] } statement { sid = "DenyBoundaryPolicyEdit" effect = "Deny" actions = [ "iam:CreatePolicyVersion", "iam:DeletePolicy", "iam:DeletePolicyVersion", "iam:SetDefaultPolicyVersion", ] resources = ["arn:aws:iam::${local.account_id}:policy/seahaven-*"] } } resource "aws_iam_role" "hcptf_apply" { name = local.apply_role assume_role_policy = data.aws_iam_policy_document.hcptf_apply_trust.json max_session_duration = 3600 # Empty list detaches seahaven-hcptf-iam-management after import. managed_policy_arns = [] tags = { Owner = "adam@seahavenind.com" ManagedBy = "terraform" } } resource "aws_iam_role" "hcptf_plan" { name = local.plan_role assume_role_policy = data.aws_iam_policy_document.hcptf_plan_trust.json max_session_duration = 3600 managed_policy_arns = ["arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"] tags = { Owner = "adam@seahavenind.com" ManagedBy = "terraform" } } resource "aws_iam_role_policy" "hcptf_scoped_iam" { name = "scoped-iam-management" role = aws_iam_role.hcptf_apply.id policy = data.aws_iam_policy_document.hcptf_scoped_iam.json } # Also import the existing service inline policy (name matches CFN PolicyName) # and the plan-refresh sidecar. Copy those documents from # lib/terraform-substrate/terraform-substrate.template.yaml. Do not invent a # new Get* allow-list. # # import { # to = aws_iam_role.hcptf_apply # id = "hcptf-STACK" # } # import { # to = aws_iam_role.hcptf_plan # id = "hcptf-STACK-plan" # } # import { # to = aws_iam_role_policy.services # id = "hcptf-STACK:STACK-services" # } # import { # to = aws_iam_role_policy.plan_refresh # id = "hcptf-STACK-plan:STACK-plan-refresh" # }