diff --git a/.github/workflows/github-oidc-test.yaml b/.github/workflows/github-oidc-test.yaml
deleted file mode 100644
index d4b903c7..00000000
--- a/.github/workflows/github-oidc-test.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: Github OIDC test
-
-on:
- workflow_dispatch:
- inputs:
- environment:
- description: Target environment
- required: true
- type: choice
- options: [dev, test, preprod]
-
-jobs:
- listS3:
- runs-on: ubuntu-latest
- environment: ${{ inputs.environment }}
- permissions:
- id-token: write
- contents: read
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Configure AWS Credentials
- uses: aws-actions/configure-aws-credentials@v4
- with:
- role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/service-roles/github-actions-api-deployment-role
- aws-region: eu-west-2
-
- - name: List S3 bucket
- run: |
- aws s3 ls s3://eligibility-signposting-api-${{ inputs.environment }}-tfstate
diff --git a/.github/workflows/manual-terraform-plan.yaml b/.github/workflows/manual-terraform-plan.yaml
new file mode 100644
index 00000000..8b32921e
--- /dev/null
+++ b/.github/workflows/manual-terraform-plan.yaml
@@ -0,0 +1,71 @@
+name: Github OIDC test
+
+on:
+ workflow_dispatch:
+ inputs:
+ environment:
+ description: Target environment
+ required: true
+ type: choice
+ options: [dev, test, preprod]
+
+jobs:
+ plan-stacks:
+ runs-on: ubuntu-latest
+ environment: ${{ inputs.environment }}
+ permissions:
+ id-token: write
+ contents: read
+
+ steps:
+ - name: "Setup Terraform"
+ uses: hashicorp/setup-terraform@v3
+ with:
+ terraform_version: ${{ vars.TF_VERSION }}
+
+ - name: "Set up Python"
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.13'
+
+ - name: "Checkout Repository"
+ uses: actions/checkout@v4
+
+ - name: "Build lambda artefact"
+ run: |
+ make dependencies install-python
+ make build
+ - name: "Upload lambda artefact"
+ uses: actions/upload-artifact@v4
+ with:
+ name: lambda
+ path: dist/lambda.zip
+
+ - name: "Download Built Lambdas"
+ uses: actions/download-artifact@v4
+ with:
+ name: lambda
+ path: ./build
+
+ - name: "Configure AWS Credentials"
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/service-roles/github-actions-api-deployment-role
+ aws-region: eu-west-2
+
+ - name: "Terraform Plan Stacks"
+ env:
+ ENVIRONMENT: "dev"
+ WORKSPACE: "default"
+ TF_VAR_API_CA_CERT: ${{ secrets.API_CA_CERT }}
+ TF_VAR_API_CLIENT_CERT: ${{ secrets.API_CLIENT_CERT }}
+ TF_VAR_API_PRIVATE_KEY_CERT: ${{ secrets.API_PRIVATE_KEY_CERT }}
+
+ run: |
+ mkdir -p ./build
+ echo "Running: make terraform env=$ENVIRONMENT workspace=$ENVIRONMENT stack=networking tf-command=plan args=\"-auto-approve\""
+ make terraform env=$ENVIRONMENT stack=networking tf-command=plan workspace=$ENVIRONMENT
+ echo "Running: make terraform env=$ENVIRONMENT workspace=$WORKSPACE stack=api-layer tf-command=plan args=\"-auto-approve\""
+ make terraform env=$ENVIRONMENT stack=api-layer tf-command=plan workspace=$WORKSPACE
+
+ working-directory: ./infrastructure
diff --git a/infrastructure/modules/_shared/default_variables.tf b/infrastructure/modules/_shared/default_variables.tf
index 38738be7..1ebf1eb7 100644
--- a/infrastructure/modules/_shared/default_variables.tf
+++ b/infrastructure/modules/_shared/default_variables.tf
@@ -1,18 +1,31 @@
-# tflint-ignore: terraform_unused_declarations
variable "project_name" {
default = "eligibility-signposting-api"
type = string
}
-# tflint-ignore: terraform_unused_declarations
variable "environment" {
description = "The purpose of the account dev/test/ref/prod or the workspace"
type = string
}
-# tflint-ignore: terraform_unused_declarations
variable "tags" {
description = "A map of tags to assign to resources."
type = map(string)
default = {}
}
+
+variable "workspace" {
+ description = "Usually the developer short code or the name of the environment."
+ type = string
+}
+
+variable "stack_name" {
+ description = "The name of the stack being deployed"
+ type = string
+}
+
+variable "region" {
+ type = string
+ description = "The aws region."
+ default = "eu-west-2"
+}
diff --git a/infrastructure/modules/api_gateway/api_gateway.tf b/infrastructure/modules/api_gateway/api_gateway.tf
new file mode 100644
index 00000000..8bcf52e9
--- /dev/null
+++ b/infrastructure/modules/api_gateway/api_gateway.tf
@@ -0,0 +1,18 @@
+resource "aws_api_gateway_rest_api" "api_gateway" {
+ name = var.workspace == "default" ? "${var.api_gateway_name}-rest-api" : "${var.workspace}-${var.api_gateway_name}-rest-api"
+ description = "The API Gateway for ${var.project_name} ${var.environment} environment"
+
+ disable_execute_api_endpoint = var.disable_default_endpoint # We would want to disable this if we are using a custom domain name
+
+ lifecycle {
+ create_before_destroy = true
+ }
+
+ tags = {
+ Stack = var.stack_name
+ }
+}
+
+resource "aws_api_gateway_account" "api_gateway" {
+ cloudwatch_role_arn = aws_iam_role.api_gateway.arn
+}
diff --git a/infrastructure/modules/api_gateway/cloudwatch.tf b/infrastructure/modules/api_gateway/cloudwatch.tf
new file mode 100644
index 00000000..b8edfbd6
--- /dev/null
+++ b/infrastructure/modules/api_gateway/cloudwatch.tf
@@ -0,0 +1,10 @@
+resource "aws_cloudwatch_log_group" "api_gateway" {
+ name = "/aws/apigateway/${var.workspace}-${var.api_gateway_name}"
+ retention_in_days = 14
+ tags = var.tags
+ kms_key_id = aws_kms_key.api_gateway.arn
+
+ lifecycle {
+ prevent_destroy = false
+ }
+}
diff --git a/infrastructure/modules/api_gateway/data.tf b/infrastructure/modules/api_gateway/data.tf
new file mode 100644
index 00000000..8fc4b38c
--- /dev/null
+++ b/infrastructure/modules/api_gateway/data.tf
@@ -0,0 +1 @@
+data "aws_caller_identity" "current" {}
diff --git a/infrastructure/modules/api_gateway/default_variables.tf b/infrastructure/modules/api_gateway/default_variables.tf
new file mode 120000
index 00000000..062daf61
--- /dev/null
+++ b/infrastructure/modules/api_gateway/default_variables.tf
@@ -0,0 +1 @@
+../_shared/default_variables.tf
\ No newline at end of file
diff --git a/infrastructure/modules/api_gateway/iam.tf b/infrastructure/modules/api_gateway/iam.tf
new file mode 100644
index 00000000..20cd13aa
--- /dev/null
+++ b/infrastructure/modules/api_gateway/iam.tf
@@ -0,0 +1,43 @@
+data "aws_iam_policy_document" "assume_role" {
+ statement {
+ effect = "Allow"
+ actions = ["sts:AssumeRole"]
+ principals {
+ type = "Service"
+ identifiers = ["apigateway.amazonaws.com"]
+ }
+ }
+}
+
+resource "aws_iam_role" "api_gateway" {
+ name = "${var.workspace}-${var.api_gateway_name}-role"
+ assume_role_policy = data.aws_iam_policy_document.assume_role.json
+}
+
+data "aws_iam_policy_document" "api_gateway_logging" {
+ statement {
+ sid = "AllowCloudWatchLogging"
+ effect = "Allow"
+ actions = [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogGroups",
+ "logs:DescribeLogStreams",
+ "logs:PutLogEvents",
+ "logs:GetLogEvents",
+ "logs:FilterLogEvents"
+ ]
+ resources = ["*"]
+ }
+}
+
+resource "aws_iam_policy" "api_gateway_logging" {
+ name = "${var.workspace}-${var.api_gateway_name}-api-gateway-logging-policy"
+ description = "Policy to allow API Gateway push logs to Cloudwatch"
+ policy = data.aws_iam_policy_document.api_gateway_logging.json
+}
+
+resource "aws_iam_role_policy_attachment" "api_gateway_logging" {
+ role = aws_iam_role.api_gateway.name
+ policy_arn = aws_iam_policy.api_gateway_logging.arn
+}
diff --git a/infrastructure/modules/api_gateway/kms.tf b/infrastructure/modules/api_gateway/kms.tf
new file mode 100644
index 00000000..4994b9b4
--- /dev/null
+++ b/infrastructure/modules/api_gateway/kms.tf
@@ -0,0 +1,48 @@
+resource "aws_kms_key" "api_gateway" {
+ description = "${var.workspace} - KMS Key for ${var.api_gateway_name} API Gateway"
+ deletion_window_in_days = 14
+ enable_key_rotation = true
+
+ tags = {
+ Stack = var.stack_name
+ }
+}
+
+resource "aws_kms_alias" "api_gateway" {
+ name = "alias/${var.workspace}-${var.api_gateway_name}-cloudwatch-logs"
+ target_key_id = aws_kms_key.api_gateway.key_id
+}
+
+resource "aws_kms_key_policy" "api_gateway" {
+ key_id = aws_kms_key.api_gateway.id
+ policy = data.aws_iam_policy_document.api_gateway.json
+}
+
+data "aws_iam_policy_document" "api_gateway" {
+ statement {
+ sid = "Enable IAM User Permissions for ${var.api_gateway_name} API Gateway"
+ effect = "Allow"
+ principals {
+ type = "AWS"
+ identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
+ }
+ actions = ["kms:*"]
+ resources = [aws_kms_key.api_gateway.arn]
+ }
+ statement {
+ sid = "APIGatewayCloudwatchKMSAccess"
+ effect = "Allow"
+ principals {
+ type = "Service"
+ identifiers = ["logs.${var.region}.amazonaws.com"]
+ }
+ actions = [
+ "kms:Encrypt*",
+ "kms:Decrypt*",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:Describe*"
+ ]
+ resources = [aws_kms_key.api_gateway.arn]
+ }
+}
diff --git a/infrastructure/modules/api_gateway/outputs.tf b/infrastructure/modules/api_gateway/outputs.tf
new file mode 100644
index 00000000..4937500f
--- /dev/null
+++ b/infrastructure/modules/api_gateway/outputs.tf
@@ -0,0 +1,27 @@
+output "rest_api_id" {
+ value = aws_api_gateway_rest_api.api_gateway.id
+}
+
+output "root_resource_id" {
+ value = aws_api_gateway_rest_api.api_gateway.root_resource_id
+}
+
+output "execution_arn" {
+ value = aws_api_gateway_rest_api.api_gateway.execution_arn
+}
+
+output "cloudwatch_destination_arn" {
+ value = aws_cloudwatch_log_group.api_gateway.arn
+}
+
+output "api_gateway_account" {
+ value = aws_api_gateway_account.api_gateway
+}
+
+output "logging_policy_attachment" {
+ value = aws_iam_role_policy_attachment.api_gateway_logging
+}
+
+output "iam_role_name" {
+ value = aws_iam_role.api_gateway.name
+}
diff --git a/infrastructure/modules/api_gateway/providers.tf b/infrastructure/modules/api_gateway/providers.tf
new file mode 100644
index 00000000..4b9d1aa1
--- /dev/null
+++ b/infrastructure/modules/api_gateway/providers.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.11.1"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 5.6, != 5.71.0"
+ }
+ }
+}
diff --git a/infrastructure/modules/api_gateway/variables.tf b/infrastructure/modules/api_gateway/variables.tf
new file mode 100644
index 00000000..a871aa48
--- /dev/null
+++ b/infrastructure/modules/api_gateway/variables.tf
@@ -0,0 +1,9 @@
+variable "api_gateway_name" {
+ type = string
+ description = "The name of the API Gateway"
+}
+
+variable "disable_default_endpoint" {
+ type = bool
+ description = "Indicates whether the default endpoint the API Gateway generates should be disabled. If true, the API will need to be called from a Custom Domain Name"
+}
diff --git a/infrastructure/modules/lambda/outputs.tf b/infrastructure/modules/lambda/outputs.tf
index dfb6abc1..7f27a237 100644
--- a/infrastructure/modules/lambda/outputs.tf
+++ b/infrastructure/modules/lambda/outputs.tf
@@ -4,3 +4,11 @@ output "aws_lambda_function_id" {
output "aws_lambda_function_arn" {
value = aws_lambda_function.eligibility_signposting_lambda.arn
}
+
+output "aws_lambda_function_name" {
+ value = aws_lambda_function.eligibility_signposting_lambda.function_name
+}
+
+output "aws_lambda_invoke_arn" {
+ value = aws_lambda_function.eligibility_signposting_lambda.invoke_arn
+}
diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf
index 23185dd6..4151a6ae 100644
--- a/infrastructure/modules/lambda/variables.tf
+++ b/infrastructure/modules/lambda/variables.tf
@@ -1,8 +1,3 @@
-variable "workspace" {
- description = "Usually the developer short code or the name of the environment."
- type = string
-}
-
variable "eligibility_lambda_role_arn" {
description = "lambda read role arn for dynamodb"
type = string
@@ -42,4 +37,3 @@ variable "eligibility_status_table_name" {
description = "eligibility datastore table name"
type = string
}
-
diff --git a/infrastructure/modules/s3/outputs.tf b/infrastructure/modules/s3/outputs.tf
index d6518eb5..e1aa6ba3 100644
--- a/infrastructure/modules/s3/outputs.tf
+++ b/infrastructure/modules/s3/outputs.tf
@@ -13,3 +13,7 @@ output "storage_bucket_arn" {
output "storage_bucket_name" {
value = aws_s3_bucket.storage_bucket.bucket
}
+
+output "storage_bucket_id" {
+ value = aws_s3_bucket.storage_bucket.id
+}
diff --git a/infrastructure/stacks/api-layer/api_gateway.tf b/infrastructure/stacks/api-layer/api_gateway.tf
new file mode 100644
index 00000000..de8f1efc
--- /dev/null
+++ b/infrastructure/stacks/api-layer/api_gateway.tf
@@ -0,0 +1,106 @@
+module "eligibility_signposting_api_gateway" {
+ source = "../../modules/api_gateway"
+ api_gateway_name = "eligibility-signposting-api"
+ disable_default_endpoint = var.environment == "dev" && local.workspace != "default" ? false : true
+ workspace = local.workspace
+ stack_name = local.stack_name
+ environment = var.environment
+ tags = local.tags
+}
+
+resource "aws_api_gateway_resource" "_status" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ parent_id = module.eligibility_signposting_api_gateway.root_resource_id
+ path_part = "_status"
+}
+
+resource "aws_api_gateway_resource" "patient_check" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ parent_id = module.eligibility_signposting_api_gateway.root_resource_id
+ path_part = "patient-check"
+}
+
+resource "aws_api_gateway_resource" "patient" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ parent_id = aws_api_gateway_resource.patient_check.id
+ path_part = "{id}"
+}
+
+# deployment
+
+resource "aws_api_gateway_deployment" "eligibility_signposting_api" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+
+ triggers = {
+ redeployment = sha1(jsonencode([
+ aws_api_gateway_integration.get_patient_check.id,
+ aws_api_gateway_integration._status.id,
+ ]))
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "aws_api_gateway_stage" "eligibility-signposting-api" {
+ deployment_id = aws_api_gateway_deployment.eligibility_signposting_api.id
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ stage_name = "${local.workspace}-eligibility-signposting-api-live"
+ xray_tracing_enabled = true
+
+ access_log_settings {
+ destination_arn = module.eligibility_signposting_api_gateway.cloudwatch_destination_arn
+ format = "{ \"requestId\":\"$context.requestId\", \"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\", \"accountId\":\"$context.accountId\", \"apiId\":\"$context.apiId\", \"stage\":\"$context.stage\", \"domainName\":\"$context.domainName\", \"error_message\":\"$context.error.message\", \"clientCertSerialNumber\":\"$context.identity.clientCert.serialNumber\", \"clientCertValidityNotBefore\":\"$context.identity.clientCert.validity.notBefore\", \"clientCertValidityNotAfter\":\"$context.identity.clientCert.validity.notAfter\" }"
+ }
+
+ depends_on = [
+ module.eligibility_signposting_api_gateway.api_gateway_account,
+ module.eligibility_signposting_api_gateway.logging_policy_attachment
+ ]
+}
+
+resource "aws_api_gateway_method_settings" "check_eligibility" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ stage_name = aws_api_gateway_stage.eligibility-signposting-api.stage_name
+ method_path = "*/*"
+
+ settings {
+ metrics_enabled = true
+ logging_level = "INFO"
+ }
+
+ depends_on = [
+ module.eligibility_signposting_api_gateway.api_gateway_account,
+ module.eligibility_signposting_api_gateway.logging_policy_attachment
+ ]
+}
+
+resource "aws_api_gateway_domain_name" "check_eligibility" {
+ count = var.environment == "dev" && local.workspace != "default" ? 0 : 1
+ domain_name = "${local.api_subdomain}.${local.api_domain_name}"
+ regional_certificate_arn = data.aws_acm_certificate.imported_cert.arn
+ ownership_verification_certificate_arn = data.aws_acm_certificate.validation_cert.arn
+
+ mutual_tls_authentication {
+ truststore_uri = "s3://${module.s3_truststore_bucket.storage_bucket_name}/truststore.pem"
+ truststore_version = aws_s3_object.pem_file.version_id
+ }
+
+ security_policy = "TLS_1_2"
+
+ endpoint_configuration {
+ types = ["REGIONAL"]
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "aws_api_gateway_base_path_mapping" "eligibility-signposting-api" {
+ count = var.environment == "dev" && local.workspace != "default" ? 0 : 1
+ api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ stage_name = aws_api_gateway_stage.eligibility-signposting-api.stage_name
+ domain_name = "${local.api_subdomain}.${local.api_domain_name}"
+}
diff --git a/infrastructure/stacks/api-layer/assumed_role_permissions_boundary.tf b/infrastructure/stacks/api-layer/assumed_role_permissions_boundary.tf
new file mode 100644
index 00000000..2fd4e845
--- /dev/null
+++ b/infrastructure/stacks/api-layer/assumed_role_permissions_boundary.tf
@@ -0,0 +1,80 @@
+# Policy document for Permissions boundary
+data "aws_iam_policy_document" "assumed_role_permissions_boundary" {
+ #checkov:skip=CKV2_AWS_40: Ensure AWS IAM policy does not allow full IAM privileges
+ statement {
+ sid = "RestrictRegion"
+ effect = "Allow"
+
+ actions = [
+ "acm:*",
+ "application-autoscaling:*",
+ "apigateway:*",
+ "cloudtrail:*",
+ "cloudwatch:*",
+ "config:*",
+ "dynamodb:*",
+ "ec2:*",
+ "events:*",
+ "firehose:*",
+ "glue:*",
+ "health:*",
+ "iam:*",
+ "kms:*",
+ "lambda:*",
+ "logs:*",
+ "network-firewall:*",
+ "pipes:*",
+ "s3:*",
+ "schemas:*",
+ "sns:*",
+ "servicequotas:*",
+ "ssm:*",
+ "states:*",
+ "support:*",
+ "sqs:*",
+ "tag:*",
+ "trustedadvisor:*"
+ ]
+
+ resources = ["*"]
+
+ condition {
+ test = "StringEquals"
+ variable = "aws:RequestedRegion"
+ values = [var.default_aws_region]
+ }
+ }
+
+ statement {
+ sid = "DenyPrivEsculationViaIamRoles"
+ effect = "Deny"
+ actions = ["iam:*"]
+ resources = ["*"]
+ condition {
+ test = "ArnLike"
+ variable = "iam:PolicyARN"
+ values = ["arn:aws:iam::*:policy/${upper(var.project_name)}-*"]
+ }
+ }
+
+ statement {
+ sid = "DenyPrivEsculationViaIamProfiles"
+ effect = "Deny"
+ actions = ["iam:*"]
+ resources = ["arn:aws:iam::*:role/${upper(var.project_name)}-*"]
+ }
+}
+
+# Permissions Boundary policy
+resource "aws_iam_policy" "assumed_role_permissions_boundary" {
+ name = "${local.stack_name}-${upper(var.project_name)}-PermissionsBoundary"
+ description = "Allows access to AWS services in the regions the client uses only"
+ policy = data.aws_iam_policy_document.assumed_role_permissions_boundary.json
+
+ tags = merge(
+ local.tags,
+ {
+ Stack = "api-layer"
+ }
+ )
+}
diff --git a/infrastructure/stacks/api-layer/data.tf b/infrastructure/stacks/api-layer/data.tf
index 8fc4b38c..ae018e6b 100644
--- a/infrastructure/stacks/api-layer/data.tf
+++ b/infrastructure/stacks/api-layer/data.tf
@@ -1 +1,30 @@
data "aws_caller_identity" "current" {}
+
+data "aws_acm_certificate" "imported_cert" {
+ domain = "${var.environment}.${local.api_domain_name}"
+ types = ["IMPORTED"]
+ provider = aws.eu-west-2
+ key_types = ["RSA_4096"]
+}
+
+data "aws_acm_certificate" "validation_cert" {
+ domain = "${var.environment}.${local.api_domain_name}"
+ types = ["AMAZON_ISSUED"]
+ provider = aws.eu-west-2
+ key_types = ["RSA_2048"]
+ most_recent = true
+}
+
+data "aws_kms_alias" "networking_ssm_key" {
+ name = "alias/dev-Networking-ssm-parameters"
+}
+
+data "aws_ssm_parameter" "mtls_api_client_cert" {
+ name = "/${var.environment}/mtls/api_client_cert"
+ with_decryption = true
+}
+
+data "aws_ssm_parameter" "mtls_api_ca_cert" {
+ name = "/${var.environment}/mtls/api_ca_cert"
+ with_decryption = true
+}
diff --git a/infrastructure/stacks/api-layer/healthcheck_status.tf b/infrastructure/stacks/api-layer/healthcheck_status.tf
new file mode 100644
index 00000000..12671fca
--- /dev/null
+++ b/infrastructure/stacks/api-layer/healthcheck_status.tf
@@ -0,0 +1,64 @@
+resource "aws_api_gateway_method" "_status" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ resource_id = aws_api_gateway_resource._status.id
+ http_method = "GET"
+ authorization = "NONE"
+
+ depends_on = [
+ aws_api_gateway_resource._status,
+ ]
+}
+
+resource "aws_api_gateway_integration" "_status" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ resource_id = aws_api_gateway_resource._status.id
+ http_method = aws_api_gateway_method._status.http_method
+ type = "MOCK"
+
+ request_templates = {
+ "application/json" = jsonencode({
+ statusCode = 200
+ })
+ }
+}
+
+resource "aws_api_gateway_method_response" "_status" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ resource_id = aws_api_gateway_resource._status.id
+ http_method = aws_api_gateway_method._status.http_method
+ status_code = "200"
+
+ response_models = {
+ "application/json" = "Empty"
+ }
+}
+
+resource "aws_api_gateway_integration_response" "_status" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ resource_id = aws_api_gateway_resource._status.id
+ http_method = aws_api_gateway_method._status.http_method
+ status_code = aws_api_gateway_method_response._status.status_code
+
+ response_templates = {
+ "application/json" = jsonencode({
+ status = "pass",
+ version = "",
+ revision = "",
+ releaseId = "",
+ commitId = "",
+ checks = {
+ "healthcheckService:status" = [
+ {
+ status = "pass",
+ timeout = false,
+ responseCode = 200,
+ outcome = "
Ok
",
+ links = {
+ self = "http://healthcheckService.example.com/_status"
+ }
+ }
+ ]
+ }
+ })
+ }
+}
diff --git a/infrastructure/stacks/api-layer/iam_roles.tf b/infrastructure/stacks/api-layer/iam_roles.tf
index 94d833b8..e0f618fe 100644
--- a/infrastructure/stacks/api-layer/iam_roles.tf
+++ b/infrastructure/stacks/api-layer/iam_roles.tf
@@ -1,8 +1,4 @@
-data "aws_iam_policy" "permissions_boundary" {
- arn = "arn:aws:iam::${local.current_account_id}:policy/${upper(var.project_name)}-PermissionsBoundary"
-}
-
# Lambda trust policy
data "aws_iam_policy_document" "lambda_assume_role" {
@@ -30,12 +26,12 @@ data "aws_iam_policy_document" "dps_assume_role" {
resource "aws_iam_role" "eligibility_lambda_role" {
name = "eligibility_lambda-role${terraform.workspace == "default" ? "" : "-${terraform.workspace}"}"
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
- permissions_boundary = data.aws_iam_policy.permissions_boundary.arn
+ permissions_boundary = aws_iam_policy.assumed_role_permissions_boundary.arn
}
resource "aws_iam_role" "write_access_role" {
name = "external-write-role-${terraform.workspace == "default" ? "" : "-${terraform.workspace}"}"
assume_role_policy = data.aws_iam_policy_document.dps_assume_role.json
- permissions_boundary = data.aws_iam_policy.permissions_boundary.arn
+ permissions_boundary = aws_iam_policy.assumed_role_permissions_boundary.arn
}
diff --git a/infrastructure/stacks/api-layer/lambda.tf b/infrastructure/stacks/api-layer/lambda.tf
index a6018a6e..d8e09005 100644
--- a/infrastructure/stacks/api-layer/lambda.tf
+++ b/infrastructure/stacks/api-layer/lambda.tf
@@ -22,5 +22,5 @@ module "eligibility_signposting_lambda_function" {
handler = "eligibility_signposting_api.app.lambda_handler"
eligibility_rules_bucket_name = module.s3_rules_bucket.storage_bucket_name
eligibility_status_table_name = module.eligibility_status_table.table_name
+ stack_name = local.stack_name
}
-
diff --git a/infrastructure/stacks/api-layer/locals.tf b/infrastructure/stacks/api-layer/locals.tf
index 7d032685..e3094a3a 100644
--- a/infrastructure/stacks/api-layer/locals.tf
+++ b/infrastructure/stacks/api-layer/locals.tf
@@ -1,3 +1,12 @@
locals {
stack_name = "api-layer"
+
+ api_subdomain = var.environment
+ api_domain_name = "eligibility-signposting-api.nhs.uk"
+
+ # PEM file for certificate
+ pem_file_content = join("\n", [
+ data.aws_ssm_parameter.mtls_api_client_cert.value,
+ data.aws_ssm_parameter.mtls_api_ca_cert.value
+ ])
}
diff --git a/infrastructure/stacks/api-layer/patient_check.tf b/infrastructure/stacks/api-layer/patient_check.tf
new file mode 100644
index 00000000..d0d54d46
--- /dev/null
+++ b/infrastructure/stacks/api-layer/patient_check.tf
@@ -0,0 +1,34 @@
+resource "aws_api_gateway_method" "get_patient_check" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ resource_id = aws_api_gateway_resource.patient.id
+ http_method = "GET"
+ authorization = "NONE"
+ api_key_required = false
+
+ depends_on = [
+ aws_api_gateway_resource.patient,
+ aws_api_gateway_resource.patient_check,
+ ]
+}
+
+resource "aws_api_gateway_integration" "get_patient_check" {
+ rest_api_id = module.eligibility_signposting_api_gateway.rest_api_id
+ resource_id = aws_api_gateway_resource.patient.id
+ http_method = aws_api_gateway_method.get_patient_check.http_method
+ integration_http_method = "POST" # Needed for lambda proxy integration
+ type = "AWS_PROXY"
+ uri = module.eligibility_signposting_lambda_function.aws_lambda_invoke_arn
+
+ depends_on = [
+ aws_api_gateway_method.get_patient_check
+ ]
+}
+
+resource "aws_lambda_permission" "get_patient_check" {
+ statement_id = "AllowExecutionFromAPIGateway"
+ action = "lambda:InvokeFunction"
+ function_name = module.eligibility_signposting_lambda_function.aws_lambda_function_name
+ principal = "apigateway.amazonaws.com"
+
+ source_arn = "${module.eligibility_signposting_api_gateway.execution_arn}/*/*"
+}
diff --git a/infrastructure/stacks/api-layer/provider.tf b/infrastructure/stacks/api-layer/provider.tf
index b64be2af..40495eb6 100644
--- a/infrastructure/stacks/api-layer/provider.tf
+++ b/infrastructure/stacks/api-layer/provider.tf
@@ -1,3 +1,9 @@
provider "aws" {
region = "eu-west-2"
}
+
+# Used by ACM
+provider "aws" {
+ alias = "eu-west-2"
+ region = "eu-west-2"
+}
diff --git a/infrastructure/stacks/api-layer/truststore_s3_bucket.tf b/infrastructure/stacks/api-layer/truststore_s3_bucket.tf
new file mode 100644
index 00000000..c3dc330a
--- /dev/null
+++ b/infrastructure/stacks/api-layer/truststore_s3_bucket.tf
@@ -0,0 +1,37 @@
+module "s3_truststore_bucket" {
+ source = "../../modules/s3"
+ bucket_name = "truststore"
+ environment = var.environment
+ project_name = var.project_name
+}
+
+resource "aws_s3_bucket_policy" "truststore" {
+ bucket = module.s3_truststore_bucket.storage_bucket_id
+ policy = data.aws_iam_policy_document.truststore_api_gateway.json
+}
+
+data "aws_iam_policy_document" "truststore_api_gateway" {
+ statement {
+ sid = "Enable S3 access permissions for API Gateway"
+ effect = "Allow"
+
+ principals {
+ type = "Service"
+ identifiers = ["apigateway.amazonaws.com"]
+ }
+
+ actions = ["s3:GetObject"]
+
+ resources = [
+ "${module.s3_truststore_bucket.storage_bucket_arn}/truststore.pem"
+ ]
+ }
+}
+
+resource "aws_s3_object" "pem_file" {
+ bucket = module.s3_truststore_bucket.storage_bucket_name
+ key = "truststore.pem"
+ content = local.pem_file_content
+
+ acl = "private"
+}
diff --git a/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf b/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf
index a7b4e897..c5e80d4f 100644
--- a/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf
+++ b/infrastructure/stacks/iams-developer-roles/github_actions_policies.tf
@@ -55,13 +55,43 @@ resource "aws_iam_policy" "api_infrastructure" {
# S3 permissions
"s3:*",
+ # KMS permissions
+ "kms:List*",
+ "kms:Describe*",
+ "kms:GetKeyPolicy*",
+ "kms:GetKeyRotationStatus",
+ "kms:Decrypt*",
+
+ # Cloudwatch permissions
+ "logs:Describe*",
+ "logs:ListTagsForResource",
+
+ #EC2 permissions
+ "ec2:Describe*",
+
# IAM permissions (scoped to resources with specific path prefix)
"iam:Get*",
+ "iam:GetPolicy*",
+ "iam:GetRole*",
"iam:List*",
"iam:Create*",
"iam:Update*",
"iam:Delete*",
+
+ # ssm
+ "ssm:GetParameter",
+ "ssm:GetParameters",
+ "ssm:DescribeParameters",
+ "ssm:ListTagsForResource",
+
+ # acm
+ "acm:ListCertificates",
+ "acm:DescribeCertificate",
+ "acm:GetCertificate",
+ "acm:ListTagsForCertificate",
],
+
+
Resource = "*"
}
]
diff --git a/infrastructure/stacks/iams-developer-roles/iams_permissions_boundary.tf b/infrastructure/stacks/iams-developer-roles/iams_permissions_boundary.tf
index f6555f1b..27909c88 100644
--- a/infrastructure/stacks/iams-developer-roles/iams_permissions_boundary.tf
+++ b/infrastructure/stacks/iams-developer-roles/iams_permissions_boundary.tf
@@ -45,10 +45,24 @@ data "aws_iam_policy_document" "permissions_boundary" {
}
}
+ # Allow access to IAM actions for us-east-1 region only
statement {
- sid = "DenyPrivEsculationViaIamRoles"
- effect = "Deny"
- actions = ["iam:*"]
+ sid = "AllowIamActionsInUsEast1"
+ effect = "Allow"
+ actions = ["iam:*"]
+ resources = ["*"]
+
+ condition {
+ test = "StringEquals"
+ variable = "aws:RequestedRegion"
+ values = ["us-east-1"]
+ }
+ }
+
+ statement {
+ sid = "DenyPrivEsculationViaIamRoles"
+ effect = "Deny"
+ actions = ["iam:*"]
resources = ["*"]
condition {
test = "ArnLike"
@@ -58,16 +72,16 @@ data "aws_iam_policy_document" "permissions_boundary" {
}
statement {
- sid = "DenyPrivEsculationViaIamProfiles"
- effect = "Deny"
- actions = ["iam:*"]
+ sid = "DenyPrivEsculationViaIamProfiles"
+ effect = "Deny"
+ actions = ["iam:*"]
resources = ["arn:aws:iam::*:role/${upper(var.project_name)}-*"]
}
}
# Permissions Boundary policy
resource "aws_iam_policy" "permissions_boundary" {
- name = "${upper(var.project_name)}-PermissionsBoundary"
+ name = "${local.stack_name}-${upper(var.project_name)}-PermissionsBoundary"
description = "Allows access to AWS services in the regions the client uses only"
policy = data.aws_iam_policy_document.permissions_boundary.json
diff --git a/infrastructure/stacks/iams-developer-roles/locals.tf b/infrastructure/stacks/iams-developer-roles/locals.tf
new file mode 100644
index 00000000..1696637c
--- /dev/null
+++ b/infrastructure/stacks/iams-developer-roles/locals.tf
@@ -0,0 +1,3 @@
+locals {
+ stack_name = "iams-developer-roles"
+}
diff --git a/infrastructure/stacks/networking/README.md b/infrastructure/stacks/networking/README.md
index 6e1995c3..4bb7c715 100644
--- a/infrastructure/stacks/networking/README.md
+++ b/infrastructure/stacks/networking/README.md
@@ -1,78 +1,283 @@
-# Networking stack
+# Networking Stack
-The networking stack contains the networking resources that are securing the Eligibility Signposting API application resources.
+This stack provisions the networking resources that secure the Eligibility Signposting API application.
-The stack is documented on [this Confluence page](https://nhsd-confluence.digital.nhs.uk/spaces/Vacc/pages/1054575846/VPC+structure)
+For a high-level overview, see the [VPC Structure Confluence Page](https://nhsd-confluence.digital.nhs.uk/spaces/Vacc/pages/1054575846/VPC+structure).
+
+---
+
+## Table of Contents
+
+- [Networking Stack](#networking-stack)
+ - [Table of Contents](#table-of-contents)
+ - [Traffic Flow Explanation](#traffic-flow-explanation)
+ - [Public HTTPS Request Flow](#public-https-request-flow)
+ - [Outbound Internet Access](#outbound-internet-access)
+ - [Internal-Only Traffic](#internal-only-traffic)
+ - [Security Controls](#security-controls)
+ - [Network ACLs](#network-acls)
+ - [Security Groups](#security-groups)
+ - [Route Tables](#route-tables)
+ - [Deployment to AWS Development Environment](#deployment-to-aws-development-environment)
+ - [1. Initialize Terraform and Plan](#1-initialize-terraform-and-plan)
+ - [2. Apply Terraform Changes](#2-apply-terraform-changes)
+ - [Release Deployment to AWS (Int, Ref, and Prod)](#release-deployment-to-aws-int-ref-and-prod)
+ - [ACM Certificates Needed for Each Environment](#acm-certificates-needed-for-each-environment)
+ - [Domain Validation](#domain-validation)
+ - [Steps](#steps)
+ - [mTLS Certificates](#mtls-certificates)
+ - [1. Generate a CSR](#1-generate-a-csr)
+ - [2. Send the CSR for Signing](#2-send-the-csr-for-signing)
+ - [3. Add Root and Intermediate CAs to Truststore](#3-add-root-and-intermediate-cas-to-truststore)
+ - [4. Validate the CSR](#4-validate-the-csr)
+ - [5. Upload the CSR and Keys to SSM](#5-upload-the-csr-and-keys-to-ssm)
+ - [6. Upload the mTLS Secrets to Proxygen](#6-upload-the-mtls-secrets-to-proxygen)
+ - [Additional Resources](#additional-resources)
+
+---
## Traffic Flow Explanation
### Public HTTPS Request Flow
-* External client makes HTTPS request → Internet Gateway
-* Request routes to Load Balancer or API Gateway in public subnet
-* Request forwards to Lambda (or other application) in private subnet
-* Lambda processes the request and returns response
-* Response returns to client through the same path
+1. External client makes HTTPS request → Internet Gateway
+2. Request routes to Load Balancer or API Gateway in public subnet
+3. Request forwards to Lambda (or other application) in private subnet
+4. Lambda processes the request and returns response
+5. Response returns to client through the same path
### Outbound Internet Access
-* Lambda functions in private subnets can make outbound internet calls via NAT Gateways
-* This allows Lambda to call external APIs, download packages, etc.
-* No direct inbound access to Lambda from the internet
+- Lambda functions in private subnets can make outbound internet calls via NAT Gateways
+- No direct inbound access to Lambda from the internet
### Internal-Only Traffic
-* Lambda functions access AWS services via VPC Endpoints:
- * Gateway Endpoints: S3, DynamoDB
- * Interface Endpoints: KMS, CloudWatch, SSM, Secrets Manager, Lambda, STS, SQS
-* All traffic between Lambda and AWS services stays within the AWS network
-* No internet transit required for AWS service access
+- Lambda functions access AWS services via VPC Endpoints:
+ - **Gateway Endpoints:** S3, DynamoDB
+ - **Interface Endpoints:** KMS, CloudWatch, SSM, Secrets Manager, Lambda, STS, SQS
+- All traffic between Lambda and AWS services stays within the AWS network
-### Security Controls
+---
-#### Network ACLs
+## Security Controls
-Public subnets: Allow HTTP(80), HTTPS(443), ephemeral ports
-Private subnets: Allow VPC traffic and responses to outbound requests
+### Network ACLs
-#### Security Groups
+- **Public subnets:** Allow HTTP (80), HTTPS (443), ephemeral ports
+- **Private subnets:** Allow VPC traffic and responses to outbound requests
-Default security group: Deny all
-VPC Endpoint security group: Allow HTTPS(443) from within VPC
+### Security Groups
-#### Route Tables
+- **Default security group:** Deny all
+- **VPC Endpoint security group:** Allow HTTPS (443) from within VPC
-Public subnets: Route to Internet Gateway for external access
-Private subnets: Route to NAT Gateways for outbound-only access
+### Route Tables
-## Deployment to AWS Development Environment
+- **Public subnets:** Route to Internet Gateway for external access
+- **Private subnets:** Route to NAT Gateways for outbound-only access
-This stack should only ever be deployed once per account (e.g. the use of Terraform workspaces is explicitly not recommended beyond specifying `dev` as the environment).
+---
-Deployment to the Development environment is done through use of `make` commands
+## Deployment to AWS Development Environment
+
+> **Note:** This stack should only be deployed once per account. Use of Terraform workspaces is not recommended beyond specifying `dev` as the environment.
-### Initialize Terraform and Plan
+### 1. Initialize Terraform and Plan
-Run the following command to initialize Terraform and generate a plan. Replace `` with the target environment:
+Run the following command to initialize Terraform and generate a plan. Replace `` with your target environment (e.g., `dev`):
```bash
make terraform env=dev stack=networking tf-command=init workspace=
+make terraform env=dev stack=networking tf-command=plan workspace=
```
-then
+### 2. Apply Terraform Changes
+
+Deploy the Terraform configuration:
```bash
-make terraform env=dev stack=networking tf-command=plan workspace=
+make terraform env=dev stack=networking tf-command=apply workspace=
```
-### 1.4 Apply Terraform Changes
+For more on Terraform, see the [Terraform Documentation](https://developer.hashicorp.com/terraform/docs).
+
+---
+
+## Release Deployment to AWS (Int, Ref, and Prod)
+
+Deployment to Int, Ref, and Prod, as well as running automated tests, can be done via GitHub Actions (when implemented).
+
+---
+
+## ACM Certificates Needed for Each Environment
+
+To verify domain ownership and enable secure communication with the APIM API Proxy, you need:
+
+1. **A domain validation certificate**
+2. **mTLS certificates and secrets**
+
+> **Manual intervention is required for creation of these assets.**
+
+---
+
+### Domain Validation
+
+The ACM Certificate Manager is used to request a domain validation certificate (see `infrastructure/stacks/networking/acm_certificates.tf`).
+Running Terraform will create this certificate, but DNS validation requires action from the DNS Team.
+
+#### Steps
+
+1. In the AWS Console, navigate to **Certificate Manager** for the environment account.
+2. In **Domains**, download the CSV of the CNAME details.
+3. Email `england.dnsteam@nhs.net` with the subject:
+ `eligibility-signposting-api.nhs.uk - Requesting a sub domain - `
+ Attach the exported CSV and use the following template:
+
+ ```text
+ Hi DNS Team,
+
+ Please could we add the subdomain '' for our top level domain 'eligibility-signposting-api.nhs.uk'?
+
+ e.g.
+
+ .eligibility-signposting-api.nhs.uk
+
+ We've generated an ACM certificate for DNS validation, which has generated a CNAME name and value which I think would need adding to your records to allow us to validate. I've attached this to the email in a CSV, please let us know if this isn't the approach you expect.
+
+ Thanks,
+
+ Edd
+ ```
+
+4. Check the certificate for DNS validation once you receive confirmation that the subdomain was created.
+
+- [AWS ACM DNS Validation Guide](https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html)
+
+---
+
+### mTLS Certificates
+
+See the [APIM documentation](https://nhsd-confluence.digital.nhs.uk/spaces/APM/pages/734397569/How+to+implement+mutual+TLS+mTLS+security+for+your+API+backend) for more details.
+
+#### 1. Generate a CSR
-Deploy the Terraform configuration using the following command:
+1. Create a configuration file for the environment, e.g., `dev.conf`:
+
+ ```ini
+ [req]
+ default_bits = 4096
+ distinguished_name = req_distinguished_name
+ req_extensions = v3_req
+ prompt = no
+
+ [req_distinguished_name]
+ C = GB
+ ST = West Yorkshire
+ L = Leeds
+ O = NHS England
+ OU = API Management
+ CN = dev.eligibility-signposting-api.nhs.uk
+
+ [v3_req]
+ keyUsage = keyEncipherment, dataEncipherment
+ extendedKeyUsage = serverAuth
+ ```
+
+2. Generate the CSR and private key:
+
+ ```bash
+ mkdir -p csrs keys
+ openssl req -new -newkey rsa:4096 -nodes -sha256 \
+ -keyout keys/.eligibility-signposting-api.nhs.uk.key \
+ -out csrs/.eligibility-signposting-api.nhs.uk.csr \
+ -config .conf -extensions v3_req
+ ```
+
+ - [OpenSSL CSR Generation Guide](https://www.openssl.org/docs/manmaster/man1/openssl-req.html)
+
+#### 2. Send the CSR for Signing
+
+- **Non-production environments:**
+ Email `itoc.supportdesk@nhs.net` and reference the INT / PTL environment.
+- **Production environments:**
+ Email `dir@nhs.net`.
+
+Template:
+
+```text
+Hi,
+
+Please could I get the attached CSR signed. Details as follows:
+
+Name:
+Company/Organisation: Eligibility Data Team, NHS England
+Contact Number:
+Common Name (CN): dev.eligibility-signposting-api.nhs.uk
+Reason for the Certificate: New APIM client
+Environment: INT / PTL
+
+Thanks,
+
+Edd
+```
+
+#### 3. Add Root and Intermediate CAs to Truststore
+
+1. Download the INT CA certificates from [NHS Digital G2 Certificate Technical Implementation](https://digital.nhs.uk/services/spine/updating-nhs-public-key-infrastructure-certificates/g2-certificate-technical-implementation#certificate-downloads).
+2. Concatenate the certificates (order matters):
+
+ ```bash
+ cat intermediate-cert.pem root-cert.pem > truststore.pem
+ ```
+
+ This file is used in `infrastructure/stacks/api-layer/truststore_s3_bucket.tf`.
+
+3. Upload `truststore.pem` to the specified S3 bucket.
+
+#### 4. Validate the CSR
```bash
-make terraform env=dev stack=networking tf-command=apply workspace=
+openssl verify -CAfile truststore.pem new-cert.crt
+```
+
+You should see an `OK` response.
+
+#### 5. Upload the CSR and Keys to SSM
+
+Store the following in AWS SSM Parameter Store (see `infrastructure/stacks/networking/ssm.tf`):
+
+- `/${var.environment}/mtls/api_ca_cert` — the combined CA cert (`truststore.pem`)
+- `/${var.environment}/mtls/api_client_cert` — the signed client certificate
+- `/${var.environment}/mtls/api_private_key_cert` — the private key
+
+- [AWS SSM Parameter Store Documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html)
+
+#### 6. Upload the mTLS Secrets to Proxygen
+
+Requires the [Proxygen CLI](https://github.com/NHSDigital/proxygen-cli). The [specification repository](https://github.com/NHSDigital/eligibility-signposting-api-specification)
+includes the CLI, and includes details of how to set up authentication with Proxygen.
+
+After logging into AWS for the appropriate environment, run:
+
+```bash
+mkdir -p ~/.proxygen
+aws ssm get-parameter --name /$AWS_ENV/mtls/api_client_cert --with-decryption | jq ".Parameter.Value" --raw-output > ~/.proxygen/client_cert.pem
+aws ssm get-parameter --name /$AWS_ENV/mtls/api_private_key_cert --with-decryption | jq ".Parameter.Value" --raw-output > ~/.proxygen/private_key.pem
+proxygen secret put --mtls-cert ~/.proxygen/client_cert.pem --mtls-key ~/.proxygen/private_key.pem $APIM_ENV $secret_name
+rm ~/.proxygen/client_cert.pem ~/.proxygen/private_key.pem
```
-## Release Deployment to AWS (Int, Ref and Prod)
+---
+
+## Additional Resources
+
+- [Terraform Documentation](https://developer.hashicorp.com/terraform/docs)
+- [AWS ACM Documentation](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html)
+- [AWS SSM Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html)
+- [OpenSSL Documentation](https://www.openssl.org/docs/)
+- [Proxygen CLI](https://github.com/NHSDigital/proxygen-cli)
+
+---
-Deployment to Int, Ref and Prod, as well as running the automated tests can be done via GitHub actions, when they are developed.
+*For any issues or questions, please contact the Eligibility Data Team.*
diff --git a/infrastructure/stacks/networking/acm_certificates.tf b/infrastructure/stacks/networking/acm_certificates.tf
new file mode 100644
index 00000000..ba5f9591
--- /dev/null
+++ b/infrastructure/stacks/networking/acm_certificates.tf
@@ -0,0 +1,35 @@
+resource "aws_acm_certificate" "imported_cert" {
+ private_key = aws_ssm_parameter.mtls_api_private_key_cert.value
+ certificate_body = aws_ssm_parameter.mtls_api_client_cert.value
+ certificate_chain = aws_ssm_parameter.mtls_api_ca_cert.value
+
+ lifecycle {
+ create_before_destroy = true
+ }
+
+ tags = {
+ Region = local.region
+ Stack = local.stack_name
+ CertificateType = "Imported"
+ }
+}
+
+resource "aws_acm_certificate" "domain_validation" {
+ domain_name = "${var.environment}.eligibility-signposting-api.nhs.uk"
+ validation_method = "DNS"
+
+ subject_alternative_names = var.environment != "prod" ? [
+ "${var.environment}.eligibility-signposting-api.nhs.uk",
+ "*.${var.environment}.eligibility-signposting-api.nhs.uk"
+ ] : null
+
+ lifecycle {
+ create_before_destroy = true
+ }
+
+ tags = {
+ Region = local.region
+ Stack = local.stack_name
+ CerticateType = "DomainValidation"
+ }
+}
diff --git a/infrastructure/stacks/networking/ssm.tf b/infrastructure/stacks/networking/ssm.tf
index f5fa3015..666396b9 100644
--- a/infrastructure/stacks/networking/ssm.tf
+++ b/infrastructure/stacks/networking/ssm.tf
@@ -10,35 +10,47 @@
# }
# }
#
-# resource "aws_ssm_parameter" "mtls_api_ca_cert" {
-# name = "/${var.environment}/mtls/api_ca_cert"
-# type = "SecureString"
-# key_id = aws_kms_key.networking_ssm_key.id
-# value = var.API_CA_CERT
-# tier = "Advanced"
-# tags = {
-# Stack = local.stack_name
-# }
-# }
-#
-# resource "aws_ssm_parameter" "mtls_api_client_cert" {
-# name = "/${var.environment}/mtls/api_client_cert"
-# type = "SecureString"
-# key_id = aws_kms_key.networking_ssm_key.id
-# value = var.API_CLIENT_CERT
-# tier = "Advanced"
-# tags = {
-# Stack = local.stack_name
-# }
-# }
-#
-# resource "aws_ssm_parameter" "mtls_api_private_key_cert" {
-# name = "/${var.environment}/mtls/api_private_key_cert"
-# type = "SecureString"
-# key_id = aws_kms_key.networking_ssm_key.id
-# value = var.API_PRIVATE_KEY_CERT
-# tier = "Advanced"
-# tags = {
-# Stack = local.stack_name
-# }
-# }
+resource "aws_ssm_parameter" "mtls_api_ca_cert" {
+ name = "/${var.environment}/mtls/api_ca_cert"
+ type = "SecureString"
+ key_id = aws_kms_key.networking_ssm_key.id
+ value = var.API_CA_CERT
+ tier = "Advanced"
+ tags = {
+ Stack = local.stack_name
+ }
+
+ lifecycle {
+ ignore_changes = [value]
+ }
+}
+
+resource "aws_ssm_parameter" "mtls_api_client_cert" {
+ name = "/${var.environment}/mtls/api_client_cert"
+ type = "SecureString"
+ key_id = aws_kms_key.networking_ssm_key.id
+ value = var.API_CLIENT_CERT
+ tier = "Advanced"
+ tags = {
+ Stack = local.stack_name
+ }
+
+ lifecycle {
+ ignore_changes = [value]
+ }
+}
+
+resource "aws_ssm_parameter" "mtls_api_private_key_cert" {
+ name = "/${var.environment}/mtls/api_private_key_cert"
+ type = "SecureString"
+ key_id = aws_kms_key.networking_ssm_key.id
+ value = var.API_PRIVATE_KEY_CERT
+ tier = "Advanced"
+ tags = {
+ Stack = local.stack_name
+ }
+
+ lifecycle {
+ ignore_changes = [value]
+ }
+}
diff --git a/infrastructure/stacks/networking/variables.tf b/infrastructure/stacks/networking/variables.tf
index 66a5ed0a..54b28dce 100644
--- a/infrastructure/stacks/networking/variables.tf
+++ b/infrastructure/stacks/networking/variables.tf
@@ -1,22 +1,15 @@
-# variable "API_CA_CERT" {
-# type = string
-# description = "The Certificate Authority (CA) Root for the Client Certificate with Intermediate Certificate"
-# sensitive = true
-# default = "Test Value"
-#
-# }
-#
-# variable "API_CLIENT_CERT" {
-# type = string
-# description = "The signed Client Certificate"
-# sensitive = true
-# default = "Test Value"
-# }
-#
-# variable "API_PRIVATE_KEY_CERT" {
-# type = string
-# description = "The private key for the signed Client Certificate"
-# sensitive = true
-# default = "Test Value"
-# }
-#
+variable "API_CA_CERT" {
+ type = string
+ description = "The Certificate Authority (CA) Root for the Client Certificate with Intermediate Certificate"
+ sensitive = true
+}
+variable "API_CLIENT_CERT" {
+ type = string
+ description = "The signed Client Certificate"
+ sensitive = true
+}
+variable "API_PRIVATE_KEY_CERT" {
+ type = string
+ description = "The private key for the signed Client Certificate"
+ sensitive = true
+}