terraform { required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } random = { source = "hashicorp/random" version = "~> 3.0" } } backend "s3" { bucket = "calculator-127local-net-terraform-state" key = "email/infrastructure/terraform.tfstate" region = "us-west-2" } } provider "aws" { region = var.aws_region default_tags { tags = local.common_tags } } # Common tags for all resources locals { common_tags = { Project = "calculator-127local-net" Environment = "production" ManagedBy = "terraform" Owner = "will@aerenserve.net" Purpose = "email-forwarding" CostCenter = "calculator-services" } } # S3 bucket for email storage resource "aws_s3_bucket" "email_storage" { bucket = "calculator-127local-net-emails-${random_string.bucket_suffix.result}" tags = merge(local.common_tags, { Name = "Calculator Email Storage" }) } resource "random_string" "bucket_suffix" { length = 8 special = false upper = false } # S3 bucket versioning resource "aws_s3_bucket_versioning" "email_storage" { bucket = aws_s3_bucket.email_storage.id versioning_configuration { status = "Enabled" } } # S3 bucket server-side encryption resource "aws_s3_bucket_server_side_encryption_configuration" "email_storage" { bucket = aws_s3_bucket.email_storage.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } # S3 bucket public access block resource "aws_s3_bucket_public_access_block" "email_storage" { bucket = aws_s3_bucket.email_storage.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } # SES Domain identity for 127local.net resource "aws_ses_domain_identity" "calculator" { domain = var.domain_name } # SES Domain DKIM for 127local.net resource "aws_ses_domain_dkim" "calculator" { domain = aws_ses_domain_identity.calculator.domain } # Route53 records for SES DKIM validation resource "aws_route53_record" "ses_dkim" { count = 3 zone_id = var.route53_zone_id name = "${element(aws_ses_domain_dkim.calculator.dkim_tokens, count.index)}._domainkey.${var.domain_name}" type = "CNAME" ttl = "600" records = ["${element(aws_ses_domain_dkim.calculator.dkim_tokens, count.index)}.dkim.amazonses.com"] } # SES Email receiving rule set resource "aws_ses_receipt_rule_set" "calculator" { rule_set_name = "calculator-main-rule-set" } # SES Receipt rule for calculator@127local.net resource "aws_ses_receipt_rule" "calculator_forward" { name = "calculator-forward" rule_set_name = aws_ses_receipt_rule_set.calculator.rule_set_name recipients = ["calculator@${var.domain_name}"] enabled = true scan_enabled = true tls_policy = "Optional" add_header_action { header_name = "X-Forwarded-To" header_value = var.forward_email position = 1 } s3_action { bucket_name = aws_s3_bucket.email_storage.bucket object_key_prefix = "calculator/" position = 2 } } # Set this rule set as active resource "aws_ses_active_receipt_rule_set" "calculator" { rule_set_name = aws_ses_receipt_rule_set.calculator.rule_set_name } # Lambda function for email processing resource "aws_lambda_function" "email_processor" { filename = "email_processor.zip" function_name = "calculator-email-processor" role = aws_iam_role.lambda_role.arn handler = "email_processor.handler" source_code_hash = data.archive_file.email_processor_zip.output_base64sha256 runtime = "nodejs18.x" timeout = 30 environment { variables = { FORWARD_EMAIL = var.forward_email DOMAIN_NAME = var.domain_name ENVIRONMENT = "production" } } tags = merge(local.common_tags, { Name = "Calculator Email Processor" }) } # Create zip file for Lambda function with dependencies data "archive_file" "email_processor_zip" { type = "zip" source_dir = "." output_path = "email_processor.zip" excludes = ["email_processor.zip", "*.tf", "*.tfvars", "README.md", ".terraform", ".terraform.lock.hcl"] } # IAM role for Lambda function resource "aws_iam_role" "lambda_role" { name = "calculator-email-processor-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } } ] }) tags = local.common_tags } # IAM policy for Lambda function resource "aws_iam_role_policy" "lambda_policy" { name = "calculator-email-processor-policy" role = aws_iam_role.lambda_role.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ] Resource = "arn:aws:logs:*:*:*" }, { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "${aws_s3_bucket.email_storage.arn}/*" }, { Effect = "Allow" Action = [ "ses:SendEmail", "ses:SendRawEmail" ] Resource = "*" } ] }) } # S3 bucket notification to trigger Lambda resource "aws_s3_bucket_notification" "email_processor" { bucket = aws_s3_bucket.email_storage.id lambda_function { lambda_function_arn = aws_lambda_function.email_processor.arn events = ["s3:ObjectCreated:*"] filter_prefix = "calculator/" filter_suffix = "" } } # Lambda permission for S3 to invoke the function resource "aws_lambda_permission" "allow_s3" { statement_id = "AllowExecutionFromS3Bucket" action = "lambda:InvokeFunction" function_name = aws_lambda_function.email_processor.function_name principal = "s3.amazonaws.com" source_arn = aws_s3_bucket.email_storage.arn } # Outputs output "email_storage_bucket_name" { value = aws_s3_bucket.email_storage.bucket } output "ses_domain_identity" { value = aws_ses_domain_identity.calculator.domain } output "ses_receipt_rule_set_name" { value = aws_ses_receipt_rule_set.calculator.rule_set_name } output "lambda_function_name" { value = aws_lambda_function.email_processor.function_name } output "dkim_tokens" { value = aws_ses_domain_dkim.calculator.dkim_tokens }