From 35bd989882c0a378df72738f283ef9fa0cbf68da Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Mon, 21 Sep 2026 15:41:56 -0400 Subject: [PATCH] fix(infra): split hcptf apply policy and omit empty queue ARNs (PLAT-216) The combined services inline policy exceeded 10KB, and an empty checkcomponents ARN made CreatePolicy reject the Lambda boundary in dev. --- terraform/hcp_iam.tf | 9 +++++++ terraform/iam.tf | 13 ++++++---- terraform/lambda.tf | 44 +++++++++++++++++--------------- terraform/lambda_boundary.tf | 21 ++++++++------- terraform/vpc.tf | 5 +++- tests/infra/test_hcp_contract.py | 2 ++ 6 files changed, 59 insertions(+), 35 deletions(-) diff --git a/terraform/hcp_iam.tf b/terraform/hcp_iam.tf index 8425e75..bb77db2 100644 --- a/terraform/hcp_iam.tf +++ b/terraform/hcp_iam.tf @@ -562,6 +562,9 @@ data "aws_iam_policy_document" "hcptf_apply_services" { resources = ["*"] } +} + +data "aws_iam_policy_document" "hcptf_apply_ecs" { statement { sid = "EcsWorkload" effect = "Allow" @@ -1083,6 +1086,12 @@ resource "aws_iam_role_policy" "hcptf_apply_services" { policy = data.aws_iam_policy_document.hcptf_apply_services.json } +resource "aws_iam_role_policy" "hcptf_apply_ecs" { + name = "afterhours-shift-manager-ecs" + role = aws_iam_role.hcptf_apply.id + policy = data.aws_iam_policy_document.hcptf_apply_ecs.json +} + resource "aws_iam_role_policy" "hcptf_plan_refresh" { name = "afterhours-shift-manager-plan-refresh" role = aws_iam_role.hcptf_plan.id diff --git a/terraform/iam.tf b/terraform/iam.tf index 3089692..a2b4024 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -96,11 +96,14 @@ data "aws_iam_policy_document" "ecs_task_boundary" { resources = [aws_sqs_queue.jobs.arn] } - statement { - sid = "CheckcomponentsSend" - effect = "Allow" - actions = ["sqs:SendMessage"] - resources = compact([var.checkcomponents_queue_arn]) + dynamic "statement" { + for_each = var.checkcomponents_queue_arn == "" ? [] : [1] + content { + sid = "CheckcomponentsSend" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [var.checkcomponents_queue_arn] + } } statement { diff --git a/terraform/lambda.tf b/terraform/lambda.tf index c7e3f9d..1c06fbc 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -57,26 +57,30 @@ locals { condition = null }, ] - weekly_post = [ - { - sid = "DdbCrud" - actions = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem", "dynamodb:DescribeTable", "dynamodb:ConditionCheckItem"] - resources = [local.table_arn, "${local.table_arn}/*"] - condition = null - }, - { - sid = "Secrets" - actions = ["secretsmanager:GetSecretValue"] - resources = ["arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:afterhours-shift-manager/slack-bot-token-*"] - condition = null - }, - { - sid = "CheckcomponentsSend" - actions = ["sqs:SendMessage"] - resources = [var.checkcomponents_queue_arn] - condition = null - }, - ] + weekly_post = concat( + [ + { + sid = "DdbCrud" + actions = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem", "dynamodb:DescribeTable", "dynamodb:ConditionCheckItem"] + resources = [local.table_arn, "${local.table_arn}/*"] + condition = null + }, + { + sid = "Secrets" + actions = ["secretsmanager:GetSecretValue"] + resources = ["arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:afterhours-shift-manager/slack-bot-token-*"] + condition = null + }, + ], + var.checkcomponents_queue_arn == "" ? [] : [ + { + sid = "CheckcomponentsSend" + actions = ["sqs:SendMessage"] + resources = [var.checkcomponents_queue_arn] + condition = null + }, + ], + ), roster_sync = [ { sid = "DdbCrud" diff --git a/terraform/lambda_boundary.tf b/terraform/lambda_boundary.tf index ff6aac4..d4916e7 100644 --- a/terraform/lambda_boundary.tf +++ b/terraform/lambda_boundary.tf @@ -121,15 +121,18 @@ data "aws_iam_policy_document" "lambda_boundary" { ] } - statement { - sid = "AfterhoursCheckcomponentsSend" - effect = "Allow" - actions = [ - "sqs:SendMessage", - ] - resources = [ - var.checkcomponents_queue_arn, - ] + dynamic "statement" { + for_each = var.checkcomponents_queue_arn == "" ? [] : [1] + content { + sid = "AfterhoursCheckcomponentsSend" + effect = "Allow" + actions = [ + "sqs:SendMessage", + ] + resources = [ + var.checkcomponents_queue_arn, + ] + } } } diff --git a/terraform/vpc.tf b/terraform/vpc.tf index 1a8e625..f2a35a4 100644 --- a/terraform/vpc.tf +++ b/terraform/vpc.tf @@ -12,7 +12,10 @@ resource "aws_vpc" "this" { } # First apply updates the live hcptf apply role before CreateVpc. - depends_on = [aws_iam_role_policy.hcptf_apply_services] + depends_on = [ + aws_iam_role_policy.hcptf_apply_services, + aws_iam_role_policy.hcptf_apply_ecs, + ] } resource "aws_internet_gateway" "this" { diff --git a/tests/infra/test_hcp_contract.py b/tests/infra/test_hcp_contract.py index ad8f581..152ef5b 100644 --- a/tests/infra/test_hcp_contract.py +++ b/tests/infra/test_hcp_contract.py @@ -81,6 +81,8 @@ def test_stack_owns_a_vpc_instead_of_looking_up_default(): assert "aws_subnet.public[*].id" in ecs assert "ec2:CreateVpc" in HCP_IAM assert "sid = \"RefreshVpc\"" in HCP_IAM + assert "afterhours-shift-manager-ecs" in HCP_IAM + assert "hcptf_apply_ecs" in HCP_IAM def test_deploy_api_workflow_exists():