meal-order-manager/terraform/hcp_iam.tf
Adam Moussa b043b86fc7
feat(iam): import hcptf roles into app Terraform (PLAT-146) (#183)
* feat(iam): import hcptf roles into app Terraform (PLAT-146)

Move the existing hcptf pair into this repo so app Terraform owns prod IAM after the substrate handoff.

* fix(iam): add apply-role IAM list permissions (PLAT-146)

IamReadOnly omitted ListRoleTags and ListInstanceProfilesForRole needed after detaching the substrate guardrail.
2026-09-02 21:47:12 +00:00

755 lines
21 KiB
HCL

# HCP plan/apply roles imported from seahaven-terraform-substrate (PLAT-146).
# Import, do not recreate. Role names stay hcptf-meal-order-manager / hcptf-meal-order-manager-plan.
#
# Live seahaven-hcptf-iam-management DenySelfMutation blocks DetachRolePolicy
# and PutRolePolicy on hcptf-* (including this role). Import apply sequence:
# 1. seahaven-org-baseline scripts/create-hcptf-bootstrap-roles.sh
# --account prod --allow-workspace meal-order-manager-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-meal-order-manager / hcptf-meal-order-manager-plan.
# 5. Re-run the script without --allow-workspace to pin trust back to
# iam-bootstrap-prod only.
# seahaven-lambda-execution-boundary remains seahaven-lambda-execution-boundary-meal-order-manager.
import {
to = aws_iam_role.hcptf_apply
id = "hcptf-meal-order-manager"
}
import {
to = aws_iam_role.hcptf_plan
id = "hcptf-meal-order-manager-plan"
}
import {
to = aws_iam_role_policy.hcptf_apply_services
id = "hcptf-meal-order-manager:meal-order-manager-services"
}
import {
to = aws_iam_role_policy.hcptf_plan_refresh
id = "hcptf-meal-order-manager-plan:meal-order-manager-plan-refresh"
}
import {
to = aws_iam_role_policy_attachments_exclusive.hcptf_apply
id = "hcptf-meal-order-manager"
}
import {
to = aws_iam_role_policy_attachment.hcptf_plan_viewonly
id = "hcptf-meal-order-manager-plan/arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
}
import {
to = aws_iam_role_policy_attachments_exclusive.hcptf_plan
id = "hcptf-meal-order-manager-plan"
}
data "aws_iam_policy_document" "hcptf_apply_trust" {
statement {
sid = "HcpApply"
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:seahaven-prod:workspace:meal-order-manager-prod:run_phase:apply",
]
}
}
}
data "aws_iam_policy_document" "hcptf_plan_trust" {
statement {
sid = "HcpPlan"
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:seahaven-prod:workspace:meal-order-manager-prod:run_phase:plan",
]
}
}
}
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/meal-order-manager-*"]
condition {
test = "StringLike"
variable = "iam:PermissionsBoundary"
values = [
"arn:aws:iam::${local.account_id}:policy/tf-managed/meal-order-manager-*",
"arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-meal-order-manager"
]
}
}
statement {
sid = "MutateExecRoleWithBoundary"
effect = "Allow"
actions = [
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:PutRolePermissionsBoundary",
]
resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/meal-order-manager-*"]
condition {
test = "StringLike"
variable = "iam:PermissionsBoundary"
values = [
"arn:aws:iam::${local.account_id}:policy/tf-managed/meal-order-manager-*",
"arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-meal-order-manager"
]
}
}
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/meal-order-manager-*"]
}
statement {
sid = "PassExecRolesToLambda"
effect = "Allow"
actions = ["iam:PassRole"]
resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/meal-order-manager-*"]
condition {
test = "StringEquals"
variable = "iam:PassedToService"
values = ["lambda.amazonaws.com"]
}
}
statement {
sid = "WriteDeployRoles"
effect = "Allow"
actions = [
"iam:AttachRolePolicy",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:TagRole",
"iam:UntagRole",
"iam:UpdateAssumeRolePolicy",
"iam:UpdateRole",
"iam:UpdateRoleDescription",
]
resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/githubdeploy-meal-order-manager-weekly-menu"]
}
statement {
sid = "IamReadOnly"
effect = "Allow"
actions = [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListInstanceProfilesForRole",
"iam:ListPolicies",
"iam:ListPolicyVersions",
"iam:ListRolePolicies",
"iam:ListRoleTags",
"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_policy" "hcptf_apply_services" {
name = "meal-order-manager-services"
role = aws_iam_role.hcptf_apply.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"lambda:*",
]
Resource = [
"arn:aws:lambda:us-east-1:${local.account_id}:function:meal-order-manager-*",
"arn:aws:lambda:us-east-1:${local.account_id}:layer:meal-order-manager-*",
]
Effect = "Allow"
Sid = "LambdaAll"
},
{
Action = [
"lambda:ListFunctions",
"lambda:ListLayers",
"lambda:GetAccountSettings",
]
Resource = "*"
Effect = "Allow"
Sid = "LambdaList"
},
{
Action = [
"events:*",
]
Resource = [
"arn:aws:events:us-east-1:${local.account_id}:rule/meal-order-manager-*",
]
Effect = "Allow"
Sid = "EventBridgeRules"
},
{
Action = [
"logs:CreateLogGroup",
"logs:DeleteLogGroup",
"logs:PutRetentionPolicy",
"logs:DeleteRetentionPolicy",
"logs:TagResource",
"logs:UntagResource",
"logs:ListTagsForResource",
"logs:PutMetricFilter",
"logs:DeleteMetricFilter",
"logs:DescribeMetricFilters",
]
Resource = [
"arn:aws:logs:us-east-1:${local.account_id}:log-group:/aws/lambda/meal-order-manager-*",
"arn:aws:logs:us-east-1:${local.account_id}:log-group:/aws/apigateway/meal-order-manager*",
]
Effect = "Allow"
Sid = "CloudWatchLogs"
},
{
Action = [
"logs:DescribeLogGroups",
]
Resource = "*"
Effect = "Allow"
Sid = "CloudWatchLogsDescribe"
},
{
Action = [
"logs:CreateLogDelivery",
"logs:GetLogDelivery",
"logs:UpdateLogDelivery",
"logs:DeleteLogDelivery",
"logs:ListLogDeliveries",
"logs:DescribeResourcePolicies",
]
Resource = "*"
Effect = "Allow"
Sid = "MealOrderApiGwAccessLogDelivery"
},
{
Action = [
"s3:*",
]
Resource = [
"arn:aws:s3:::meal-order-manager-artifacts-${local.account_id}",
"arn:aws:s3:::meal-order-manager-artifacts-${local.account_id}/*",
"arn:aws:s3:::meal-order-manager-form-${local.account_id}",
"arn:aws:s3:::meal-order-manager-form-${local.account_id}/*",
"arn:aws:s3:::meal-order-manager-reports-${local.account_id}",
"arn:aws:s3:::meal-order-manager-reports-${local.account_id}/*",
]
Effect = "Allow"
Sid = "StackBuckets"
},
{
Action = [
"dynamodb:*",
]
Resource = [
"arn:aws:dynamodb:us-east-1:${local.account_id}:table/meal-order-manager-orders",
"arn:aws:dynamodb:us-east-1:${local.account_id}:table/meal-order-manager-orders/*",
]
Effect = "Allow"
Sid = "DynamoDBTable"
},
{
Action = [
"dynamodb:ListTables",
]
Resource = "*"
Effect = "Allow"
Sid = "DynamoDBList"
},
{
Action = [
"apigateway:*",
]
Resource = [
"arn:aws:apigateway:us-east-1::/apis",
"arn:aws:apigateway:us-east-1::/apis/*",
"arn:aws:apigateway:us-east-1::/tags/*",
"arn:aws:apigateway:us-east-1::/vpclinks",
"arn:aws:apigateway:us-east-1::/vpclinks/*",
]
Effect = "Allow"
Sid = "HttpApiManage"
},
{
Action = [
"cloudfront:*",
]
Resource = "*"
Effect = "Allow"
Sid = "CloudFrontManage"
},
{
Condition = {
StringEquals = {
"aws:RequestTag/Project" = "meal-order-manager"
}
}
Action = [
"acm:RequestCertificate",
]
Resource = "*"
Effect = "Allow"
Sid = "AcmCreate"
},
{
Action = [
"acm:ListCertificates",
"acm:ListTagsForCertificate",
]
Resource = "*"
Effect = "Allow"
Sid = "AcmList"
},
{
Condition = {
StringEquals = {
"aws:ResourceTag/Project" = "meal-order-manager"
}
}
Action = [
"acm:DescribeCertificate",
"acm:GetCertificate",
"acm:DeleteCertificate",
"acm:AddTagsToCertificate",
"acm:RemoveTagsFromCertificate",
"acm:RenewCertificate",
]
Resource = "*"
Effect = "Allow"
Sid = "AcmManageTagged"
},
{
Action = [
"ssm:GetParameter",
"ssm:GetParameters",
]
Resource = [
"arn:aws:ssm:us-east-1:${local.account_id}:parameter/seahaven/waf/app-web-acl-arn",
]
Effect = "Allow"
Sid = "ReadAppWebAclSsm"
},
{
Action = [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:PutParameter",
"ssm:DeleteParameter",
"ssm:AddTagsToResource",
"ssm:RemoveTagsFromResource",
"ssm:ListTagsForResource",
]
Resource = [
"arn:aws:ssm:us-east-1:${local.account_id}:parameter/meal-order-manager/*",
]
Effect = "Allow"
Sid = "MealOrderSsm"
},
{
Action = [
"ssm:DescribeParameters",
]
Resource = "*"
Effect = "Allow"
Sid = "MealOrderSsmDescribeParameters"
},
{
Condition = {
StringEquals = {
"iam:PassedToService" = "apigateway.amazonaws.com"
}
}
Action = [
"iam:PassRole",
]
Resource = [
"arn:aws:iam::${local.account_id}:role/tf-managed/meal-order-manager-*",
]
Effect = "Allow"
Sid = "MealOrderPassRoleApiGateway"
},
{
Action = [
"wafv2:GetWebACL",
"wafv2:GetWebACLForResource",
"wafv2:ListWebACLs",
"wafv2:ListResourcesForWebACL",
]
Resource = "*"
Effect = "Allow"
Sid = "ReadWafWebAcl"
},
{
Action = [
"cloudwatch:PutMetricAlarm",
"cloudwatch:DeleteAlarms",
"cloudwatch:DescribeAlarms",
"cloudwatch:TagResource",
"cloudwatch:UntagResource",
"cloudwatch:ListTagsForResource",
]
Resource = [
"arn:aws:cloudwatch:us-east-1:${local.account_id}:alarm:meal-order-manager-*",
]
Effect = "Allow"
Sid = "CloudWatchAlarms"
},
{
Action = [
"sns:Publish",
"sns:GetTopicAttributes",
"sns:ListTagsForResource",
]
Resource = [
"arn:aws:sns:us-east-1:${local.account_id}:site-alerts",
]
Effect = "Allow"
Sid = "SnsPublishSiteAlerts"
},
{
Action = [
"ses:GetIdentityVerificationAttributes",
"ses:GetSendQuota",
]
Resource = "*"
Effect = "Allow"
Sid = "SesIdentityRead"
},
]
})
}
resource "aws_iam_role_policy" "hcptf_plan_refresh" {
name = "meal-order-manager-plan-refresh"
role = aws_iam_role.hcptf_plan.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListRolePolicies",
"iam:ListAttachedRolePolicies",
"iam:ListRoleTags",
]
Resource = [
"arn:aws:iam::${local.account_id}:role/tf-managed/meal-order-manager-*",
"arn:aws:iam::${local.account_id}:role/tf-managed/githubdeploy-meal-order-manager*",
"arn:aws:iam::${local.account_id}:role/hcptf-meal-order-manager",
"arn:aws:iam::${local.account_id}:role/hcptf-meal-order-manager-plan",
]
Effect = "Allow"
Sid = "RefreshIamRoles"
},
{
Action = [
"iam:GetPolicy",
"iam:GetPolicyVersion",
]
Resource = "*"
Effect = "Allow"
Sid = "RefreshManagedPolicies"
},
{
Action = [
"events:DescribeRule",
"events:ListTargetsByRule",
"events:ListTagsForResource",
]
Resource = [
"arn:aws:events:us-east-1:${local.account_id}:rule/meal-order-manager-*",
]
Effect = "Allow"
Sid = "RefreshEventBridge"
},
{
Action = [
"lambda:Get*",
"lambda:List*",
]
Resource = [
"arn:aws:lambda:us-east-1:${local.account_id}:function:meal-order-manager-*",
"arn:aws:lambda:us-east-1:${local.account_id}:layer:meal-order-manager-*",
]
Effect = "Allow"
Sid = "RefreshLambda"
},
{
Action = [
"s3:Get*",
"s3:ListBucket",
]
Resource = [
"arn:aws:s3:::meal-order-manager-artifacts-${local.account_id}",
"arn:aws:s3:::meal-order-manager-artifacts-${local.account_id}/*",
"arn:aws:s3:::meal-order-manager-form-${local.account_id}",
"arn:aws:s3:::meal-order-manager-form-${local.account_id}/*",
"arn:aws:s3:::meal-order-manager-reports-${local.account_id}",
"arn:aws:s3:::meal-order-manager-reports-${local.account_id}/*",
]
Effect = "Allow"
Sid = "RefreshBuckets"
},
{
Action = [
"dynamodb:DescribeTable",
"dynamodb:DescribeTimeToLive",
"dynamodb:DescribeContinuousBackups",
"dynamodb:ListTagsOfResource",
]
Resource = [
"arn:aws:dynamodb:us-east-1:${local.account_id}:table/meal-order-manager-orders",
]
Effect = "Allow"
Sid = "RefreshDynamoDB"
},
{
Action = [
"logs:DescribeLogGroups",
"logs:ListTagsForResource",
]
Resource = "*"
Effect = "Allow"
Sid = "RefreshLogs"
},
{
Action = [
"cloudfront:Get*",
"cloudfront:List*",
]
Resource = "*"
Effect = "Allow"
Sid = "RefreshCloudFront"
},
{
Action = [
"acm:DescribeCertificate",
"acm:ListCertificates",
"acm:ListTagsForCertificate",
"acm:GetCertificate",
]
Resource = "*"
Effect = "Allow"
Sid = "RefreshAcm"
},
{
Action = [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:ListTagsForResource",
]
Resource = [
"arn:aws:ssm:us-east-1:${local.account_id}:parameter/seahaven/waf/app-web-acl-arn",
"arn:aws:ssm:us-east-1:${local.account_id}:parameter/meal-order-manager/*",
]
Effect = "Allow"
Sid = "RefreshAppWebAclSsm"
},
{
Action = [
"ssm:DescribeParameters",
]
Resource = "*"
Effect = "Allow"
Sid = "RefreshSsmDescribeParameters"
},
{
Action = [
"wafv2:GetWebACL",
"wafv2:ListWebACLs",
]
Resource = "*"
Effect = "Allow"
Sid = "RefreshWafWebAcl"
},
{
Action = [
"apigateway:GET",
]
Resource = [
"arn:aws:apigateway:us-east-1::/apis/*",
"arn:aws:apigateway:us-east-1::/tags/*",
]
Effect = "Allow"
Sid = "RefreshHttpApi"
},
{
Action = [
"cloudwatch:DescribeAlarms",
"cloudwatch:ListTagsForResource",
]
Resource = "*"
Effect = "Allow"
Sid = "RefreshAlarms"
},
]
})
}
resource "aws_iam_role" "hcptf_apply" {
name = "hcptf-meal-order-manager"
assume_role_policy = data.aws_iam_policy_document.hcptf_apply_trust.json
max_session_duration = 3600
tags = {
Project = "meal-order-manager"
Owner = "adam@seahavenind.com"
ManagedBy = "terraform"
}
}
# Empty exclusive set keeps seahaven-hcptf-iam-management detached.
resource "aws_iam_role_policy_attachments_exclusive" "hcptf_apply" {
role_name = aws_iam_role.hcptf_apply.name
policy_arns = []
}
resource "aws_iam_role" "hcptf_plan" {
name = "hcptf-meal-order-manager-plan"
assume_role_policy = data.aws_iam_policy_document.hcptf_plan_trust.json
max_session_duration = 3600
tags = {
Project = "meal-order-manager"
Owner = "adam@seahavenind.com"
ManagedBy = "terraform"
}
}
resource "aws_iam_role_policy_attachment" "hcptf_plan_viewonly" {
role = aws_iam_role.hcptf_plan.name
policy_arn = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
}
resource "aws_iam_role_policy_attachments_exclusive" "hcptf_plan" {
role_name = aws_iam_role.hcptf_plan.name
policy_arns = [
aws_iam_role_policy_attachment.hcptf_plan_viewonly.policy_arn,
]
}
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
}