From 9016261cd5bf54361a2aa46ba405b1f2c3162063 Mon Sep 17 00:00:00 2001 From: Rocket Date: Tue, 18 Jun 2024 18:40:34 -0700 Subject: [PATCH 01/25] Add identity provider modules --- .../access-control.tf | 15 +++ .../modules/identity-provider-client/main.tf | 37 +++++++ .../identity-provider-client/outputs.tf | 13 +++ .../identity-provider-client/variables.tf | 21 ++++ infra/modules/identity-provider/main.tf | 96 +++++++++++++++++++ infra/modules/identity-provider/outputs.tf | 4 + infra/modules/identity-provider/variables.tf | 58 +++++++++++ 7 files changed, 244 insertions(+) create mode 100644 infra/modules/identity-provider-client/access-control.tf create mode 100644 infra/modules/identity-provider-client/main.tf create mode 100644 infra/modules/identity-provider-client/outputs.tf create mode 100644 infra/modules/identity-provider-client/variables.tf create mode 100644 infra/modules/identity-provider/main.tf create mode 100644 infra/modules/identity-provider/outputs.tf create mode 100644 infra/modules/identity-provider/variables.tf diff --git a/infra/modules/identity-provider-client/access-control.tf b/infra/modules/identity-provider-client/access-control.tf new file mode 100644 index 00000000..db1421df --- /dev/null +++ b/infra/modules/identity-provider-client/access-control.tf @@ -0,0 +1,15 @@ +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +resource "aws_iam_policy" "cognito_access" { + name = "${var.name}-cognito-access" + policy = data.aws_iam_policy_document.cognito_access.json +} + +data "aws_iam_policy_document" "cognito_access" { + statement { + actions = ["cognito-idp:*"] + effect = "Allow" + resources = ["arn:aws:cognito-idp:${data.aws_region.current.name}:${data.aws_caller_identity.current.id}:userpool/${var.cognito_user_pool_id}"] + } +} diff --git a/infra/modules/identity-provider-client/main.tf b/infra/modules/identity-provider-client/main.tf new file mode 100644 index 00000000..d8110d52 --- /dev/null +++ b/infra/modules/identity-provider-client/main.tf @@ -0,0 +1,37 @@ +resource "aws_cognito_user_pool_client" "client" { + name = var.name + user_pool_id = var.cognito_user_pool_id + + callback_urls = var.callback_urls + logout_urls = var.logout_urls + supported_identity_providers = ["COGNITO"] + refresh_token_validity = 1 + access_token_validity = 60 + id_token_validity = 60 + token_validity_units { + refresh_token = "days" + access_token = "minutes" + id_token = "minutes" + } + + generate_secret = true + allowed_oauth_flows_user_pool_client = true + allowed_oauth_flows = ["code"] + allowed_oauth_scopes = ["phone", "email", "openid", "profile"] + explicit_auth_flows = ["ALLOW_ADMIN_USER_PASSWORD_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"] + + # Avoid security issue where error messages indicate when a user doesn't exist + prevent_user_existence_errors = "ENABLED" + + enable_token_revocation = true + enable_propagate_additional_user_context_data = false + + read_attributes = ["email", "email_verified", "phone_number", "phone_number_verified", "updated_at"] + write_attributes = ["email", "updated_at", "phone_number"] +} + +resource "aws_ssm_parameter" "client_secret" { + name = "/${var.name}/identity-provider/client-secret" + type = "SecureString" + value = aws_cognito_user_pool_client.client.client_secret +} diff --git a/infra/modules/identity-provider-client/outputs.tf b/infra/modules/identity-provider-client/outputs.tf new file mode 100644 index 00000000..dfcf2653 --- /dev/null +++ b/infra/modules/identity-provider-client/outputs.tf @@ -0,0 +1,13 @@ +output "client_id" { + description = "The ID of the user pool client" + value = aws_cognito_user_pool_client.client.id +} + +output "client_secret_arn" { + description = "The arn for the SSM parameter storing the user pool client secret" + value = aws_ssm_parameter.client_secret.arn +} + +output "access_policy_arn" { + value = aws_iam_policy.cognito_access.arn +} diff --git a/infra/modules/identity-provider-client/variables.tf b/infra/modules/identity-provider-client/variables.tf new file mode 100644 index 00000000..46132d86 --- /dev/null +++ b/infra/modules/identity-provider-client/variables.tf @@ -0,0 +1,21 @@ +variable "name" { + type = string + description = "Name of the application or service that will act as a client to the identity provider" +} + +variable "cognito_user_pool_id" { + type = string + description = "The ID of the user pool that the client will be associated with" +} + +variable "callback_urls" { + type = list(string) + description = "The URL(s) that the identity provider will redirect to after a successful login" + default = [] +} + +variable "logout_urls" { + type = list(string) + description = "The URL that the identity provider will redirect to after a successful logout" + default = [] +} diff --git a/infra/modules/identity-provider/main.tf b/infra/modules/identity-provider/main.tf new file mode 100644 index 00000000..b30d1000 --- /dev/null +++ b/infra/modules/identity-provider/main.tf @@ -0,0 +1,96 @@ +############################################################################################ +## A module for configuring a Cognito User Pool +## - Configures for email, but not SMS +## - 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 + + # Use a separate line to support automated terraform destroy commands + deletion_protection = "ACTIVE" + + username_attributes = ["email"] + auto_verified_attributes = ["email"] + + account_recovery_setting { + recovery_mechanism { + name = "verified_email" + priority = 1 + } + } + + device_configuration { + challenge_required_on_new_device = true + device_only_remembered_on_user_prompt = true + } + + email_configuration { + # 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" + # 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} <${var.sender_email}>" : null + reply_to_email_address = var.reply_to_email != null ? var.reply_to_email : null + } + + password_policy { + minimum_length = var.password_minimum_length + temporary_password_validity_days = var.temporary_password_validity_days + } + + mfa_configuration = "OPTIONAL" + software_token_mfa_configuration { + enabled = true + } + + user_pool_add_ons { + advanced_security_mode = "AUDIT" + } + + username_configuration { + case_sensitive = false + } + + user_attribute_update_settings { + attributes_require_verification_before_update = ["email"] + } + + schema { + name = "email" + attribute_data_type = "String" + mutable = "true" + required = "true" + + string_attribute_constraints { + max_length = 2048 + min_length = 0 + } + } + + admin_create_user_config { + # Optionally configures email template for activating the account + dynamic "invite_message_template" { + for_each = var.invite_email_message != null || var.invite_email_subject != null ? [0] : [] + content { + email_message = var.invite_email_message != null ? var.invite_email_message : null + email_subject = var.invite_email_subject != null ? var.invite_email_subject : null + } + } + } + + # Optionally configures email template for resetting a password + verification_message_template { + default_email_option = "CONFIRM_WITH_CODE" + email_message = var.verification_email_message != null ? var.verification_email_message : null + email_subject = var.verification_email_subject != null ? var.verification_email_subject : null + } +} diff --git a/infra/modules/identity-provider/outputs.tf b/infra/modules/identity-provider/outputs.tf new file mode 100644 index 00000000..03914275 --- /dev/null +++ b/infra/modules/identity-provider/outputs.tf @@ -0,0 +1,4 @@ +output "user_pool_id" { + description = "The ID of the user pool." + value = aws_cognito_user_pool.main.id +} diff --git a/infra/modules/identity-provider/variables.tf b/infra/modules/identity-provider/variables.tf new file mode 100644 index 00000000..54089e47 --- /dev/null +++ b/infra/modules/identity-provider/variables.tf @@ -0,0 +1,58 @@ +variable "name" { + type = string + description = "The name of the Cognito User Pool" +} + +variable "sender_email" { + type = string + description = "Email address to use to send identity provider emails. If none is provided, the identity service will be configured to use Cognito's default email functionality, which should only be relied on outside of production." + default = null +} + +variable "sender_display_name" { + type = string + description = "The display name for the identity service's emails. Only used if sender_email is provided" + default = null +} + +variable "reply_to_email" { + type = string + description = "Email address used as the REPLY-TO for identity service emails" + default = null +} + +variable "password_minimum_length" { + type = number + description = "The password minimum length" + default = 12 +} + +variable "temporary_password_validity_days" { + type = number + description = "The number of days a temporary password is valid for" + default = 7 +} + +variable "invite_email_message" { + type = string + description = "The email body for an account invitation email sent by an admin user. Must contain {username} and {####} placeholders, for username and temporary password, respectively." + default = null +} + +variable "invite_email_subject" { + type = string + description = "The email subject for an account invitation email sent by an admin user" + default = null +} + +variable "verification_email_message" { + type = string + description = "The email body for a password reset email. Must contain the {####} placeholder." + default = null +} + +variable "verification_email_subject" { + type = string + description = "The email subject for a password reset email" + default = null +} From 9bbc5deed8413d0c4fe0693b27aedaf57755b230 Mon Sep 17 00:00:00 2001 From: Rocket Date: Tue, 18 Jun 2024 18:41:19 -0700 Subject: [PATCH 02/25] Add identity provider option to service layer --- infra/app/app-config/dev.tf | 1 + infra/app/app-config/env-config/outputs.tf | 6 +++ infra/app/app-config/env-config/variables.tf | 6 +++ infra/app/app-config/main.tf | 7 +++ infra/app/app-config/prod.tf | 1 + infra/app/app-config/staging.tf | 1 + infra/app/service/main.tf | 48 ++++++++++++++++---- 7 files changed, 61 insertions(+), 9 deletions(-) diff --git a/infra/app/app-config/dev.tf b/infra/app/app-config/dev.tf index 1af435a7..e3fafd1d 100644 --- a/infra/app/app-config/dev.tf +++ b/infra/app/app-config/dev.tf @@ -10,6 +10,7 @@ module "dev_config" { enable_https = false has_database = local.has_database has_incident_management_service = local.has_incident_management_service + enable_identity_provider = local.enable_identity_provider # Enables ECS Exec access for debugging or jump access. # See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html diff --git a/infra/app/app-config/env-config/outputs.tf b/infra/app/app-config/env-config/outputs.tf index 5e10aae7..103e6e68 100644 --- a/infra/app/app-config/env-config/outputs.tf +++ b/infra/app/app-config/env-config/outputs.tf @@ -34,6 +34,12 @@ output "service_config" { # For job configs that don't define a source_bucket, add the source_bucket config property job_name => merge({ source_bucket = local.bucket_name }, job_config) } + + # Identity provider configuration + enable_identity_provider = var.enable_identity_provider + # Support local development against remote resources + auth_callback_urls = var.domain_name != null ? ["https://${var.domain_name}", "http://localhost:3000"] : ["http://localhost:3000"] + logout_urls = var.domain_name != null ? ["https://${var.domain_name}", "http://localhost:3000"] : ["http://localhost:3000"] } } diff --git a/infra/app/app-config/env-config/variables.tf b/infra/app/app-config/env-config/variables.tf index 60356f90..af5228f7 100644 --- a/infra/app/app-config/env-config/variables.tf +++ b/infra/app/app-config/env-config/variables.tf @@ -87,3 +87,9 @@ variable "enable_command_execution" { description = "Enables the ability to manually execute commands on running service containers using AWS ECS Exec" default = false } + +variable "enable_identity_provider" { + type = bool + description = "Enables identity provider" + default = false +} \ No newline at end of file diff --git a/infra/app/app-config/main.tf b/infra/app/app-config/main.tf index a1e7eebd..0298f993 100644 --- a/infra/app/app-config/main.tf +++ b/infra/app/app-config/main.tf @@ -24,6 +24,13 @@ locals { has_incident_management_service = false + # Whether or not the application should deploy an identity provider + # If enabled: + # 1. A Cognito user pool will be created + # 2. A Cognito user pool app client will be created + # 3. Environment variables for the app client will be added to the service + enable_identity_provider = false + environment_configs = { dev = module.dev_config staging = module.staging_config diff --git a/infra/app/app-config/prod.tf b/infra/app/app-config/prod.tf index 386eb080..a2dd7054 100644 --- a/infra/app/app-config/prod.tf +++ b/infra/app/app-config/prod.tf @@ -10,6 +10,7 @@ module "prod_config" { enable_https = false has_database = local.has_database has_incident_management_service = local.has_incident_management_service + enable_identity_provider = local.enable_identity_provider # These numbers are a starting point based on this article # Update the desired instance size and counts based on the project's specific needs diff --git a/infra/app/app-config/staging.tf b/infra/app/app-config/staging.tf index ec46f3f8..ffb7defe 100644 --- a/infra/app/app-config/staging.tf +++ b/infra/app/app-config/staging.tf @@ -10,6 +10,7 @@ module "staging_config" { enable_https = false has_database = local.has_database has_incident_management_service = local.has_incident_management_service + enable_identity_provider = local.enable_identity_provider # Enables ECS Exec access for debugging or jump access. # See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index 27c6ae12..06f7ffab 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -153,19 +153,33 @@ module "service" { extra_environment_variables = merge({ FEATURE_FLAGS_PROJECT = module.feature_flags.evidently_project_name BUCKET_NAME = local.storage_config.bucket_name - }, local.service_config.extra_environment_variables) - - secrets = [ - for secret_name in keys(local.service_config.secrets) : { + }, + local.service_config.enable_identity_provider ? { + COGNITO_USER_POOL_ID = module.identity_provider[0].user_pool_id + COGNITO_CLIENT_ID = module.identity_provider_client[0].client_id + } : {}, + local.service_config.extra_environment_variables + ) + + secrets = concat( + [for secret_name in keys(local.service_config.secrets) : { name = secret_name valueFrom = module.secrets[secret_name].secret_arn - } - ] - - extra_policies = { + }], + local.service_config.enable_identity_provider ? [{ + name = "COGNITO_CLIENT_SECRET" + valueFrom = module.identity_provider_client[0].client_secret_arn + }] : [] + ) + + extra_policies = merge({ feature_flags_access = module.feature_flags.access_policy_arn, storage_access = module.storage.access_policy_arn - } + }, + local.service_config.enable_identity_provider ? { + identity_access = module.identity_provider_client[0].access_policy_arn, + } : {} + ) is_temporary = local.is_temporary } @@ -192,3 +206,19 @@ module "storage" { name = local.storage_config.bucket_name is_temporary = local.is_temporary } + +module "identity_provider" { + count = local.service_config.enable_identity_provider ? 1 : 0 + source = "../../modules/identity-provider" + name = local.service_config.service_name +} + +module "identity_provider_client" { + count = local.service_config.enable_identity_provider ? 1 : 0 + source = "../../modules/identity-provider-client" + name = local.service_config.service_name + + cognito_user_pool_id = module.identity_provider[0].user_pool_id + callback_urls = local.service_config.auth_callback_urls + logout_urls = local.service_config.logout_urls +} \ No newline at end of file From 321e8736e52839a7b3ad39a99f9d894e96a1f0ac Mon Sep 17 00:00:00 2001 From: Rocket Date: Tue, 18 Jun 2024 18:41:57 -0700 Subject: [PATCH 03/25] Add support for auto deleting the identity provider --- template-only-bin/destroy-app-service | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/template-only-bin/destroy-app-service b/template-only-bin/destroy-app-service index beb5dd89..00b01104 100755 --- a/template-only-bin/destroy-app-service +++ b/template-only-bin/destroy-app-service @@ -18,11 +18,12 @@ backend_config_file="${environment}.s3.tfbackend" sed -i.bak 's/force_destroy = var.is_temporary/force_destroy = true/g' infra/modules/service/access-logs.tf sed -i.bak 's/force_destroy = var.is_temporary/force_destroy = true/g' infra/modules/storage/main.tf sed -i.bak 's/enable_deletion_protection = !var.is_temporary/enable_deletion_protection = false/g' infra/modules/service/load-balancer.tf +sed -i.bak 's/deletion_protection = "ACTIVE"/deletion_protection = "INACTIVE"/g' infra/modules/identity-provider/main.tf cd "infra/${app_name}/service" terraform init -reconfigure -backend-config="${backend_config_file}" -terraform apply -auto-approve -target="module.service.aws_s3_bucket.access_logs" -target="module.service.aws_lb.alb" -var="environment_name=${environment}" +terraform apply -auto-approve -target="module.service.aws_s3_bucket.access_logs" -target="module.service.aws_lb.alb" -target="module.identity_provider.aws_cognito_user_pool.main" -var="environment_name=${ENVIRONMENT}" terraform destroy -auto-approve -var="environment_name=${environment}" From 33550ee4dcb3890a2d5bfdc1a3fc1b245a57bfda Mon Sep 17 00:00:00 2001 From: Rocket Date: Mon, 24 Jun 2024 15:37:31 -0700 Subject: [PATCH 04/25] Use is_temporary --- infra/app/service/main.tf | 3 ++- infra/modules/identity-provider/main.tf | 2 +- infra/modules/identity-provider/variables.tf | 6 ++++++ template-only-bin/destroy-app-service | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index 06f7ffab..da4cde64 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -211,6 +211,7 @@ module "identity_provider" { count = local.service_config.enable_identity_provider ? 1 : 0 source = "../../modules/identity-provider" name = local.service_config.service_name + is_temporary = local.is_temporary } module "identity_provider_client" { @@ -221,4 +222,4 @@ module "identity_provider_client" { cognito_user_pool_id = module.identity_provider[0].user_pool_id callback_urls = local.service_config.auth_callback_urls logout_urls = local.service_config.logout_urls -} \ No newline at end of file +} diff --git a/infra/modules/identity-provider/main.tf b/infra/modules/identity-provider/main.tf index b30d1000..7280bf9c 100644 --- a/infra/modules/identity-provider/main.tf +++ b/infra/modules/identity-provider/main.tf @@ -13,7 +13,7 @@ resource "aws_cognito_user_pool" "main" { name = var.name # Use a separate line to support automated terraform destroy commands - deletion_protection = "ACTIVE" + deletion_protection = var.is_temporary ? "INACTIVE" : "ACTIVE" username_attributes = ["email"] auto_verified_attributes = ["email"] diff --git a/infra/modules/identity-provider/variables.tf b/infra/modules/identity-provider/variables.tf index 54089e47..d7190e0e 100644 --- a/infra/modules/identity-provider/variables.tf +++ b/infra/modules/identity-provider/variables.tf @@ -56,3 +56,9 @@ variable "verification_email_subject" { description = "The email subject for a password reset email" 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 + default = false +} diff --git a/template-only-bin/destroy-app-service b/template-only-bin/destroy-app-service index 00b01104..a335163e 100755 --- a/template-only-bin/destroy-app-service +++ b/template-only-bin/destroy-app-service @@ -18,7 +18,7 @@ backend_config_file="${environment}.s3.tfbackend" sed -i.bak 's/force_destroy = var.is_temporary/force_destroy = true/g' infra/modules/service/access-logs.tf sed -i.bak 's/force_destroy = var.is_temporary/force_destroy = true/g' infra/modules/storage/main.tf sed -i.bak 's/enable_deletion_protection = !var.is_temporary/enable_deletion_protection = false/g' infra/modules/service/load-balancer.tf -sed -i.bak 's/deletion_protection = "ACTIVE"/deletion_protection = "INACTIVE"/g' infra/modules/identity-provider/main.tf +sed -i.bak 's/deletion_protection = var.is_temporary ? "INACTIVE" : "ACTIVE"/deletion_protection = "INACTIVE"/g' infra/modules/identity-provider/main.tf cd "infra/${app_name}/service" From 14415505026853e4a458e7048700073fe631cc60 Mon Sep 17 00:00:00 2001 From: Rocket Date: Mon, 24 Jun 2024 15:37:46 -0700 Subject: [PATCH 05/25] Update ci-infra-service test --- infra/test/infra_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/infra/test/infra_test.go b/infra/test/infra_test.go index 8f63ced9..bd6e3191 100644 --- a/infra/test/infra_test.go +++ b/infra/test/infra_test.go @@ -133,12 +133,23 @@ func EnableDestroyService(t *testing.T, terraformOptions *terraform.Options) { WorkingDir: "../../", }) + shell.RunCommand(t, shell.Command{ + Command: "sed", + Args: []string{ + "-i.bak", + 's/deletion_protection = var.is_temporary ? "INACTIVE" : "ACTIVE"/deletion_protection = "INACTIVE"/g', + "infra/modules/identity-provider/main.tf", + }, + WorkingDir: "../../", + }) + // Clone the options and set targets to only apply to the buckets terraformOptions, err := terraformOptions.Clone() require.NoError(t, err) terraformOptions.Targets = []string{ "module.service.aws_s3_bucket.access_logs", "module.storage.aws_s3_bucket.storage", + "module.identity_provider.aws_cognito_user_pool.main", } terraform.Apply(t, terraformOptions) fmt.Println("::endgroup::") From dbd45a9b24926254697dc64a34ee0ad0d944d7e1 Mon Sep 17 00:00:00 2001 From: Rocket Date: Mon, 24 Jun 2024 15:45:27 -0700 Subject: [PATCH 06/25] Format --- infra/app/app-config/env-config/variables.tf | 2 +- infra/app/service/main.tf | 6 +++--- infra/test/infra_test.go | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/infra/app/app-config/env-config/variables.tf b/infra/app/app-config/env-config/variables.tf index af5228f7..aa8d0fd5 100644 --- a/infra/app/app-config/env-config/variables.tf +++ b/infra/app/app-config/env-config/variables.tf @@ -92,4 +92,4 @@ variable "enable_identity_provider" { type = bool description = "Enables identity provider" default = false -} \ No newline at end of file +} diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index da4cde64..9362cd26 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -208,9 +208,9 @@ module "storage" { } module "identity_provider" { - count = local.service_config.enable_identity_provider ? 1 : 0 - source = "../../modules/identity-provider" - name = local.service_config.service_name + count = local.service_config.enable_identity_provider ? 1 : 0 + source = "../../modules/identity-provider" + name = local.service_config.service_name is_temporary = local.is_temporary } diff --git a/infra/test/infra_test.go b/infra/test/infra_test.go index bd6e3191..209048e1 100644 --- a/infra/test/infra_test.go +++ b/infra/test/infra_test.go @@ -132,7 +132,6 @@ func EnableDestroyService(t *testing.T, terraformOptions *terraform.Options) { }, WorkingDir: "../../", }) - shell.RunCommand(t, shell.Command{ Command: "sed", Args: []string{ From 78cb5193d1036993ff671ff91380868758fa5c97 Mon Sep 17 00:00:00 2001 From: Rocket Date: Mon, 24 Jun 2024 15:54:59 -0700 Subject: [PATCH 07/25] Replace old all caps var with lowercase --- template-only-bin/destroy-app-service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template-only-bin/destroy-app-service b/template-only-bin/destroy-app-service index a335163e..6f327653 100755 --- a/template-only-bin/destroy-app-service +++ b/template-only-bin/destroy-app-service @@ -24,6 +24,6 @@ cd "infra/${app_name}/service" terraform init -reconfigure -backend-config="${backend_config_file}" -terraform apply -auto-approve -target="module.service.aws_s3_bucket.access_logs" -target="module.service.aws_lb.alb" -target="module.identity_provider.aws_cognito_user_pool.main" -var="environment_name=${ENVIRONMENT}" +terraform apply -auto-approve -target="module.service.aws_s3_bucket.access_logs" -target="module.service.aws_lb.alb" -target="module.identity_provider.aws_cognito_user_pool.main" -var="environment_name=${environment}" terraform destroy -auto-approve -var="environment_name=${environment}" From b2137e5b0df93e30e3a52cda329633f0124e1db8 Mon Sep 17 00:00:00 2001 From: Rocket Date: Mon, 24 Jun 2024 15:55:12 -0700 Subject: [PATCH 08/25] Escape double quotes in go --- infra/test/infra_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/test/infra_test.go b/infra/test/infra_test.go index 209048e1..cb58a541 100644 --- a/infra/test/infra_test.go +++ b/infra/test/infra_test.go @@ -136,7 +136,7 @@ func EnableDestroyService(t *testing.T, terraformOptions *terraform.Options) { Command: "sed", Args: []string{ "-i.bak", - 's/deletion_protection = var.is_temporary ? "INACTIVE" : "ACTIVE"/deletion_protection = "INACTIVE"/g', + "s/deletion_protection = var.is_temporary ? \"INACTIVE\" : \"ACTIVE\"/deletion_protection = \"INACTIVE\"/g", "infra/modules/identity-provider/main.tf", }, WorkingDir: "../../", From 1d720809df19aa9d8e80892d35322782654ee236 Mon Sep 17 00:00:00 2001 From: Rocket Date: Tue, 25 Jun 2024 14:05:16 -0700 Subject: [PATCH 09/25] Address PR feedback --- infra/app/app-config/dev.tf | 5 ++- .../env-config/identity-provider.tf | 43 +++++++++++++++++++ infra/app/app-config/env-config/outputs.tf | 10 ++--- infra/app/app-config/env-config/variables.tf | 12 ++++++ infra/app/service/main.tf | 41 +++++++++++------- infra/modules/identity-provider/main.tf | 11 ----- infra/modules/identity-provider/variables.tf | 12 ------ 7 files changed, 89 insertions(+), 45 deletions(-) create mode 100644 infra/app/app-config/env-config/identity-provider.tf diff --git a/infra/app/app-config/dev.tf b/infra/app/app-config/dev.tf index e3fafd1d..2457f69d 100644 --- a/infra/app/app-config/dev.tf +++ b/infra/app/app-config/dev.tf @@ -10,7 +10,10 @@ module "dev_config" { enable_https = false has_database = local.has_database has_incident_management_service = local.has_incident_management_service - enable_identity_provider = local.enable_identity_provider + + # Enable and configure identity provider. + enable_identity_provider = local.enable_identity_provider + extra_identity_provider_callback_urls = ["http://localhost:3000"] # Enables ECS Exec access for debugging or jump access. # See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf new file mode 100644 index 00000000..229f6bca --- /dev/null +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -0,0 +1,43 @@ +# Identity provider configuration +locals { + enable_identity_provider = var.enable_identity_provider + identity_provider_name = "${local.prefix}${var.app_name}-${var.environment}" + + password_policy = { + password_minimum_length = 12 + temporary_password_validity_days = 7 + } + + client = { + # Support local development against remote resources + auth_callback_urls = concat( + var.domain_name != null ? ["https://${var.domain_name}"] : [], + var.extra_identity_provider_callback_urls + ) + logout_urls = concat( + var.domain_name != null ? ["https://${var.domain_name}"] : [], + var.extra_identity_provider_logout_urls + ) + } + + # Set any values not `null` to override AWS Cognito defaults. + email = { + # When you're ready to use SES instead of the Cognito default to send emails, set this + # to the SES-verified email address to be used when sending emails. + sender_email = 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 = null + + # Configure the REPLY-TO email address if it should be different from the sender. + reply_to_email = null + } + + # Optionally configure email template for resetting a password. + # Set any values not `null` to override AWS Cognito defaults. + verification_email = { + verification_email_message = null + verification_email_subject = null + } +} diff --git a/infra/app/app-config/env-config/outputs.tf b/infra/app/app-config/env-config/outputs.tf index 103e6e68..aa512a83 100644 --- a/infra/app/app-config/env-config/outputs.tf +++ b/infra/app/app-config/env-config/outputs.tf @@ -34,15 +34,13 @@ output "service_config" { # For job configs that don't define a source_bucket, add the source_bucket config property job_name => merge({ source_bucket = local.bucket_name }, job_config) } - - # Identity provider configuration - enable_identity_provider = var.enable_identity_provider - # Support local development against remote resources - auth_callback_urls = var.domain_name != null ? ["https://${var.domain_name}", "http://localhost:3000"] : ["http://localhost:3000"] - logout_urls = var.domain_name != null ? ["https://${var.domain_name}", "http://localhost:3000"] : ["http://localhost:3000"] } } +output "identity_provider_config" { + value = local.identity_provider_config +} + output "storage_config" { value = { # Include project name in bucket name since buckets need to be globally unique across AWS diff --git a/infra/app/app-config/env-config/variables.tf b/infra/app/app-config/env-config/variables.tf index aa8d0fd5..a2722326 100644 --- a/infra/app/app-config/env-config/variables.tf +++ b/infra/app/app-config/env-config/variables.tf @@ -93,3 +93,15 @@ variable "enable_identity_provider" { description = "Enables identity provider" default = false } + +variable "extra_identity_provider_callback_urls" { + type = list(string) + description = "A list of additional callback urls to pass to the identity provider" + default = [] +} + +variable "extra_identity_provider_logout_urls" { + type = list(string) + description = "A list of additional logout urls to pass to the identity provider" + default = [] +} diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index 9362cd26..b08236b0 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -35,6 +35,7 @@ locals { database_config = local.environment_config.database_config storage_config = local.environment_config.storage_config incident_management_service_integration_config = local.environment_config.incident_management_service_integration + identity_provider_config = local.environment_config.identity_provider_config network_config = module.project_config.network_configs[local.environment_config.network_name] } @@ -150,11 +151,12 @@ module "service" { } } : null - extra_environment_variables = merge({ - FEATURE_FLAGS_PROJECT = module.feature_flags.evidently_project_name - BUCKET_NAME = local.storage_config.bucket_name + extra_environment_variables = merge( + { + FEATURE_FLAGS_PROJECT = module.feature_flags.evidently_project_name + BUCKET_NAME = local.storage_config.bucket_name }, - local.service_config.enable_identity_provider ? { + local.identity_provider_config.enable_identity_provider ? { COGNITO_USER_POOL_ID = module.identity_provider[0].user_pool_id COGNITO_CLIENT_ID = module.identity_provider_client[0].client_id } : {}, @@ -166,17 +168,18 @@ module "service" { name = secret_name valueFrom = module.secrets[secret_name].secret_arn }], - local.service_config.enable_identity_provider ? [{ + local.identity_provider_config.enable_identity_provider ? [{ name = "COGNITO_CLIENT_SECRET" valueFrom = module.identity_provider_client[0].client_secret_arn }] : [] ) - extra_policies = merge({ - feature_flags_access = module.feature_flags.access_policy_arn, - storage_access = module.storage.access_policy_arn + extra_policies = merge( + { + feature_flags_access = module.feature_flags.access_policy_arn, + storage_access = module.storage.access_policy_arn }, - local.service_config.enable_identity_provider ? { + local.identity_provider_config.enable_identity_provider ? { identity_access = module.identity_provider_client[0].access_policy_arn, } : {} ) @@ -208,18 +211,26 @@ module "storage" { } module "identity_provider" { - count = local.service_config.enable_identity_provider ? 1 : 0 + count = local.identity_provider_config.enable_identity_provider ? 1 : 0 source = "../../modules/identity-provider" - name = local.service_config.service_name is_temporary = local.is_temporary + + name = local.identity_provider_config.identity_provider_name + sender_email = local.identity_provider_config.email.sender_email + sender_display_name = local.identity_provider_config.email.sender_display_name + reply_to_email = local.identity_provider_config.email.reply_to_email + password_minimum_length = local.identity_provider_config.password_policy.password_minimum_length + temporary_password_validity_days = local.identity_provider_config.password_policy.temporary_password_validity_days + verification_email_message = local.identity_provider_config.verification_email.verification_email_message + verification_email_subject = local.identity_provider_config.verification_email.verification_email_subject } module "identity_provider_client" { - count = local.service_config.enable_identity_provider ? 1 : 0 + count = local.identity_provider_config.enable_identity_provider ? 1 : 0 source = "../../modules/identity-provider-client" - name = local.service_config.service_name + name = local.identity_provider_config.identity_provider_name cognito_user_pool_id = module.identity_provider[0].user_pool_id - callback_urls = local.service_config.auth_callback_urls - logout_urls = local.service_config.logout_urls + callback_urls = local.identity_provider_config.client.auth_callback_urls + logout_urls = local.identity_provider_config.client.logout_urls } diff --git a/infra/modules/identity-provider/main.tf b/infra/modules/identity-provider/main.tf index 7280bf9c..4d3477c9 100644 --- a/infra/modules/identity-provider/main.tf +++ b/infra/modules/identity-provider/main.tf @@ -76,17 +76,6 @@ resource "aws_cognito_user_pool" "main" { } } - admin_create_user_config { - # Optionally configures email template for activating the account - dynamic "invite_message_template" { - for_each = var.invite_email_message != null || var.invite_email_subject != null ? [0] : [] - content { - email_message = var.invite_email_message != null ? var.invite_email_message : null - email_subject = var.invite_email_subject != null ? var.invite_email_subject : null - } - } - } - # Optionally configures email template for resetting a password verification_message_template { default_email_option = "CONFIRM_WITH_CODE" diff --git a/infra/modules/identity-provider/variables.tf b/infra/modules/identity-provider/variables.tf index d7190e0e..eabc6b4e 100644 --- a/infra/modules/identity-provider/variables.tf +++ b/infra/modules/identity-provider/variables.tf @@ -33,18 +33,6 @@ variable "temporary_password_validity_days" { default = 7 } -variable "invite_email_message" { - type = string - description = "The email body for an account invitation email sent by an admin user. Must contain {username} and {####} placeholders, for username and temporary password, respectively." - default = null -} - -variable "invite_email_subject" { - type = string - description = "The email subject for an account invitation email sent by an admin user" - default = null -} - variable "verification_email_message" { type = string description = "The email body for a password reset email. Must contain the {####} placeholder." From 9c6b3a284ebd119681826ee190c6f3b9270554a2 Mon Sep 17 00:00:00 2001 From: Rocket Date: Tue, 25 Jun 2024 14:18:43 -0700 Subject: [PATCH 10/25] Fix locals output structure --- .../env-config/identity-provider.tf | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf index 229f6bca..3fbd3b58 100644 --- a/infra/app/app-config/env-config/identity-provider.tf +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -1,43 +1,46 @@ # Identity provider configuration locals { enable_identity_provider = var.enable_identity_provider - identity_provider_name = "${local.prefix}${var.app_name}-${var.environment}" - password_policy = { - password_minimum_length = 12 - temporary_password_validity_days = 7 - } + identity_provider_config = var.enable_identity_provider ? { + identity_provider_name = "${local.prefix}${var.app_name}-${var.environment}" - client = { - # Support local development against remote resources - auth_callback_urls = concat( - var.domain_name != null ? ["https://${var.domain_name}"] : [], - var.extra_identity_provider_callback_urls - ) - logout_urls = concat( - var.domain_name != null ? ["https://${var.domain_name}"] : [], - var.extra_identity_provider_logout_urls - ) - } + password_policy = { + password_minimum_length = 12 + temporary_password_validity_days = 7 + } - # Set any values not `null` to override AWS Cognito defaults. - email = { - # When you're ready to use SES instead of the Cognito default to send emails, set this - # to the SES-verified email address to be used when sending emails. - sender_email = null + client = { + # Support local development against remote resources + auth_callback_urls = concat( + var.domain_name != null ? ["https://${var.domain_name}"] : [], + var.extra_identity_provider_callback_urls + ) + logout_urls = concat( + var.domain_name != null ? ["https://${var.domain_name}"] : [], + var.extra_identity_provider_logout_urls + ) + } - # 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 + # Set any values not `null` to override AWS Cognito defaults. + email = { + # When you're ready to use SES instead of the Cognito default to send emails, set this + # to the SES-verified email address to be used when sending emails. + sender_email = null - # Configure the REPLY-TO email address if it should be different from the sender. - reply_to_email = 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 = null - # Optionally configure email template for resetting a password. - # Set any values not `null` to override AWS Cognito defaults. - verification_email = { - verification_email_message = null - verification_email_subject = null - } + # Configure the REPLY-TO email address if it should be different from the sender. + reply_to_email = null + } + + # Optionally configure email template for resetting a password. + # Set any values not `null` to override AWS Cognito defaults. + verification_email = { + verification_email_message = null + verification_email_subject = null + } + } : null } From 61fc8b602ffd0b0257657333cb58315e47a96297 Mon Sep 17 00:00:00 2001 From: Rocket Date: Tue, 25 Jun 2024 14:53:44 -0700 Subject: [PATCH 11/25] Move config to right level --- infra/app/app-config/dev.tf | 5 ++++- infra/app/app-config/env-config/identity-provider.tf | 4 +--- infra/app/app-config/outputs.tf | 4 ++++ infra/app/service/main.tf | 10 +++++----- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/infra/app/app-config/dev.tf b/infra/app/app-config/dev.tf index 2457f69d..f5bdf33b 100644 --- a/infra/app/app-config/dev.tf +++ b/infra/app/app-config/dev.tf @@ -12,8 +12,11 @@ module "dev_config" { has_incident_management_service = local.has_incident_management_service # Enable and configure identity provider. - enable_identity_provider = local.enable_identity_provider + enable_identity_provider = local.enable_identity_provider + + # Support local development against the dev instance. extra_identity_provider_callback_urls = ["http://localhost:3000"] + extra_identity_provider_logout_urls = ["http://localhost:3000"] # Enables ECS Exec access for debugging or jump access. # See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf index 3fbd3b58..c8909613 100644 --- a/infra/app/app-config/env-config/identity-provider.tf +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -1,9 +1,7 @@ # Identity provider configuration locals { - enable_identity_provider = var.enable_identity_provider - identity_provider_config = var.enable_identity_provider ? { - identity_provider_name = "${local.prefix}${var.app_name}-${var.environment}" + identity_provider_name = "${local.prefix}${var.app_name}-${var.environment}" password_policy = { password_minimum_length = 12 diff --git a/infra/app/app-config/outputs.tf b/infra/app/app-config/outputs.tf index ac79190d..8e915f06 100644 --- a/infra/app/app-config/outputs.tf +++ b/infra/app/app-config/outputs.tf @@ -32,6 +32,10 @@ output "has_incident_management_service" { value = local.has_incident_management_service } +output "enable_identity_provider" { + value = local.enable_identity_provider +} + output "image_repository_name" { value = local.image_repository_name } diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index b08236b0..f72c686e 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -156,7 +156,7 @@ module "service" { FEATURE_FLAGS_PROJECT = module.feature_flags.evidently_project_name BUCKET_NAME = local.storage_config.bucket_name }, - local.identity_provider_config.enable_identity_provider ? { + module.app_config.enable_identity_provider ? { COGNITO_USER_POOL_ID = module.identity_provider[0].user_pool_id COGNITO_CLIENT_ID = module.identity_provider_client[0].client_id } : {}, @@ -168,7 +168,7 @@ module "service" { name = secret_name valueFrom = module.secrets[secret_name].secret_arn }], - local.identity_provider_config.enable_identity_provider ? [{ + module.app_config.enable_identity_provider ? [{ name = "COGNITO_CLIENT_SECRET" valueFrom = module.identity_provider_client[0].client_secret_arn }] : [] @@ -179,7 +179,7 @@ module "service" { feature_flags_access = module.feature_flags.access_policy_arn, storage_access = module.storage.access_policy_arn }, - local.identity_provider_config.enable_identity_provider ? { + module.app_config.enable_identity_provider ? { identity_access = module.identity_provider_client[0].access_policy_arn, } : {} ) @@ -211,7 +211,7 @@ module "storage" { } module "identity_provider" { - count = local.identity_provider_config.enable_identity_provider ? 1 : 0 + count = module.app_config.enable_identity_provider ? 1 : 0 source = "../../modules/identity-provider" is_temporary = local.is_temporary @@ -226,7 +226,7 @@ module "identity_provider" { } module "identity_provider_client" { - count = local.identity_provider_config.enable_identity_provider ? 1 : 0 + count = module.app_config.enable_identity_provider ? 1 : 0 source = "../../modules/identity-provider-client" name = local.identity_provider_config.identity_provider_name From c36bbf128952ab4083fcccf8d65336b95491785d Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 11:18:47 -0700 Subject: [PATCH 12/25] Remove extra _auth_ in var name --- infra/app/app-config/dev.tf | 4 ++-- infra/app/service/main.tf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/app/app-config/dev.tf b/infra/app/app-config/dev.tf index f5bdf33b..1884fe35 100644 --- a/infra/app/app-config/dev.tf +++ b/infra/app/app-config/dev.tf @@ -15,8 +15,8 @@ module "dev_config" { enable_identity_provider = local.enable_identity_provider # Support local development against the dev instance. - extra_identity_provider_callback_urls = ["http://localhost:3000"] - extra_identity_provider_logout_urls = ["http://localhost:3000"] + extra_identity_provider_callback_urls = ["http://localhost"] + extra_identity_provider_logout_urls = ["http://localhost"] # Enables ECS Exec access for debugging or jump access. # See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index f72c686e..ddc6354c 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -231,6 +231,6 @@ module "identity_provider_client" { name = local.identity_provider_config.identity_provider_name cognito_user_pool_id = module.identity_provider[0].user_pool_id - callback_urls = local.identity_provider_config.client.auth_callback_urls + callback_urls = local.identity_provider_config.client.callback_urls logout_urls = local.identity_provider_config.client.logout_urls } From 54b8fbaffba28c260eba609085b28f05b6893a87 Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 11:19:20 -0700 Subject: [PATCH 13/25] Sort variables.tf and outputs.tf alphabetically --- .../identity-provider-client/outputs.tf | 8 ++--- .../identity-provider-client/variables.tf | 18 +++++------ infra/modules/identity-provider/variables.tf | 32 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/infra/modules/identity-provider-client/outputs.tf b/infra/modules/identity-provider-client/outputs.tf index dfcf2653..2bf81ddf 100644 --- a/infra/modules/identity-provider-client/outputs.tf +++ b/infra/modules/identity-provider-client/outputs.tf @@ -1,3 +1,7 @@ +output "access_policy_arn" { + value = aws_iam_policy.cognito_access.arn +} + output "client_id" { description = "The ID of the user pool client" value = aws_cognito_user_pool_client.client.id @@ -7,7 +11,3 @@ output "client_secret_arn" { description = "The arn for the SSM parameter storing the user pool client secret" value = aws_ssm_parameter.client_secret.arn } - -output "access_policy_arn" { - value = aws_iam_policy.cognito_access.arn -} diff --git a/infra/modules/identity-provider-client/variables.tf b/infra/modules/identity-provider-client/variables.tf index 46132d86..f75f5296 100644 --- a/infra/modules/identity-provider-client/variables.tf +++ b/infra/modules/identity-provider-client/variables.tf @@ -1,6 +1,7 @@ -variable "name" { - type = string - description = "Name of the application or service that will act as a client to the identity provider" +variable "callback_urls" { + type = list(string) + description = "The URL(s) that the identity provider will redirect to after a successful login" + default = [] } variable "cognito_user_pool_id" { @@ -8,14 +9,13 @@ variable "cognito_user_pool_id" { description = "The ID of the user pool that the client will be associated with" } -variable "callback_urls" { - type = list(string) - description = "The URL(s) that the identity provider will redirect to after a successful login" - default = [] -} - variable "logout_urls" { type = list(string) description = "The URL that the identity provider will redirect to after a successful logout" default = [] } + +variable "name" { + type = string + description = "Name of the application or service that will act as a client to the identity provider" +} diff --git a/infra/modules/identity-provider/variables.tf b/infra/modules/identity-provider/variables.tf index eabc6b4e..f980c705 100644 --- a/infra/modules/identity-provider/variables.tf +++ b/infra/modules/identity-provider/variables.tf @@ -1,11 +1,23 @@ +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 + default = false +} + variable "name" { type = string description = "The name of the Cognito User Pool" } -variable "sender_email" { +variable "password_minimum_length" { + type = number + description = "The password minimum length" + default = 12 +} + +variable "reply_to_email" { type = string - description = "Email address to use to send identity provider emails. If none is provided, the identity service will be configured to use Cognito's default email functionality, which should only be relied on outside of production." + description = "Email address used as the REPLY-TO for identity service emails" default = null } @@ -15,18 +27,12 @@ variable "sender_display_name" { default = null } -variable "reply_to_email" { +variable "sender_email" { type = string - description = "Email address used as the REPLY-TO for identity service emails" + description = "Email address to use to send identity provider emails. If none is provided, the identity service will be configured to use Cognito's default email functionality, which should only be relied on outside of production." default = null } -variable "password_minimum_length" { - type = number - description = "The password minimum length" - default = 12 -} - variable "temporary_password_validity_days" { type = number description = "The number of days a temporary password is valid for" @@ -44,9 +50,3 @@ variable "verification_email_subject" { description = "The email subject for a password reset email" 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 - default = false -} From c8bf7d2d024d077d2b7c286d68603ef37b494b64 Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 11:19:37 -0700 Subject: [PATCH 14/25] Use active voice --- infra/app/app-config/main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/app/app-config/main.tf b/infra/app/app-config/main.tf index 0298f993..a79fb9f8 100644 --- a/infra/app/app-config/main.tf +++ b/infra/app/app-config/main.tf @@ -26,9 +26,9 @@ locals { # Whether or not the application should deploy an identity provider # If enabled: - # 1. A Cognito user pool will be created - # 2. A Cognito user pool app client will be created - # 3. Environment variables for the app client will be added to the service + # 1. Creates a Cognito user pool + # 2. Creates a Cognito user pool app client + # 3. Adds environment variables for the app client to the service enable_identity_provider = false environment_configs = { From 96c771bc1f8be7d108d2ec7a54cf921afb196e8c Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 11:19:55 -0700 Subject: [PATCH 15/25] Use consistent naming convention --- infra/app/service/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index ddc6354c..cc54db45 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -180,7 +180,7 @@ module "service" { storage_access = module.storage.access_policy_arn }, module.app_config.enable_identity_provider ? { - identity_access = module.identity_provider_client[0].access_policy_arn, + identity_provider_access = module.identity_provider_client[0].access_policy_arn, } : {} ) From dcf394b3df9dfb595ede27cd7976e89d6ae9d4cd Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 11:20:23 -0700 Subject: [PATCH 16/25] Use better variable description --- infra/app/app-config/env-config/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/app/app-config/env-config/variables.tf b/infra/app/app-config/env-config/variables.tf index a2722326..f524e07e 100644 --- a/infra/app/app-config/env-config/variables.tf +++ b/infra/app/app-config/env-config/variables.tf @@ -96,12 +96,12 @@ variable "enable_identity_provider" { variable "extra_identity_provider_callback_urls" { type = list(string) - description = "A list of additional callback urls to pass to the identity provider" + description "List of additional URLs that the identity provider will redirect the user to after a successful sign-in. Used for local development." default = [] } variable "extra_identity_provider_logout_urls" { type = list(string) - description = "A list of additional logout urls to pass to the identity provider" + description = "List of additional URLs that the identity provider will redirect the user to after signing out. Used for local development." default = [] } From c24f5821557089d1135c454c8f068dfaa9b1a912 Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 11:20:48 -0700 Subject: [PATCH 17/25] Use clearer comment language --- infra/app/app-config/env-config/identity-provider.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf index c8909613..2c9b2a66 100644 --- a/infra/app/app-config/env-config/identity-provider.tf +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -20,7 +20,7 @@ locals { ) } - # Set any values not `null` to override AWS Cognito defaults. + # Set attributes to non-null values to override AWS Cognito defaults. email = { # When you're ready to use SES instead of the Cognito default to send emails, set this # to the SES-verified email address to be used when sending emails. @@ -35,7 +35,7 @@ locals { } # Optionally configure email template for resetting a password. - # Set any values not `null` to override AWS Cognito defaults. + # Set any attribute to a non-null value to override AWS Cognito defaults. verification_email = { verification_email_message = null verification_email_subject = null From 75600365fed609a9c46a1e2084df927e37da9741 Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 11:21:06 -0700 Subject: [PATCH 18/25] Support callback/logout paths --- .../app-config/env-config/identity-provider.tf | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf index 2c9b2a66..432c66ff 100644 --- a/infra/app/app-config/env-config/identity-provider.tf +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -8,14 +8,24 @@ locals { temporary_password_validity_days = 7 } + # If your application should redirect users, after successful authentication, to a + # page other than the homepage, specify the path fragment here. + # Example: "profile" + callback_url_path = "" + + # If your application should redirect users, after signing out, to a page other than + # the homepage, specify the path fragment here. + # Example: "logout" + logout_url_path = "" + + # Do not modify this block directly. client = { - # Support local development against remote resources - auth_callback_urls = concat( - var.domain_name != null ? ["https://${var.domain_name}"] : [], + callback_urls = concat( + var.domain_name != null ? ["https://${var.domain_name}/${local.callback_url_path}"] : [], var.extra_identity_provider_callback_urls ) logout_urls = concat( - var.domain_name != null ? ["https://${var.domain_name}"] : [], + var.domain_name != null ? ["https://${var.domain_name}/${local.logout_url_path}"] : [], var.extra_identity_provider_logout_urls ) } From bc414e55d38dc109c1937fe0c884a287c446155a Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 12:19:16 -0700 Subject: [PATCH 19/25] Fix typo --- infra/app/app-config/env-config/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/app/app-config/env-config/variables.tf b/infra/app/app-config/env-config/variables.tf index f524e07e..326ed00a 100644 --- a/infra/app/app-config/env-config/variables.tf +++ b/infra/app/app-config/env-config/variables.tf @@ -96,7 +96,7 @@ variable "enable_identity_provider" { variable "extra_identity_provider_callback_urls" { type = list(string) - description "List of additional URLs that the identity provider will redirect the user to after a successful sign-in. Used for local development." + description = "List of additional URLs that the identity provider will redirect the user to after a successful sign-in. Used for local development." default = [] } From d1ab6af3ec508929e85d88dcf44c4ee32acbeab9 Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 27 Jun 2024 12:20:40 -0700 Subject: [PATCH 20/25] Break out email configuration into separate config --- .../env-config/identity-provider.tf | 32 ++++++------------- .../app-config/env-config/notifications.tf | 15 +++++++++ infra/app/app-config/env-config/outputs.tf | 4 +++ infra/app/app-config/main.tf | 5 +++ infra/app/service/main.tf | 8 +++-- 5 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 infra/app/app-config/env-config/notifications.tf diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf index 432c66ff..1b2e2146 100644 --- a/infra/app/app-config/env-config/identity-provider.tf +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -1,4 +1,6 @@ -# Identity provider configuration +# Identity provider configuration. +# If the notification service is configured, the identity provider will use the +# SES-verified email to send notifications. locals { identity_provider_config = var.enable_identity_provider ? { identity_provider_name = "${local.prefix}${var.app_name}-${var.environment}" @@ -18,6 +20,13 @@ locals { # Example: "logout" logout_url_path = "" + # Optionally configure email template for resetting a password. + # Set any attribute to a non-null value to override AWS Cognito defaults. + verification_email = { + verification_email_message = null + verification_email_subject = null + } + # Do not modify this block directly. client = { callback_urls = concat( @@ -29,26 +38,5 @@ locals { var.extra_identity_provider_logout_urls ) } - - # Set attributes to non-null values to override AWS Cognito defaults. - email = { - # When you're ready to use SES instead of the Cognito default to send emails, set this - # to the SES-verified email address to be used when sending emails. - sender_email = 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 = null - - # Configure the REPLY-TO email address if it should be different from the sender. - reply_to_email = null - } - - # Optionally configure email template for resetting a password. - # Set any attribute to a non-null value to override AWS Cognito defaults. - verification_email = { - verification_email_message = null - verification_email_subject = null - } } : null } diff --git a/infra/app/app-config/env-config/notifications.tf b/infra/app/app-config/env-config/notifications.tf new file mode 100644 index 00000000..95c463f4 --- /dev/null +++ b/infra/app/app-config/env-config/notifications.tf @@ -0,0 +1,15 @@ +# Notifications configuration +locals { + notifications_config = var.enable_notifications ? { + # Set to an SES-verified email address to be used when sending emails. + sender_email = 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 = null + + # 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 + } : null +} diff --git a/infra/app/app-config/env-config/outputs.tf b/infra/app/app-config/env-config/outputs.tf index aa512a83..b679bfe4 100644 --- a/infra/app/app-config/env-config/outputs.tf +++ b/infra/app/app-config/env-config/outputs.tf @@ -41,6 +41,10 @@ output "identity_provider_config" { value = local.identity_provider_config } +output "notifications_config" { + value = local.notifications_config +} + output "storage_config" { value = { # Include project name in bucket name since buckets need to be globally unique across AWS diff --git a/infra/app/app-config/main.tf b/infra/app/app-config/main.tf index a79fb9f8..1ef98a81 100644 --- a/infra/app/app-config/main.tf +++ b/infra/app/app-config/main.tf @@ -31,6 +31,11 @@ locals { # 3. Adds environment variables for the app client to the service enable_identity_provider = false + # 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 + environment_configs = { dev = module.dev_config staging = module.staging_config diff --git a/infra/app/service/main.tf b/infra/app/service/main.tf index cc54db45..3631a4a8 100644 --- a/infra/app/service/main.tf +++ b/infra/app/service/main.tf @@ -36,6 +36,7 @@ locals { storage_config = local.environment_config.storage_config incident_management_service_integration_config = local.environment_config.incident_management_service_integration identity_provider_config = local.environment_config.identity_provider_config + notifications_config = local.environment_config.notifications_config network_config = module.project_config.network_configs[local.environment_config.network_name] } @@ -216,13 +217,14 @@ module "identity_provider" { is_temporary = local.is_temporary name = local.identity_provider_config.identity_provider_name - sender_email = local.identity_provider_config.email.sender_email - sender_display_name = local.identity_provider_config.email.sender_display_name - reply_to_email = local.identity_provider_config.email.reply_to_email password_minimum_length = local.identity_provider_config.password_policy.password_minimum_length temporary_password_validity_days = local.identity_provider_config.password_policy.temporary_password_validity_days verification_email_message = local.identity_provider_config.verification_email.verification_email_message verification_email_subject = local.identity_provider_config.verification_email.verification_email_subject + + sender_email = local.notifications_config == null ? null : local.notifications_config.sender_email + sender_display_name = local.notifications_config == null ? null : local.notifications_config.sender_display_name + reply_to_email = local.notifications_config == null ? null : local.notifications_config.reply_to_email } module "identity_provider_client" { From fb4e88ee1e77103bc7444fd10ecca1a908098944 Mon Sep 17 00:00:00 2001 From: Rocket Date: Fri, 26 Jul 2024 17:48:58 -0700 Subject: [PATCH 21/25] Handle case if sender_display_name is null --- infra/modules/identity-provider/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/modules/identity-provider/main.tf b/infra/modules/identity-provider/main.tf index 4d3477c9..70096f4f 100644 --- a/infra/modules/identity-provider/main.tf +++ b/infra/modules/identity-provider/main.tf @@ -38,7 +38,7 @@ resource "aws_cognito_user_pool" "main" { email_sending_account = var.sender_email != 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} <${var.sender_email}>" : null + 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 34728aa20262a4e733312cc9399e36d23cfa08d1 Mon Sep 17 00:00:00 2001 From: Rocket Date: Fri, 26 Jul 2024 17:51:32 -0700 Subject: [PATCH 22/25] Alphabetize env config vars --- infra/app/app-config/env-config/variables.tf | 37 ++++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/infra/app/app-config/env-config/variables.tf b/infra/app/app-config/env-config/variables.tf index 32112eb1..e63ad74b 100644 --- a/infra/app/app-config/env-config/variables.tf +++ b/infra/app/app-config/env-config/variables.tf @@ -31,11 +31,29 @@ variable "enable_https" { default = false } +variable "enable_identity_provider" { + type = bool + description = "Enables identity provider" + default = false +} + variable "environment" { description = "name of the application environment (e.g. dev, staging, prod)" type = string } +variable "extra_identity_provider_callback_urls" { + type = list(string) + description = "List of additional URLs that the identity provider will redirect the user to after a successful sign-in. Used for local development." + default = [] +} + +variable "extra_identity_provider_logout_urls" { + type = list(string) + description = "List of additional URLs that the identity provider will redirect the user to after signing out. Used for local development." + default = [] +} + variable "has_database" { type = bool } @@ -76,22 +94,3 @@ variable "service_override_extra_environment_variables" { EOT default = {} } - -variable "enable_identity_provider" { - type = bool - description = "Enables identity provider" - default = false -} - -variable "extra_identity_provider_callback_urls" { - type = list(string) - description = "List of additional URLs that the identity provider will redirect the user to after a successful sign-in. Used for local development." - default = [] -} - -variable "extra_identity_provider_logout_urls" { - type = list(string) - description = "List of additional URLs that the identity provider will redirect the user to after signing out. Used for local development." - default = [] -} - From 08008e45705c1b14f3b7f6b567809fed817115eb Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 1 Aug 2024 10:53:00 -0700 Subject: [PATCH 23/25] Fix bad locals path --- .../env-config/identity-provider.tf | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf index 1b2e2146..011c68d9 100644 --- a/infra/app/app-config/env-config/identity-provider.tf +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -2,6 +2,16 @@ # If the notification service is configured, the identity provider will use the # SES-verified email to send notifications. locals { + # If your application should redirect users, after successful authentication, to a + # page other than the homepage, specify the path fragment here. + # Example: "profile" + callback_url_path = "" + + # If your application should redirect users, after signing out, to a page other than + # the homepage, specify the path fragment here. + # Example: "logout" + logout_url_path = "" + identity_provider_config = var.enable_identity_provider ? { identity_provider_name = "${local.prefix}${var.app_name}-${var.environment}" @@ -10,16 +20,6 @@ locals { temporary_password_validity_days = 7 } - # If your application should redirect users, after successful authentication, to a - # page other than the homepage, specify the path fragment here. - # Example: "profile" - callback_url_path = "" - - # If your application should redirect users, after signing out, to a page other than - # the homepage, specify the path fragment here. - # Example: "logout" - logout_url_path = "" - # Optionally configure email template for resetting a password. # Set any attribute to a non-null value to override AWS Cognito defaults. verification_email = { From 906b3c30cd7e88fb2a5ad66d4a2e2c7f1c97addb Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 1 Aug 2024 10:53:08 -0700 Subject: [PATCH 24/25] Add missing variable --- infra/app/app-config/env-config/variables.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/infra/app/app-config/env-config/variables.tf b/infra/app/app-config/env-config/variables.tf index e63ad74b..bdeb1c08 100644 --- a/infra/app/app-config/env-config/variables.tf +++ b/infra/app/app-config/env-config/variables.tf @@ -37,6 +37,12 @@ variable "enable_identity_provider" { default = false } +variable "enable_notifications" { + type = bool + description = "Enables notifications" + default = false +} + variable "environment" { description = "name of the application environment (e.g. dev, staging, prod)" type = string From 99283714499091d49bf902ba9c60aea1e5946a35 Mon Sep 17 00:00:00 2001 From: Rocket Date: Thu, 1 Aug 2024 14:56:04 -0700 Subject: [PATCH 25/25] Add links to aws docs --- infra/app/app-config/env-config/identity-provider.tf | 3 +++ infra/app/app-config/env-config/notifications.tf | 1 + 2 files changed, 4 insertions(+) diff --git a/infra/app/app-config/env-config/identity-provider.tf b/infra/app/app-config/env-config/identity-provider.tf index 011c68d9..e3d8ba3f 100644 --- a/infra/app/app-config/env-config/identity-provider.tf +++ b/infra/app/app-config/env-config/identity-provider.tf @@ -5,11 +5,13 @@ locals { # If your application should redirect users, after successful authentication, to a # page other than the homepage, specify the path fragment here. # Example: "profile" + # Docs: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html callback_url_path = "" # If your application should redirect users, after signing out, to a page other than # the homepage, specify the path fragment here. # Example: "logout" + # Docs: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html logout_url_path = "" identity_provider_config = var.enable_identity_provider ? { @@ -22,6 +24,7 @@ locals { # Optionally configure email template for resetting a password. # Set any attribute to a non-null value to override AWS Cognito defaults. + # Docs: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-customizations.html verification_email = { verification_email_message = null verification_email_subject = null diff --git a/infra/app/app-config/env-config/notifications.tf b/infra/app/app-config/env-config/notifications.tf index 95c463f4..d4f2bb42 100644 --- a/infra/app/app-config/env-config/notifications.tf +++ b/infra/app/app-config/env-config/notifications.tf @@ -2,6 +2,7 @@ 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 # Configure the name that users see in the "From" section of their inbox, so that it's