diff --git a/.gitignore b/.gitignore index 6349e36..f33aa50 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,8 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc + +# --------------- +# lambda build output +# --------------- +dist/ diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl index dac85a6..0447c33 100644 --- a/.terraform.lock.hcl +++ b/.terraform.lock.hcl @@ -1,26 +1,46 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/hashicorp/archive" { + version = "2.7.1" + constraints = "~> 2.4" + hashes = [ + "h1:A7EnRBVm4h9ryO9LwxYnKr4fy7ExPMwD5a1DsY7m1Y0=", + "zh:19881bb356a4a656a865f48aee70c0b8a03c35951b7799b6113883f67f196e8e", + "zh:2fcfbf6318dd514863268b09bbe19bfc958339c636bcbcc3664b45f2b8bf5cc6", + "zh:3323ab9a504ce0a115c28e64d0739369fe85151291a2ce480d51ccbb0c381ac5", + "zh:362674746fb3da3ab9bd4e70c75a3cdd9801a6cf258991102e2c46669cf68e19", + "zh:7140a46d748fdd12212161445c46bbbf30a3f4586c6ac97dd497f0c2565fe949", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:875e6ce78b10f73b1efc849bfcc7af3a28c83a52f878f503bb22776f71d79521", + "zh:b872c6ed24e38428d817ebfb214da69ea7eefc2c38e5a774db2ccd58e54d3a22", + "zh:cd6a44f731c1633ae5d37662af86e7b01ae4c96eb8b04144255824c3f350392d", + "zh:e0600f5e8da12710b0c52d6df0ba147a5486427c1a2cc78f31eea37a47ee1b07", + "zh:f21b2e2563bbb1e44e73557bcd6cdbc1ceb369d471049c40eb56cb84b6317a60", + "zh:f752829eba1cc04a479cf7ae7271526b402e206d5bcf1fcce9f535de5ff9e4e6", + ] +} + provider "registry.terraform.io/hashicorp/aws" { - version = "6.15.0" + version = "6.20.0" constraints = ">= 6.0.0, < 7.0.0" hashes = [ - "h1:nP988jNuJMcQIB3HLzVK7EfLk5mgWmoWgOsdZDZpIOs=", - "zh:05a3d3b268761cd90cabd6106bff2bf27f480ab31305cd8ef8c749060855f84d", - "zh:0edae750ebaee784624e41b1e18fe6179a513d63c5bb8fbffab4631391092b4f", - "zh:17f3d20951662ffd6a610d9c7f44afa281db6f220685796147e4ffb6374cc8b8", - "zh:373a5446fca3aeff76bc5637babd732d6c78d9a66c82a828a1b009e8b21f33bc", - "zh:3ce69866d23b7d0bb5bfa06f5407147ed90713924cd65246858c414313a96ffc", - "zh:40ab0ca19845890df706784bb62d9fc9961a15c23c894f0e9f89b66524c4be55", - "zh:66bd5554c582c1f01c1a509eedf4a81c861065b48a49d1be3e3ea98a89b1f801", - "zh:798b66f98cc8d8ff9c6844a8238d2639f951ef3956d412fb438708ba3e4ae9e3", - "zh:943e5f918d3b470fbfb9ea1c8bcc3b97a8218a0842e77a0fdbac0941dd461cdf", + "h1:x9OpnfcX0zYkPdJ38K5vSNe78QZReatBzk/pIlSDU1I=", + "zh:05173910d3031e6ba7b985188cff620bbe48cc6bb35ffc30238b9dcd9201c0d4", + "zh:0fd36a822e15c473593b5ca826ddfabe248a89833721960781aac65f05a55bc1", + "zh:13f478b843ef1fb52780e2771098ae42d6aaceab50d56bc31fdb02ddce6f4621", + "zh:16c5a17af7d0fc5329957bc48b5d10b987240076203453a108190132622d40bf", + "zh:290342af2da821383b8b5a897920cde2ae01f25768fe5e2df80b80bcc34421cb", + "zh:4cfe46a2ed6324467b896aa0dd7fbcf74a94a046d963c8a8fb865e817bcfb955", + "zh:78c243a5ec47f6d7066104191480fb13eb85995000cc7c80bc92383baea286a5", + "zh:88004fb58967b32061120d1d668e1af1f988da694994cddd11d2d4f7726f5054", + "zh:94184d7f00ec5e30d572d56979110e6cf6d5071583713b0e357cca72785e5365", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9e95f017fae84d07d6cd627949715dbe8749d4d819c13c8b0bef1a679a26671b", - "zh:aac7e07599a17fccbdd21b092a7741534af5bec60b492299f2bcd3d7279be4a9", - "zh:c6292faaf05a6dc45e170f67f251aaad9b7e1159b5946219908dd11025f4146b", - "zh:df892b9eca5ecfb3c0a0e829511aea7e6b30f08b862c7fba9de67d2ae9729983", - "zh:fb8c5ff7296d01bf60d983c64f45969ec664a40bdd768d90a35a6afe7df1aeb7", + "zh:b219d9c012db507a761c3f3a9147573f25d81d524a076e7c13099f87a50ae386", + "zh:beee1b49f414d1d8c03c9c4264725e288d77a676c093e3017de7d10a8ac45582", + "zh:e2e76be18ff4c2bf7cfcea9898475ec320b5574c01181581d76d047a2ee7be5c", + "zh:fa9afad49b0b2586032afcda2a5813da6e4ba9e56a8a25167b8f659b68e3983d", + "zh:fd284feb9f95a20ba848a9027f3c4298d75be6e882df40f075d60955f7bcf170", ] } diff --git a/AWS_TF_permission.json b/AWS_TF_permission.json index c4d1004..a549be4 100644 --- a/AWS_TF_permission.json +++ b/AWS_TF_permission.json @@ -114,6 +114,30 @@ "arn:aws:s3:::awsserverlessrepo-changesets*", "arn:aws:s3:::secrets-manager-rotation-apps-*/*" ] + }, + { + "Sid": "FullKMSAccess", + "Effect": "Allow", + "Action": "kms:*", + "Resource": "*" + }, + { + "Sid": "FullDynamoDBAccess", + "Effect": "Allow", + "Action": "dynamodb:*", + "Resource": "*" + }, + { + "Sid": "FullLambdaAccess", + "Effect": "Allow", + "Action": "lambda:*", + "Resource": "*" + }, + { + "Sid": "FullACMAccess", + "Effect": "Allow", + "Action": "acm:*", + "Resource": "*" } ] } diff --git a/dynamodb.tf b/dynamodb.tf new file mode 100644 index 0000000..2b4cb22 --- /dev/null +++ b/dynamodb.tf @@ -0,0 +1,11 @@ +resource "aws_dynamodb_table" "sent_emails" { + name = "SentEmails" + billing_mode = "PAY_PER_REQUEST" + hash_key = "messageId" + + # defines which attributes exist and their types. + attribute { + name = "messageId" + type = "S" + } +} diff --git a/ec2.tf b/ec2.tf index d01172a..d865217 100644 --- a/ec2.tf +++ b/ec2.tf @@ -3,66 +3,6 @@ locals { for az, s in aws_subnet.public : az => s.id } - - az_to_private_subnet_id = { - for az, s in aws_subnet.private : - az => s.id - } - - default_az = sort(keys(local.az_to_public_subnet_id))[0] - chosen_az = coalesce(var.target_az, local.default_az) - - chosen_subnet_id = (var.subnet_tier == "public" - ? lookup(local.az_to_public_subnet_id, local.chosen_az, null) - : lookup(local.az_to_private_subnet_id, local.chosen_az, null) - ) -} - -# Only if you want to create an isolated EC2 not part of ASG to use this -resource "aws_instance" "app" { - count = var.create_ec2_instance ? 1 : 0 - ami = var.ami_id - instance_type = var.instance_type - subnet_id = local.chosen_subnet_id - vpc_security_group_ids = [aws_security_group.app_sg.id] - iam_instance_profile = aws_iam_instance_profile.app_ec2_profile.name - - # assign ssh key - key_name = var.key_name != "" ? var.key_name : null - - # Do NOT protect from accidental termination - disable_api_termination = false - - # Root volume requirements - root_block_device { - volume_type = "gp2" - volume_size = 25 - delete_on_termination = true - } - - # ensure a public IP if your subnet doesn't auto-assign - associate_public_ip_address = var.subnet_tier == "public" ? true : false - - user_data = templatefile("${path.module}/scripts/user_data.sh", { - app_user = var.app_user - app_group = var.app_group - app_dir = var.app_dir - service_name = var.service_name - - db_host = aws_db_instance.db.address - db_port = aws_db_instance.db.port - db_name = var.db_name - db_username = var.db_username - db_password = random_password.rds.result - - aws_region = var.region - s3_bucket = aws_s3_bucket.images.bucket - }) - - tags = { - Name = "${var.name_prefix}-ec2" - Role = "webapp" - } } # ------------------ @@ -86,6 +26,20 @@ resource "aws_launch_template" "app" { associate_public_ip_address = true } + # Encrypt the root EBS volume with KMS key + # block_device_mappings { + # device_name = "/dev/xvda" # Root volume device name (varies by AMI) + # + # ebs { + # volume_size = 20 # or var.root_volume_size if you have one + # volume_type = "gp3" + # encrypted = true + # + # # Use the customer-managed KMS key dedicated for EC2 EBS encryption. + # kms_key_id = aws_kms_alias.ec2_key_alias.arn + # } + # } + user_data = base64encode(templatefile("${path.module}/scripts/user_data.sh", { app_user = var.app_user app_group = var.app_group @@ -98,8 +52,9 @@ resource "aws_launch_template" "app" { db_username = var.db_username db_password = random_password.rds.result - aws_region = var.region - s3_bucket = aws_s3_bucket.images.bucket + aws_region = var.region + s3_bucket = aws_s3_bucket.images.bucket + sns_topic_arn = aws_sns_topic.user_signup.arn })) tag_specifications { diff --git a/iam_role.tf b/iam_role.tf index 41d7f14..2b399a9 100644 --- a/iam_role.tf +++ b/iam_role.tf @@ -11,6 +11,7 @@ resource "aws_iam_role" "app_ec2_role" { }] }) } + # Instance Profile is a container for the IAM Role. # EC2 cannot directly attach an IAM Role — it must attach an Instance Profile instead. # The profile allows EC2 to assume the role and get temporary credentials automatically. @@ -100,3 +101,125 @@ resource "aws_iam_role_policy_attachment" "ssm_core" { role = aws_iam_role.app_ec2_role.name policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } + +# ------------------------ +# EC2 publish to SNS Topic +# ------------------------ + +# policy for EC2 can publish msg to subscribed SND Topic +resource "aws_iam_role_policy" "app_publish_sns" { + name = "app-publish-sns" + role = aws_iam_role.app_ec2_role.name + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Action = ["sns:Publish"], + Resource = aws_sns_topic.user_signup.arn + }] + }) +} + +# ------------------- +# Email Lambda Role +# ------------------- + +# Assume role +resource "aws_iam_role" "lambda_email_role" { + name = "lambda-email-sender-role" + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Principal = { Service = "lambda.amazonaws.com" }, + Action = "sts:AssumeRole" + }] + }) +} + +# Get current AWS account identity (used to build ARNs) +data "aws_caller_identity" "me" {} + +# Logs +# Basic logging permissions for the Lambda function. +# Allows the function to create log groups/streams and send log events to CloudWatch Logs. +resource "aws_iam_role_policy" "lambda_logs" { + name = "lambda-basic-logs" + role = aws_iam_role.lambda_email_role.id + + # Lambda will automatically create a log group in CloudWatch e.g. /aws/lambda/ + policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], + Resource = "arn:aws:logs:${var.region}:${data.aws_caller_identity.me.account_id}:log-group:/aws/lambda/*" + }] + }) +} + +# Secrets +# Allow the Lambda function to read the Mailgun API key +# stored in AWS Secrets Manager. +resource "aws_iam_role_policy" "lambda_secrets_read" { + name = "lambda-secrets-read" + role = aws_iam_role.lambda_email_role.id + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Action = ["secretsmanager:GetSecretValue"], + Resource = aws_secretsmanager_secret.mailgun.arn + }] + }) +} + +# KMS decrypt (for that secret's CMK) +resource "aws_iam_role_policy" "lambda_kms_decrypt" { + name = "lambda-kms-decrypt" + role = aws_iam_role.lambda_email_role.id + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Action = ["kms:Decrypt"], + Resource = aws_kms_alias.secrets_key_alias.arn + }] + }) +} + +# SES send +resource "aws_iam_role_policy" "lambda_ses_send" { + name = "lambda-ses-send" + role = aws_iam_role.lambda_email_role.id + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Action = ["ses:SendEmail", "ses:SendRawEmail"], + Resource = "*" + }] + }) +} + +# Allow lambda to r/w items in the DynamoDB table +# to perform deduplication +resource "aws_iam_role_policy" "lambda_dedup_policy" { + name = "lambda-dedup-policy" + role = aws_iam_role.lambda_email_role.id + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = ["dynamodb:PutItem", "dynamodb:GetItem"], + Resource = aws_dynamodb_table.sent_emails.arn + } + ] + }) +} diff --git a/kms.tf b/kms.tf new file mode 100644 index 0000000..58661bf --- /dev/null +++ b/kms.tf @@ -0,0 +1,65 @@ +# EBS +resource "aws_kms_key" "ec2_key" { + description = "Customer managed KMS key for EC2 EBS volume encryption" + enable_key_rotation = true # AWS rotates the key automatically + rotation_period_in_days = 90 + + tags = { + Name = "${var.name_prefix}-kms-ec2" + } +} + +resource "aws_kms_alias" "ec2_key_alias" { + name = "alias/${var.name_prefix}-ec2-kms" + target_key_id = aws_kms_key.ec2_key.key_id +} + +# RDS +resource "aws_kms_key" "rds_key" { + description = "Customer managed KMS key for RDS instance encryption" + enable_key_rotation = true # AWS rotates the key automatically + rotation_period_in_days = 90 + + tags = { + Name = "${var.name_prefix}-kms-rds" + } +} + +# KMS alias provide a stable and permanent identifier for the key +# The KMS key itself will rotate over time (new key versions are created during rotation), +# which means the key ARN changes. By using an alias, AWS automatically points the alias +# to the newest key version, ensuring that all encrypted resources continue to work without +resource "aws_kms_alias" "rds_key_alias" { + name = "alias/${var.name_prefix}-rds-kms" + target_key_id = aws_kms_key.rds_key.key_id +} + +# S3 bucket +resource "aws_kms_key" "s3_key" { + description = "Customer managed KMS key for S3 object encryption" + enable_key_rotation = true # AWS rotates the key automatically + rotation_period_in_days = 90 + + tags = { + Name = "${var.name_prefix}-kms-s3" + } +} + +resource "aws_kms_alias" "s3_key_alias" { + name = "alias/${var.name_prefix}-s3-kms" + target_key_id = aws_kms_key.s3_key.key_id +} + +resource "aws_kms_key" "secrets_key" { + description = "Customer managed KMS key for Secrets Manager (DB + email secrets)" + enable_key_rotation = true + + tags = { + Name = "${var.name_prefix}-kms-secrets" + } +} + +resource "aws_kms_alias" "secrets_key_alias" { + name = "alias/${var.name_prefix}-secrets-kms" + target_key_id = aws_kms_key.secrets_key.key_id +} diff --git a/lambda.tf b/lambda.tf new file mode 100644 index 0000000..159a38c --- /dev/null +++ b/lambda.tf @@ -0,0 +1,60 @@ +resource "aws_lambda_permission" "allow_sns_invoke" { + statement_id = "AllowExecutionFromSNS" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.email_sender.function_name + + # Grant SNS service permission to invoke this Lambda + principal = "sns.amazonaws.com" + + # Restrict invocation to only this specific SNS topic for security + source_arn = aws_sns_topic.user_signup.arn +} + +# package lambda directory as a ZIP for Lambda deployment +data "archive_file" "lambda_zip" { + type = "zip" + source_dir = "${path.module}/../serverless" + output_path = "${path.module}/dist/email_sender.zip" + excludes = ["node_modules", ".git", "dist"] +} + +resource "aws_lambda_function" "email_sender" { + function_name = "email_sender" + runtime = "nodejs20.x" + handler = "src/sendEmail.handler" # File name + exported function name + role = aws_iam_role.lambda_email_role.arn # IAM role with SES + CloudWatch permissions + + # Use the zip generated by the archive_file data source + filename = data.archive_file.lambda_zip.output_path + + # Ensures Lambda is automatically updated when the ZIP content changes. + # Terraform compares this hash to detect code updates. + source_code_hash = data.archive_file.lambda_zip.output_base64sha256 + + # Environment variables available in process.env + environment { + variables = { + MAILGUN_SECRET_ID = aws_secretsmanager_secret.mailgun.id + FROM_EMAIL = var.verifiedSenderEmail # Verified sender email in SES + VERIFY_URL_BASE = var.verificationEndPoint # Your web app verification endpoint + MAILGUN_DOMAIN = var.mailgun_domain # e.g. "mg.isaactai13.me" + } + } +} + +# Secrets Manager secret for Mailgun email service credentials +resource "aws_secretsmanager_secret" "mailgun" { + name = "${var.name_prefix}-mailgun" + kms_key_id = aws_kms_alias.secrets_key_alias.arn +} + + +# Actual value of the secret (Mailgun API key) +resource "aws_secretsmanager_secret_version" "mailgun" { + secret_id = aws_secretsmanager_secret.mailgun.id + + # Store the API key as a JSON payload so Lambda can parse it + secret_string = jsonencode({ + apiKey = var.mailgun_api_key + }) +} diff --git a/load_balancer.tf b/load_balancer.tf index 71475e1..880f682 100644 --- a/load_balancer.tf +++ b/load_balancer.tf @@ -12,6 +12,32 @@ resource "aws_lb_listener" "http" { load_balancer_arn = aws_lb.app_alb.arn port = 80 protocol = "HTTP" + + default_action { + type = "redirect" + + redirect { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } +} + +# enable https with AWS ACM +data "aws_acm_certificate" "dev_cert" { + domain = "*.${var.domain_name}" + most_recent = true + statuses = ["ISSUED"] +} + +resource "aws_lb_listener" "https" { + load_balancer_arn = aws_lb.app_alb.arn + port = 443 + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-2016-08" + certificate_arn = data.aws_acm_certificate.dev_cert.arn + default_action { type = "forward" target_group_arn = aws_lb_target_group.app_tg.arn diff --git a/rds.tf b/rds.tf index 704426d..ff2fd78 100644 --- a/rds.tf +++ b/rds.tf @@ -24,13 +24,19 @@ resource "random_password" "rds" { override_special = "!#$%&'()*+,-.:;<=>?[]^_{|}~" } -# Store the generated pwd in AWS Secrets Manager resource "aws_secretsmanager_secret" "rds" { - name = "${var.name_prefix}-rds-master-strong-password" - description = "Master password for the ${var.name_prefix} RDS instance" + name = "${var.name_prefix}-rds-master-strong-password" + description = "Master password for the ${var.name_prefix} RDS instance" + + # Use a customer-managed KMS key to encrypt the secret. + kms_key_id = aws_kms_alias.secrets_key_alias.arn + + # Set the recovery window to 0 days so the secret is deleted immediately + # when destroyed by Terraform, instead of waiting for the default 30-day recovery period. recovery_window_in_days = 0 } +# Store the generated pwd in AWS Secrets Manager resource "aws_secretsmanager_secret_version" "rds" { secret_id = aws_secretsmanager_secret.rds.id secret_string = random_password.rds.result @@ -53,6 +59,12 @@ resource "aws_db_instance" "db" { multi_az = false publicly_accessible = false + # Enable encryption at rest for the database storage + storage_encrypted = true + + # Use the customer-managed KMS key dedicated for RDS encryption + kms_key_id = aws_kms_alias.rds_key_alias.arn + vpc_security_group_ids = [aws_security_group.db_sg.id] db_subnet_group_name = aws_db_subnet_group.db_private.name parameter_group_name = aws_db_parameter_group.postgres.name diff --git a/route53_records.tf b/route53_records.tf index 53da0ac..12e51fd 100644 --- a/route53_records.tf +++ b/route53_records.tf @@ -1,5 +1,5 @@ data "aws_route53_zone" "env" { - name = var.route53_zone_name + name = var.domain_name private_zone = false } diff --git a/s3.tf b/s3.tf index 0fbd0de..a1dba84 100644 --- a/s3.tf +++ b/s3.tf @@ -23,7 +23,11 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "images" { rule { apply_server_side_encryption_by_default { - sse_algorithm = "AES256" + # Use AWS KMS for server-side encryption instead of S3-managed AES256 + sse_algorithm = "aws:kms" + + # Encrypt objects with our customer-managed KMS key dedicated for S3. + kms_master_key_id = aws_kms_alias.s3_key_alias.arn } } } diff --git a/scripts/user_data.sh b/scripts/user_data.sh index 4727a6d..633eb99 100644 --- a/scripts/user_data.sh +++ b/scripts/user_data.sh @@ -22,6 +22,7 @@ DB_USERNAME=${db_username} DB_PASSWORD=${db_password} AWS_REGION=${aws_region} S3_BUCKET=${s3_bucket} +SNS_TOPIC_ARN=${sns_topic_arn} EOF chown "${app_user}:${app_group}" "$ENV_FILE" # Change owner of the .env file to normal user diff --git a/security-group.tf b/security-group.tf index 486102a..d36d71c 100644 --- a/security-group.tf +++ b/security-group.tf @@ -20,10 +20,29 @@ resource "aws_vpc_security_group_ingress_rule" "lb_ingress_ipv4" { description = "Allow TCP ${each.value} from anywhere (IPv4)" } -resource "aws_vpc_security_group_egress_rule" "lb_all_out" { +resource "aws_vpc_security_group_ingress_rule" "lb_ingress_ipv6" { + for_each = toset([for p in local.lb_ingress_ports : tostring(p)]) + security_group_id = aws_security_group.lb_sg.id + cidr_ipv6 = "::/0" + from_port = tonumber(each.value) + to_port = tonumber(each.value) + ip_protocol = "tcp" + description = "Allow TCP ${each.value} from anywhere (IPv6)" +} + +resource "aws_vpc_security_group_egress_rule" "lb_all_out_ipv4" { security_group_id = aws_security_group.lb_sg.id cidr_ipv4 = "0.0.0.0/0" ip_protocol = "-1" + description = "Allow all outbound traffic (IPv4)" +} + +# Egress IPv6 +resource "aws_vpc_security_group_egress_rule" "lb_all_out_ipv6" { + security_group_id = aws_security_group.lb_sg.id + cidr_ipv6 = "::/0" + ip_protocol = "-1" + description = "Allow all outbound traffic (IPv6)" } #---------------------- diff --git a/sns.tf b/sns.tf new file mode 100644 index 0000000..27c752a --- /dev/null +++ b/sns.tf @@ -0,0 +1,13 @@ +resource "aws_sns_topic" "user_signup" { + name = "user-signup-topic" +} + +# The Lambda function subscribes to this SNS topic. +# 'topic_arn' specifies **which SNS topic** the subscription belongs to. +# 'endpoint' specifies **the subscriber** — in this case, the Lambda function ARN +# that will receive (be invoked with) every message published to the topic. +resource "aws_sns_topic_subscription" "lambda_sub" { + topic_arn = aws_sns_topic.user_signup.arn + protocol = "lambda" + endpoint = aws_lambda_function.email_sender.arn +} diff --git a/variables.tf b/variables.tf index 65d45c9..9772c96 100644 --- a/variables.tf +++ b/variables.tf @@ -145,7 +145,7 @@ variable "s3_prefix" { default = "" } -variable "route53_zone_name" { +variable "domain_name" { description = "Public hosted zone name for this environment (e.g., dev.domain.tld or demo.domain.tld)" type = string } @@ -176,6 +176,27 @@ variable "template_name" { default = "csye6225_asg" } +# Verified sender email address used by SES +variable "verifiedSenderEmail" { + description = "The SES verified email address used as the 'From' field for outgoing messages" + type = string +} + +# Base URL for the email verification link +variable "verificationEndPoint" { + description = "The base URL for the /validateEmail endpoint used in verification emails" + type = string +} + +variable "mailgun_domain" { + type = string +} + +variable "mailgun_api_key" { + type = string + sensitive = true +} + variable "tags" { description = "Common tags applied to all resources" type = map(string) diff --git a/versions.tf b/versions.tf index 51b7f8d..b16b8e5 100644 --- a/versions.tf +++ b/versions.tf @@ -4,5 +4,11 @@ terraform { source = "hashicorp/aws" version = ">= 6.0.0, <7.0.0" } + archive = { + source = "hashicorp/archive" + version = "~> 2.4" + } } + + required_version = ">= 1.5.0" }