mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +00:00
Enterprise-grade multi-tenant AWS cloud foundation. Modules: - GitHub OIDC for keyless CI/CD authentication - IAM account settings and security baseline - AWS Config Rules for compliance - ABAC (Attribute-Based Access Control) - SCPs (Service Control Policies) Features: - Multi-account architecture - Cost optimization patterns - Security best practices - Comprehensive documentation Tech: Terraform, AWS Organizations, IAM Identity Center
525 lines
14 KiB
HCL
525 lines
14 KiB
HCL
################################################################################
|
|
# Workload: SES Email Configuration
|
|
#
|
|
# Email sending infrastructure:
|
|
# - Domain identity with DKIM
|
|
# - Email identities for sending
|
|
# - Configuration sets with tracking
|
|
# - Event destinations (CloudWatch, SNS, Kinesis)
|
|
# - Dedicated IP pools (optional)
|
|
# - Suppression list management
|
|
#
|
|
# Use cases: Transactional email, marketing campaigns, notifications
|
|
################################################################################
|
|
|
|
terraform {
|
|
required_version = ">= 1.5"
|
|
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = ">= 5.0"
|
|
}
|
|
}
|
|
|
|
backend "s3" {
|
|
key = "05-workloads/<TENANT>-<NAME>-email/terraform.tfstate"
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Configuration - UPDATE THESE
|
|
################################################################################
|
|
|
|
locals {
|
|
# Naming
|
|
tenant = "<TENANT>"
|
|
name = "<NAME>"
|
|
env = "prod"
|
|
|
|
config_name = "${local.tenant}-${local.name}-${local.env}"
|
|
|
|
# Domain to verify (required)
|
|
domain = "example.com"
|
|
hosted_zone_id = null # Route53 zone ID for automatic DNS verification
|
|
|
|
# Additional email identities
|
|
email_identities = [
|
|
# "noreply@example.com",
|
|
# "support@example.com",
|
|
]
|
|
|
|
# MAIL FROM domain (optional custom subdomain)
|
|
mail_from_subdomain = "mail" # Results in mail.example.com
|
|
|
|
# DMARC record
|
|
enable_dmarc = true
|
|
dmarc_policy = "none" # none, quarantine, reject
|
|
dmarc_rua = null # Aggregate report email, e.g., "mailto:dmarc@example.com"
|
|
|
|
# Configuration set (for tracking)
|
|
enable_config_set = true
|
|
|
|
# Event tracking
|
|
tracking_options = {
|
|
click = true
|
|
open = true
|
|
bounce = true
|
|
complaint = true
|
|
delivery = true
|
|
reject = true
|
|
send = true
|
|
}
|
|
|
|
# Event destinations
|
|
cloudwatch_destination = true
|
|
sns_destination = true
|
|
|
|
# Reputation metrics
|
|
reputation_metrics_enabled = true
|
|
|
|
# Sending quotas (request increase via AWS support)
|
|
# These are informational - actual limits set by AWS
|
|
|
|
# Suppression list
|
|
suppression_list_reasons = ["BOUNCE", "COMPLAINT"]
|
|
|
|
# Dedicated IPs (additional cost)
|
|
enable_dedicated_ips = false
|
|
dedicated_ip_count = 0
|
|
|
|
# IAM policy for sending
|
|
create_sending_role = true
|
|
sending_role_name = "${local.config_name}-ses-sender"
|
|
}
|
|
|
|
################################################################################
|
|
# Variables
|
|
################################################################################
|
|
|
|
variable "region" {
|
|
type = string
|
|
default = "us-east-1"
|
|
}
|
|
|
|
variable "state_bucket" {
|
|
type = string
|
|
}
|
|
|
|
################################################################################
|
|
# Provider
|
|
################################################################################
|
|
|
|
provider "aws" {
|
|
region = var.region
|
|
|
|
default_tags {
|
|
tags = {
|
|
Tenant = local.tenant
|
|
App = local.name
|
|
Environment = local.env
|
|
ManagedBy = "terraform"
|
|
}
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Data Sources
|
|
################################################################################
|
|
|
|
data "aws_caller_identity" "current" {}
|
|
data "aws_region" "current" {}
|
|
|
|
################################################################################
|
|
# Domain Identity
|
|
################################################################################
|
|
|
|
resource "aws_ses_domain_identity" "main" {
|
|
domain = local.domain
|
|
}
|
|
|
|
resource "aws_ses_domain_dkim" "main" {
|
|
domain = aws_ses_domain_identity.main.domain
|
|
}
|
|
|
|
################################################################################
|
|
# DNS Records (if hosted zone provided)
|
|
################################################################################
|
|
|
|
# Domain verification
|
|
resource "aws_route53_record" "ses_verification" {
|
|
count = local.hosted_zone_id != null ? 1 : 0
|
|
zone_id = local.hosted_zone_id
|
|
name = "_amazonses.${local.domain}"
|
|
type = "TXT"
|
|
ttl = 600
|
|
records = [aws_ses_domain_identity.main.verification_token]
|
|
}
|
|
|
|
# DKIM records
|
|
resource "aws_route53_record" "dkim" {
|
|
count = local.hosted_zone_id != null ? 3 : 0
|
|
zone_id = local.hosted_zone_id
|
|
name = "${aws_ses_domain_dkim.main.dkim_tokens[count.index]}._domainkey.${local.domain}"
|
|
type = "CNAME"
|
|
ttl = 600
|
|
records = ["${aws_ses_domain_dkim.main.dkim_tokens[count.index]}.dkim.amazonses.com"]
|
|
}
|
|
|
|
# MAIL FROM domain
|
|
resource "aws_ses_domain_mail_from" "main" {
|
|
domain = aws_ses_domain_identity.main.domain
|
|
mail_from_domain = "${local.mail_from_subdomain}.${local.domain}"
|
|
}
|
|
|
|
resource "aws_route53_record" "mail_from_mx" {
|
|
count = local.hosted_zone_id != null ? 1 : 0
|
|
zone_id = local.hosted_zone_id
|
|
name = "${local.mail_from_subdomain}.${local.domain}"
|
|
type = "MX"
|
|
ttl = 600
|
|
records = ["10 feedback-smtp.${data.aws_region.current.name}.amazonses.com"]
|
|
}
|
|
|
|
resource "aws_route53_record" "mail_from_spf" {
|
|
count = local.hosted_zone_id != null ? 1 : 0
|
|
zone_id = local.hosted_zone_id
|
|
name = "${local.mail_from_subdomain}.${local.domain}"
|
|
type = "TXT"
|
|
ttl = 600
|
|
records = ["v=spf1 include:amazonses.com ~all"]
|
|
}
|
|
|
|
# DMARC record
|
|
resource "aws_route53_record" "dmarc" {
|
|
count = local.hosted_zone_id != null && local.enable_dmarc ? 1 : 0
|
|
zone_id = local.hosted_zone_id
|
|
name = "_dmarc.${local.domain}"
|
|
type = "TXT"
|
|
ttl = 600
|
|
records = [
|
|
local.dmarc_rua != null
|
|
? "v=DMARC1; p=${local.dmarc_policy}; rua=${local.dmarc_rua}"
|
|
: "v=DMARC1; p=${local.dmarc_policy}"
|
|
]
|
|
}
|
|
|
|
################################################################################
|
|
# Email Identities
|
|
################################################################################
|
|
|
|
resource "aws_ses_email_identity" "identities" {
|
|
for_each = toset(local.email_identities)
|
|
email = each.value
|
|
}
|
|
|
|
################################################################################
|
|
# Configuration Set
|
|
################################################################################
|
|
|
|
resource "aws_ses_configuration_set" "main" {
|
|
count = local.enable_config_set ? 1 : 0
|
|
name = local.config_name
|
|
|
|
reputation_metrics_enabled = local.reputation_metrics_enabled
|
|
|
|
delivery_options {
|
|
tls_policy = "REQUIRE"
|
|
}
|
|
|
|
tracking_options {
|
|
custom_redirect_domain = null
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# SNS Topic for Events
|
|
################################################################################
|
|
|
|
resource "aws_sns_topic" "ses_events" {
|
|
count = local.sns_destination ? 1 : 0
|
|
name = "${local.config_name}-ses-events"
|
|
kms_master_key_id = "alias/aws/sns"
|
|
|
|
tags = { Name = "${local.config_name}-ses-events" }
|
|
}
|
|
|
|
resource "aws_sns_topic_policy" "ses_events" {
|
|
count = local.sns_destination ? 1 : 0
|
|
arn = aws_sns_topic.ses_events[0].arn
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Sid = "AllowSES"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "ses.amazonaws.com"
|
|
}
|
|
Action = "sns:Publish"
|
|
Resource = aws_sns_topic.ses_events[0].arn
|
|
Condition = {
|
|
StringEquals = {
|
|
"AWS:SourceAccount" = data.aws_caller_identity.current.account_id
|
|
}
|
|
}
|
|
}]
|
|
})
|
|
}
|
|
|
|
################################################################################
|
|
# Event Destinations
|
|
################################################################################
|
|
|
|
resource "aws_ses_event_destination" "cloudwatch" {
|
|
count = local.enable_config_set && local.cloudwatch_destination ? 1 : 0
|
|
name = "cloudwatch"
|
|
configuration_set_name = aws_ses_configuration_set.main[0].name
|
|
enabled = true
|
|
|
|
matching_types = compact([
|
|
local.tracking_options.bounce ? "bounce" : "",
|
|
local.tracking_options.complaint ? "complaint" : "",
|
|
local.tracking_options.delivery ? "delivery" : "",
|
|
local.tracking_options.send ? "send" : "",
|
|
local.tracking_options.reject ? "reject" : "",
|
|
local.tracking_options.open ? "open" : "",
|
|
local.tracking_options.click ? "click" : "",
|
|
])
|
|
|
|
cloudwatch_destination {
|
|
default_value = "default"
|
|
dimension_name = "ses:source-ip"
|
|
value_source = "messageTag"
|
|
}
|
|
}
|
|
|
|
resource "aws_ses_event_destination" "sns" {
|
|
count = local.enable_config_set && local.sns_destination ? 1 : 0
|
|
name = "sns"
|
|
configuration_set_name = aws_ses_configuration_set.main[0].name
|
|
enabled = true
|
|
|
|
matching_types = ["bounce", "complaint"]
|
|
|
|
sns_destination {
|
|
topic_arn = aws_sns_topic.ses_events[0].arn
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# IAM Role for Sending
|
|
################################################################################
|
|
|
|
resource "aws_iam_role" "sending" {
|
|
count = local.create_sending_role ? 1 : 0
|
|
name = local.sending_role_name
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = [
|
|
"lambda.amazonaws.com",
|
|
"ecs-tasks.amazonaws.com",
|
|
"ec2.amazonaws.com"
|
|
]
|
|
}
|
|
Action = "sts:AssumeRole"
|
|
}]
|
|
})
|
|
|
|
tags = { Name = local.sending_role_name }
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "sending" {
|
|
count = local.create_sending_role ? 1 : 0
|
|
name = "ses-sending"
|
|
role = aws_iam_role.sending[0].id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Sid = "SendEmail"
|
|
Effect = "Allow"
|
|
Action = [
|
|
"ses:SendEmail",
|
|
"ses:SendRawEmail",
|
|
"ses:SendTemplatedEmail",
|
|
"ses:SendBulkTemplatedEmail"
|
|
]
|
|
Resource = "*"
|
|
Condition = {
|
|
StringEquals = {
|
|
"ses:FromAddress" = [for e in local.email_identities : e]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
Sid = "UseConfigSet"
|
|
Effect = "Allow"
|
|
Action = ["ses:SendEmail", "ses:SendRawEmail"]
|
|
Resource = local.enable_config_set ? aws_ses_configuration_set.main[0].arn : "*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
################################################################################
|
|
# SMTP Credentials (for apps that use SMTP)
|
|
################################################################################
|
|
|
|
resource "aws_iam_user" "smtp" {
|
|
name = "${local.config_name}-smtp"
|
|
tags = { Name = "${local.config_name}-smtp" }
|
|
}
|
|
|
|
resource "aws_iam_user_policy" "smtp" {
|
|
name = "ses-smtp"
|
|
user = aws_iam_user.smtp.name
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Effect = "Allow"
|
|
Action = "ses:SendRawEmail"
|
|
Resource = "*"
|
|
}]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_access_key" "smtp" {
|
|
user = aws_iam_user.smtp.name
|
|
}
|
|
|
|
################################################################################
|
|
# Email Templates (Examples)
|
|
################################################################################
|
|
|
|
resource "aws_ses_template" "welcome" {
|
|
name = "${local.config_name}-welcome"
|
|
subject = "Welcome to {{company_name}}!"
|
|
html = <<-HTML
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><meta charset="utf-8"></head>
|
|
<body>
|
|
<h1>Welcome, {{name}}!</h1>
|
|
<p>Thank you for signing up for {{company_name}}.</p>
|
|
<p>Click <a href="{{verification_link}}">here</a> to verify your email.</p>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
text = <<-TEXT
|
|
Welcome, {{name}}!
|
|
|
|
Thank you for signing up for {{company_name}}.
|
|
|
|
Click the link below to verify your email:
|
|
{{verification_link}}
|
|
TEXT
|
|
}
|
|
|
|
resource "aws_ses_template" "password_reset" {
|
|
name = "${local.config_name}-password-reset"
|
|
subject = "Reset your {{company_name}} password"
|
|
html = <<-HTML
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><meta charset="utf-8"></head>
|
|
<body>
|
|
<h1>Password Reset Request</h1>
|
|
<p>Hi {{name}},</p>
|
|
<p>Click <a href="{{reset_link}}">here</a> to reset your password.</p>
|
|
<p>This link expires in {{expiry_hours}} hours.</p>
|
|
<p>If you didn't request this, please ignore this email.</p>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
text = <<-TEXT
|
|
Password Reset Request
|
|
|
|
Hi {{name}},
|
|
|
|
Click the link below to reset your password:
|
|
{{reset_link}}
|
|
|
|
This link expires in {{expiry_hours}} hours.
|
|
|
|
If you didn't request this, please ignore this email.
|
|
TEXT
|
|
}
|
|
|
|
################################################################################
|
|
# Outputs
|
|
################################################################################
|
|
|
|
output "domain_identity_arn" {
|
|
value = aws_ses_domain_identity.main.arn
|
|
}
|
|
|
|
output "domain_verification_token" {
|
|
value = aws_ses_domain_identity.main.verification_token
|
|
}
|
|
|
|
output "dkim_tokens" {
|
|
value = aws_ses_domain_dkim.main.dkim_tokens
|
|
}
|
|
|
|
output "configuration_set" {
|
|
value = local.enable_config_set ? aws_ses_configuration_set.main[0].name : null
|
|
}
|
|
|
|
output "sns_topic_arn" {
|
|
value = local.sns_destination ? aws_sns_topic.ses_events[0].arn : null
|
|
}
|
|
|
|
output "sending_role_arn" {
|
|
value = local.create_sending_role ? aws_iam_role.sending[0].arn : null
|
|
}
|
|
|
|
output "smtp_credentials" {
|
|
value = {
|
|
username = aws_iam_access_key.smtp.id
|
|
password = aws_iam_access_key.smtp.ses_smtp_password_v4
|
|
endpoint = "email-smtp.${data.aws_region.current.name}.amazonaws.com"
|
|
port = 587
|
|
}
|
|
sensitive = true
|
|
}
|
|
|
|
output "dns_records_required" {
|
|
value = local.hosted_zone_id == null ? {
|
|
verification = {
|
|
name = "_amazonses.${local.domain}"
|
|
type = "TXT"
|
|
value = aws_ses_domain_identity.main.verification_token
|
|
}
|
|
dkim = [
|
|
for i, token in aws_ses_domain_dkim.main.dkim_tokens : {
|
|
name = "${token}._domainkey.${local.domain}"
|
|
type = "CNAME"
|
|
value = "${token}.dkim.amazonses.com"
|
|
}
|
|
]
|
|
mail_from_mx = {
|
|
name = "${local.mail_from_subdomain}.${local.domain}"
|
|
type = "MX"
|
|
value = "10 feedback-smtp.${data.aws_region.current.name}.amazonses.com"
|
|
}
|
|
mail_from_spf = {
|
|
name = "${local.mail_from_subdomain}.${local.domain}"
|
|
type = "TXT"
|
|
value = "v=spf1 include:amazonses.com ~all"
|
|
}
|
|
} : "DNS records created automatically"
|
|
}
|
|
|
|
output "templates" {
|
|
value = {
|
|
welcome = aws_ses_template.welcome.name
|
|
password_reset = aws_ses_template.password_reset.name
|
|
}
|
|
}
|