mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-09-30 07:13:13 +00:00
Terraform still pinned only shoc-backend-dev, so the next ingest apply would drop the live staging HMAC grant.
355 lines
9.7 KiB
HCL
355 lines
9.7 KiB
HCL
data "aws_iam_policy_document" "shoc_webhook_kms" {
|
|
statement {
|
|
sid = "EnableRootAccountPermissions"
|
|
effect = "Allow"
|
|
actions = ["kms:*"]
|
|
resources = ["*"]
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = ["arn:aws:iam::${local.account_id}:root"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "ShocBackendDecrypt"
|
|
effect = "Allow"
|
|
actions = ["kms:Decrypt"]
|
|
resources = ["*"]
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = local.shoc_consumer_role_arns
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "kms:ViaService"
|
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "AllowSecretsManagerViaServiceAccount"
|
|
effect = "Allow"
|
|
actions = [
|
|
"kms:Decrypt",
|
|
"kms:Encrypt",
|
|
"kms:ReEncrypt*",
|
|
"kms:GenerateDataKey*",
|
|
]
|
|
resources = ["*"]
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = ["arn:aws:iam::${local.account_id}:root"]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "kms:ViaService"
|
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "AllowSecretsManagerGrants"
|
|
effect = "Allow"
|
|
actions = [
|
|
"kms:CreateGrant",
|
|
"kms:DescribeKey",
|
|
]
|
|
resources = ["*"]
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = ["arn:aws:iam::${local.account_id}:root"]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "kms:ViaService"
|
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "ShocHmacRotator"
|
|
effect = "Allow"
|
|
actions = [
|
|
"kms:Decrypt",
|
|
"kms:Encrypt",
|
|
"kms:ReEncrypt*",
|
|
"kms:GenerateDataKey*",
|
|
]
|
|
resources = ["*"]
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = [aws_iam_role.wo_shoc_hmac_rotator.arn]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "kms:ViaService"
|
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "ShocEmitterDecrypt"
|
|
effect = "Allow"
|
|
actions = ["kms:Decrypt"]
|
|
resources = ["*"]
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = [aws_iam_role.wo_shoc_emitter.arn]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "kms:ViaService"
|
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_kms_key" "shoc_webhook" {
|
|
description = "Dedicated CMK for the workorder-ingest/shoc-webhook-hmac secret (cross-account readable by the SHOC backend)"
|
|
enable_key_rotation = true
|
|
deletion_window_in_days = 7
|
|
policy = data.aws_iam_policy_document.shoc_webhook_kms.json
|
|
}
|
|
|
|
resource "aws_kms_alias" "shoc_webhook" {
|
|
name = "alias/workorder-ingest-shoc-webhook-kms"
|
|
target_key_id = aws_kms_key.shoc_webhook.key_id
|
|
}
|
|
|
|
resource "aws_secretsmanager_secret" "shoc_webhook_hmac" {
|
|
name = "workorder-ingest/shoc-webhook-hmac"
|
|
description = "HMAC signing keys for the SHOC work-order webhook (docs/shoc-webhook-contract.md section 6)"
|
|
kms_key_id = aws_kms_key.shoc_webhook.arn
|
|
|
|
tags = {
|
|
Purpose = "shoc-webhook-hmac"
|
|
ManagedBy = "terraform"
|
|
}
|
|
}
|
|
|
|
resource "aws_secretsmanager_secret_policy" "shoc_webhook_hmac" {
|
|
secret_arn = aws_secretsmanager_secret.shoc_webhook_hmac.arn
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
AWS = local.shoc_consumer_role_arns
|
|
}
|
|
Action = [
|
|
"secretsmanager:GetSecretValue",
|
|
"secretsmanager:DescribeSecret",
|
|
]
|
|
Resource = "*"
|
|
},
|
|
{
|
|
Effect = "Deny"
|
|
Principal = {
|
|
AWS = "arn:aws:iam::${local.account_id}:root"
|
|
}
|
|
Action = "secretsmanager:DeleteSecret"
|
|
Resource = "*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "aws_cloudwatch_log_group" "wo_shoc_hmac_rotator" {
|
|
name = "/aws/lambda/workorder-shoc-hmac-rotator"
|
|
retention_in_days = 60
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_lambda_function" "wo_shoc_hmac_rotator" {
|
|
function_name = "workorder-shoc-hmac-rotator"
|
|
role = aws_iam_role.wo_shoc_hmac_rotator.arn
|
|
handler = "handler.handler"
|
|
runtime = "python3.12"
|
|
architectures = ["arm64"]
|
|
memory_size = 128
|
|
timeout = 60
|
|
|
|
s3_bucket = aws_s3_bucket.artifacts.id
|
|
s3_key = aws_s3_object.lambda["wo_shoc_hmac_rotator"].key
|
|
source_code_hash = data.archive_file.lambda["wo_shoc_hmac_rotator"].output_base64sha256
|
|
|
|
environment {
|
|
variables = {
|
|
SENTRY_DSN = var.sentry_dsn
|
|
}
|
|
}
|
|
|
|
depends_on = [
|
|
aws_s3_object.lambda,
|
|
aws_cloudwatch_log_group.wo_shoc_hmac_rotator,
|
|
aws_iam_role_policy_attachment.wo_shoc_hmac_rotator_basic,
|
|
aws_iam_role_policy.wo_shoc_hmac_rotator_secrets,
|
|
aws_iam_role_policy.wo_shoc_hmac_rotator_kms,
|
|
]
|
|
}
|
|
|
|
resource "aws_lambda_permission" "wo_shoc_hmac_rotator" {
|
|
statement_id = "AllowSecretsManagerRotate"
|
|
action = "lambda:InvokeFunction"
|
|
function_name = aws_lambda_function.wo_shoc_hmac_rotator.function_name
|
|
principal = "secretsmanager.amazonaws.com"
|
|
source_account = local.account_id
|
|
source_arn = aws_secretsmanager_secret.shoc_webhook_hmac.arn
|
|
}
|
|
|
|
resource "aws_secretsmanager_secret_rotation" "shoc_webhook_hmac" {
|
|
secret_id = aws_secretsmanager_secret.shoc_webhook_hmac.id
|
|
rotation_lambda_arn = aws_lambda_function.wo_shoc_hmac_rotator.arn
|
|
|
|
rotation_rules {
|
|
automatically_after_days = 30
|
|
}
|
|
|
|
depends_on = [aws_lambda_permission.wo_shoc_hmac_rotator]
|
|
}
|
|
|
|
resource "aws_sqs_queue" "shoc_emitter_failures" {
|
|
name = "workorder-shoc-emitter-failures"
|
|
message_retention_seconds = 1209600
|
|
sqs_managed_sse_enabled = true
|
|
}
|
|
|
|
resource "aws_sqs_queue_policy" "shoc_emitter_failures" {
|
|
queue_url = aws_sqs_queue.shoc_emitter_failures.id
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Deny"
|
|
Principal = { AWS = "*" }
|
|
Action = "sqs:*"
|
|
Resource = aws_sqs_queue.shoc_emitter_failures.arn
|
|
Condition = {
|
|
Bool = { "aws:SecureTransport" = "false" }
|
|
}
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "aws_sqs_queue" "shoc_emitter_rejected" {
|
|
name = "workorder-shoc-emitter-rejected"
|
|
message_retention_seconds = 1209600
|
|
sqs_managed_sse_enabled = true
|
|
}
|
|
|
|
resource "aws_sqs_queue_policy" "shoc_emitter_rejected" {
|
|
queue_url = aws_sqs_queue.shoc_emitter_rejected.id
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Deny"
|
|
Principal = { AWS = "*" }
|
|
Action = "sqs:*"
|
|
Resource = aws_sqs_queue.shoc_emitter_rejected.arn
|
|
Condition = {
|
|
Bool = { "aws:SecureTransport" = "false" }
|
|
}
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "aws_cloudwatch_log_group" "wo_shoc_emitter" {
|
|
name = "/aws/lambda/workorder-shoc-emitter"
|
|
retention_in_days = 60
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_lambda_function" "wo_shoc_emitter" {
|
|
function_name = "workorder-shoc-emitter"
|
|
role = aws_iam_role.wo_shoc_emitter.arn
|
|
handler = "handler.handler"
|
|
runtime = "python3.12"
|
|
architectures = ["arm64"]
|
|
memory_size = 256
|
|
timeout = 60
|
|
|
|
s3_bucket = aws_s3_bucket.artifacts.id
|
|
s3_key = aws_s3_object.lambda["wo_shoc_emitter"].key
|
|
source_code_hash = data.archive_file.lambda["wo_shoc_emitter"].output_base64sha256
|
|
|
|
environment {
|
|
variables = {
|
|
HMAC_SECRET_ARN = var.shoc_hmac_secret_arn
|
|
REJECTED_QUEUE_URL = aws_sqs_queue.shoc_emitter_rejected.url
|
|
SHOC_WEBHOOK_URL = var.shoc_webhook_url
|
|
# Stream ARN classification (PLAT-11); must match ESM source table names.
|
|
WORK_ORDERS_TABLE = aws_dynamodb_table.work_orders_kebab.name
|
|
COMMENTS_TABLE = aws_dynamodb_table.work_order_comments_kebab.name
|
|
SENTRY_DSN = var.sentry_dsn
|
|
}
|
|
}
|
|
|
|
depends_on = [
|
|
aws_s3_object.lambda,
|
|
aws_cloudwatch_log_group.wo_shoc_emitter,
|
|
aws_iam_role_policy_attachment.wo_shoc_emitter_basic,
|
|
aws_iam_role_policy.wo_shoc_emitter_streams,
|
|
aws_iam_role_policy.wo_shoc_emitter_secrets,
|
|
aws_iam_role_policy.wo_shoc_emitter_kms,
|
|
aws_iam_role_policy.wo_shoc_emitter_sqs,
|
|
]
|
|
}
|
|
|
|
resource "aws_lambda_event_source_mapping" "wo_shoc_emitter_work_orders" {
|
|
event_source_arn = aws_dynamodb_table.work_orders_kebab.stream_arn
|
|
function_name = aws_lambda_function.wo_shoc_emitter.arn
|
|
starting_position = "LATEST"
|
|
batch_size = 10
|
|
parallelization_factor = 1
|
|
maximum_record_age_in_seconds = 86400
|
|
maximum_retry_attempts = -1
|
|
bisect_batch_on_function_error = false
|
|
function_response_types = ["ReportBatchItemFailures"]
|
|
enabled = true
|
|
|
|
destination_config {
|
|
on_failure {
|
|
destination_arn = aws_sqs_queue.shoc_emitter_failures.arn
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_lambda_event_source_mapping" "wo_shoc_emitter_comments" {
|
|
event_source_arn = aws_dynamodb_table.work_order_comments_kebab.stream_arn
|
|
function_name = aws_lambda_function.wo_shoc_emitter.arn
|
|
starting_position = "LATEST"
|
|
batch_size = 10
|
|
parallelization_factor = 1
|
|
maximum_record_age_in_seconds = 86400
|
|
maximum_retry_attempts = -1
|
|
bisect_batch_on_function_error = false
|
|
function_response_types = ["ReportBatchItemFailures"]
|
|
enabled = true
|
|
|
|
destination_config {
|
|
on_failure {
|
|
destination_arn = aws_sqs_queue.shoc_emitter_failures.arn
|
|
}
|
|
}
|
|
}
|