fix(iam): list ECS tasks and fail closed on non-prod Paychex (PLAT-216)

This commit is contained in:
Adam Moussa 2026-09-21 15:06:03 -04:00
parent 2a3f30a9cb
commit ca44834732
No known key found for this signature in database
5 changed files with 42 additions and 3 deletions

View file

@ -105,15 +105,19 @@ First apply uses the hcptf-bootstrap window (exact `StringEquals` trust, never
GitHub Environment `prod`: reviewers, branch policy `main` and `v*`, Environment GitHub Environment `prod`: reviewers, branch policy `main` and `v*`, Environment
variable `DEPLOY_ROLE_ARN` = Terraform output `github_deploy_role_arn`. variable `DEPLOY_ROLE_ARN` = Terraform output `github_deploy_role_arn`.
GitHub Environment `dev`: same `DEPLOY_ROLE_ARN` in the matching account, GitHub Environment `dev`: no reviewers, `DEPLOY_ROLE_ARN` from the seahaven-dev
branch policy as needed for image CD. apply of the same output. Set `checkcomponents_queue_url` and
`checkcomponents_queue_arn` empty on the dev workspace.
Function zips: Actions → Deploy on push to `main`, or `workflow_dispatch`. Function zips: Actions → Deploy on push to `main`, or `workflow_dispatch`.
Image CD: Actions → Deploy API (`deploy-api.yaml`). Keep `schedules_enabled=false` Image CD: Actions → Deploy API (`deploy-api.yaml`). Keep `schedules_enabled=false`
and `ecs_schedules_enabled=false` until the Fargate cutover below. and `ecs_schedules_enabled=false` until the Fargate cutover below.
HCP outputs to copy: `slack_request_url`, `api_origin`, `fargate_origin`, HCP outputs to copy: `slack_request_url`, `api_origin`, `fargate_origin`,
`holiday_scheduler_role_arn`, `github_deploy_role_arn`, `jobs_queue_arn`. `holiday_scheduler_role_arn`, `github_deploy_role_arn`, `jobs_queue_arn`,
`ecs_task_role_arn`. Add `ecs_task_role_arn` to paychex-checkcomponents
(alongside `afterhours-shift-manager-weekly-post`) before Fargate weekly_post
runs. Dual-run keeps the Lambda principal until zip CD is retired.
## 4. Set the Slack Request URL ## 4. Set the Slack Request URL

25
terraform/data.tf Normal file
View file

@ -0,0 +1,25 @@
data "aws_caller_identity" "current" {}
# Resource names in locals.tf embed the account ID. If the workspace is ever
# pointed at another account, fail the plan here rather than creating a parallel
# set of oddly-named resources somewhere else.
check "correct_account" {
assert {
condition = data.aws_caller_identity.current.account_id == local.account_id
error_message = "This configuration targets account ${local.account_id} (${var.environment}), but the credentials resolve to ${data.aws_caller_identity.current.account_id}."
}
}
check "dev_has_no_paychex" {
assert {
condition = local.is_prod || var.checkcomponents_queue_url == ""
error_message = "checkcomponents_queue_url must be empty in non-prod so weekly_post cannot send to the prod Paychex queue."
}
}
check "checkcomponents_pair" {
assert {
condition = (var.checkcomponents_queue_url == "") == (var.checkcomponents_queue_arn == "")
error_message = "checkcomponents_queue_url and checkcomponents_queue_arn must both be set or both be empty."
}
}

View file

@ -112,6 +112,7 @@ data "aws_iam_policy_document" "github_deploy" {
"ecs:DescribeServices", "ecs:DescribeServices",
"ecs:DescribeTaskDefinition", "ecs:DescribeTaskDefinition",
"ecs:DescribeTasks", "ecs:DescribeTasks",
"ecs:ListTasks",
"ecs:RegisterTaskDefinition", "ecs:RegisterTaskDefinition",
"ecs:UpdateService", "ecs:UpdateService",
] ]

View file

@ -43,6 +43,11 @@ output "jobs_queue_arn" {
value = aws_sqs_queue.jobs.arn value = aws_sqs_queue.jobs.arn
} }
output "ecs_task_role_arn" {
description = "ECS task role. paychex-checkcomponents queue policy must allow this ARN before Fargate weekly_post."
value = aws_iam_role.ecs_task.arn
}
output "hcptf_apply_role_arn" { output "hcptf_apply_role_arn" {
description = "HCP apply role ARN. Set TFC_AWS_APPLY_ROLE_ARN after the bootstrap window." description = "HCP apply role ARN. Set TFC_AWS_APPLY_ROLE_ARN after the bootstrap window."
value = aws_iam_role.hcptf_apply.arn value = aws_iam_role.hcptf_apply.arn

View file

@ -106,6 +106,9 @@ def test_checkcomponents_queue_arn_variable_matches_iam_references():
assert "var.checkcomponents_queue_arn" in LAMBDA_TF assert "var.checkcomponents_queue_arn" in LAMBDA_TF
boundary = (TERRAFORM / "lambda_boundary.tf").read_text() boundary = (TERRAFORM / "lambda_boundary.tf").read_text()
assert "var.checkcomponents_queue_arn" in boundary assert "var.checkcomponents_queue_arn" in boundary
data_tf = (TERRAFORM / "data.tf").read_text()
assert "dev_has_no_paychex" in data_tf
assert "checkcomponents_pair" in data_tf
def test_eight_functions_named(): def test_eight_functions_named():
@ -134,6 +137,7 @@ def test_github_deploy_trust_covers_zip_and_image():
assert "deploy-api.yaml@refs/heads/${var.github_deploy_branch}" in iam assert "deploy-api.yaml@refs/heads/${var.github_deploy_branch}" in iam
assert "deploy-api.yaml@refs/tags/v*" in iam assert "deploy-api.yaml@refs/tags/v*" in iam
assert "deploy.yaml@*" not in iam assert "deploy.yaml@*" not in iam
assert "ecs:ListTasks" in iam
def test_plan_refresh_includes_provider6_s3_gets(): def test_plan_refresh_includes_provider6_s3_gets():