Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Email Notifications [mimics PR branch] #141

Open
wants to merge 99 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
ab8e5a4
Bring over WIP from template-infra branch
rocketnova Aug 7, 2024
9ab6473
Enable notifications
rocketnova Aug 7, 2024
e772c1e
Restrict access
rocketnova Aug 8, 2024
18ee918
Conditionally return values based on verification method
rocketnova Aug 8, 2024
d62b062
Deal with pinpoint_email_channel role_arn
rocketnova Aug 8, 2024
391b603
Separate a verified sender identity from a pinpoint app
rocketnova Aug 8, 2024
9a607bb
Refactor outputs and variables
rocketnova Aug 8, 2024
0c417ad
Add code for aws_sesv2_email_identity_policy
rocketnova Aug 8, 2024
179b385
Drop workspace prefix for notifications config
rocketnova Aug 8, 2024
79f1bb9
Connect notifications and email-identity modules with app-config and …
rocketnova Aug 9, 2024
c07cb33
Rework idp to remove dependency on data output which would otherwise …
rocketnova Aug 9, 2024
11eee11
Add test email to satisfy tests
rocketnova Aug 9, 2024
a3e203b
Fix IAM role name collision
rocketnova Aug 12, 2024
c649240
Grant Github Actions auth for notifications: ses & mobiletargeting
rocketnova Aug 12, 2024
5dbcda6
Remove deprecated pinpoint identity role
rocketnova Aug 12, 2024
f0a9759
Move email identity into resources subdir
rocketnova Aug 12, 2024
7330a31
Refactor to support re-using existing email identities
rocketnova Aug 12, 2024
d7b5e35
Fix depends_on syntax
rocketnova Aug 12, 2024
3a80dbf
Simplify
rocketnova Aug 14, 2024
f6a57ed
Simplify and use domain name
rocketnova Aug 14, 2024
06bae3a
Auto-add dns records for domain verification
rocketnova Aug 14, 2024
9a53c2c
Merge branch 'main' into rocket/notifications
coilysiren Oct 4, 2024
1e24692
diff reduction
coilysiren Oct 4, 2024
64268b1
spacing and docs
coilysiren Oct 4, 2024
46dd4cd
use jsonencode
coilysiren Oct 4, 2024
0e4eb4c
module path
coilysiren Oct 4, 2024
a3834ee
prefix
coilysiren Oct 4, 2024
2b7bda9
try to fix verification records
coilysiren Oct 5, 2024
d2c2bf3
try using count
coilysiren Oct 7, 2024
33e654d
use route53 config from truss
coilysiren Oct 7, 2024
e3bbbdd
fix vars
coilysiren Oct 7, 2024
044b13c
trigger ci
coilysiren Oct 7, 2024
c38a5ec
fix regex
coilysiren Oct 11, 2024
bb99fa7
use 'mail' subdomain
coilysiren Oct 11, 2024
939f6fd
try subdomain again
coilysiren Oct 11, 2024
ca0a0d4
try subdomain again again
coilysiren Oct 11, 2024
a9e50da
revert to the original stuff
coilysiren Oct 11, 2024
bda7066
strip last
coilysiren Oct 11, 2024
09b6f08
fix incorrect var
coilysiren Oct 11, 2024
bd3f474
add back TLD
coilysiren Oct 12, 2024
11b8712
mail dot
coilysiren Oct 12, 2024
c45728a
mail_from_domain
coilysiren Oct 12, 2024
9c967c9
outbound subdomain
coilysiren Oct 12, 2024
f52cdf5
try again
coilysiren Oct 12, 2024
ab4da73
use trussworks patterns
coilysiren Oct 12, 2024
60edf04
update vars
coilysiren Oct 12, 2024
359f8ef
pass in seperate domain names
coilysiren Oct 12, 2024
c487417
workon mail from domain
coilysiren Oct 12, 2024
802536f
update vars
coilysiren Oct 12, 2024
5580c74
typo
coilysiren Oct 12, 2024
a7df4f9
route53 is on the wrong domain
coilysiren Oct 12, 2024
57bd7c7
small TXT change
coilysiren Oct 12, 2024
e688f90
move folders, remove some complexity around PR envs
coilysiren Oct 19, 2024
bea59b1
use existing zone
coilysiren Oct 19, 2024
3ab84b2
fix validation errors
coilysiren Oct 19, 2024
6de293c
dont double up on notes services
coilysiren Oct 19, 2024
5e212a0
fix conditional
coilysiren Oct 19, 2024
27fda90
Merge branch 'main' into kai/notifications
coilysiren Oct 25, 2024
6557098
more files means less merge conflicts
coilysiren Oct 25, 2024
23e653b
move module around
coilysiren Oct 25, 2024
e667aee
setup notifications client
coilysiren Oct 26, 2024
d5aedaa
add vars to service
coilysiren Oct 26, 2024
cc8e679
swap to real versions
coilysiren Oct 26, 2024
4fdb991
update output
coilysiren Oct 26, 2024
1f00ad9
remove errant module
coilysiren Oct 26, 2024
5add212
Revert "remove errant module"
coilysiren Oct 26, 2024
7f2ebe4
remove correct stuff
coilysiren Oct 26, 2024
f36741a
Merge remote-tracking branch 'origin' into kai/notifications-2
coilysiren Nov 1, 2024
2366ca9
Merge remote-tracking branch 'origin' into kai/notifications
coilysiren Nov 1, 2024
d8ecbfe
Revert "swap to real versions"
coilysiren Nov 1, 2024
3df9175
name based on PR apps
coilysiren Nov 1, 2024
7ba2cdb
Merge branch 'kai/notifications' into kai/notifications-2
coilysiren Nov 1, 2024
67710e8
Revert "Revert "swap to real versions""
coilysiren Nov 1, 2024
42f781d
string concat
coilysiren Nov 1, 2024
bd6e835
auto code review
coilysiren Nov 1, 2024
75e7bc4
typo
coilysiren Nov 1, 2024
31ff496
narrow env var scope
coilysiren Nov 8, 2024
d421603
domain_identity_arn
coilysiren Nov 8, 2024
52643a5
cleanup some vars
coilysiren Nov 8, 2024
f2d43bf
validation + test
coilysiren Nov 8, 2024
a6d6d83
Revert "validation + test"
coilysiren Nov 8, 2024
d8b89e2
use domain name as name
coilysiren Nov 8, 2024
8a10e95
remove some name
coilysiren Nov 8, 2024
7081ed2
comment
coilysiren Nov 8, 2024
d1218bb
move stuff around
coilysiren Nov 8, 2024
592a188
pull from correct var hopefully
coilysiren Nov 8, 2024
09a192d
use default email config, instead of explicit one
coilysiren Nov 8, 2024
9794f45
various renames
coilysiren Nov 14, 2024
1fa2229
revert idp stuff
coilysiren Nov 14, 2024
5910294
delete app first
coilysiren Nov 14, 2024
31eb7b2
delete it please, thanks
coilysiren Nov 14, 2024
fcfa6f4
update some var refs
coilysiren Nov 14, 2024
b8cd1fe
put everything back
coilysiren Nov 14, 2024
103f386
update vars
coilysiren Nov 14, 2024
fe2678d
reorg
coilysiren Nov 14, 2024
e1c6c2f
a var name
coilysiren Nov 14, 2024
8a8a6e9
fix domain vars
coilysiren Nov 15, 2024
9cf59a2
sender email cleanup
coilysiren Nov 15, 2024
dfee3b4
errant )
coilysiren Nov 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infra/app/app-config/dev.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module "dev_config" {
enable_https = true
has_database = local.has_database
has_incident_management_service = local.has_incident_management_service
enable_notifications = local.enable_notifications

# Enable and configure identity provider.
enable_identity_provider = local.enable_identity_provider
Expand Down
18 changes: 10 additions & 8 deletions infra/app/app-config/env-config/notifications.tf
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
# Notifications configuration
locals {
notifications_config = var.enable_notifications ? {
# Set to an SES-verified email address to be used when sending emails.
# Docs: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html
sender_email = null
# Pinpoint app name.
name = "${var.app_name}-${var.environment}"

# Configure the name that users see in the "From" section of their inbox, so that it's
# clearer who the email is from.
sender_display_name = null
# Configure the name that users see in the "From" section of their inbox,
# so that it's clearer who the email is from.
sender_display_name = "Kai Siren"

# Set to the email address to be used when sending emails.
# If enable_notifications is true, this is required.
sender_email = "coilysiren@${var.domain_name}"

# Configure the REPLY-TO email address if it should be different from the sender.
# Note: Only used by the identity-provider service.
reply_to_email = null
reply_to_email = "coilysiren@${var.domain_name}"
} : null
}
7 changes: 4 additions & 3 deletions infra/app/app-config/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ locals {
enable_identity_provider = true

# Whether or not the application should deploy a notification service
# Note: This is not yet ready for use.
# TODO(https://github.com/navapbc/template-infra/issues/567)
enable_notifications = false
# If enabled:
# 1. Creates an AWS Pinpoint application
# 2. Configures email notifications using AWS SES
enable_notifications = true

environment_configs = {
dev = module.dev_config
Expand Down
4 changes: 4 additions & 0 deletions infra/app/app-config/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ output "enable_identity_provider" {
value = local.enable_identity_provider
}

output "enable_notifications" {
value = local.enable_notifications
}

output "shared_network_name" {
value = local.shared_network_name
}
1 change: 1 addition & 0 deletions infra/app/app-config/prod.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module "prod_config" {
has_database = local.has_database
has_incident_management_service = local.has_incident_management_service
enable_identity_provider = local.enable_identity_provider
enable_notifications = local.enable_notifications

# These numbers are a starting point based on this article
# Update the desired instance size and counts based on the project's specific needs
Expand Down
1 change: 1 addition & 0 deletions infra/app/app-config/staging.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module "staging_config" {
has_database = local.has_database
has_incident_management_service = local.has_incident_management_service
enable_identity_provider = local.enable_identity_provider
enable_notifications = local.enable_notifications

# Enables ECS Exec access for debugging or jump access.
# See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html
Expand Down
1 change: 1 addition & 0 deletions infra/app/service/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ module "service" {
BUCKET_NAME = local.storage_config.bucket_name
},
local.identity_provider_environment_variables,
local.notifications_environment_variables,
local.service_config.extra_environment_variables
)

Expand Down
46 changes: 46 additions & 0 deletions infra/app/service/notifications.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
locals {
# If this is a temporary environment, re-use an existing email identity. Otherwise, create a new one.
domain_identity_arn = module.app_config.enable_notifications ? (
!local.is_temporary ?
module.notifications_email_domain[0].domain_identity_arn :
module.existing_notifications_email_domain[0].domain_identity_arn
) : null
notifications_environment_variables = module.app_config.enable_notifications ? {
AWS_PINPOINT_APP_ID = module.notifications[0].app_id,
AWS_PINPOINT_SENDER_EMAIL = local.notifications_config.sender_email
} : {}
notifications_app_name = "${local.prefix}${local.notifications_config.name}"
}

# If the app has `enable_notifications` set to true AND this is not a temporary
# environment, then create a email notification identity.
module "notifications_email_domain" {
count = module.app_config.enable_notifications && !local.is_temporary ? 1 : 0
source = "../../modules/notifications-email-domain/resources"

domain_name = local.service_config.domain_name
sender_email = local.notifications_config.sender_email
sender_display_name = local.notifications_config.sender_display_name
coilysiren marked this conversation as resolved.
Show resolved Hide resolved
}

# If the app has `enable_notifications` set to true AND this *is* a temporary
# environment, then create a email notification identity.
module "existing_notifications_email_domain" {
count = module.app_config.enable_notifications && local.is_temporary ? 1 : 0
source = "../../modules/notifications-email-domain/data"

domain_name = local.service_config.domain_name
}

# If the app has `enable_notifications` set to true, create a new email notification
# AWS Pinpoint app for the service. A new app is created for all environments, including
# temporary environments.
module "notifications" {
count = module.app_config.enable_notifications ? 1 : 0
source = "../../modules/notifications/resources"

name = local.notifications_app_name
domain_identity_arn = local.domain_identity_arn
sender_display_name = local.notifications_config.sender_display_name
sender_email = local.notifications_config.sender_email
}
13 changes: 4 additions & 9 deletions infra/modules/identity-provider/resources/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
## - Configures MFA
############################################################################################

data "aws_ses_email_identity" "sender" {
count = var.sender_email != null ? 1 : 0
email = var.sender_email
}

resource "aws_cognito_user_pool" "main" {
name = var.name

Expand All @@ -34,12 +29,12 @@ resource "aws_cognito_user_pool" "main" {
# Use this SES email to send cognito emails. If we're not using SES for emails then use null.
# Optionally configures the FROM address and the REPLY-TO address.
# Optionally configures using the Cognito default email or using SES.
source_arn = var.sender_email != null ? data.aws_ses_email_identity.sender[0].arn : null
email_sending_account = var.sender_email != null ? "DEVELOPER" : "COGNITO_DEFAULT"
source_arn = var.email_identity_arn
email_sending_account = var.email_identity_arn != null ? "DEVELOPER" : "COGNITO_DEFAULT"
# Customize the name that users see in the "From" section of their inbox, so that it's clearer who the email is from.
# This name also needs to be updated manually in the Cognito console for each environment's Advanced Security emails.
from_email_address = var.sender_email != null ? (var.sender_display_name != null ? "${var.sender_display_name} <${var.sender_email}>" : var.sender_email) : null
reply_to_email_address = var.reply_to_email != null ? var.reply_to_email : null
from_email_address = var.email_identity_arn != null ? (var.sender_display_name != null ? "${var.sender_display_name} <${var.sender_email}>" : var.sender_email) : null
reply_to_email_address = var.reply_to_email
}
coilysiren marked this conversation as resolved.
Show resolved Hide resolved

password_policy {
Expand Down
6 changes: 6 additions & 0 deletions infra/modules/identity-provider/resources/variables.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
variable "email_identity_arn" {
type = string
description = "The arn of the SESv2 email identity to use to send emails"
default = null
}

variable "is_temporary" {
description = "Whether the service is meant to be spun up temporarily (e.g. for automated infra tests). This is used to disable deletion protection."
type = bool
Expand Down
3 changes: 3 additions & 0 deletions infra/modules/notifications-email-domain/data/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
data "aws_sesv2_email_identity" "main" {
email_identity = var.domain_name
}
3 changes: 3 additions & 0 deletions infra/modules/notifications-email-domain/data/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "domain_identity_arn" {
value = data.aws_sesv2_email_identity.main.arn
}
4 changes: 4 additions & 0 deletions infra/modules/notifications-email-domain/data/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
variable "domain_name" {
type = string
description = "The domain name to use for the email identity"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Allow AWS Pinpoint to send email on behalf of this email identity.
# Docs: https://docs.aws.amazon.com/pinpoint/latest/developerguide/security_iam_id-based-policy-examples.html#security_iam_resource-based-policy-examples-access-ses-identities
resource "aws_sesv2_email_identity_policy" "sender" {
email_identity = aws_sesv2_email_identity.sender_domain.email_identity
policy_name = "PinpointEmail"

policy = jsonencode(
{
Version = "2008-10-17",
Statement = [
{
Sid = "PinpointEmail",
Effect = "Allow",
Principal = {
Service = "pinpoint.amazonaws.com"
},
Action = "ses:*",
Resource = aws_sesv2_email_identity.sender_domain.arn,
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
},
StringLike = {
"aws:SourceArn" = "arn:aws:mobiletargeting:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:apps/*"
}
}
}
]
}
)
}
34 changes: 34 additions & 0 deletions infra/modules/notifications-email-domain/resources/dns.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
data "aws_route53_zone" "zone" {
name = var.domain_name
}

resource "aws_route53_record" "dkim" {
count = 3

allow_overwrite = true
ttl = 60
type = "CNAME"
zone_id = data.aws_route53_zone.zone.zone_id
name = "${aws_sesv2_email_identity.sender_domain.dkim_signing_attributes[0].tokens[count.index]}._domainkey"
records = ["${aws_sesv2_email_identity.sender_domain.dkim_signing_attributes[0].tokens[count.index]}.dkim.amazonses.com"]

depends_on = [aws_sesv2_email_identity.sender_domain]
}

resource "aws_route53_record" "spf_mail_from" {
allow_overwrite = true
ttl = "600"
type = "TXT"
zone_id = data.aws_route53_zone.zone.zone_id
name = aws_sesv2_email_identity_mail_from_attributes.sender_domain.mail_from_domain
records = ["v=spf1 include:amazonses.com ~all"]
}

resource "aws_route53_record" "mx_receive" {
allow_overwrite = true
type = "MX"
ttl = "600"
name = local.mail_from_domain
zone_id = data.aws_route53_zone.zone.zone_id
records = ["10 feedback-smtp.${data.aws_region.current.name}.amazonaws.com"]
}
23 changes: 23 additions & 0 deletions infra/modules/notifications-email-domain/resources/logs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Configures AWS SES to send additional logging to AWS Cloudwatch.
# See https://docs.aws.amazon.com/ses/latest/dg/event-destinations-manage.html
resource "aws_ses_event_destination" "logs" {
name = "${local.dash_domain}-email-identity-logs"
configuration_set_name = aws_sesv2_configuration_set.email.configuration_set_name
enabled = true
matching_types = [
"bounce",
"click",
"complaint",
"delivery",
"open",
"reject",
"renderingFailure",
"send"
]

cloudwatch_destination {
dimension_name = "email_type"
default_value = "other"
value_source = "messageTag"
}
}
39 changes: 39 additions & 0 deletions infra/modules/notifications-email-domain/resources/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This module manages an SESv2 email identity.
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

locals {
mail_from_domain = "mail.${var.domain_name}"
dash_domain = replace(var.domain_name, ".", "-")
}

# Verify email sender identity.
# Docs: https://docs.aws.amazon.com/pinpoint/latest/userguide/channels-email-manage-verify.html
resource "aws_sesv2_email_identity" "sender_domain" {
email_identity = local.dash_domain
lorenyu marked this conversation as resolved.
Show resolved Hide resolved
configuration_set_name = aws_sesv2_configuration_set.email.configuration_set_name
}

# The configuration set applied to messages that is sent through this email channel.
resource "aws_sesv2_configuration_set" "email" {
configuration_set_name = var.domain_name

delivery_options {
tls_policy = "REQUIRE"
}

reputation_options {
reputation_metrics_enabled = true
}

sending_options {
sending_enabled = true
}
}

resource "aws_sesv2_email_identity_mail_from_attributes" "sender_domain" {
email_identity = aws_sesv2_email_identity.sender_domain.email_identity
mail_from_domain = local.mail_from_domain

depends_on = [aws_sesv2_email_identity.sender_domain]
}
3 changes: 3 additions & 0 deletions infra/modules/notifications-email-domain/resources/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "domain_identity_arn" {
value = aws_sesv2_email_identity.sender_domain.arn
}
15 changes: 15 additions & 0 deletions infra/modules/notifications-email-domain/resources/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "domain_name" {
description = "The domain name to configure SES, also used as the resource names"
type = string
}

variable "sender_email" {
type = string
description = "Email address to use to send notification emails"
}

variable "sender_display_name" {
type = string
description = "The display name for notification emails. Only used if sender_email is provided"
default = null
}
coilysiren marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions infra/modules/notifications/resources/email.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resource "aws_pinpoint_email_channel" "app" {
application_id = aws_pinpoint_app.app.application_id
from_address = var.sender_email != null ? (var.sender_display_name != null ? "${var.sender_display_name} <${var.sender_email}>" : var.sender_email) : null
coilysiren marked this conversation as resolved.
Show resolved Hide resolved
identity = var.domain_identity_arn
}
3 changes: 3 additions & 0 deletions infra/modules/notifications/resources/main.tf
coilysiren marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "aws_pinpoint_app" "app" {
name = var.name
}
3 changes: 3 additions & 0 deletions infra/modules/notifications/resources/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "app_id" {
value = aws_pinpoint_app.app.application_id
}
20 changes: 20 additions & 0 deletions infra/modules/notifications/resources/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "name" {
type = string
description = "Name of the notifications project/application"
}

variable "sender_email" {
type = string
description = "Email address to use to send notification emails"
}
coilysiren marked this conversation as resolved.
Show resolved Hide resolved

variable "sender_display_name" {
type = string
description = "The display name for notification emails. Only used if sender_email is provided"
default = null
}

variable "domain_identity_arn" {
type = string
description = "The ARN of the domain identity to use for sending emails"
}
2 changes: 2 additions & 0 deletions infra/project-config/aws_services.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ locals {
"kms",
"lambda",
"logs",
"mobiletargeting", # this is pinpoint
"pipes",
"rds",
"route53",
Expand All @@ -28,6 +29,7 @@ locals {
"schemas",
"secretsmanager",
"servicediscovery",
"ses",
"sns",
"ssm",
"states",
Expand Down
Loading