diff --git a/azure/templates/post-deploy.yml b/azure/templates/post-deploy.yml index aace6b9033..f5bd6e04b6 100644 --- a/azure/templates/post-deploy.yml +++ b/azure/templates/post-deploy.yml @@ -41,8 +41,6 @@ steps: set -e if ! [[ $APIGEE_ENVIRONMENT =~ .*-*sandbox ]]; then export AWS_PROFILE=apim-dev - aws_account_no="$(aws sts get-caller-identity --query Account --output text)" - service_name=$(FULLY_QUALIFIED_SERVICE_NAME) pr_no=$(echo $service_name | { grep -oE '[0-9]+$' || true; }) @@ -58,10 +56,10 @@ steps: echo Apigee environment: $APIGEE_ENVIRONMENT echo pr_no: $pr_no - cd terraform + cd terraform make init - make apply aws_account_no=${aws_account_no} environment=$workspace + make apply environment=${{ parameters.aws_account_type }} sub_environment=$workspace AWS_DOMAIN_NAME=$(make -s output name=service_domain_name) IMMS_DELTA_TABLE_NAME=$(make -s output name=imms_delta_table_name) diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl index aa623431d5..999de00cd6 100644 --- a/terraform/.terraform.lock.hcl +++ b/terraform/.terraform.lock.hcl @@ -2,25 +2,25 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { - version = "6.2.0" - constraints = ">= 4.22.0, >= 5.79.0, ~> 6.0" + version = "6.4.0" + constraints = ">= 6.0.0, ~> 6.0" hashes = [ - "h1:ziUjk3KGwBa7bAwZaPuQklfcQ3Qlx2d0rlYFvdZn4g8=", - "zh:224b20371f7c7ce14d69a84e16ae94baa0a06c132474d4bc4d192d86936bc750", - "zh:2c079ad275c32b9abae7616d07c24901340207a85995ce0025ac38af16b317b7", - "zh:2d139c99d6e8e48cc5439c2945eee583bb3a2d7faf484639cdfd4590ebc294f2", - "zh:2f2dc72de43d845e5df5a1adeadd4a48f3cacfe413b89b95f50294a386a90124", - "zh:304d4e706ac34aba080a81867209c65cdd28f8e596c02b3565f6530ab697cf94", - "zh:40e69328ae11fe1711b34226d45aa4c685f73e8f958c76c49f4f6a6627a1a54f", - "zh:5c5c9faab6fe242b77f0d0ab6664313dd89409371123e2ec376c72e8f2cd97b3", - "zh:7824709f0226afec5e3ad41392233b4bd7d925bae0d35f4a2ac854b3516e955c", + "h1:hUzF9bzWMJKPJ3Q0b13sQAOTU5vHOc9m/S5HFPJl5Sk=", + "zh:05946a97a2d98d3a77f2dfb1133b39d61b1166f717f051a8aa44eca22a7446b0", + "zh:07278697234332b254e990fff84fa5608aabdb256a0dbed05dfe336905d385a1", + "zh:1b1ad46267c84fa474618048a9ad94a634cf5d0e5ec3c8e56a854638129ae4da", + "zh:1ff04914571b1dfa485358badbc81306e34d8ebec4aa1f96b8c1c3d2eb0e4d4a", + "zh:43d7fb899186ca1b355af908d0904ea94a1e06de220de0b9752f06465386f66f", + "zh:49ce34c359d5b05ba684482dace5e9c418f3beabcc2b0d129b21687cb7673cab", + "zh:4bbad3a23dd704b1548da40e9c81befb617a0c02e5a9776ef0eff5ef920881c5", + "zh:680aa4bd542c7a847f7df91cd1fa33fe8d19914aa80a2570ea6c82ab2d1f5740", + "zh:792a74fe4d6b501571c582c25067f7f4dbdce2305d559d09981e7f99025c98ef", + "zh:7c06b331b6a6f160d2d64245b9aee32922a9cb9947b7a9ad8c0ec93a702ecb1b", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:ab5417435121e8f9a6f539a6972f20c060ebc78624c1ce7c190671f163c423e4", - "zh:d6939385de4931f9fe07c87b744468fbfa96bd4b47861020d68e7ba1efb4185f", - "zh:e51434d1ba106c55d04fdc223aaf403b897ff96cba9bfce4c51854c805c36ed8", - "zh:e6d1012bafe338759ac42a59f566f8c4ad64a67faa3152c7fb758704d890cd75", - "zh:ee24b989ee3b6be79a7f24e97c144535bf7053471ae65343220db3ae78c7632a", - "zh:f61ec9482b9887c4901946407992874531b0c2eadab0c743fdfca4e6cd6dd889", + "zh:9f40add95d4f3e1c62df46bf37e13c30023d97eda47d4940904792f3b1a1827e", + "zh:b763c7c1bf5d8077d6499fd270cad249a712dd9522c6a6e4de49b278280806c5", + "zh:db69df59bef6f9d8bcb164414b4efa52c0c531c346d6b8b232917afa9b1c4a96", + "zh:dd9f98f64530386b8faaf9c55ec4b08e58725788c38683272a34684d82f866f7", ] } @@ -29,6 +29,7 @@ provider "registry.terraform.io/hashicorp/external" { constraints = ">= 1.0.0" hashes = [ "h1:FnUk98MI5nOh3VJ16cHf8mchQLewLfN1qZG/MqNgPrI=", + "h1:smKSos4zs57pJjQrNuvGBpSWth2el9SgePPbPHo0aps=", "zh:6e89509d056091266532fa64de8c06950010498adf9070bf6ff85bc485a82562", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", "zh:86868aec05b58dc0aa1904646a2c26b9367d69b890c9ad70c33c0d3aa7b1485a", @@ -48,6 +49,7 @@ provider "registry.terraform.io/hashicorp/local" { version = "2.5.3" constraints = ">= 1.0.0" hashes = [ + "h1:1Nkh16jQJMp0EuDmvP/96f5Unnir0z12WyDuoR6HjMo=", "h1:MCzg+hs1/ZQ32u56VzJMWP9ONRQPAAqAjuHuzbyshvI=", "zh:284d4b5b572eacd456e605e94372f740f6de27b71b4e1fd49b63745d8ecd4927", "zh:40d9dfc9c549e406b5aab73c023aa485633c1b6b730c933d7bcc2fa67fd1ae6e", @@ -69,6 +71,7 @@ provider "registry.terraform.io/hashicorp/null" { constraints = ">= 2.0.0" hashes = [ "h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=", + "h1:hkf5w5B6q8e2A42ND2CjAvgvSN3puAosDmOJb3zCVQM=", "zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", "zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43", diff --git a/terraform/Makefile b/terraform/Makefile index 0fc66c2d9d..c0ce50de0d 100644 --- a/terraform/Makefile +++ b/terraform/Makefile @@ -1,18 +1,18 @@ -include .env -interactionId=$(environment) +environment ?= $(ENVIRONMENT) +sub_environment ?= $(SUB_ENVIRONMENT) +sub_environment_dir := $(if $(findstring pr-,$(sub_environment)),pr,$(sub_environment)) -aws_profile = apim-dev -tf_cmd = AWS_PROFILE=$(aws_profile) terraform +tf_cmd = AWS_PROFILE=$(AWS_PROFILE) terraform -project_name = immunisation -project_short_name = imms -state_bucket = $(project_name)-$(APIGEE_ENVIRONMENT)-terraform-state-files -tf_state= -backend-config="bucket=$(state_bucket)" +bucket_name = $(if $(filter dev,$(environment)),immunisation-$(sub_environment),immunisation-$(environment))-terraform-state-files -tf_vars= -var="project_name=$(project_name)" -var="project_short_name=$(project_short_name)" +tf_state = -backend-config="bucket=$(bucket_name)" -.PHONY : lock-provider workspace init plan apply clean destroy output state-list lambda-zip catch-all-zip +tf_vars = \ + -var="sub_environment=$(sub_environment)" \ + -var-file="./environments/$(environment)/$(sub_environment_dir)/variables.tfvars" lock-provider: # Run this only when you install a new terraform provider. This will generate sha code in lock file for all platform @@ -20,11 +20,14 @@ lock-provider: $(tf_cmd) providers lock -platform=darwin_arm64 -platform=darwin_amd64 -platform=linux_amd64 -platform=windows_amd64 workspace: - $(tf_cmd) workspace new $(environment) || $(tf_cmd) workspace select $(environment) && echo "Switched to workspace/environment: $(environment)" + $(tf_cmd) workspace new $(sub_environment) || $(tf_cmd) workspace select $(sub_environment) && echo "Switched to workspace/environment: $(sub_environment)" init: $(tf_cmd) init $(tf_state) -upgrade $(tf_vars) +init-reconfigure: + $(tf_cmd) init $(tf_state) -upgrade $(tf_vars) -reconfigure + plan: workspace $(tf_cmd) plan $(tf_vars) @@ -40,7 +43,7 @@ clean: destroy: workspace $(tf_cmd) destroy $(tf_vars) -auto-approve $(tf_cmd) workspace select default - $(tf_cmd) workspace delete $(environment) + $(tf_cmd) workspace delete $(sub_environment) output: $(tf_cmd) output -raw $(name) @@ -59,3 +62,5 @@ catch-all-zip: tf-%: $(tf_cmd) $* + +.PHONY : lock-provider workspace init plan apply clean destroy output state-list lambda-zip catch-all-zip diff --git a/terraform/README.md b/terraform/README.md index 921dae4043..9d9d090bbe 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,17 +1,31 @@ -# immunisation-fhir-api Terraform +# About +The Terraform configuration in this folder is executed in each PR and sets up lambdas associated with the PR. Once the PR is merged, it will be used by the release pipeline to deploy to INT and REF. This is also run by the production release pipeline to deploy the lambdas to the prod blue and green sub environments. -## Setup for local dev +## Environments Structure -Add your workspace name to the env file. This is usually your shortcode. +Terraform is executed via a `Makefile`. +The environment-specific configuration is structured as follows: -```shell -echo environment=your-shortcode >> .env -make init -make workspace -make apply + environments/ + └── / # e.g. dev, int, prod (AWS account name) + └── / # e.g. pr, internal-dev + └── variables.tfvars + +The `Makefile` automatically reads the `.env` file to determine the correct `variables.tfvars` file to use, allowing customization of infrastructure for each sub-environment. + +## Run locally +1. Create a `.env` file with the following values: +```dotenv +ENVIRONMENT=dev # Target AWS account (e.g., dev, int, prod) +SUB_ENVIRONMENT=pr-123 # Sub-environment (e.g., pr-57, internal-dev) +AWS_REGION=eu-west-2 +AWS_PROFILE=your-aws-profile ``` +2. Run `make init` to download providers and dependencies +3. Run `make plan` to output plan with the changes that terraform will perform +4. **WARNING**: Run `make apply` only after thoroughly reviewing the plan as this might destroy or modify existing infrastructure -See the Makefile for other commands. +Note: If you switch environment configuration in .env ensure that you run `make init-reconfigure` to reconfigure the backend to prevent migrating the existing state to the new backend. -If you want to apply Terraform to a workspace created by a PR you can set the above environment to the PR number. +If you want to apply Terraform to a workspace created by a PR you can set the above SUB_ENVIRONMENT to the `PR-number` and ENVIRONMENT set to `dev`. E.g. `pr-57`. You can use this to test out changes when tests fail in CI. diff --git a/terraform/ack_lambda.tf b/terraform/ack_lambda.tf index c517bedde3..0da24ef9ab 100644 --- a/terraform/ack_lambda.tf +++ b/terraform/ack_lambda.tf @@ -68,7 +68,7 @@ resource "aws_ecr_repository_policy" "ack_lambda_ECRImageRetreival_policy" { ], "Condition" : { "StringLike" : { - "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}-ack-lambda" + "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}-ack-lambda" } } } @@ -105,7 +105,7 @@ resource "aws_iam_policy" "ack_lambda_exec_policy" { "logs:CreateLogStream", "logs:PutLogEvents" ] - Resource = "arn:aws:logs:eu-west-2:${local.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-ack-lambda:*" + Resource = "arn:aws:logs:eu-west-2:${var.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-ack-lambda:*" }, { Effect = "Allow" @@ -148,7 +148,7 @@ resource "aws_iam_policy" "ack_lambda_exec_policy" { "sqs:DeleteMessage", "sqs:GetQueueAttributes" ], - Resource = "arn:aws:sqs:eu-west-2:${local.immunisation_account_id}:${local.short_prefix}-ack-metadata-queue.fifo" }, + Resource = "arn:aws:sqs:eu-west-2:${var.immunisation_account_id}:${local.short_prefix}-ack-metadata-queue.fifo" }, { "Effect" : "Allow", "Action" : [ @@ -216,7 +216,7 @@ resource "aws_lambda_function" "ack_processor_lambda" { variables = { ACK_BUCKET_NAME = aws_s3_bucket.batch_data_destination_bucket.bucket SPLUNK_FIREHOSE_NAME = module.splunk.firehose_stream_name - ENVIRONMENT = terraform.workspace + ENVIRONMENT = var.sub_environment AUDIT_TABLE_NAME = aws_dynamodb_table.audit-table.name FILE_NAME_PROC_LAMBDA_NAME = aws_lambda_function.file_processor_lambda.function_name } diff --git a/terraform/api_gateway/acm_cert.tf b/terraform/api_gateway/acm_cert.tf deleted file mode 100644 index dab9fe55d1..0000000000 --- a/terraform/api_gateway/acm_cert.tf +++ /dev/null @@ -1,31 +0,0 @@ -resource "aws_acm_certificate" "service_certificate" { - domain_name = var.api_domain_name - subject_alternative_names = [] - validation_method = "DNS" - - lifecycle { - create_before_destroy = true - } -} - -resource "aws_acm_certificate_validation" "service_certificate" { - certificate_arn = aws_acm_certificate.service_certificate.arn - validation_record_fqdns = [for record in aws_route53_record.dns_validation : record.fqdn] -} - -resource "aws_route53_record" "dns_validation" { - for_each = { - for dvo in aws_acm_certificate.service_certificate.domain_validation_options : dvo.domain_name => { - name = dvo.resource_record_name - record = dvo.resource_record_value - type = dvo.resource_record_type - } - } - - allow_overwrite = true - name = each.value.name - records = [each.value.record] - ttl = 60 - type = each.value.type - zone_id = var.zone_id -} diff --git a/terraform/api_gateway/api.tf b/terraform/api_gateway/api.tf deleted file mode 100644 index d949d4f3e0..0000000000 --- a/terraform/api_gateway/api.tf +++ /dev/null @@ -1,66 +0,0 @@ -locals { - api_stage_name = var.environment -} - -resource "aws_apigatewayv2_api" "service_api" { - name = "${var.prefix}-api" - description = "Immunisation FHIR API - ${var.environment}" - protocol_type = "HTTP" - disable_execute_api_endpoint = true - body = var.oas -} - -resource "aws_apigatewayv2_stage" "default" { - depends_on = [aws_cloudwatch_log_group.api_access_log] - api_id = aws_apigatewayv2_api.service_api.id - name = local.api_stage_name - auto_deploy = true - - default_route_settings { - logging_level = "INFO" - throttling_burst_limit = 500 - throttling_rate_limit = 500 - detailed_metrics_enabled = true - } - access_log_settings { - destination_arn = aws_cloudwatch_log_group.api_access_log.arn - format = "{ \"requestId\":\"$context.requestId\", \"extendedRequestId\":\"$context.extendedRequestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\", \"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\", \"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\", \"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\", \"authorizerError\":\"$context.authorizer.error\", \"authorizerStatus\":\"$context.authorizer.status\", \"requestIsValid\":\"$context.authorizer.is_valid\"\"environment\":\"$context.authorizer.environment\" }" - } - - # Bug in terraform-aws-provider with perpetual diff - lifecycle { - ignore_changes = [deployment_id] - } -} - -resource "aws_apigatewayv2_domain_name" "service_api_domain_name" { - domain_name = var.api_domain_name - domain_name_configuration { - certificate_arn = aws_acm_certificate_validation.service_certificate.certificate_arn - endpoint_type = "REGIONAL" - security_policy = "TLS_1_2" - } - mutual_tls_authentication { - truststore_uri = "s3://${aws_s3_bucket.truststore_bucket.bucket}/${local.truststore_file_name}" - } - tags = { - Name = "${var.prefix}-api-domain-name" - } -} - -resource "aws_apigatewayv2_api_mapping" "api_mapping" { - api_id = aws_apigatewayv2_api.service_api.id - domain_name = aws_apigatewayv2_domain_name.service_api_domain_name.id - stage = aws_apigatewayv2_stage.default.id -} - -resource "aws_route53_record" "api_domain" { - zone_id = var.zone_id - name = aws_apigatewayv2_domain_name.service_api_domain_name.domain_name - type = "A" - alias { - evaluate_target_health = true - name = aws_apigatewayv2_domain_name.service_api_domain_name.domain_name_configuration[0].target_domain_name - zone_id = aws_apigatewayv2_domain_name.service_api_domain_name.domain_name_configuration[0].hosted_zone_id - } -} diff --git a/terraform/api_gateway/variables.tf b/terraform/api_gateway/variables.tf deleted file mode 100644 index f2b578b3c5..0000000000 --- a/terraform/api_gateway/variables.tf +++ /dev/null @@ -1,11 +0,0 @@ -variable "prefix" {} -variable "short_prefix" {} -variable "zone_id" {} -variable "api_domain_name" {} -variable "environment" {} -variable "oas" {} - -locals { - environment = terraform.workspace == "green" ? "prod" : terraform.workspace == "blue" ? "prod" : terraform.workspace - config_env = local.environment == "prod" ? "prod" : "dev" -} diff --git a/terraform/configs.tf b/terraform/configs.tf deleted file mode 100644 index fc3f074380..0000000000 --- a/terraform/configs.tf +++ /dev/null @@ -1,6 +0,0 @@ -locals { - // Flag so we can force delete s3 buckets with items in for pr and shortcode environments only. - is_temp = length(regexall("[a-z]{2,4}-?[0-9]+", local.env)) > 0 - dspp_core_account_id = local.environment == "prod" ? 232116723729 : 603871901111 - immunisation_account_id = local.environment == "prod" ? 664418956997 : 345594581768 -} diff --git a/terraform/delta.tf b/terraform/delta.tf index 7c10b7fce1..78e68a2f3b 100644 --- a/terraform/delta.tf +++ b/terraform/delta.tf @@ -69,7 +69,7 @@ resource "aws_ecr_repository_policy" "delta_lambda_ECRImageRetreival_policy" { ], "Condition" : { "StringLike" : { - "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}-${local.function_name}" + "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}-${local.function_name}" } } } diff --git a/terraform/dps_role_creation.tf b/terraform/dps_role_creation.tf index 220cd007d8..dd91402aa1 100644 --- a/terraform/dps_role_creation.tf +++ b/terraform/dps_role_creation.tf @@ -6,7 +6,7 @@ resource "aws_iam_role" "dynamo_s3_access_role" { { Effect : "Allow", Principal : { - AWS : "arn:aws:iam::${local.dspp_core_account_id}:root" + AWS : "arn:aws:iam::${var.dspp_core_account_id}:root" }, Action : "sts:AssumeRole" } @@ -22,7 +22,7 @@ resource "aws_iam_role_policy" "dynamo_s3_access_policy" { Statement = [ { Effect = "Allow", - Action = local.environment == "prod" ? [ + Action = var.environment == "prod" ? [ "dynamodb:GetItem", "dynamodb:Query" ] : [ diff --git a/terraform/dynamodb.tf b/terraform/dynamodb.tf index 0866d46ef6..5ed73f4a13 100644 --- a/terraform/dynamodb.tf +++ b/terraform/dynamodb.tf @@ -1,5 +1,5 @@ resource "aws_dynamodb_table" "audit-table" { - name = "immunisation-batch-${local.environment}-audit-table" + name = "immunisation-batch-${local.resource_scope}-audit-table" billing_mode = "PAY_PER_REQUEST" hash_key = "message_id" @@ -37,7 +37,7 @@ resource "aws_dynamodb_table" "audit-table" { } point_in_time_recovery { - enabled = local.environment == "prod" + enabled = var.environment == "prod" } server_side_encryption { @@ -47,7 +47,7 @@ resource "aws_dynamodb_table" "audit-table" { } resource "aws_dynamodb_table" "delta-dynamodb-table" { - name = "imms-${local.environment}-delta" + name = "imms-${local.resource_scope}-delta" billing_mode = "PAY_PER_REQUEST" hash_key = "PK" @@ -96,7 +96,7 @@ resource "aws_dynamodb_table" "delta-dynamodb-table" { } point_in_time_recovery { - enabled = local.environment == "prod" + enabled = var.environment == "prod" } server_side_encryption { @@ -106,7 +106,7 @@ resource "aws_dynamodb_table" "delta-dynamodb-table" { } resource "aws_dynamodb_table" "events-dynamodb-table" { - name = "imms-${local.environment}-imms-events" + name = "imms-${local.resource_scope}-imms-events" billing_mode = "PAY_PER_REQUEST" hash_key = "PK" stream_enabled = true @@ -147,7 +147,7 @@ resource "aws_dynamodb_table" "events-dynamodb-table" { } point_in_time_recovery { - enabled = local.environment == "prod" + enabled = var.environment == "prod" } server_side_encryption { diff --git a/terraform/ecs_batch_processor_config.tf b/terraform/ecs_batch_processor_config.tf index c6a26c5cc0..dc02c5e58c 100644 --- a/terraform/ecs_batch_processor_config.tf +++ b/terraform/ecs_batch_processor_config.tf @@ -93,7 +93,7 @@ resource "aws_iam_policy" "ecs_task_exec_policy" { "logs:CreateLogStream", "logs:PutLogEvents" ], - Resource = "arn:aws:logs:${var.aws_region}:${local.immunisation_account_id}:log-group:/aws/vendedlogs/ecs/${local.short_prefix}-processor-task:*" + Resource = "arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/vendedlogs/ecs/${local.short_prefix}-processor-task:*" }, { Effect = "Allow", @@ -148,7 +148,7 @@ resource "aws_iam_policy" "ecs_task_exec_policy" { Action = [ "ecr:GetAuthorizationToken" ], - Resource = "arn:aws:ecr:${var.aws_region}:${local.immunisation_account_id}:repository/${local.short_prefix}-processing-repo" + Resource = "arn:aws:ecr:${var.aws_region}:${var.immunisation_account_id}:repository/${local.short_prefix}-processing-repo" }, { Effect = "Allow" @@ -279,7 +279,7 @@ resource "aws_iam_policy" "fifo_pipe_policy" { "pipes:DescribePipe" ], Resource = [ - "arn:aws:pipes:${var.aws_region}:${local.immunisation_account_id}:pipe/${local.short_prefix}-pipe", + "arn:aws:pipes:${var.aws_region}:${var.immunisation_account_id}:pipe/${local.short_prefix}-pipe", aws_ecs_task_definition.ecs_task.arn ] }, @@ -296,11 +296,11 @@ resource "aws_iam_policy" "fifo_pipe_policy" { ], Effect = "Allow", Resource = [ - "arn:aws:logs:${var.aws_region}:${local.immunisation_account_id}:log-group:/aws/vendedlogs/pipes/${local.short_prefix}-pipe-logs:*", - "arn:aws:ecs:${var.aws_region}:${local.immunisation_account_id}:task/${local.short_prefix}-ecs-cluster/*", - "arn:aws:logs:${var.aws_region}:${local.immunisation_account_id}:log-group:/aws/vendedlogs/ecs/${local.short_prefix}-processor-task:*", + "arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/vendedlogs/pipes/${local.short_prefix}-pipe-logs:*", + "arn:aws:ecs:${var.aws_region}:${var.immunisation_account_id}:task/${local.short_prefix}-ecs-cluster/*", + "arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/vendedlogs/ecs/${local.short_prefix}-processor-task:*", aws_sqs_queue.supplier_fifo_queue.arn, - "arn:aws:ecs:${var.aws_region}:${local.immunisation_account_id}:cluster/${local.short_prefix}-ecs-cluster", + "arn:aws:ecs:${var.aws_region}:${var.immunisation_account_id}:cluster/${local.short_prefix}-ecs-cluster", aws_ecs_task_definition.ecs_task.arn ] }, diff --git a/terraform/endpoints.tf b/terraform/endpoints.tf index 5424c32c3a..ad59840b68 100644 --- a/terraform/endpoints.tf +++ b/terraform/endpoints.tf @@ -8,7 +8,7 @@ data "aws_iam_policy_document" "logs_policy_document" { source_policy_documents = [templatefile("${local.policy_path}/log.json", {})] } module "get_status" { - source = "./lambda" + source = "./modules/lambda" prefix = local.prefix short_prefix = local.short_prefix function_name = "get_status" @@ -23,13 +23,13 @@ locals { imms_table_name = aws_dynamodb_table.events-dynamodb-table.name imms_lambda_env_vars = { "DYNAMODB_TABLE_NAME" = local.imms_table_name, - "IMMUNIZATION_ENV" = local.environment, - "IMMUNIZATION_BASE_PATH" = strcontains(local.environment, "pr-") ? "immunisation-fhir-api-${local.environment}" : "immunisation-fhir-api" + "IMMUNIZATION_ENV" = local.resource_scope, + "IMMUNIZATION_BASE_PATH" = strcontains(var.sub_environment, "pr-") ? "immunisation-fhir-api-${var.sub_environment}" : "immunisation-fhir-api" # except for prod and ref, any other env uses PDS int environment - "PDS_ENV" = local.environment == "prod" ? "prod" : local.environment == "ref" ? "ref" : "int", - "PDS_CHECK_ENABLED" = tostring(local.environment != "int") + "PDS_ENV" = var.pds_environment + "PDS_CHECK_ENABLED" = tostring(var.pds_check_enabled) "SPLUNK_FIREHOSE_NAME" = module.splunk.firehose_stream_name - "SQS_QUEUE_URL" = "https://sqs.eu-west-2.amazonaws.com/${local.immunisation_account_id}/${local.short_prefix}-ack-metadata-queue.fifo" + "SQS_QUEUE_URL" = "https://sqs.eu-west-2.amazonaws.com/${var.immunisation_account_id}/${local.short_prefix}-ack-metadata-queue.fifo" "REDIS_HOST" = data.aws_elasticache_cluster.existing_redis.cache_nodes[0].address "REDIS_PORT" = data.aws_elasticache_cluster.existing_redis.cache_nodes[0].port } @@ -41,7 +41,7 @@ data "aws_iam_policy_document" "imms_policy_document" { }), templatefile("${local.policy_path}/log.json", {}), templatefile("${local.policy_path}/lambda_to_sqs.json", { - "local_account" : local.immunisation_account_id + "local_account" : var.immunisation_account_id "queue_prefix" : local.short_prefix }), templatefile("${local.policy_path}/dynamo_key_access.json", { @@ -58,7 +58,7 @@ data "aws_iam_policy_document" "imms_policy_document" { } module "imms_event_endpoint_lambdas" { - source = "./lambda" + source = "./modules/lambda" count = length(local.imms_endpoints) prefix = local.prefix @@ -66,7 +66,7 @@ module "imms_event_endpoint_lambdas" { function_name = local.imms_endpoints[count.index] image_uri = module.docker_image.image_uri policy_json = data.aws_iam_policy_document.imms_policy_document.json - environments = local.imms_lambda_env_vars + environment_variables = local.imms_lambda_env_vars vpc_subnet_ids = local.private_subnet_ids vpc_security_group_ids = [data.aws_security_group.existing_securitygroup.id] } @@ -106,13 +106,14 @@ output "oas" { } module "api_gateway" { - source = "./api_gateway" + source = "./modules/api_gateway" prefix = local.prefix short_prefix = local.short_prefix zone_id = data.aws_route53_zone.project_zone.zone_id api_domain_name = local.service_domain_name - environment = local.environment + environment = var.environment + sub_environment = var.sub_environment oas = local.oas } diff --git a/terraform/environments/dev/int/variables.tfvars b/terraform/environments/dev/int/variables.tfvars new file mode 100644 index 0000000000..21dae85961 --- /dev/null +++ b/terraform/environments/dev/int/variables.tfvars @@ -0,0 +1,7 @@ +environment = "dev" +immunisation_account_id = "345594581768" +dspp_core_account_id = "603871901111" +pds_environment = "int" +pds_check_enabled = false +create_mesh_processor = true +has_sub_environment_scope = true diff --git a/terraform/environments/dev/internal-dev/variables.tfvars b/terraform/environments/dev/internal-dev/variables.tfvars new file mode 100644 index 0000000000..0c410efe95 --- /dev/null +++ b/terraform/environments/dev/internal-dev/variables.tfvars @@ -0,0 +1,7 @@ +environment = "dev" +immunisation_account_id = "345594581768" +dspp_core_account_id = "603871901111" +pds_environment = "int" +pds_check_enabled = true +create_mesh_processor = false +has_sub_environment_scope = true diff --git a/terraform/environments/dev/pr/variables.tfvars b/terraform/environments/dev/pr/variables.tfvars new file mode 100644 index 0000000000..0c410efe95 --- /dev/null +++ b/terraform/environments/dev/pr/variables.tfvars @@ -0,0 +1,7 @@ +environment = "dev" +immunisation_account_id = "345594581768" +dspp_core_account_id = "603871901111" +pds_environment = "int" +pds_check_enabled = true +create_mesh_processor = false +has_sub_environment_scope = true diff --git a/terraform/environments/dev/ref/variables.tfvars b/terraform/environments/dev/ref/variables.tfvars new file mode 100644 index 0000000000..c54c315677 --- /dev/null +++ b/terraform/environments/dev/ref/variables.tfvars @@ -0,0 +1,7 @@ +environment = "dev" +immunisation_account_id = "345594581768" +dspp_core_account_id = "603871901111" +pds_environment = "ref" +pds_check_enabled = true +create_mesh_processor = false +has_sub_environment_scope = true diff --git a/terraform/environments/int/blue/variables.tfvars b/terraform/environments/int/blue/variables.tfvars new file mode 100644 index 0000000000..f76c0b46ae --- /dev/null +++ b/terraform/environments/int/blue/variables.tfvars @@ -0,0 +1,7 @@ +environment = "int" +immunisation_account_id = "084828561157" +dspp_core_account_id = "603871901111" +pds_environment = "int" +pds_check_enabled = false +create_mesh_processor = true +has_sub_environment_scope = false diff --git a/terraform/environments/int/green/variables.tfvars b/terraform/environments/int/green/variables.tfvars new file mode 100644 index 0000000000..f76c0b46ae --- /dev/null +++ b/terraform/environments/int/green/variables.tfvars @@ -0,0 +1,7 @@ +environment = "int" +immunisation_account_id = "084828561157" +dspp_core_account_id = "603871901111" +pds_environment = "int" +pds_check_enabled = false +create_mesh_processor = true +has_sub_environment_scope = false diff --git a/terraform/environments/prod/blue/variables.tfvars b/terraform/environments/prod/blue/variables.tfvars new file mode 100644 index 0000000000..7cb2d4f652 --- /dev/null +++ b/terraform/environments/prod/blue/variables.tfvars @@ -0,0 +1,7 @@ +environment = "prod" +immunisation_account_id = "664418956997" +dspp_core_account_id = "232116723729" +pds_environment = "prod" +pds_check_enabled = true +create_mesh_processor = true +has_sub_environment_scope = false diff --git a/terraform/environments/prod/green/variables.tfvars b/terraform/environments/prod/green/variables.tfvars new file mode 100644 index 0000000000..7cb2d4f652 --- /dev/null +++ b/terraform/environments/prod/green/variables.tfvars @@ -0,0 +1,7 @@ +environment = "prod" +immunisation_account_id = "664418956997" +dspp_core_account_id = "232116723729" +pds_environment = "prod" +pds_check_enabled = true +create_mesh_processor = true +has_sub_environment_scope = false diff --git a/terraform/file_name_processor.tf b/terraform/file_name_processor.tf index 118e5d48c1..1d9b83ccd9 100644 --- a/terraform/file_name_processor.tf +++ b/terraform/file_name_processor.tf @@ -68,7 +68,7 @@ resource "aws_ecr_repository_policy" "filenameprocessor_lambda_ECRImageRetreival ], "Condition" : { "StringLike" : { - "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}-filenameproc_lambda" + "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}-filenameproc_lambda" } } } @@ -105,7 +105,7 @@ resource "aws_iam_policy" "filenameprocessor_lambda_exec_policy" { "logs:CreateLogStream", "logs:PutLogEvents" ] - Resource = "arn:aws:logs:${var.aws_region}:${local.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-filenameproc_lambda:*" + Resource = "arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-filenameproc_lambda:*" }, { Effect = "Allow" @@ -166,7 +166,7 @@ resource "aws_iam_policy" "filenameprocessor_lambda_exec_policy" { Effect = "Allow" Action = "lambda:InvokeFunction" Resource = [ - "arn:aws:lambda:${var.aws_region}:${local.immunisation_account_id}:function:imms-${local.env}-filenameproc_lambda", + "arn:aws:lambda:${var.aws_region}:${var.immunisation_account_id}:function:imms-${var.sub_environment}-filenameproc_lambda", ] } ] @@ -291,7 +291,7 @@ resource "aws_lambda_function" "file_processor_lambda" { SPLUNK_FIREHOSE_NAME = module.splunk.firehose_stream_name AUDIT_TABLE_NAME = aws_dynamodb_table.audit-table.name FILE_NAME_GSI = "filename_index" - FILE_NAME_PROC_LAMBDA_NAME = "imms-${local.env}-filenameproc_lambda" + FILE_NAME_PROC_LAMBDA_NAME = "imms-${var.sub_environment}-filenameproc_lambda" } } diff --git a/terraform/forwarder_lambda.tf b/terraform/forwarder_lambda.tf index 1f1aa2c457..dde81ab346 100644 --- a/terraform/forwarder_lambda.tf +++ b/terraform/forwarder_lambda.tf @@ -72,7 +72,7 @@ resource "aws_ecr_repository_policy" "forwarder_lambda_ECRImageRetreival_policy" ], "Condition" : { "StringLike" : { - "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}-forwarding_lambda" + "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}-forwarding_lambda" } } } @@ -109,7 +109,7 @@ resource "aws_iam_policy" "forwarding_lambda_exec_policy" { "logs:CreateLogStream", "logs:PutLogEvents" ] - Resource = "arn:aws:logs:${var.aws_region}:${local.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-forwarding_lambda:*", + Resource = "arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-forwarding_lambda:*", }, { Effect = "Allow" diff --git a/terraform/lambda.tf b/terraform/lambda.tf index d1ad8f28db..5410066f1f 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -74,13 +74,13 @@ resource "aws_ecr_repository_policy" "operation_lambda_ECRImageRetreival_policy" "Condition" : { "StringLike" : { "aws:sourceArn" : [ - "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}_get_status", - "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}_not_found", - "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}_search_imms", - "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}_get_imms", - "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}_delete_imms", - "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}_create_imms", - "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}_update_imms" + "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}_get_status", + "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}_not_found", + "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}_search_imms", + "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}_get_imms", + "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}_delete_imms", + "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}_create_imms", + "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}_update_imms" ] } } diff --git a/terraform/lambda/lambda.tf b/terraform/lambda/lambda.tf deleted file mode 100644 index 7cc24e1dd8..0000000000 --- a/terraform/lambda/lambda.tf +++ /dev/null @@ -1,52 +0,0 @@ -module "lambda_function_container_image" { - source = "terraform-aws-modules/lambda/aws" - version = "7.20.2" - - create_role = false - lambda_role = aws_iam_role.lambda_role.arn - function_name = "${var.short_prefix}_${var.function_name}" - handler = "${var.function_name}_handler.${var.function_name}_handler" - cloudwatch_logs_retention_in_days = 30 - create_package = false - image_uri = var.image_uri - package_type = "Image" - architectures = ["x86_64"] - timeout = 6 - - vpc_subnet_ids = var.vpc_subnet_ids - vpc_security_group_ids = var.vpc_security_group_ids - - # A JWT encode took 7 seconds at default memory size of 128 and 0.8 seconds at 1024. - # 2048 gets it down to around 0.5 but since Lambda is charged at GB * ms then it costs more for minimal benefit. - memory_size = 1024 - - environment_variables = var.environments - image_config_command = ["${var.function_name}_handler.${var.function_name}_handler"] -} - -resource "aws_cloudwatch_metric_alarm" "memory_alarm" { - alarm_name = "${var.short_prefix}_${var.function_name} memory alarm" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = 1 - metric_name = aws_cloudwatch_log_metric_filter.max_memory_used_metric.metric_transformation[0].name - namespace = aws_cloudwatch_log_metric_filter.max_memory_used_metric.metric_transformation[0].namespace - period = 600 - statistic = "Maximum" - threshold = 256 - alarm_description = "This metric monitors Lambda memory usage" - insufficient_data_actions = [] - -} - -resource "aws_cloudwatch_log_metric_filter" "max_memory_used_metric" { - name = "${var.short_prefix}_${var.function_name} max memory used" - pattern = "[type=REPORT, ...]" - - log_group_name = module.lambda_function_container_image.lambda_cloudwatch_log_group_name - - metric_transformation { - name = "max-memory-used" - namespace = "${var.short_prefix}_${var.function_name}" - value = "$18" - } -} diff --git a/terraform/lambda/variables.tf b/terraform/lambda/variables.tf deleted file mode 100644 index f4984553a9..0000000000 --- a/terraform/lambda/variables.tf +++ /dev/null @@ -1,34 +0,0 @@ -variable "prefix" { - type = string -} - -variable "short_prefix" { - type = string -} - -variable "function_name" { - type = string -} - -variable "image_uri" { - type = string -} - -variable "environments" { - type = map(string) - default = {} -} - -variable "policy_json" { - type = string -} - -variable "vpc_security_group_ids" { - type = list(string) - default = null -} - -variable "vpc_subnet_ids" { - type = list(string) - default = null -} diff --git a/terraform/main.tf b/terraform/main.tf index 7bfedb122b..e3a8724d15 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -17,21 +17,16 @@ terraform { } provider "aws" { - region = var.aws_region - profile = "apim-dev" + region = var.aws_region default_tags { tags = { Project = var.project_name - Environment = local.environment + Environment = local.resource_scope Service = var.service } } } -data "aws_region" "current" {} -data "aws_caller_identity" "current" {} -data "aws_ecr_authorization_token" "token" {} - provider "docker" { registry_auth { address = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.region}.amazonaws.com" @@ -39,3 +34,73 @@ provider "docker" { password = data.aws_ecr_authorization_token.token.password } } + +data "aws_region" "current" {} +data "aws_caller_identity" "current" {} +data "aws_ecr_authorization_token" "token" {} + +check "private_subnets" { + assert { + condition = length(local.private_subnet_ids) > 0 + error_message = "No private subnets with internet access found in VPC ${data.aws_vpc.default.id}" + } +} + +data "aws_vpc" "default" { + default = true +} + +data "aws_subnets" "all" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} + +data "aws_route_table" "route_table_by_subnet" { + for_each = toset(data.aws_subnets.all.ids) + + subnet_id = each.value +} + +data "aws_route" "internet_traffic_route_by_subnet" { + for_each = data.aws_route_table.route_table_by_subnet + + route_table_id = each.value.id + destination_cidr_block = "0.0.0.0/0" +} + +data "aws_kms_key" "existing_s3_encryption_key" { + key_id = "alias/imms-batch-s3-shared-key" +} + +data "aws_kms_key" "existing_dynamo_encryption_key" { + key_id = "alias/imms-event-dynamodb-encryption" +} + +data "aws_elasticache_cluster" "existing_redis" { + cluster_id = "immunisation-redis-cluster" +} + +data "aws_security_group" "existing_securitygroup" { + filter { + name = "group-name" + values = ["immunisation-security-group"] + } +} + +data "aws_kms_key" "existing_lambda_encryption_key" { + key_id = "alias/imms-batch-lambda-env-encryption" +} + +data "aws_kms_key" "existing_kinesis_encryption_key" { + key_id = "alias/imms-batch-kinesis-stream-encryption" +} + +data "aws_kms_key" "mesh_s3_encryption_key" { + key_id = "alias/local-immunisation-mesh" +} + +data "aws_route53_zone" "project_zone" { + name = local.project_domain_name +} diff --git a/terraform/mesh_processor.tf b/terraform/mesh_processor.tf index 4e38188b84..f53f1131fe 100644 --- a/terraform/mesh_processor.tf +++ b/terraform/mesh_processor.tf @@ -1,27 +1,26 @@ # Define the directory containing the Docker image and calculate its SHA-256 hash for triggering redeployments locals { - create_mesh_processor = local.environment == "int" || local.environment == "prod" mesh_processor_lambda_dir = abspath("${path.root}/../mesh_processor") mesh_processor_lambda_files = fileset(local.mesh_processor_lambda_dir, "**") mesh_processor_lambda_dir_sha = sha1(join("", [for f in local.mesh_processor_lambda_files : filesha1("${local.mesh_processor_lambda_dir}/${f}")])) # This should match the prefix used in the infra Terraform - mesh_module_prefix = "imms-${local.config_env}" + mesh_module_prefix = "imms-${var.environment}-mesh" } data "aws_s3_bucket" "mesh" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 - bucket = "${local.mesh_module_prefix}-mesh" + bucket = local.mesh_module_prefix } data "aws_kms_key" "mesh" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 - key_id = "alias/${local.mesh_module_prefix}-mesh" + key_id = "alias/${local.mesh_module_prefix}" } resource "aws_ecr_repository" "mesh_file_converter_lambda_repository" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 image_scanning_configuration { scan_on_push = true @@ -32,7 +31,7 @@ resource "aws_ecr_repository" "mesh_file_converter_lambda_repository" { # Module for building and pushing Docker image to ECR module "mesh_processor_docker_image" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 source = "terraform-aws-modules/lambda/aws//modules/docker-build" version = "8.0.1" @@ -66,7 +65,7 @@ module "mesh_processor_docker_image" { # Define the lambdaECRImageRetreival policy resource "aws_ecr_repository_policy" "mesh_processor_lambda_ECRImageRetreival_policy" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 repository = aws_ecr_repository.mesh_file_converter_lambda_repository[0].name @@ -88,7 +87,7 @@ resource "aws_ecr_repository_policy" "mesh_processor_lambda_ECRImageRetreival_po ], "Condition" : { "StringLike" : { - "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}-mesh_processor_lambda" + "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${var.immunisation_account_id}:function:${local.short_prefix}-mesh_processor_lambda" } } } @@ -98,7 +97,7 @@ resource "aws_ecr_repository_policy" "mesh_processor_lambda_ECRImageRetreival_po # IAM Role for Lambda resource "aws_iam_role" "mesh_processor_lambda_exec_role" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 name = "${local.short_prefix}-mesh_processor-lambda-exec-role" assume_role_policy = jsonencode({ @@ -116,7 +115,7 @@ resource "aws_iam_role" "mesh_processor_lambda_exec_role" { # Policy for Lambda execution role resource "aws_iam_policy" "mesh_processor_lambda_exec_policy" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 name = "${local.short_prefix}-mesh_processor-lambda-exec-policy" policy = jsonencode({ @@ -129,7 +128,7 @@ resource "aws_iam_policy" "mesh_processor_lambda_exec_policy" { "logs:CreateLogStream", "logs:PutLogEvents" ] - Resource = "arn:aws:logs:${var.aws_region}:${local.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-mesh_processor_lambda:*" + Resource = "arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-mesh_processor_lambda:*" }, { Effect = "Allow" @@ -164,7 +163,7 @@ resource "aws_iam_policy" "mesh_processor_lambda_exec_policy" { } resource "aws_iam_policy" "mesh_processor_lambda_kms_access_policy" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 name = "${local.short_prefix}-mesh_processor-lambda-kms-policy" description = "Allow Lambda to decrypt environment variables" @@ -189,7 +188,7 @@ resource "aws_iam_policy" "mesh_processor_lambda_kms_access_policy" { # Attach the execution policy to the Lambda role resource "aws_iam_role_policy_attachment" "mesh_processor_lambda_exec_policy_attachment" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 role = aws_iam_role.mesh_processor_lambda_exec_role[0].name policy_arn = aws_iam_policy.mesh_processor_lambda_exec_policy[0].arn @@ -198,7 +197,7 @@ resource "aws_iam_role_policy_attachment" "mesh_processor_lambda_exec_policy_att # Attach the kms policy to the Lambda role resource "aws_iam_role_policy_attachment" "mesh_processor_lambda_kms_policy_attachment" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 role = aws_iam_role.mesh_processor_lambda_exec_role[0].name policy_arn = aws_iam_policy.mesh_processor_lambda_kms_access_policy[0].arn @@ -206,7 +205,7 @@ resource "aws_iam_role_policy_attachment" "mesh_processor_lambda_kms_policy_atta # Lambda Function with Security Group and VPC. resource "aws_lambda_function" "mesh_file_converter_lambda" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 function_name = "${local.short_prefix}-mesh_processor_lambda" role = aws_iam_role.mesh_processor_lambda_exec_role[0].arn @@ -225,7 +224,7 @@ resource "aws_lambda_function" "mesh_file_converter_lambda" { # Permission for S3 to invoke Lambda function resource "aws_lambda_permission" "mesh_s3_invoke_permission" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 statement_id = "AllowExecutionFromS3" action = "lambda:InvokeFunction" @@ -235,7 +234,7 @@ resource "aws_lambda_permission" "mesh_s3_invoke_permission" { } resource "aws_s3_bucket_notification" "mesh_datasources_lambda_notification" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 bucket = data.aws_s3_bucket.mesh[0].bucket @@ -247,7 +246,7 @@ resource "aws_s3_bucket_notification" "mesh_datasources_lambda_notification" { } resource "aws_cloudwatch_log_group" "mesh_file_converter_log_group" { - count = local.create_mesh_processor ? 1 : 0 + count = var.create_mesh_processor ? 1 : 0 name = "/aws/lambda/${local.short_prefix}-mesh_processor_lambda" retention_in_days = 30 diff --git a/terraform/modules/api_gateway/acm_cert.tf b/terraform/modules/api_gateway/acm_cert.tf new file mode 100644 index 0000000000..14d00f436e --- /dev/null +++ b/terraform/modules/api_gateway/acm_cert.tf @@ -0,0 +1,33 @@ +resource "aws_acm_certificate" "service_certificate" { + domain_name = var.api_domain_name + subject_alternative_names = [] + validation_method = "DNS" + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_acm_certificate_validation" "service_certificate" { + certificate_arn = aws_acm_certificate.service_certificate.arn + validation_record_fqdns = [for record in aws_route53_record.dns_validation : record.fqdn] + depends_on = [aws_acm_certificate.service_certificate, aws_route53_record.dns_validation] +} + +resource "aws_route53_record" "dns_validation" { + for_each = { + for dvo in aws_acm_certificate.service_certificate.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + record = dvo.resource_record_value + type = dvo.resource_record_type + } + } + + allow_overwrite = true + name = each.value.name + records = [each.value.record] + ttl = 60 + type = each.value.type + zone_id = var.zone_id + depends_on = [aws_acm_certificate.service_certificate] +} diff --git a/terraform/modules/api_gateway/api.tf b/terraform/modules/api_gateway/api.tf new file mode 100644 index 0000000000..b151577b09 --- /dev/null +++ b/terraform/modules/api_gateway/api.tf @@ -0,0 +1,63 @@ + +resource "aws_apigatewayv2_api" "service_api" { + name = "${var.prefix}-api" + description = "Immunisation FHIR API - ${var.sub_environment}" + protocol_type = "HTTP" + disable_execute_api_endpoint = true + body = var.oas +} + +resource "aws_apigatewayv2_stage" "default" { + depends_on = [aws_cloudwatch_log_group.api_access_log] + api_id = aws_apigatewayv2_api.service_api.id + name = var.sub_environment + auto_deploy = true + + default_route_settings { + logging_level = "INFO" + throttling_burst_limit = 500 + throttling_rate_limit = 500 + detailed_metrics_enabled = true + } + access_log_settings { + destination_arn = aws_cloudwatch_log_group.api_access_log.arn + format = "{ \"requestId\":\"$context.requestId\", \"extendedRequestId\":\"$context.extendedRequestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\", \"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\", \"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\", \"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\", \"authorizerError\":\"$context.authorizer.error\", \"authorizerStatus\":\"$context.authorizer.status\", \"requestIsValid\":\"$context.authorizer.is_valid\"\"environment\":\"$context.authorizer.environment\" }" + } + + # Bug in terraform-aws-provider with perpetual diff + lifecycle { + ignore_changes = [deployment_id] + } +} + +resource "aws_apigatewayv2_domain_name" "service_api_domain_name" { + domain_name = var.api_domain_name + domain_name_configuration { + certificate_arn = aws_acm_certificate_validation.service_certificate.certificate_arn + endpoint_type = "REGIONAL" + security_policy = "TLS_1_2" + } + mutual_tls_authentication { + truststore_uri = "s3://${aws_s3_bucket.truststore_bucket.bucket}/${local.truststore_file_name}" + } + tags = { + Name = "${var.prefix}-api-domain-name" + } +} + +resource "aws_apigatewayv2_api_mapping" "api_mapping" { + api_id = aws_apigatewayv2_api.service_api.id + domain_name = aws_apigatewayv2_domain_name.service_api_domain_name.id + stage = aws_apigatewayv2_stage.default.id +} + +resource "aws_route53_record" "api_domain" { + zone_id = var.zone_id + name = aws_apigatewayv2_domain_name.service_api_domain_name.domain_name + type = "A" + alias { + evaluate_target_health = true + name = aws_apigatewayv2_domain_name.service_api_domain_name.domain_name_configuration[0].target_domain_name + zone_id = aws_apigatewayv2_domain_name.service_api_domain_name.domain_name_configuration[0].hosted_zone_id + } +} diff --git a/terraform/api_gateway/logs.tf b/terraform/modules/api_gateway/logs.tf similarity index 72% rename from terraform/api_gateway/logs.tf rename to terraform/modules/api_gateway/logs.tf index 4a286a6685..719a55712d 100644 --- a/terraform/api_gateway/logs.tf +++ b/terraform/modules/api_gateway/logs.tf @@ -1,17 +1,17 @@ resource "aws_cloudwatch_log_group" "api_access_log" { - name = "/aws/vendedlogs/${aws_apigatewayv2_api.service_api.id}/${local.api_stage_name}" - retention_in_days = 30 + name = "/aws/vendedlogs/${aws_apigatewayv2_api.service_api.id}/${var.sub_environment}" + retention_in_days = 30 } # TODO - This is global, so is overwritten by each deployment - move to infra Terraform? resource "aws_api_gateway_account" "api_account" { - cloudwatch_role_arn = aws_iam_role.api_cloudwatch.arn + cloudwatch_role_arn = aws_iam_role.api_cloudwatch.arn } resource "aws_iam_role" "api_cloudwatch" { - name = "${var.short_prefix}-api-logs" + name = "${var.short_prefix}-api-logs" - assume_role_policy = < 0 -} -resource "aws_s3_bucket" "failed_logs_backup" { - bucket = "${local.prefix}-failure-logs" - // To facilitate deletion of non empty busckets - force_destroy = local.is_temp -} diff --git a/terraform/variables.tf b/terraform/variables.tf index b3b707740c..8cb2ea0dd6 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -1,96 +1,61 @@ -variable "project_name" { - default = "immunisations" -} +variable "environment" {} -variable "project_short_name" { - default = "imms" +variable "sub_environment" { + description = "The value is set in the makefile" } -variable "service" { - default = "fhir-api" -} +variable "immunisation_account_id" {} +variable "dspp_core_account_id" {} -variable "aws_region" { - default = "eu-west-2" +variable "create_mesh_processor" { + default = false } -locals { - environment = terraform.workspace == "green" ? "prod" : terraform.workspace == "blue" ? "prod" : terraform.workspace - env = terraform.workspace - prefix = "${var.project_name}-${var.service}-${local.env}" - short_prefix = "${var.project_short_name}-${local.env}" - batch_prefix = "immunisation-batch-${local.env}" - # TODO - add int when we switch to the new account - config_env = local.environment == "prod" ? "prod" : "dev" - - root_domain = "${local.config_env}.vds.platform.nhs.uk" - project_domain_name = data.aws_route53_zone.project_zone.name - service_domain_name = "${local.env}.${local.project_domain_name}" - - config_bucket_arn = aws_s3_bucket.batch_config_bucket.arn - config_bucket_name = aws_s3_bucket.batch_config_bucket.bucket - - - # Public subnet - The subnet has a direct route to an internet gateway. Resources in a public subnet can access the public internet. - # public_subnet_ids = [for k, v in data.aws_route.internet_traffic_route_by_subnet : k if length(v.gateway_id) > 0] - # Private subnet - The subnet does not have a direct route to an internet gateway. Resources in a private subnet require a NAT device to access the public internet. - private_subnet_ids = [for k, v in data.aws_route.internet_traffic_route_by_subnet : k if length(v.nat_gateway_id) > 0] +variable "project_name" { + default = "immunisation" } -check "private_subnets" { - assert { - condition = length(local.private_subnet_ids) > 0 - error_message = "No private subnets with internet access found in VPC ${data.aws_vpc.default.id}" - } +variable "project_short_name" { + default = "imms" } -data "aws_vpc" "default" { +variable "use_new_aws_preprod_account" { default = true } - -data "aws_subnets" "all" { - filter { - name = "vpc-id" - values = [data.aws_vpc.default.id] - } -} - -data "aws_route_table" "route_table_by_subnet" { - for_each = toset(data.aws_subnets.all.ids) - - subnet_id = each.value -} - -data "aws_route" "internet_traffic_route_by_subnet" { - for_each = data.aws_route_table.route_table_by_subnet - - route_table_id = each.value.id - destination_cidr_block = "0.0.0.0/0" -} - -data "aws_kms_key" "existing_s3_encryption_key" { - key_id = "alias/imms-batch-s3-shared-key" +variable "service" { + default = "fhir-api" } -data "aws_kms_key" "existing_dynamo_encryption_key" { - key_id = "alias/imms-event-dynamodb-encryption" +variable "aws_region" { + default = "eu-west-2" } -data "aws_elasticache_cluster" "existing_redis" { - cluster_id = "immunisation-redis-cluster" +variable "pds_environment" { + default = "int" } -data "aws_security_group" "existing_securitygroup" { - filter { - name = "group-name" - values = ["immunisation-security-group"] - } +variable "pds_check_enabled" { + default = true } -data "aws_kms_key" "existing_lambda_encryption_key" { - key_id = "alias/imms-batch-lambda-env-encryption" +variable "has_sub_environment_scope" { + default = false } -data "aws_kms_key" "existing_kinesis_encryption_key" { - key_id = "alias/imms-batch-kinesis-stream-encryption" +locals { + prefix = "${var.project_name}-${var.service}-${var.sub_environment}" + short_prefix = "${var.project_short_name}-${var.sub_environment}" + batch_prefix = "immunisation-batch-${var.sub_environment}" + vpc_name = "imms-${var.environment}-fhir-api-vpc" + root_domain_name = "${var.environment}.vds.platform.nhs.uk" + project_domain_name = "imms.${local.root_domain_name}" + service_domain_name = "${var.sub_environment}.${local.project_domain_name}" + config_bucket_arn = aws_s3_bucket.batch_config_bucket.arn + config_bucket_name = aws_s3_bucket.batch_config_bucket.bucket + is_temp = length(regexall("[a-z]{2,4}-?[0-9]+", var.sub_environment)) > 0 + resource_scope = var.has_sub_environment_scope ? var.sub_environment : var.environment + # Public subnet - The subnet has a direct route to an internet gateway. Resources in a public subnet can access the public internet. + # public_subnet_ids = [for k, v in data.aws_route.internet_traffic_route_by_subnet : k if length(v.gateway_id) > 0] + # Private subnet - The subnet does not have a direct route to an internet gateway. Resources in a private subnet require a NAT device to access the public internet. + private_subnet_ids = [for k, v in data.aws_route.internet_traffic_route_by_subnet : k if length(v.nat_gateway_id) > 0] }