meal-order-manager/terraform/iam.tf
Adam Moussa c1552f0bd3
Some checks failed
Build Lambda Layer / build (push) Has been cancelled
chore(meals): remove email_report payroll SES path (PLAT-135) (#187)
* chore(meals): remove email_report payroll SES path (PLAT-135)

Stop Monday SES deduction emails now that Flex checkcomponents owns payroll posting.

* chore(meals): delete email_report handler and SAM resources

Remove the leftover SES Lambda source so it cannot be redeployed from template.yaml.
2026-09-10 19:29:58 +00:00

327 lines
10 KiB
HCL

# Execution roles for the six Lambda functions plus the API Gateway role that
# invokes the admin authorizer.
#
# Every Lambda execution role is created under the /tf-managed/ path and
# carries seahaven-lambda-execution-boundary-meal-order-manager (PLAT-52).
# The path distinguishes Terraform-owned roles from the retired SAM
# /cfn-managed/ roles.
#
# The inline policies below are hand-expanded from the SAM policy templates in
# template.yaml (DynamoDBCrudPolicy, DynamoDBReadPolicy, S3CrudPolicy,
# S3ReadPolicy). Two deliberate narrowings from the SAM expansions:
# - s3:PutObjectAcl is omitted. The reports bucket enforces
# BucketOwnerEnforced ownership, so ACL writes fail regardless.
# - s3:GetLifecycleConfiguration / s3:PutLifecycleConfiguration are omitted.
# Bucket lifecycle is owned by this configuration, not by function code.
data "aws_iam_policy_document" "lambda_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
# ---------------------------------------------------------------------------
# Reusable policy documents
# ---------------------------------------------------------------------------
data "aws_iam_policy_document" "dynamodb_crud" {
statement {
sid = "OrdersTableCrud"
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.orders.arn,
"${aws_dynamodb_table.orders.arn}/index/*",
]
}
}
data "aws_iam_policy_document" "dynamodb_read" {
statement {
sid = "OrdersTableRead"
effect = "Allow"
actions = [
"dynamodb:BatchGetItem",
"dynamodb:ConditionCheckItem",
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:Query",
"dynamodb:Scan",
]
resources = [
aws_dynamodb_table.orders.arn,
"${aws_dynamodb_table.orders.arn}/index/*",
]
}
}
data "aws_iam_policy_document" "ssm_read" {
statement {
sid = "ReadProjectParameters"
effect = "Allow"
actions = ["ssm:GetParameter"]
resources = [local.ssm_parameter_arn_wildcard]
}
}
# The SAM template granted secretsmanager:GetSecretValue on
# `secret:meal-order-manager/*`. Scoped here to the one secret the code actually
# reads, supplied as an ARN so the grant cannot drift onto a future secret that
# happens to share the prefix.
data "aws_iam_policy_document" "slack_bot_secret_read" {
statement {
sid = "ReadSlackBotToken"
effect = "Allow"
actions = ["secretsmanager:GetSecretValue"]
resources = [var.slack_bot_secret_arn]
}
}
# ---------------------------------------------------------------------------
# submit-order
# ---------------------------------------------------------------------------
resource "aws_iam_role" "submit_order" {
name = "${local.project}-submit-order"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
permissions_boundary = local.boundary_arn
}
resource "aws_iam_role_policy_attachment" "submit_order_basic" {
role = aws_iam_role.submit_order.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "aws_iam_policy_document" "submit_order" {
source_policy_documents = [
data.aws_iam_policy_document.dynamodb_crud.json,
data.aws_iam_policy_document.ssm_read.json,
]
statement {
sid = "InvokeSlackNotifier"
effect = "Allow"
actions = ["lambda:InvokeFunction"]
resources = [aws_lambda_function.slack_notifier.arn]
}
# Read-only access to the weekly summary PDFs only — not the payroll or order
# CSVs — for the admin summary-pdf presigned-URL endpoint.
statement {
sid = "ReadWeeklySummaryPdfs"
effect = "Allow"
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.reports.arn}/reports/*/weekly-summary-*.pdf"]
}
}
resource "aws_iam_role_policy" "submit_order" {
name = "submit-order"
role = aws_iam_role.submit_order.id
policy = data.aws_iam_policy_document.submit_order.json
}
# ---------------------------------------------------------------------------
# admin-authorizer
# ---------------------------------------------------------------------------
resource "aws_iam_role" "admin_authorizer" {
name = "${local.project}-admin-authorizer"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
permissions_boundary = local.boundary_arn
}
resource "aws_iam_role_policy_attachment" "admin_authorizer_basic" {
role = aws_iam_role.admin_authorizer.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "aws_iam_policy_document" "admin_authorizer" {
source_policy_documents = [
data.aws_iam_policy_document.dynamodb_read.json,
data.aws_iam_policy_document.ssm_read.json,
]
}
resource "aws_iam_role_policy" "admin_authorizer" {
name = "admin-authorizer"
role = aws_iam_role.admin_authorizer.id
policy = data.aws_iam_policy_document.admin_authorizer.json
}
# ---------------------------------------------------------------------------
# close-form
# ---------------------------------------------------------------------------
resource "aws_iam_role" "close_form" {
name = "${local.project}-close-form"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
permissions_boundary = local.boundary_arn
}
resource "aws_iam_role_policy_attachment" "close_form_basic" {
role = aws_iam_role.close_form.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "aws_iam_policy_document" "close_form" {
source_policy_documents = [data.aws_iam_policy_document.dynamodb_crud.json]
statement {
sid = "InvokeAggregateOrders"
effect = "Allow"
actions = ["lambda:InvokeFunction"]
resources = [aws_lambda_function.aggregate_orders.arn]
}
}
resource "aws_iam_role_policy" "close_form" {
name = "close-form"
role = aws_iam_role.close_form.id
policy = data.aws_iam_policy_document.close_form.json
}
# ---------------------------------------------------------------------------
# aggregate-orders
# ---------------------------------------------------------------------------
resource "aws_iam_role" "aggregate_orders" {
name = "${local.project}-aggregate-orders"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
permissions_boundary = local.boundary_arn
}
resource "aws_iam_role_policy_attachment" "aggregate_orders_basic" {
role = aws_iam_role.aggregate_orders.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "aws_iam_policy_document" "aggregate_orders" {
source_policy_documents = [data.aws_iam_policy_document.dynamodb_crud.json]
statement {
sid = "ReportsBucketCrud"
effect = "Allow"
actions = [
"s3:DeleteObject",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject",
]
resources = ["${aws_s3_bucket.reports.arn}/*"]
}
statement {
sid = "ReportsBucketList"
effect = "Allow"
actions = ["s3:GetBucketLocation", "s3:ListBucket"]
resources = [aws_s3_bucket.reports.arn]
}
statement {
sid = "InvokeSlackNotifier"
effect = "Allow"
actions = ["lambda:InvokeFunction"]
resources = [aws_lambda_function.slack_notifier.arn]
}
statement {
sid = "CheckcomponentsSend"
effect = "Allow"
actions = ["sqs:SendMessage"]
resources = [var.checkcomponents_queue_arn]
}
}
resource "aws_iam_role_policy" "aggregate_orders" {
name = "aggregate-orders"
role = aws_iam_role.aggregate_orders.id
policy = data.aws_iam_policy_document.aggregate_orders.json
}
# ---------------------------------------------------------------------------
# slack-notifier
# ---------------------------------------------------------------------------
resource "aws_iam_role" "slack_notifier" {
name = "${local.project}-slack-notifier"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
permissions_boundary = local.boundary_arn
}
resource "aws_iam_role_policy_attachment" "slack_notifier_basic" {
role = aws_iam_role.slack_notifier.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "aws_iam_policy_document" "slack_notifier" {
source_policy_documents = [
data.aws_iam_policy_document.dynamodb_read.json,
data.aws_iam_policy_document.ssm_read.json,
data.aws_iam_policy_document.slack_bot_secret_read.json,
]
}
resource "aws_iam_role_policy" "slack_notifier" {
name = "slack-notifier"
role = aws_iam_role.slack_notifier.id
policy = data.aws_iam_policy_document.slack_notifier.json
}
# ---------------------------------------------------------------------------
# sync-roster
# ---------------------------------------------------------------------------
resource "aws_iam_role" "sync_roster" {
name = "${local.project}-sync-roster"
path = "/tf-managed/"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
permissions_boundary = local.boundary_arn
}
resource "aws_iam_role_policy_attachment" "sync_roster_basic" {
role = aws_iam_role.sync_roster.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "aws_iam_policy_document" "sync_roster" {
source_policy_documents = [
data.aws_iam_policy_document.dynamodb_crud.json,
data.aws_iam_policy_document.ssm_read.json,
data.aws_iam_policy_document.slack_bot_secret_read.json,
]
}
resource "aws_iam_role_policy" "sync_roster" {
name = "sync-roster"
role = aws_iam_role.sync_roster.id
policy = data.aws_iam_policy_document.sync_roster.json
}