mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 14:43:11 +00:00
hcptf-bootstrap-plan cannot ssm:GetParameter or DescribeSecret, so the first plan must not data-source those values.
250 lines
8.1 KiB
HCL
250 lines
8.1 KiB
HCL
# Terraform owns the function skeletons (role, runtime, memory, environment).
|
|
# Code is owned by .github/workflows/deploy.yaml, which uploads
|
|
# functions/<name>/<sha>.zip and calls update-function-code. The lifecycle
|
|
# block is the seam: an app deploy is not drift, and a Terraform apply never
|
|
# rolls the code back to the bootstrap stub.
|
|
|
|
data "aws_iam_policy_document" "lambda_assume" {
|
|
statement {
|
|
effect = "Allow"
|
|
actions = ["sts:AssumeRole"]
|
|
|
|
principals {
|
|
type = "Service"
|
|
identifiers = ["lambda.amazonaws.com"]
|
|
}
|
|
}
|
|
}
|
|
|
|
locals {
|
|
table_arn = aws_dynamodb_table.dashboard.arn
|
|
cmk_arn = local.dynamodb_cmk_arn
|
|
|
|
lambda_identity = {
|
|
process_csv = [
|
|
{
|
|
sid = "CsvRead"
|
|
actions = ["s3:GetObject", "s3:GetObjectVersion"]
|
|
resources = ["${aws_s3_bucket.csv.arn}/*"]
|
|
},
|
|
{
|
|
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}/*"]
|
|
},
|
|
{
|
|
sid = "Cmk"
|
|
actions = ["kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"]
|
|
resources = [local.cmk_arn]
|
|
},
|
|
{
|
|
sid = "BoaCheckMgmtSecret"
|
|
actions = ["secretsmanager:GetSecretValue"]
|
|
resources = [local.secret_arns["payments-dashboard/boa-check-mgmt"]]
|
|
},
|
|
{
|
|
sid = "DlqSend"
|
|
actions = ["sqs:SendMessage"]
|
|
resources = [aws_sqs_queue.process_csv_dlq.arn]
|
|
},
|
|
]
|
|
slack_app_home = [
|
|
{
|
|
sid = "DdbRead"
|
|
actions = ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:BatchGetItem", "dynamodb:DescribeTable"]
|
|
resources = [local.table_arn, "${local.table_arn}/*"]
|
|
},
|
|
{
|
|
sid = "CmkDecrypt"
|
|
actions = ["kms:Decrypt", "kms:DescribeKey"]
|
|
resources = [local.cmk_arn]
|
|
},
|
|
{
|
|
sid = "SlackSecrets"
|
|
actions = ["secretsmanager:GetSecretValue"]
|
|
resources = [
|
|
local.secret_arns["payments-dashboard/slack-bot-token"],
|
|
local.secret_arns["payments-dashboard/slack-signing-secret"],
|
|
]
|
|
},
|
|
]
|
|
fetch_boa = [
|
|
{
|
|
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}/*"]
|
|
},
|
|
{
|
|
sid = "BoaRawPut"
|
|
actions = ["s3:PutObject"]
|
|
resources = ["${aws_s3_bucket.boa_raw.arn}/*"]
|
|
},
|
|
{
|
|
sid = "Cmk"
|
|
actions = ["kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"]
|
|
resources = [local.cmk_arn]
|
|
},
|
|
{
|
|
sid = "BoaReportingSecret"
|
|
actions = ["secretsmanager:GetSecretValue"]
|
|
resources = [local.secret_arns["payments-dashboard/boa-reporting"]]
|
|
},
|
|
]
|
|
expense_receiver = [
|
|
{
|
|
sid = "InvokeProcessor"
|
|
actions = ["lambda:InvokeFunction"]
|
|
resources = ["arn:aws:lambda:${var.aws_region}:${local.account_id}:function:payments-expenseProcessor"]
|
|
},
|
|
{
|
|
sid = "ExpenseSigningSecret"
|
|
actions = ["secretsmanager:GetSecretValue"]
|
|
resources = [local.secret_arns["payments-dashboard/expense-slack-signing-secret"]]
|
|
},
|
|
]
|
|
expense_processor = [
|
|
{
|
|
sid = "ExpenseBotSecret"
|
|
actions = ["secretsmanager:GetSecretValue"]
|
|
resources = [local.secret_arns["payments-dashboard/expense-slack-token"]]
|
|
},
|
|
]
|
|
}
|
|
|
|
lambda_env = {
|
|
process_csv = {
|
|
TABLE_NAME = aws_dynamodb_table.dashboard.name
|
|
BOA_BASE_URL = var.boa_base_url
|
|
BOA_CHECK_MGMT_SECRET_NAME = "payments-dashboard/boa-check-mgmt"
|
|
}
|
|
slack_app_home = {
|
|
TABLE_NAME = aws_dynamodb_table.dashboard.name
|
|
SLACK_BOT_TOKEN_SECRET_NAME = "payments-dashboard/slack-bot-token"
|
|
SLACK_SIGNING_SECRET_NAME = "payments-dashboard/slack-signing-secret"
|
|
}
|
|
fetch_boa = {
|
|
TABLE_NAME = aws_dynamodb_table.dashboard.name
|
|
BOA_BASE_URL = var.boa_base_url
|
|
BOA_REPORTING_SECRET_NAME = "payments-dashboard/boa-reporting"
|
|
BOA_RAW_BUCKET = aws_s3_bucket.boa_raw.id
|
|
}
|
|
expense_receiver = {
|
|
TABLE_NAME = aws_dynamodb_table.dashboard.name
|
|
EXPENSE_PROCESSOR_FN = "payments-expenseProcessor"
|
|
EXPENSE_SIGNING_SECRET_NAME = "payments-dashboard/expense-slack-signing-secret"
|
|
}
|
|
expense_processor = {
|
|
TABLE_NAME = aws_dynamodb_table.dashboard.name
|
|
EXPENSE_BOT_TOKEN_SECRET_NAME = "payments-dashboard/expense-slack-token"
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role" "lambda" {
|
|
for_each = local.functions
|
|
|
|
name = each.value.role_name
|
|
path = "/tf-managed/"
|
|
description = "Lambda execution role for ${each.value.function_name}"
|
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
|
permissions_boundary = aws_iam_policy.lambda_boundary.arn
|
|
}
|
|
|
|
data "aws_iam_policy_document" "lambda" {
|
|
for_each = local.functions
|
|
|
|
dynamic "statement" {
|
|
for_each = local.lambda_identity[each.key]
|
|
|
|
content {
|
|
sid = statement.value.sid
|
|
effect = "Allow"
|
|
actions = statement.value.actions
|
|
resources = statement.value.resources
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "lambda" {
|
|
for_each = local.functions
|
|
|
|
name = each.key
|
|
role = aws_iam_role.lambda[each.key].id
|
|
policy = data.aws_iam_policy_document.lambda[each.key].json
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "lambda_basic" {
|
|
for_each = local.functions
|
|
|
|
role = aws_iam_role.lambda[each.key].name
|
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "lambda_vpc" {
|
|
for_each = { for k, v in local.functions : k => v if v.vpc }
|
|
|
|
role = aws_iam_role.lambda[each.key].name
|
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
}
|
|
|
|
resource "aws_lambda_function" "this" {
|
|
for_each = local.functions
|
|
|
|
function_name = each.value.function_name
|
|
role = aws_iam_role.lambda[each.key].arn
|
|
handler = each.value.handler
|
|
runtime = "nodejs24.x"
|
|
architectures = ["arm64"]
|
|
memory_size = 256
|
|
timeout = each.value.timeout
|
|
|
|
s3_bucket = aws_s3_bucket.artifacts.id
|
|
s3_key = aws_s3_object.bootstrap_stub.key
|
|
source_code_hash = data.archive_file.bootstrap_stub.output_base64sha256
|
|
|
|
environment {
|
|
variables = local.lambda_env[each.key]
|
|
}
|
|
|
|
dynamic "vpc_config" {
|
|
for_each = each.value.vpc ? [1] : []
|
|
|
|
content {
|
|
subnet_ids = [aws_subnet.private.id]
|
|
security_group_ids = [aws_security_group.lambda.id]
|
|
}
|
|
}
|
|
|
|
lifecycle {
|
|
ignore_changes = [filename, s3_bucket, s3_key, s3_object_version, source_code_hash]
|
|
}
|
|
|
|
depends_on = [
|
|
aws_cloudwatch_log_group.lambda,
|
|
aws_iam_role_policy.lambda,
|
|
aws_iam_role_policy_attachment.lambda_basic,
|
|
aws_iam_role_policy_attachment.lambda_vpc,
|
|
aws_nat_gateway.this,
|
|
]
|
|
}
|
|
|
|
resource "aws_lambda_function_event_invoke_config" "process_csv" {
|
|
function_name = aws_lambda_function.this["process_csv"].function_name
|
|
maximum_event_age_in_seconds = 21600
|
|
maximum_retry_attempts = 2
|
|
|
|
destination_config {
|
|
on_failure {
|
|
destination = aws_sqs_queue.process_csv_dlq.arn
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_lambda_permission" "s3_csv" {
|
|
statement_id = "AllowS3InvokeProcessCsv"
|
|
action = "lambda:InvokeFunction"
|
|
function_name = aws_lambda_function.this["process_csv"].function_name
|
|
principal = "s3.amazonaws.com"
|
|
source_arn = aws_s3_bucket.csv.arn
|
|
source_account = local.account_id
|
|
}
|