diff --git a/infra/modules/network/variables.tf b/infra/modules/network/variables.tf index 2a27053ca..afaccc7b2 100644 --- a/infra/modules/network/variables.tf +++ b/infra/modules/network/variables.tf @@ -14,6 +14,12 @@ variable "enable_command_execution" { default = false } +variable "enable_notifications" { + type = bool + description = "Whether the application(s) in this network need AWS Pinpoint access." + default = false +} + variable "has_database" { type = bool description = "Whether the application(s) in this network have a database. Determines whether to create VPC endpoints needed by the database layer." diff --git a/infra/modules/network/vpc_endpoints.tf b/infra/modules/network/vpc_endpoints.tf index 8ac8a720f..f631e7400 100644 --- a/infra/modules/network/vpc_endpoints.tf +++ b/infra/modules/network/vpc_endpoints.tf @@ -18,6 +18,9 @@ locals { # AWS services used by ECS Exec var.enable_command_execution ? ["ssmmessages"] : [], + + # AWS services used by notifications + var.enable_notifications ? ["pinpoint", "email-smtp"] : [], ) # S3 and DynamoDB use Gateway VPC endpoints. All other services use Interface VPC endpoints diff --git a/infra/modules/notifications/resources/access_control.tf b/infra/modules/notifications/resources/access_control.tf new file mode 100644 index 000000000..c2ab418bf --- /dev/null +++ b/infra/modules/notifications/resources/access_control.tf @@ -0,0 +1,32 @@ +resource "aws_iam_policy" "access" { + name = "${var.name}-notifications-access" + description = "Policy for calling SendMessages and SendUsersMessages on Pinpoint app ${var.name}" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + # From https://docs.aws.amazon.com/pinpoint/latest/developerguide/permissions-actions.html#permissions-actions-apiactions-messages + { + Effect = "Allow" + Action = [ + "mobiletargeting:SendMessages", + "mobiletargeting:SendUsersMessages" + ] + Resource = "${aws_pinpoint_app.app.arn}/messages" + }, + + # From https://docs.aws.amazon.com/pinpoint/latest/developerguide/permissions-ses.html + { + Effect = "Allow" + Action = [ + "ses:SendEmail", + "ses:SendRawEmail", + ] + Resource = [ + var.domain_identity_arn, + "arn:*:ses:*:*:configuration-set/*", + ] + } + ] + }) +} diff --git a/infra/modules/notifications/resources/outputs.tf b/infra/modules/notifications/resources/outputs.tf index 0efd8378c..87f285280 100644 --- a/infra/modules/notifications/resources/outputs.tf +++ b/infra/modules/notifications/resources/outputs.tf @@ -1,3 +1,7 @@ output "app_id" { value = aws_pinpoint_app.app.application_id } + +output "access_policy_arn" { + value = aws_iam_policy.access.arn +} diff --git a/infra/networks/main.tf.jinja b/infra/networks/main.tf.jinja index ce58d014b..772da91dd 100644 --- a/infra/networks/main.tf.jinja +++ b/infra/networks/main.tf.jinja @@ -45,6 +45,9 @@ locals { for environment_config in app.environment_configs : true if environment_config.service_config.enable_command_execution == true && environment_config.network_name == var.network_name ]) ]) + + # Whether any of the applications in the network has enabled notifications + enable_notifications = anytrue([for app in local.apps_in_network : app.enable_notifications]) } terraform { @@ -88,6 +91,7 @@ module "network" { has_database = local.has_database has_external_non_aws_service = local.has_external_non_aws_service enable_command_execution = local.enable_command_execution + enable_notifications = local.enable_notifications } module "domain" { diff --git a/infra/{{app_name}}/service/main.tf b/infra/{{app_name}}/service/main.tf index 65c2ba2c4..71ee152ea 100644 --- a/infra/{{app_name}}/service/main.tf +++ b/infra/{{app_name}}/service/main.tf @@ -195,7 +195,10 @@ module "service" { }, module.app_config.enable_identity_provider ? { identity_provider_access = module.identity_provider_client[0].access_policy_arn, - } : {} + } : {}, + module.app_config.enable_notifications ? { + notifications_access = module.notifications[0].access_policy_arn, + } : {}, ) is_temporary = local.is_temporary diff --git a/template-only-app/app.py b/template-only-app/app.py index 683386b47..786b22307 100644 --- a/template-only-app/app.py +++ b/template-only-app/app.py @@ -3,8 +3,9 @@ from datetime import datetime import click -from flask import Flask, render_template +from flask import Flask, redirect, render_template, request +import notifications import storage from db import get_db_connection @@ -63,6 +64,17 @@ def document_upload(): return f'
{additional_fields}
' +@app.route("/email-notifications", methods=["GET", "POST"]) +def email_notifications(): + if request.method == "POST": + to = request.form["to"] + subject = "Test notification" + message = "This is a system generated test notification. If you received this email in error, please ignore it." + logger.info("Sending test email to %s", to) + notifications.send_email(to, subject, message) + return f'
Send test email to:
' + + @app.route("/secrets") def secrets(): secret_sauce = os.environ["SECRET_SAUCE"] diff --git a/template-only-app/notifications.py b/template-only-app/notifications.py new file mode 100644 index 000000000..a57d1a595 --- /dev/null +++ b/template-only-app/notifications.py @@ -0,0 +1,38 @@ +import os +import boto3 + +def send_email(to: str, subject: str, message: str): + pinpoint_client = boto3.client("pinpoint") + app_id = os.environ["AWS_PINPOINT_APP_ID"] + + response = pinpoint_client.send_messages( + ApplicationId=app_id, + MessageRequest={ + "Addresses": { + to: { + "ChannelType": "EMAIL" + } + }, + "MessageConfiguration": { + "EmailMessage": { + "SimpleEmail": { + "Subject": { + "Charset": "UTF-8", + "Data": subject + }, + "HtmlPart": { + "Charset": "UTF-8", + "Data": message + }, + "TextPart": { + "Charset": "UTF-8", + "Data": message + } + } + } + } + } + ) + print(response) + + return response diff --git a/template-only-app/templates/index.html b/template-only-app/templates/index.html index 5263bce60..15cc4b155 100644 --- a/template-only-app/templates/index.html +++ b/template-only-app/templates/index.html @@ -5,6 +5,14 @@

Hello, world

+