diff --git a/.github/workflows/tf-azure-deploy.yml b/.github/workflows/tf-azure-deploy.yml index 295537f58..1c0a671a8 100644 --- a/.github/workflows/tf-azure-deploy.yml +++ b/.github/workflows/tf-azure-deploy.yml @@ -34,6 +34,14 @@ env: ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} TF_VAR_environment: ${{ vars.ENVIRONMENT }} TF_VAR_resource_name_prefix: ${{ vars.RESOURCE_PREFIX }} + TF_VAR_kv_certificate_authority_username: ${{ secrets.CERTIFICATE_AUTHORITY_USERNAME }} + TF_VAR_kv_certificate_authority_password: ${{ secrets.CERTIFICATE_AUTHORITY_PASSWORD }} + TF_VAR_kv_certificate_authority_admin_email: ${{ vars.CERTIFICATE_AUTHORITY_ADMIN_EMAIL }} + TF_VAR_kv_certificate_authority_admin_first_name: ${{ secrets.CERTIFICATE_AUTHORITY_ADMIN_FIRST_NAME }} + TF_VAR_kv_certificate_authority_admin_last_name: ${{ secrets.CERTIFICATE_AUTHORITY_ADMIN_LAST_NAME }} + TF_VAR_kv_certificate_authority_admin_phone_no: ${{ secrets.CERTIFICATE_AUTHORITY_ADMIN_PHONE_NO }} + TF_VAR_kv_certificate_label: ${{ vars.CERTIFICATE_LABEL }} + TF_VAR_kv_certificate_subject: ${{ vars.CERTIFICATE_SUBJECT }} TF_VAR_psqlfs_sku: ${{ vars.PSQLFS_SKU }} TF_VAR_psqlfs_storage: ${{ vars.PSQLFS_STORAGE }} TF_VAR_psqlfs_username: ${{ secrets.PSQLFS_USERNAME }} @@ -49,6 +57,7 @@ env: TF_VAR_webapp_docker_registry_url: https://ghcr.io TF_VAR_webapp_docker_image: dfe-digital/early-years-foundation-recovery TF_VAR_webapp_docker_image_tag: latest + TF_VAR_custom_domain_name: ${{ vars.CUSTOM_DOMAIN }} TF_VAR_webapp_config_bot_token: ${{ secrets.WEBAPP_CONFIG_BOT_TOKEN }} TF_VAR_webapp_config_contentful_environment: ${{ vars.WEBAPP_CONFIG_CONTENTFUL_ENVIRONMENT }} TF_VAR_webapp_config_contentful_preview: ${{ vars.WEBAPP_CONFIG_CONTENTFUL_PREVIEW }} @@ -180,4 +189,4 @@ jobs: # Terraform Apply - name: Terraform Apply - run: terraform apply -auto-approve tfplan/tfplan + run: terraform apply -auto-approve tfplan/tfplan \ No newline at end of file diff --git a/terraform-azure/main.tf b/terraform-azure/main.tf index 658a79949..aadbe1fd7 100644 --- a/terraform-azure/main.tf +++ b/terraform-azure/main.tf @@ -5,6 +5,11 @@ provider "azurerm" { resource_group { prevent_deletion_if_contains_resources = false } + + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = true + } } } @@ -24,10 +29,21 @@ resource "azurerm_resource_group" "rg" { module "network" { source = "./terraform-azure-network" - environment = var.environment - location = var.azure_region - resource_group = azurerm_resource_group.rg.name - resource_name_prefix = var.resource_name_prefix + environment = var.environment + location = var.azure_region + resource_group = azurerm_resource_group.rg.name + resource_name_prefix = var.resource_name_prefix + domain_name_label = var.webapp_name + kv_certificate_authority_label = "GlobalSignCA" + kv_certificate_authority_name = "GlobalSign" + kv_certificate_authority_username = var.kv_certificate_authority_username + kv_certificate_authority_password = var.kv_certificate_authority_password + kv_certificate_authority_admin_email = var.kv_certificate_authority_admin_email + kv_certificate_authority_admin_first_name = var.kv_certificate_authority_admin_first_name + kv_certificate_authority_admin_last_name = var.kv_certificate_authority_admin_last_name + kv_certificate_authority_admin_phone_no = var.kv_certificate_authority_admin_phone_no + kv_certificate_label = var.kv_certificate_label + kv_certificate_subject = var.kv_certificate_subject } # Create Database resources @@ -52,6 +68,7 @@ module "database" { module "webapp" { source = "./terraform-azure-web" + environment = var.environment asp_sku = var.asp_sku webapp_worker_count = var.webapp_worker_count location = var.azure_region @@ -63,8 +80,16 @@ module "webapp" { webapp_docker_image = var.webapp_docker_image webapp_docker_image_tag = var.webapp_docker_image_tag webapp_docker_registry_url = var.webapp_docker_registry_url + webapp_session_cookie_name = "_early_years_foundation_recovery_session" + webapp_custom_domain_name = var.webapp_custom_domain_name + webapp_custom_domain_cert_secret_label = var.kv_certificate_label webapp_health_check_path = "/health" webapp_health_check_eviction_time_in_min = 10 + agw_subnet_id = module.network.agw_subnet_id + agw_pip_id = module.network.agw_pip_id + kv_id = module.network.kv_id + kv_cert_secret_id = module.network.kv_cert_secret_id + kv_mi_id = module.network.kv_mi_id depends_on = [module.network, module.database] } diff --git a/terraform-azure/terraform-azure-app/main.tf b/terraform-azure/terraform-azure-app/main.tf index d27c6b59b..5386feedd 100644 --- a/terraform-azure/terraform-azure-app/main.tf +++ b/terraform-azure/terraform-azure-app/main.tf @@ -44,6 +44,7 @@ resource "azurerm_log_analytics_workspace" "app_worker_logs" { resource_group_name = var.resource_group sku = "PerGB2018" retention_in_days = 30 + daily_quota_gb = 1 lifecycle { ignore_changes = [tags] diff --git a/terraform-azure/terraform-azure-network/cert.tf b/terraform-azure/terraform-azure-network/cert.tf new file mode 100644 index 000000000..1feaa2450 --- /dev/null +++ b/terraform-azure/terraform-azure-network/cert.tf @@ -0,0 +1,136 @@ +# Create Key Vault +data "azurerm_client_config" "az_config" {} + +resource "azurerm_key_vault" "kv" { + # Key Vault only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-kv" + resource_group_name = var.resource_group + location = var.location + tenant_id = data.azurerm_client_config.az_config.tenant_id + enabled_for_disk_encryption = true + soft_delete_retention_days = 7 + purge_protection_enabled = true + sku_name = "standard" + + lifecycle { + ignore_changes = [tags] + } + + #checkov:skip=CKV_AZURE_109:Access Policies configured + #checkov:skip=CKV_AZURE_189:Access Policies configured + #checkov:skip=CKV2_AZURE_32:VNET configuration adequate +} + +resource "azurerm_user_assigned_identity" "kv_mi" { + # Key Vault only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-agw-mi" + location = var.location + resource_group_name = var.resource_group +} + +resource "azurerm_key_vault_access_policy" "kv_ap" { + # Key Vault only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + key_vault_id = azurerm_key_vault.kv[0].id + tenant_id = data.azurerm_client_config.az_config.tenant_id + object_id = data.azurerm_client_config.az_config.object_id + + secret_permissions = [ + "Get" + ] + + certificate_permissions = [ + "Create", + "Get", + "GetIssuers", + "Import", + "List", + "ListIssuers", + "ManageContacts", + "ManageIssuers", + "SetIssuers", + "Update" + ] +} + +resource "azurerm_key_vault_access_policy" "kv_mi_ap" { + # Key Vault only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + key_vault_id = azurerm_key_vault.kv[0].id + tenant_id = data.azurerm_client_config.az_config.tenant_id + object_id = azurerm_user_assigned_identity.kv_mi[0].principal_id + + secret_permissions = [ + "Get" + ] + + certificate_permissions = [ + "Get" + ] +} + +resource "azurerm_key_vault_certificate_issuer" "kv_ca" { + # Key Vault only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = var.kv_certificate_authority_label + key_vault_id = azurerm_key_vault.kv[0].id + provider_name = var.kv_certificate_authority_name + account_id = var.kv_certificate_authority_username + password = var.kv_certificate_authority_password + + admin { + email_address = var.kv_certificate_authority_admin_email + first_name = var.kv_certificate_authority_admin_first_name + last_name = var.kv_certificate_authority_admin_last_name + phone = var.kv_certificate_authority_admin_phone_no + } +} + +resource "azurerm_key_vault_certificate" "kv_cert" { + # Key Vault only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = var.kv_certificate_label + key_vault_id = azurerm_key_vault.kv[0].id + + certificate_policy { + issuer_parameters { + name = var.kv_certificate_authority_label + } + + key_properties { + exportable = true + key_size = 2048 + key_type = "RSA" + reuse_key = true + } + + lifetime_action { + action { + action_type = "AutoRenew" + } + + trigger { + days_before_expiry = 30 + } + } + + secret_properties { + content_type = "application/x-pkcs12" + } + + x509_certificate_properties { + extended_key_usage = ["1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2"] + key_usage = ["digitalSignature", "keyEncipherment"] + subject = var.kv_certificate_subject + validity_in_months = 12 + } + } +} \ No newline at end of file diff --git a/terraform-azure/terraform-azure-network/outputs.tf b/terraform-azure/terraform-azure-network/outputs.tf index 6ace7db08..da246d833 100644 --- a/terraform-azure/terraform-azure-network/outputs.tf +++ b/terraform-azure/terraform-azure-network/outputs.tf @@ -26,4 +26,29 @@ output "webapp_subnet_id" { output "app_worker_subnet_id" { description = "ID of the delegated Subnet for the Background Worker" value = azurerm_subnet.app_worker_snet.id +} + +output "agw_subnet_id" { + description = "ID of the Subnet for the App Gateway" + value = var.environment != "development" ? azurerm_subnet.agw_snet[0].id : null +} + +output "agw_pip_id" { + description = "ID of the Public IP address for the App Gateway" + value = var.environment != "development" ? azurerm_public_ip.agw_pip[0].id : null +} + +output "kv_id" { + description = "ID of the Key Vault" + value = var.environment != "development" ? azurerm_key_vault.kv[0].id : null +} + +output "kv_cert_secret_id" { + description = "SSL certificate Secret ID" + value = var.environment != "development" ? azurerm_key_vault_certificate.kv_cert[0].secret_id : null +} + +output "kv_mi_id" { + description = "ID of the Managed Identity for the Key Vault" + value = var.environment != "development" ? azurerm_user_assigned_identity.kv_mi[0].id : null } \ No newline at end of file diff --git a/terraform-azure/terraform-azure-network/pip.tf b/terraform-azure/terraform-azure-network/pip.tf new file mode 100644 index 000000000..186a6d948 --- /dev/null +++ b/terraform-azure/terraform-azure-network/pip.tf @@ -0,0 +1,19 @@ +# Create PIP for App Gateway +resource "azurerm_public_ip" "agw_pip" { + # Application Gateway is not deployed to the Development subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-agw-pip" + resource_group_name = var.resource_group + location = var.location + allocation_method = "Static" + ip_version = "IPv4" + sku = "Standard" + sku_tier = "Regional" + zones = [] + idle_timeout_in_minutes = 4 + + lifecycle { + ignore_changes = [tags] + } +} \ No newline at end of file diff --git a/terraform-azure/terraform-azure-network/variables.tf b/terraform-azure/terraform-azure-network/variables.tf index aff12b40e..11c4403c8 100644 --- a/terraform-azure/terraform-azure-network/variables.tf +++ b/terraform-azure/terraform-azure-network/variables.tf @@ -16,4 +16,59 @@ variable "resource_group" { variable "resource_name_prefix" { description = "Prefix for resource names" type = string +} + +variable "domain_name_label" { + description = "DNS name label for assignment to Application Gateway" + type = string +} + +variable "kv_certificate_authority_label" { + description = "Label for the Certificate Authority" + type = string +} + +variable "kv_certificate_authority_name" { + description = "Name of the Certificate Authority" + type = string +} + +variable "kv_certificate_authority_username" { + description = "Username for the Certificate provider" + type = string +} + +variable "kv_certificate_authority_password" { + description = "Password the Certificate provider" + type = string +} + +variable "kv_certificate_authority_admin_email" { + description = "Email Address of the Certificate Authority Admin" + type = string +} + +variable "kv_certificate_authority_admin_first_name" { + description = "First Name of the Certificate Authority Admin" + type = string +} + +variable "kv_certificate_authority_admin_last_name" { + description = "Last Name of the Certificate Authority Admin" + type = string +} + +variable "kv_certificate_authority_admin_phone_no" { + description = "Phone No. of the Certificate Authority Admin" + type = string +} + +variable "kv_certificate_label" { + description = "Label for the Certificate" + type = string +} + +variable "kv_certificate_subject" { + description = "Subject of the Certificate" + type = string } \ No newline at end of file diff --git a/terraform-azure/terraform-azure-network/main.tf b/terraform-azure/terraform-azure-network/vnet.tf similarity index 84% rename from terraform-azure/terraform-azure-network/main.tf rename to terraform-azure/terraform-azure-network/vnet.tf index 184a094a7..d74af7669 100644 --- a/terraform-azure/terraform-azure-network/main.tf +++ b/terraform-azure/terraform-azure-network/vnet.tf @@ -89,5 +89,19 @@ resource "azurerm_subnet" "app_worker_snet" { } } + #checkov:skip=CKV2_AZURE_31:NSG not required +} + +# Create Subnet for App Gateway +resource "azurerm_subnet" "agw_snet" { + # Subnet only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-agw-snet" + virtual_network_name = azurerm_virtual_network.vnet.name + resource_group_name = var.resource_group + address_prefixes = ["172.1.3.0/24"] + service_endpoints = ["Microsoft.Storage", "Microsoft.Web"] + #checkov:skip=CKV2_AZURE_31:NSG not required } \ No newline at end of file diff --git a/terraform-azure/terraform-azure-web/appgateway.tf b/terraform-azure/terraform-azure-web/appgateway.tf new file mode 100644 index 000000000..aa3a56258 --- /dev/null +++ b/terraform-azure/terraform-azure-web/appgateway.tf @@ -0,0 +1,191 @@ +# Create App Gateway +resource "azurerm_web_application_firewall_policy" "agw_wafp" { + # App Gateway only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-agw-wafp" + location = var.location + resource_group_name = var.resource_group + + managed_rules { + managed_rule_set { + type = "OWASP" + version = "3.2" + } + + managed_rule_set { + type = "Microsoft_BotManagerRuleSet" + version = "0.1" + } + + exclusion { + match_variable = "RequestCookieNames" + selector = var.webapp_session_cookie_name + selector_match_operator = "Equals" + + excluded_rule_set { + type = "OWASP" + version = "3.2" + + rule_group { + rule_group_name = "REQUEST-942-APPLICATION-ATTACK-SQLI" + excluded_rules = [942440] + } + } + } + } + + policy_settings { + enabled = true + file_upload_limit_in_mb = 100 + max_request_body_size_in_kb = 128 + mode = "Prevention" + request_body_check = true + } + + lifecycle { + ignore_changes = [tags] + } +} + +locals { + backend_address_pool_name = "${var.resource_name_prefix}-agw-beap" + frontend_port_name = "${var.resource_name_prefix}-agw-feport" + frontend_ip_configuration_name = "${var.resource_name_prefix}-agw-feip" + http_setting_name = "${var.resource_name_prefix}-agw-best" + health_probe_name = "${var.resource_name_prefix}-agw-hp" + listener_name = "${var.resource_name_prefix}-agw-lstn" + ssl_certificate_name = "${var.resource_name_prefix}-agw-cert" +} + +resource "azurerm_application_gateway" "agw" { + # App Gateway only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-agw" + location = var.location + resource_group_name = var.resource_group + enable_http2 = true + firewall_policy_id = azurerm_web_application_firewall_policy.agw_wafp[0].id + force_firewall_policy_association = true + + sku { + name = "WAF_v2" + tier = "WAF_v2" + } + + autoscale_configuration { + min_capacity = 2 + max_capacity = 10 + } + + gateway_ip_configuration { + name = "${var.resource_name_prefix}-agw-ipc" + subnet_id = var.agw_subnet_id + } + + identity { + type = "UserAssigned" + identity_ids = [var.kv_mi_id] + } + + ssl_certificate { + name = local.ssl_certificate_name + key_vault_secret_id = var.kv_cert_secret_id + } + + ssl_policy { + policy_type = "Predefined" + policy_name = "AppGwSslPolicy20220101" + } + + probe { + host = var.webapp_custom_domain_name + name = local.health_probe_name + interval = 30 + path = "/health" + protocol = "Https" + timeout = 30 + unhealthy_threshold = 3 + + match { + status_code = [200] + body = null + } + } + + frontend_port { + name = local.frontend_port_name + port = 443 + } + + frontend_ip_configuration { + name = local.frontend_ip_configuration_name + public_ip_address_id = var.agw_pip_id + } + + backend_address_pool { + name = local.backend_address_pool_name + fqdns = [azurerm_linux_web_app.webapp.default_hostname] + } + + backend_http_settings { + name = local.http_setting_name + cookie_based_affinity = "Disabled" + port = 443 + protocol = "Https" + probe_name = local.health_probe_name + request_timeout = 300 + + connection_draining { + enabled = true + drain_timeout_sec = 60 + } + } + + http_listener { + name = local.listener_name + frontend_ip_configuration_name = local.frontend_ip_configuration_name + frontend_port_name = local.frontend_port_name + firewall_policy_id = azurerm_web_application_firewall_policy.agw_wafp[0].id + protocol = "Https" + ssl_certificate_name = local.ssl_certificate_name + } + + request_routing_rule { + name = "${var.resource_name_prefix}-agw-rr" + rule_type = "Basic" + priority = 100 + http_listener_name = local.listener_name + backend_address_pool_name = local.backend_address_pool_name + backend_http_settings_name = local.http_setting_name + } + + lifecycle { + ignore_changes = [tags] + } + + #checkov:skip=CKV_AZURE_218:Secure transit protocols used + #checkov:skip=CKV_AZURE_120:WAF is enabled +} + +resource "azurerm_monitor_diagnostic_setting" "agw_logs_monitor" { + # App Gateway only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-agw-mon" + target_resource_id = azurerm_application_gateway.agw[0].id + log_analytics_workspace_id = azurerm_log_analytics_workspace.webapp_logs.id + + enabled_log { + category = "ApplicationGatewayFirewallLog" + } + + timeouts { + read = "30m" + } + + lifecycle { + ignore_changes = [metric] + } +} \ No newline at end of file diff --git a/terraform-azure/terraform-azure-web/variables.tf b/terraform-azure/terraform-azure-web/variables.tf index df1a9c741..3a30c73e8 100644 --- a/terraform-azure/terraform-azure-web/variables.tf +++ b/terraform-azure/terraform-azure-web/variables.tf @@ -1,3 +1,8 @@ +variable "environment" { + description = "Environment to deploy resources" + type = string +} + variable "location" { description = "Name of the Azure region to deploy resources" type = string @@ -53,6 +58,11 @@ variable "webapp_docker_image_tag" { type = string } +variable "webapp_session_cookie_name" { + description = "Name of the user session Cookie" + type = string +} + variable "webapp_health_check_path" { default = null description = "Path to health check endpoint" @@ -65,8 +75,43 @@ variable "webapp_health_check_eviction_time_in_min" { type = number } +variable "webapp_custom_domain_name" { + description = "Custom domain hostname" + type = string +} + +variable "webapp_custom_domain_cert_secret_label" { + description = "Label for the Certificate" + type = string +} + variable "webapp_startup_command" { default = null description = "Startup command to pass into the Web Application" type = string +} + +variable "agw_subnet_id" { + description = "ID of the Subnet for the App Gateway" + type = string +} + +variable "agw_pip_id" { + description = "ID of the Public IP address for the App Gateway" + type = string +} + +variable "kv_id" { + description = "ID of the Key Vault" + type = string +} + +variable "kv_cert_secret_id" { + description = "SSL certificate Secret ID" + type = string +} + +variable "kv_mi_id" { + description = "ID of the Managed Identity for the Key Vault" + type = string } \ No newline at end of file diff --git a/terraform-azure/terraform-azure-web/main.tf b/terraform-azure/terraform-azure-web/webapp.tf similarity index 57% rename from terraform-azure/terraform-azure-web/main.tf rename to terraform-azure/terraform-azure-web/webapp.tf index 0d19162c1..0a15f6592 100644 --- a/terraform-azure/terraform-azure-web/main.tf +++ b/terraform-azure/terraform-azure-web/webapp.tf @@ -35,6 +35,39 @@ resource "azurerm_linux_web_app" "webapp" { docker_image_name = "${var.webapp_docker_image}:${var.webapp_docker_image_tag}" docker_registry_url = var.webapp_docker_registry_url } + + dynamic "ip_restriction" { + # Deploy App Gateway rules only to the Test and Production subscription + for_each = var.environment != "development" ? [1] : [] + content { + name = "Allow app gateway" + action = "Allow" + priority = 300 + virtual_network_subnet_id = var.agw_subnet_id + } + } + + dynamic "ip_restriction" { + # Deploy App Gateway rules only to the Test and Production subscription + for_each = var.environment != "development" ? [1] : [] + content { + name = "Allow health check" + action = "Allow" + priority = 400 + ip_address = "127.0.0.1/0" + } + } + + dynamic "ip_restriction" { + # Deploy App Gateway rules only to the Test and Production subscription + for_each = var.environment != "development" ? [1] : [] + content { + name = "Deny public" + action = "Deny" + priority = 500 + ip_address = "0.0.0.0/0" + } + } } sticky_settings { @@ -118,6 +151,7 @@ resource "azurerm_log_analytics_workspace" "webapp_logs" { resource_group_name = var.resource_group sku = "PerGB2018" retention_in_days = 30 + daily_quota_gb = 1 lifecycle { ignore_changes = [tags] @@ -125,7 +159,7 @@ resource "azurerm_log_analytics_workspace" "webapp_logs" { } resource "azurerm_monitor_diagnostic_setting" "webapp_logs_monitor" { - name = "${var.resource_name_prefix}-mon" + name = "${var.resource_name_prefix}-webapp-mon" target_resource_id = azurerm_linux_web_app.webapp.id log_analytics_workspace_id = azurerm_log_analytics_workspace.webapp_logs.id @@ -147,7 +181,7 @@ resource "azurerm_monitor_diagnostic_setting" "webapp_logs_monitor" { } resource "azurerm_monitor_diagnostic_setting" "webapp_slot_logs_monitor" { - name = "${var.resource_name_prefix}-green-mon" + name = "${var.resource_name_prefix}-webapp-green-mon" target_resource_id = azurerm_linux_web_app_slot.webapp_slot.id log_analytics_workspace_id = azurerm_log_analytics_workspace.webapp_logs.id @@ -166,4 +200,55 @@ resource "azurerm_monitor_diagnostic_setting" "webapp_slot_logs_monitor" { lifecycle { ignore_changes = [metric] } +} + +# Create Custom Domain Name +resource "azurerm_app_service_custom_hostname_binding" "webapp_custom_domain" { + # Custom hostname only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + resource_group_name = var.resource_group + hostname = var.webapp_custom_domain_name + app_service_name = azurerm_linux_web_app.webapp.name + + lifecycle { + ignore_changes = [ssl_state, thumbprint] + } +} + +data "azurerm_client_config" "az_config" {} + +data "azuread_service_principal" "microsoft_webapp" { + # application_id: https://learn.microsoft.com/en-us/azure/app-service/configure-ssl-certificate?tabs=apex#authorize-app-service-to-read-from-the-vault + application_id = "abfa0a7c-a6b6-4736-8310-5855508787cd" +} + +resource "azurerm_key_vault_access_policy" "webapp_kv_ap" { + # Custom hostname only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + key_vault_id = var.kv_id + tenant_id = data.azurerm_client_config.az_config.tenant_id + object_id = data.azuread_service_principal.microsoft_webapp.object_id + secret_permissions = ["Get"] + certificate_permissions = ["Get"] +} + +resource "azurerm_app_service_certificate" "webapp_custom_domain_cert" { + # Custom hostname only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = var.webapp_custom_domain_cert_secret_label + resource_group_name = var.resource_group + location = var.location + key_vault_secret_id = var.kv_cert_secret_id +} + +resource "azurerm_app_service_certificate_binding" "webapp_custom_domain_cert_bind" { + # Custom hostname only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + hostname_binding_id = azurerm_app_service_custom_hostname_binding.webapp_custom_domain[0].id + certificate_id = azurerm_app_service_certificate.webapp_custom_domain_cert[0].id + ssl_state = "SniEnabled" } \ No newline at end of file diff --git a/terraform-azure/variables.tf b/terraform-azure/variables.tf index ffe5072fb..9f7f97d77 100644 --- a/terraform-azure/variables.tf +++ b/terraform-azure/variables.tf @@ -16,6 +16,52 @@ variable "resource_name_prefix" { type = string } +variable "kv_certificate_authority_username" { + description = "Username for the Certificate provider" + type = string + sensitive = true +} + +variable "kv_certificate_authority_password" { + description = "Password the Certificate provider" + type = string + sensitive = true +} + +variable "kv_certificate_authority_admin_email" { + description = "Email Address of the Certificate Authority Admin" + type = string + sensitive = true +} + +variable "kv_certificate_authority_admin_first_name" { + description = "First Name of the Certificate Authority Admin" + type = string + sensitive = true +} + +variable "kv_certificate_authority_admin_last_name" { + description = "Last Name of the Certificate Authority Admin" + type = string + sensitive = true +} + +variable "kv_certificate_authority_admin_phone_no" { + description = "Phone No. of the Certificate Authority Admin" + type = string + sensitive = true +} + +variable "kv_certificate_label" { + description = "Label for the Certificate" + type = string +} + +variable "kv_certificate_subject" { + description = "Subject of the Certificate" + type = string +} + variable "psqlfs_sku" { default = "B_Standard_B1ms" description = "SKU name for the Database Server" @@ -101,6 +147,11 @@ variable "webapp_docker_image_tag" { type = string } +variable "webapp_custom_domain_name" { + description = "Custom domain hostname" + type = string +} + variable "webapp_config_bot_token" { type = string sensitive = true