afterhours-shift-manager/terraform/iam.tf
Adam Moussa 26adb8e6c0
feat(api): collapse Slack, portal, and jobs onto Fargate (PLAT-216) (#259)
* feat(api): collapse Slack, portal, and jobs onto Fargate (PLAT-216)

Move HTTP and scheduled work onto one always-on Flask task so after-hours
loses Lambda cold start without changing the Cognito or roster contracts.

* fix(portal-api): keep CORS headers on unexpected 500s

Portal SPA error handling needs Access-Control-Allow-Origin even when
DynamoDB or other internals fail, otherwise the browser hides the 500.

* fix(api): retarget holidays per account and ship App Home changelog (PLAT-216)

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

* fix(portal-api): serve portal JSON with an explicit JSON content type
2026-09-21 19:13:30 +00:00

191 lines
4.9 KiB
HCL

# Execution, task, and EventBridge Scheduler roles for the Fargate API.
data "aws_iam_policy_document" "ecs_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
data "aws_iam_policy_document" "jobs_scheduler_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["scheduler.amazonaws.com"]
}
}
}
data "aws_iam_policy_document" "ecs_task_boundary" {
statement {
sid = "DdbCrud"
effect = "Allow"
actions = [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:ConditionCheckItem",
"dynamodb:DeleteItem",
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem",
]
resources = [
aws_dynamodb_table.shifts.arn,
"${aws_dynamodb_table.shifts.arn}/index/*",
]
}
statement {
sid = "Secrets"
effect = "Allow"
actions = ["secretsmanager:GetSecretValue"]
resources = ["arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:afterhours-shift-manager/*"]
}
statement {
sid = "HolidaySchedules"
effect = "Allow"
actions = [
"scheduler:CreateSchedule",
"scheduler:DeleteSchedule",
"scheduler:GetSchedule",
]
resources = ["arn:aws:scheduler:${var.aws_region}:${local.account_id}:schedule/default/holiday-*"]
}
statement {
sid = "PassHolidayScheduler"
effect = "Allow"
actions = ["iam:PassRole"]
resources = [local.holiday_scheduler_role_arn]
condition {
test = "StringEquals"
variable = "iam:PassedToService"
values = ["scheduler.amazonaws.com"]
}
}
statement {
sid = "InvokeHolidayRouter"
effect = "Allow"
actions = ["lambda:InvokeFunction"]
resources = [local.holiday_router_arn]
}
statement {
sid = "JobsQueue"
effect = "Allow"
actions = [
"sqs:SendMessage",
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes",
]
resources = [aws_sqs_queue.jobs.arn]
}
statement {
sid = "CheckcomponentsSend"
effect = "Allow"
actions = ["sqs:SendMessage"]
resources = compact([var.checkcomponents_queue_arn])
}
statement {
sid = "EcrAuth"
effect = "Allow"
actions = ["ecr:GetAuthorizationToken"]
resources = ["*"]
}
statement {
sid = "EcrPull"
effect = "Allow"
actions = [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
]
resources = [aws_ecr_repository.api.arn]
}
statement {
sid = "TaskLogs"
effect = "Allow"
actions = [
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:CreateLogGroup",
]
resources = [
aws_cloudwatch_log_group.api.arn,
"${aws_cloudwatch_log_group.api.arn}:*",
]
}
}
resource "aws_iam_policy" "ecs_task_boundary" {
name = "${local.project}-ecs-task-boundary"
path = "/tf-managed/"
description = "Permissions boundary for the afterhours-shift-manager ECS task role"
policy = data.aws_iam_policy_document.ecs_task_boundary.json
}
resource "aws_iam_role" "ecs_execution" {
name = "${local.project}-ecs-exec"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.ecs_assume.json
permissions_boundary = aws_iam_policy.ecs_task_boundary.arn
}
resource "aws_iam_role_policy_attachment" "ecs_execution" {
role = aws_iam_role.ecs_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_iam_role" "ecs_task" {
name = "${local.project}-api"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.ecs_assume.json
permissions_boundary = aws_iam_policy.ecs_task_boundary.arn
}
resource "aws_iam_role_policy" "ecs_task" {
name = "api-runtime"
role = aws_iam_role.ecs_task.id
policy = data.aws_iam_policy_document.ecs_task_boundary.json
}
resource "aws_iam_role" "jobs_scheduler" {
name = "${local.project}-scheduler"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.jobs_scheduler_assume.json
permissions_boundary = aws_iam_policy.ecs_task_boundary.arn
}
data "aws_iam_policy_document" "jobs_scheduler" {
statement {
sid = "SendJobs"
effect = "Allow"
actions = ["sqs:SendMessage"]
resources = [aws_sqs_queue.jobs.arn]
}
}
resource "aws_iam_role_policy" "jobs_scheduler" {
name = "enqueue-jobs"
role = aws_iam_role.jobs_scheduler.id
policy = data.aws_iam_policy_document.jobs_scheduler.json
}