# ACDL platform infrastructure — contract ingestion Lambda + DynamoDB (D-051) # # Deploys: # - DynamoDB table acdl-contracts (PK consumerRepo, SK contractId#submittedAt, SSE via CMK, PITR) # - KMS customer-managed key for DynamoDB + SSM (shared CMK) # - Lambda function acdl-contract-ingestor (Python 3.12, handler contract_ingestor.lambda_handler) # - Lambda Function URL (IAM auth — consumers invoke via SigV4) # - Secrets Manager secret acdl/github-token (stores the Lambda's GitHub PAT for issue creation) # - IAM execution role for the Lambda (DynamoDB write + Secrets Manager read + KMS decrypt) # # State: terraform/platform/terraform.tfstate (separate from spike/ and microservice/) terraform { required_version = ">= 1.9, < 1.10" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } backend "s3" { bucket = "acdl-tfstate-581513795199-us-east-1" key = "platform/terraform.tfstate" region = "us-east-1" } } provider "aws" { region = "us-east-1" } # KMS customer-managed key for DynamoDB SSE + SSM Parameter Store encryption resource "aws_kms_key" "acdl_platform" { description = "ACDL platform KMS key (DynamoDB SSE + SSM + Secrets Manager)" enable_key_rotation = true deletion_window_in_days = 30 } resource "aws_kms_alias" "acdl_platform" { name = "alias/acdl-platform" target_key_id = aws_kms_key.acdl_platform.key_id } # DynamoDB table for contract ingestion resource "aws_dynamodb_table" "acdl_contracts" { name = "acdl-contracts" billing_mode = "PAY_PER_REQUEST" hash_key = "consumerRepo" range_key = "contractId#submittedAt" attribute { name = "consumerRepo" type = "S" } attribute { name = "contractId#submittedAt" type = "S" } point_in_time_recovery { enabled = true } server_side_encryption { enabled = true kms_key_arn = aws_kms_key.acdl_platform.arn } tags = { acdl:owner = "acdl" acdl:contract = "platform" acdl:environment = "prod" acdl:cost-center = "acdl-default" } } # Secrets Manager secret for the Lambda's GitHub token (issue creation) resource "aws_secretsmanager_secret" "github_token" { name = "acdl/github-token" description = "GitHub PAT for the platform Lambda to create issues on the platform repo (D-055)." kms_key_id = aws_kms_key.acdl_platform.arn tags = { acdl:owner = "acdl" acdl:contract = "platform" acdl:environment = "prod" acdl:cost-center = "acdl-default" } } # IAM execution role for the Lambda resource "aws_iam_role" "lambda_exec" { name = "acdl-contract-ingestor-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }] }) } resource "aws_iam_role_policy" "lambda_permissions" { name = "acdl-contract-ingestor-policy" role = aws_iam_role.lambda_exec.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:UpdateItem"] Resource = aws_dynamodb_table.acdl_contracts.arn }, { Effect = "Allow" Action = ["dynamodb:GetItem", "dynamodb:Query"] Resource = aws_dynamodb_table.acdl_change_requests.arn }, { Effect = "Allow" Action = ["secretsmanager:GetSecretValue"] Resource = aws_secretsmanager_secret.github_token.arn }, { Effect = "Allow" Action = ["kms:Decrypt"] Resource = aws_kms_key.acdl_platform.arn }, { Effect = "Allow" Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"] Resource = "arn:aws:logs:*:*:*" } ] }) } # Lambda function resource "aws_lambda_function" "contract_ingestor" { function_name = "acdl-contract-ingestor" handler = "contract_ingestor.lambda_handler" runtime = "python3.12" role = aws_iam_role.lambda_exec.arn filename = "contract_ingestor.zip" source_code_hash = filebase64sha256("contract_ingestor.zip") environment { variables = { CONTRACTS_TABLE = aws_dynamodb_table.acdl_contracts.name GITHUB_TOKEN_SECRET_ID = aws_secretsmanager_secret.github_token.name PLATFORM_REPO = "acdl/acdl" } } tags = { acdl:owner = "acdl" acdl:contract = "platform" acdl:environment = "prod" acdl:cost-center = "acdl-default" } } # Lambda Function URL (IAM auth — consumers invoke via SigV4) resource "aws_lambda_function_url" "contract_ingestor" { function_name = aws_lambda_function.contract_ingestor.function_name authorization_type = "AWS_IAM" } # P1-6: Render the consumer invoke policy with the live AWS account ID. # The JSON template (consumer_invoke_policy.json) uses ${account_id} and # ${region} placeholders. Terraform renders them at apply time using the # caller's live account ID — no hardcoded placeholder account IDs. data "aws_caller_identity" "current" {} data "aws_region" "current" {} locals { invoke_policy_template = file("${path.module}/consumer_invoke_policy.json") rendered_invoke_policy = replace( replace(local.invoke_policy_template, "${account_id}", data.aws_caller_identity.current.account_id), "${region}", data.aws_region.current.name ) } output "consumer_invoke_policy_rendered" { value = local.rendered_invoke_policy description = "The consumer invoke policy JSON with the live account ID rendered. Distribute this to consumer accounts during onboarding." } # REQ-93: DynamoDB table for change requests (CMDB for decommission validation) resource "aws_dynamodb_table" "acdl_change_requests" { name = "acdl-change-requests" billing_mode = "PAY_PER_REQUEST" hash_key = "changeRequestId" range_key = "submittedAt" attribute { name = "changeRequestId" type = "S" } attribute { name = "submittedAt" type = "S" } point_in_time_recovery { enabled = true } server_side_encryption { enabled = true kms_key_arn = aws_kms_key.acdl_platform.arn } tags = { acdl:owner = "acdl" acdl:contract = "platform" acdl:environment = "prod" acdl:cost-center = "acdl-default" } } # REQ-107: SNS topic for separation-of-duties halt artifacts. # route_halt_artifact publishes here when ACDL_SOD_HALT_TOPIC_ARN is set. resource "aws_sns_topic" "acdl_sod_halt" { name = "acdl-sod-halt" kms_master_key_id = aws_kms_key.acdl_platform.id tags = { acdl:owner = "acdl" acdl:contract = "platform" acdl:environment = "prod" acdl:cost-center = "acdl-default" } } output "acdl_sod_halt_topic_arn" { value = aws_sns_topic.acdl_sod_halt.arn }