data "aws_iam_policy_document" "lambda_assume" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["lambda.amazonaws.com"] } } } locals { bedrock_invoke_policy_json = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream", ] Resource = [ "arn:aws:bedrock:${var.aws_region}:${local.account_id}:inference-profile/${local.bedrock_model_id}", "arn:aws:bedrock:${var.aws_region}::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0", "arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0", "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0", ] } ] }) dynamodb_cmk_rw_policy_json = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", "kms:GenerateDataKey*", "kms:ReEncrypt*", ] Resource = [data.aws_ssm_parameter.dynamodb_cmk_arn.value] Condition = { StringEquals = { "kms:ViaService" = "dynamodb.${var.aws_region}.amazonaws.com" } } } ] }) dynamodb_cmk_read_policy_json = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "kms:Decrypt", "kms:DescribeKey", ] Resource = [data.aws_ssm_parameter.dynamodb_cmk_arn.value] Condition = { StringEquals = { "kms:ViaService" = "dynamodb.${var.aws_region}.amazonaws.com" } } } ] }) web_ui_auth_secret_policy_json = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["secretsmanager:GetSecretValue"] Resource = [var.web_ui_auth_token_secret_arn] } ] }) } # --------------------------------------------------------------------------- # po-email-processor # --------------------------------------------------------------------------- resource "aws_iam_role" "po_email_processor" { name = "po-email-processor" 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" "po_email_processor_basic" { role = aws_iam_role.po_email_processor.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "po_email_processor_s3" { name = "s3-read-emails" role = aws_iam_role.po_email_processor.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket", ] Resource = [ aws_s3_bucket.po_emails.arn, "${aws_s3_bucket.po_emails.arn}/*", ] } ] }) } resource "aws_iam_role_policy" "po_email_processor_ddb" { name = "dynamodb-rw" role = aws_iam_role.po_email_processor.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem", "dynamodb:ConditionCheckItem", "dynamodb:DeleteItem", "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:UpdateItem", ] Resource = [ aws_dynamodb_table.purchase_orders.arn, "${aws_dynamodb_table.purchase_orders.arn}/index/*", ] } ] }) } resource "aws_iam_role_policy" "po_email_processor_kms" { name = "dynamodb-cmk" role = aws_iam_role.po_email_processor.id policy = local.dynamodb_cmk_rw_policy_json } resource "aws_iam_role_policy" "po_email_processor_bedrock" { name = "bedrock-invoke" role = aws_iam_role.po_email_processor.id policy = local.bedrock_invoke_policy_json } resource "aws_iam_role_policy" "po_email_processor_dlq" { name = "sqs-dlq-send" role = aws_iam_role.po_email_processor.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["sqs:SendMessage"] Resource = [aws_sqs_queue.po_email_processor_dlq.arn] } ] }) } # --------------------------------------------------------------------------- # po-web-ui # --------------------------------------------------------------------------- resource "aws_iam_role" "po_web_ui" { name = "po-web-ui" 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" "po_web_ui_basic" { role = aws_iam_role.po_web_ui.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "po_web_ui_ddb" { name = "dynamodb-read" role = aws_iam_role.po_web_ui.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "dynamodb:BatchGetItem", "dynamodb:ConditionCheckItem", "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", ] Resource = [ aws_dynamodb_table.purchase_orders.arn, "${aws_dynamodb_table.purchase_orders.arn}/index/*", ] } ] }) } resource "aws_iam_role_policy" "po_web_ui_kms" { name = "dynamodb-cmk" role = aws_iam_role.po_web_ui.id policy = local.dynamodb_cmk_read_policy_json } resource "aws_iam_role_policy" "po_web_ui_secrets" { name = "secretsmanager-get" role = aws_iam_role.po_web_ui.id policy = local.web_ui_auth_secret_policy_json } # --------------------------------------------------------------------------- # po-site-extractor # --------------------------------------------------------------------------- resource "aws_iam_role" "po_site_extractor" { name = "po-site-extractor" 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" "po_site_extractor_basic" { role = aws_iam_role.po_site_extractor.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "po_site_extractor_ddb" { name = "dynamodb-rw-sites" role = aws_iam_role.po_site_extractor.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem", "dynamodb:ConditionCheckItem", "dynamodb:DeleteItem", "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:UpdateItem", ] Resource = [ aws_dynamodb_table.verified_sites.arn, "${aws_dynamodb_table.verified_sites.arn}/index/*", aws_dynamodb_table.pending_site_review.arn, "${aws_dynamodb_table.pending_site_review.arn}/index/*", ] }, { Effect = "Allow" Action = [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", "dynamodb:ListStreams", ] Resource = [aws_dynamodb_table.purchase_orders.stream_arn] }, { Effect = "Allow" Action = ["dynamodb:DescribeTable"] Resource = [aws_dynamodb_table.purchase_orders.arn] } ] }) } resource "aws_iam_role_policy" "po_site_extractor_kms" { name = "dynamodb-cmk" role = aws_iam_role.po_site_extractor.id policy = local.dynamodb_cmk_read_policy_json } # --------------------------------------------------------------------------- # workorder-email-processor # --------------------------------------------------------------------------- resource "aws_iam_role" "wo_email_processor" { name = "workorder-email-processor" 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" "wo_email_processor_basic" { role = aws_iam_role.wo_email_processor.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "wo_email_processor_s3" { name = "s3-read-emails" role = aws_iam_role.wo_email_processor.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket", ] Resource = [ aws_s3_bucket.wo_emails.arn, "${aws_s3_bucket.wo_emails.arn}/*", ] } ] }) } resource "aws_iam_role_policy" "wo_email_processor_ddb" { name = "dynamodb-rw" role = aws_iam_role.wo_email_processor.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem", "dynamodb:ConditionCheckItem", "dynamodb:DeleteItem", "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:UpdateItem", ] Resource = [ aws_dynamodb_table.work_orders_kebab.arn, "${aws_dynamodb_table.work_orders_kebab.arn}/index/*", aws_dynamodb_table.work_order_comments_kebab.arn, "${aws_dynamodb_table.work_order_comments_kebab.arn}/index/*", ] } ] }) } resource "aws_iam_role_policy" "wo_email_processor_bedrock" { name = "bedrock-invoke" role = aws_iam_role.wo_email_processor.id policy = local.bedrock_invoke_policy_json } resource "aws_iam_role_policy" "wo_email_processor_dlq" { name = "sqs-dlq-send" role = aws_iam_role.wo_email_processor.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["sqs:SendMessage"] Resource = [aws_sqs_queue.wo_email_processor_dlq.arn] } ] }) } # --------------------------------------------------------------------------- # workorder-web-ui # --------------------------------------------------------------------------- resource "aws_iam_role" "wo_web_ui" { name = "workorder-web-ui" 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" "wo_web_ui_basic" { role = aws_iam_role.wo_web_ui.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "wo_web_ui_ddb" { name = "dynamodb-read" role = aws_iam_role.wo_web_ui.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "dynamodb:BatchGetItem", "dynamodb:ConditionCheckItem", "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", ] Resource = [ aws_dynamodb_table.work_orders_kebab.arn, "${aws_dynamodb_table.work_orders_kebab.arn}/index/*", aws_dynamodb_table.work_order_comments_kebab.arn, "${aws_dynamodb_table.work_order_comments_kebab.arn}/index/*", ] } ] }) } resource "aws_iam_role_policy" "wo_web_ui_secrets" { name = "secretsmanager-get" role = aws_iam_role.wo_web_ui.id policy = local.web_ui_auth_secret_policy_json } # --------------------------------------------------------------------------- # workorder-shoc-emitter # --------------------------------------------------------------------------- resource "aws_iam_role" "wo_shoc_emitter" { name = "workorder-shoc-emitter" 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" "wo_shoc_emitter_basic" { role = aws_iam_role.wo_shoc_emitter.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "wo_shoc_emitter_streams" { name = "dynamodb-stream-read" role = aws_iam_role.wo_shoc_emitter.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", "dynamodb:ListStreams", ] Resource = [ aws_dynamodb_table.work_orders_kebab.stream_arn, aws_dynamodb_table.work_order_comments_kebab.stream_arn, ] }, { Effect = "Allow" Action = ["dynamodb:DescribeTable"] Resource = [ aws_dynamodb_table.work_orders_kebab.arn, aws_dynamodb_table.work_order_comments_kebab.arn, ] } ] }) } resource "aws_iam_role_policy" "wo_shoc_emitter_secrets" { name = "secretsmanager-get" role = aws_iam_role.wo_shoc_emitter.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"] Resource = [var.shoc_hmac_secret_arn] } ] }) } resource "aws_iam_role_policy" "wo_shoc_emitter_kms" { name = "shoc-hmac-kms" role = aws_iam_role.wo_shoc_emitter.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["kms:Decrypt"] Resource = [aws_kms_key.shoc_webhook.arn] Condition = { StringEquals = { "kms:ViaService" = "secretsmanager.${var.aws_region}.amazonaws.com" } } } ] }) } resource "aws_iam_role_policy" "wo_shoc_emitter_sqs" { name = "sqs-send" role = aws_iam_role.wo_shoc_emitter.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["sqs:SendMessage"] Resource = [ aws_sqs_queue.shoc_emitter_rejected.arn, aws_sqs_queue.shoc_emitter_failures.arn, ] } ] }) } # --------------------------------------------------------------------------- # workorder-shoc-hmac-rotator # --------------------------------------------------------------------------- resource "aws_iam_role" "wo_shoc_hmac_rotator" { name = "workorder-shoc-hmac-rotator" 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" "wo_shoc_hmac_rotator_basic" { role = aws_iam_role.wo_shoc_hmac_rotator.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "wo_shoc_hmac_rotator_secrets" { name = "secretsmanager-rotate" role = aws_iam_role.wo_shoc_hmac_rotator.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "secretsmanager:DescribeSecret", "secretsmanager:GetSecretValue", "secretsmanager:PutSecretValue", "secretsmanager:UpdateSecretVersionStage", ] Resource = [var.shoc_hmac_secret_arn] } ] }) } resource "aws_iam_role_policy" "wo_shoc_hmac_rotator_kms" { name = "shoc-hmac-kms" role = aws_iam_role.wo_shoc_hmac_rotator.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey*", "kms:ReEncrypt*", ] Resource = [aws_kms_key.shoc_webhook.arn] Condition = { StringEquals = { "kms:ViaService" = "secretsmanager.${var.aws_region}.amazonaws.com" } } } ] }) } # --------------------------------------------------------------------------- # procurement-api # --------------------------------------------------------------------------- resource "aws_iam_role" "procurement_api" { name = "procurement-api" 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" "procurement_api_basic" { role = aws_iam_role.procurement_api.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } resource "aws_iam_role_policy" "procurement_api_ddb" { name = "dynamodb-read" role = aws_iam_role.procurement_api.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "dynamodb:BatchGetItem", "dynamodb:ConditionCheckItem", "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", ] Resource = [ aws_dynamodb_table.purchase_orders.arn, "${aws_dynamodb_table.purchase_orders.arn}/index/*", aws_dynamodb_table.verified_sites.arn, "${aws_dynamodb_table.verified_sites.arn}/index/*", aws_dynamodb_table.work_orders_kebab.arn, "${aws_dynamodb_table.work_orders_kebab.arn}/index/*", aws_dynamodb_table.work_order_comments_kebab.arn, "${aws_dynamodb_table.work_order_comments_kebab.arn}/index/*", ] } ] }) } resource "aws_iam_role_policy" "procurement_api_kms" { name = "dynamodb-cmk" role = aws_iam_role.procurement_api.id policy = local.dynamodb_cmk_read_policy_json } resource "aws_iam_role_policy" "procurement_api_secrets" { name = "secretsmanager-get" role = aws_iam_role.procurement_api.id policy = local.web_ui_auth_secret_policy_json }