diff --git a/CHANGELOG.md b/CHANGELOG.md index b54fb7023f..775914429a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ENHANCEMENTS: * Update Terraform to use Azure AD authentication rather than storage account keys ([#4103](https://github.com/microsoft/AzureTRE/issues/4103)) * Update obsolete Terraform properties ([#4136](https://github.com/microsoft/AzureTRE/issues/4136)) * Update Guacamole version and dependencies ([#4140](https://github.com/microsoft/AzureTRE/issues/4140)) +* Add partial (core resources only) support for customer managed keys ([#4141](https://github.com/microsoft/AzureTRE/issues/4142)) BUG FIXES: - Update KeyVault references in API to use the version so Terraform cascades the update ([#4112](https://github.com/microsoft/AzureTRE/pull/4112)) diff --git a/config.sample.yaml b/config.sample.yaml index 8b613a580c..44777e89a3 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -8,6 +8,10 @@ management: mgmt_storage_account_name: __CHANGE_ME__ terraform_state_container_name: tfstate acr_name: __CHANGE_ME__ + # ID of external Key Vault to store CMKs in (only required if enable_cmk_encryption is true) + # external_key_store_id: __CHANGE_ME__ + # Name of Key Vault for encryption keys, required only if enable_cmk_encryption is true and not using external_key_store_id + # encryption_kv_name: __CHANGE_ME__ # Azure Resource Manager credentials used for CI/CD pipelines arm_subscription_id: __CHANGE_ME__ @@ -80,6 +84,9 @@ developer_settings: # the base workspace. # enable_local_debugging: true +# This setting enables customer-managed key encryption for all supported resources +# enable_cmk_encryption: true + # Used by the API and Resource processor application to change log level # Can be "ERROR", "WARNING", "INFO", "DEBUG" # logging_level: "INFO" diff --git a/core/terraform/airlock/airlock_processor.tf b/core/terraform/airlock/airlock_processor.tf index 20be31bc1d..32c9a8c506 100644 --- a/core/terraform/airlock/airlock_processor.tf +++ b/core/terraform/airlock/airlock_processor.tf @@ -28,9 +28,25 @@ resource "azurerm_storage_account" "sa_airlock_processor_func_app" { cross_tenant_replication_enabled = false tags = var.tre_core_tags + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + lifecycle { ignore_changes = [tags] } } +resource "azurerm_storage_account_customer_managed_key" "sa_airlock_processor_func_app_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.sa_airlock_processor_func_app.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id +} + resource "azurerm_linux_function_app" "airlock_function_app" { name = local.airlock_function_app_name resource_group_name = var.resource_group_name diff --git a/core/terraform/airlock/storage_accounts.tf b/core/terraform/airlock/storage_accounts.tf index 84db2a153b..87b8c74907 100644 --- a/core/terraform/airlock/storage_accounts.tf +++ b/core/terraform/airlock/storage_accounts.tf @@ -1,3 +1,5 @@ + + # 'External' storage account - drop location for import resource "azurerm_storage_account" "sa_import_external" { name = local.import_external_storage_name @@ -14,6 +16,14 @@ resource "azurerm_storage_account" "sa_import_external" { # This is true ONLY when Hierarchical Namespace is DISABLED is_hns_enabled = false + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;external" }) @@ -43,6 +53,14 @@ resource "azurerm_private_endpoint" "stg_import_external_pe" { } } +resource "azurerm_storage_account_customer_managed_key" "sa_import_external_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.sa_import_external.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id +} + # 'Approved' export resource "azurerm_storage_account" "sa_export_approved" { name = local.export_approved_storage_name @@ -59,6 +77,14 @@ resource "azurerm_storage_account" "sa_export_approved" { # This is true ONLY when Hierarchical Namespace is DISABLED is_hns_enabled = false + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge(var.tre_core_tags, { description = "airlock;export;approved" }) @@ -88,6 +114,14 @@ resource "azurerm_private_endpoint" "stg_export_approved_pe" { } } +resource "azurerm_storage_account_customer_managed_key" "sa_export_approved_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.sa_export_approved.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id +} + # 'In-Progress' storage account resource "azurerm_storage_account" "sa_import_in_progress" { name = local.import_in_progress_storage_name @@ -102,6 +136,14 @@ resource "azurerm_storage_account" "sa_import_in_progress" { # This is true ONLY when Hierarchical Namespace is DISABLED is_hns_enabled = false + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;in-progress" }) @@ -114,6 +156,14 @@ resource "azurerm_storage_account" "sa_import_in_progress" { lifecycle { ignore_changes = [tags] } } +resource "azurerm_storage_account_customer_managed_key" "sa_import_in_progress_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.sa_import_in_progress.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id +} + # Enable Airlock Malware Scanning on Core TRE resource "azapi_resource_action" "enable_defender_for_storage" { @@ -177,6 +227,14 @@ resource "azurerm_storage_account" "sa_import_rejected" { # This is true ONLY when Hierarchical Namespace is DISABLED is_hns_enabled = false + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;rejected" }) @@ -212,6 +270,14 @@ resource "azurerm_private_endpoint" "stg_import_rejected_pe" { lifecycle { ignore_changes = [tags] } } +resource "azurerm_storage_account_customer_managed_key" "sa_import_rejected_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.sa_import_rejected.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id +} + # 'Blocked' storage account resource "azurerm_storage_account" "sa_import_blocked" { name = local.import_blocked_storage_name @@ -226,6 +292,14 @@ resource "azurerm_storage_account" "sa_import_blocked" { # This is true ONLY when Hierarchical Namespace is DISABLED is_hns_enabled = false + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;blocked" }) @@ -260,3 +334,11 @@ resource "azurerm_private_endpoint" "stg_import_blocked_pe" { lifecycle { ignore_changes = [tags] } } + +resource "azurerm_storage_account_customer_managed_key" "sa_import_blocked_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.sa_import_blocked.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id +} diff --git a/core/terraform/airlock/variables.tf b/core/terraform/airlock/variables.tf index 3cf167705a..f88f1fc50f 100644 --- a/core/terraform/airlock/variables.tf +++ b/core/terraform/airlock/variables.tf @@ -91,3 +91,24 @@ variable "queue_core_dns_zone_id" { variable "table_core_dns_zone_id" { type = string } + +variable "encryption_identity_id" { + type = string + description = "User Managed Identity with permissions to get encryption keys from key vault" +} + +variable "enable_cmk_encryption" { + type = bool + description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" +} + +variable "key_store_id" { + type = string + description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" + default = null +} + +variable "kv_encryption_key_name" { + type = string + description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" +} diff --git a/core/terraform/appgateway/staticweb.tf b/core/terraform/appgateway/staticweb.tf index 76b4600335..0f391b7d33 100644 --- a/core/terraform/appgateway/staticweb.tf +++ b/core/terraform/appgateway/staticweb.tf @@ -22,6 +22,22 @@ resource "azurerm_storage_account" "staticweb" { bypass = ["AzureServices"] default_action = "Deny" } + + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } +} + +resource "azurerm_storage_account_customer_managed_key" "staticweb_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.staticweb.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id } # Assign the "Storage Blob Data Contributor" role needed for uploading certificates to the storage account diff --git a/core/terraform/appgateway/variables.tf b/core/terraform/appgateway/variables.tf index fb88c6dd83..21d50f61b6 100644 --- a/core/terraform/appgateway/variables.tf +++ b/core/terraform/appgateway/variables.tf @@ -29,3 +29,21 @@ variable "log_analytics_workspace_id" { variable "app_gateway_sku" { type = string } + +variable "encryption_identity_id" { + type = string + description = "User Managed Identity with permissions to get encryption keys from key vault" +} +variable "enable_cmk_encryption" { + type = bool + description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" +} +variable "key_store_id" { + type = string + description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" + default = null +} +variable "kv_encryption_key_name" { + type = string + description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" +} diff --git a/core/terraform/azure-monitor/azure-monitor.tf b/core/terraform/azure-monitor/azure-monitor.tf index 0e91d89588..09446545bc 100644 --- a/core/terraform/azure-monitor/azure-monitor.tf +++ b/core/terraform/azure-monitor/azure-monitor.tf @@ -29,9 +29,25 @@ resource "azurerm_storage_account" "az_monitor" { bypass = ["AzureServices"] } + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + lifecycle { ignore_changes = [tags] } } +resource "azurerm_storage_account_customer_managed_key" "az_monitor_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.az_monitor.id + key_vault_id = var.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = var.encryption_identity_id +} + resource "azurerm_log_analytics_linked_storage_account" "workspace_storage_ingestion" { data_source_type = "Ingestion" resource_group_name = var.resource_group_name diff --git a/core/terraform/azure-monitor/variables.tf b/core/terraform/azure-monitor/variables.tf index 6debd34f29..30b99dc0a7 100644 --- a/core/terraform/azure-monitor/variables.tf +++ b/core/terraform/azure-monitor/variables.tf @@ -31,3 +31,21 @@ variable "tre_core_tags" { variable "enable_local_debugging" { type = bool } + +variable "encryption_identity_id" { + type = string + description = "User Managed Identity with permissions to get encryption keys from key vault" +} +variable "enable_cmk_encryption" { + type = bool + description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" +} +variable "key_store_id" { + type = string + description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" + default = null +} +variable "kv_encryption_key_name" { + type = string + description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" +} diff --git a/core/terraform/cmk_encryption.tf b/core/terraform/cmk_encryption.tf new file mode 100644 index 0000000000..c8b4b9a483 --- /dev/null +++ b/core/terraform/cmk_encryption.tf @@ -0,0 +1,38 @@ +resource "azurerm_user_assigned_identity" "encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + resource_group_name = azurerm_resource_group.core.name + location = azurerm_resource_group.core.location + tags = local.tre_core_tags + + name = "id-encryption-${var.tre_id}" + + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_role_assignment" "kv_encryption_key_user" { + count = var.enable_cmk_encryption ? 1 : 0 + scope = local.key_store_id + role_definition_name = "Key Vault Crypto Service Encryption User" + principal_id = azurerm_user_assigned_identity.encryption[0].principal_id +} + +# Key used to encrypt resources +resource "azurerm_key_vault_key" "tre_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + + name = var.kv_encryption_key_name + key_vault_id = local.key_store_id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "unwrapKey", + "wrapKey", + ] + + depends_on = [ + azurerm_role_assignment.kv_encryption_key_user + ] +} diff --git a/core/terraform/data.tf b/core/terraform/data.tf index f2dd4630e1..ec18987993 100644 --- a/core/terraform/data.tf +++ b/core/terraform/data.tf @@ -13,6 +13,12 @@ data "azurerm_container_registry" "mgmt_acr" { resource_group_name = var.mgmt_resource_group_name } +data "azurerm_key_vault" "encryption_kv" { + count = var.enable_cmk_encryption && var.external_key_store_id == null ? 1 : 0 + name = var.encryption_kv_name + resource_group_name = var.mgmt_resource_group_name +} + data "http" "myip" { count = var.public_deployment_ip_address == "" ? 1 : 0 url = "https://ipecho.net/plain" diff --git a/core/terraform/locals.tf b/core/terraform/locals.tf index c3539c9fb3..0b8bc5dfc7 100644 --- a/core/terraform/locals.tf +++ b/core/terraform/locals.tf @@ -40,4 +40,7 @@ locals { # The followig regex extracts different parts of the service bus endpoint: scheme, fqdn, port, path, query and fragment. This allows us to extract the needed fqdn part. service_bus_namespace_fqdn = regex("(?:(?P[^:/?#]+):)?(?://(?P[^/?#:]*))?(?::(?P[0-9]+))?(?P[^?#]*)(?:\\?(?P[^#]*))?(?:#(?P.*))?", azurerm_servicebus_namespace.sb.endpoint).fqdn + + # The key store for encryption keys could either be external or created by terraform + key_store_id = var.enable_cmk_encryption ? (var.external_key_store_id != null ? var.external_key_store_id : data.azurerm_key_vault.encryption_kv[0].id) : null } diff --git a/core/terraform/main.tf b/core/terraform/main.tf index 8eebea1109..bfdf5e168a 100644 --- a/core/terraform/main.tf +++ b/core/terraform/main.tf @@ -73,9 +73,14 @@ module "azure_monitor" { blob_core_dns_zone_id = module.network.blob_core_dns_zone_id tre_core_tags = local.tre_core_tags enable_local_debugging = var.enable_local_debugging + enable_cmk_encryption = var.enable_cmk_encryption + key_store_id = local.key_store_id + kv_encryption_key_name = var.kv_encryption_key_name + encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption[0].id : null depends_on = [ - module.network + module.network, + azurerm_key_vault_key.tre_encryption[0] ] } @@ -101,11 +106,17 @@ module "appgateway" { log_analytics_workspace_id = module.azure_monitor.log_analytics_workspace_id app_gateway_sku = var.app_gateway_sku + enable_cmk_encryption = var.enable_cmk_encryption + key_store_id = local.key_store_id + kv_encryption_key_name = var.kv_encryption_key_name + encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption[0].id : null + depends_on = [ module.network, azurerm_key_vault.kv, azurerm_role_assignment.keyvault_deployer_role, - azurerm_private_endpoint.api_private_endpoint + azurerm_private_endpoint.api_private_endpoint, + azurerm_key_vault_key.tre_encryption[0] ] } @@ -135,10 +146,15 @@ module "airlock_resources" { enable_local_debugging = var.enable_local_debugging myip = local.myip + enable_cmk_encryption = var.enable_cmk_encryption + key_store_id = local.key_store_id + kv_encryption_key_name = var.kv_encryption_key_name + encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption[0].id : null depends_on = [ azurerm_servicebus_namespace.sb, - module.network + module.network, + azurerm_key_vault_key.tre_encryption[0] ] } @@ -170,12 +186,16 @@ module "resource_processor_vmss_porter" { logging_level = var.logging_level firewall_sku = var.firewall_sku rp_bundle_values = var.rp_bundle_values + enable_cmk_encryption = var.enable_cmk_encryption + key_store_id = local.key_store_id + kv_encryption_key_name = var.kv_encryption_key_name depends_on = [ module.network, module.azure_monitor, azurerm_key_vault.kv, - azurerm_role_assignment.keyvault_deployer_role + azurerm_role_assignment.keyvault_deployer_role, + azurerm_key_vault_key.tre_encryption[0] ] } diff --git a/core/terraform/resource_processor/vmss_porter/data.tf b/core/terraform/resource_processor/vmss_porter/data.tf index f8776eca0d..8ae856343f 100644 --- a/core/terraform/resource_processor/vmss_porter/data.tf +++ b/core/terraform/resource_processor/vmss_porter/data.tf @@ -45,6 +45,12 @@ data "template_cloudinit_config" "config" { } } +data "azurerm_key_vault_key" "tre_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = var.kv_encryption_key_name + key_vault_id = var.key_store_id +} + data "azurerm_storage_account" "mgmt_storage" { name = var.mgmt_storage_account_name resource_group_name = var.mgmt_resource_group_name diff --git a/core/terraform/resource_processor/vmss_porter/main.tf b/core/terraform/resource_processor/vmss_porter/main.tf index 0f2840f361..7b51a4fd34 100644 --- a/core/terraform/resource_processor/vmss_porter/main.tf +++ b/core/terraform/resource_processor/vmss_porter/main.tf @@ -50,6 +50,25 @@ resource "azurerm_user_assigned_identity" "vmss_msi" { lifecycle { ignore_changes = [tags] } } +resource "azurerm_disk_encryption_set" "vmss_disk_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = "vmss-disk-encryption-rp-porter-${var.tre_id}" + location = var.location + resource_group_name = var.resource_group_name + key_vault_key_id = data.azurerm_key_vault_key.tre_encryption[0].versionless_id + encryption_type = "EncryptionAtRestWithPlatformAndCustomerKeys" + auto_key_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.vmss_msi.id] + } + + depends_on = [ + azurerm_role_assignment.vmss_kv_encryption_key_user + ] +} + resource "azurerm_linux_virtual_machine_scale_set" "vm_linux" { name = "vmss-rp-porter-${var.tre_id}" location = var.location @@ -113,9 +132,10 @@ resource "azurerm_linux_virtual_machine_scale_set" "vm_linux" { } os_disk { - storage_account_type = "Standard_LRS" - caching = "ReadWrite" - disk_size_gb = 64 + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + disk_size_gb = 64 + disk_encryption_set_id = var.enable_cmk_encryption ? azurerm_disk_encryption_set.vmss_disk_encryption[0].id : null } network_interface { @@ -195,6 +215,13 @@ resource "azurerm_role_assignment" "keyvault_vmss_role" { principal_id = azurerm_user_assigned_identity.vmss_msi.principal_id // id-vmss- } +resource "azurerm_role_assignment" "vmss_kv_encryption_key_user" { + count = var.enable_cmk_encryption ? 1 : 0 + scope = var.key_store_id + role_definition_name = "Key Vault Crypto Service Encryption User" + principal_id = azurerm_user_assigned_identity.vmss_msi.principal_id +} + module "terraform_azurerm_environment_configuration" { source = "git::https://github.com/microsoft/terraform-azurerm-environment-configuration.git?ref=0.2.0" arm_environment = var.arm_environment diff --git a/core/terraform/resource_processor/vmss_porter/variables.tf b/core/terraform/resource_processor/vmss_porter/variables.tf index 8c2b835fa0..9537cd79c5 100644 --- a/core/terraform/resource_processor/vmss_porter/variables.tf +++ b/core/terraform/resource_processor/vmss_porter/variables.tf @@ -79,3 +79,19 @@ variable "rp_bundle_values" { locals { rp_bundle_values_formatted = join("\n ", [for key in keys(var.rp_bundle_values) : "RP_BUNDLE_${key}=${var.rp_bundle_values[key]}"]) } + +variable "enable_cmk_encryption" { + type = bool + description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" +} + +variable "key_store_id" { + type = string + description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" + default = null +} + +variable "kv_encryption_key_name" { + type = string + description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" +} diff --git a/core/terraform/storage.tf b/core/terraform/storage.tf index b4a077765e..ee49178c9f 100644 --- a/core/terraform/storage.tf +++ b/core/terraform/storage.tf @@ -6,7 +6,19 @@ resource "azurerm_storage_account" "stg" { account_replication_type = "LRS" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false - tags = local.tre_core_tags + + + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.encryption[0].id] + } + } + + tags = local.tre_core_tags + + lifecycle { ignore_changes = [tags] } } @@ -62,3 +74,15 @@ resource "azurerm_private_endpoint" "filepe" { azurerm_private_endpoint.blobpe ] } + +resource "azurerm_storage_account_customer_managed_key" "encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.stg.id + key_vault_id = local.key_store_id + key_name = var.kv_encryption_key_name + user_assigned_identity_id = azurerm_user_assigned_identity.encryption[0].id + + depends_on = [ + azurerm_role_assignment.kv_encryption_key_user[0] + ] +} diff --git a/core/terraform/variables.tf b/core/terraform/variables.tf index 6b3f86763a..d364d027b8 100644 --- a/core/terraform/variables.tf +++ b/core/terraform/variables.tf @@ -223,3 +223,28 @@ variable "logging_level" { error_message = "logging_level must be one of ERROR, WARNING, INFO, DEBUG" } } + +variable "enable_cmk_encryption" { + type = bool + description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" + default = false +} + +variable "external_key_store_id" { + type = string + description = "ID of external Key Vault to store CMKs in (only required if enable_cmk_encryption is true)" + default = null +} + +variable "encryption_kv_name" { + type = string + description = "Name of Key Vault for encryption keys, required only if external_key_store_id is not set (only used if enable_cmk_encryption is true)" + default = null +} + +variable "kv_encryption_key_name" { + type = string + description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" + default = "tre-encryption" +} + diff --git a/core/version.txt b/core/version.txt index 04cffaf6d2..1bebb74e80 100644 --- a/core/version.txt +++ b/core/version.txt @@ -1 +1 @@ -__version__ = "0.11.2" \ No newline at end of file +__version__ = "0.11.3" diff --git a/devops/terraform/cmk_encryption.tf b/devops/terraform/cmk_encryption.tf new file mode 100644 index 0000000000..bd9af71a7f --- /dev/null +++ b/devops/terraform/cmk_encryption.tf @@ -0,0 +1,37 @@ +resource "azurerm_user_assigned_identity" "tre_mgmt_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + resource_group_name = azurerm_resource_group.mgmt.name + location = azurerm_resource_group.mgmt.location + + name = "id-tre-mgmt-encryption" + + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_role_assignment" "kv_mgmt_encryption_key_user" { + count = var.enable_cmk_encryption ? 1 : 0 + scope = local.key_store_id + role_definition_name = "Key Vault Crypto Service Encryption User" + principal_id = azurerm_user_assigned_identity.tre_mgmt_encryption[0].principal_id +} + +# Key used to encrypt management resources +resource "azurerm_key_vault_key" "tre_mgmt_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + + name = var.kv_mgmt_encryption_key_name + key_vault_id = local.key_store_id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + "unwrapKey", + "wrapKey", + ] + + depends_on = [ + azurerm_role_assignment.current_user_to_key_vault_crypto_officer + ] +} diff --git a/devops/terraform/locals.tf b/devops/terraform/locals.tf new file mode 100644 index 0000000000..3beb01d417 --- /dev/null +++ b/devops/terraform/locals.tf @@ -0,0 +1,4 @@ +locals { + # The key store for encryption keys could either be external or created by terraform + key_store_id = var.enable_cmk_encryption ? (var.external_key_store_id != null ? var.external_key_store_id : azurerm_key_vault.encryption_kv[0].id) : null +} diff --git a/devops/terraform/main.tf b/devops/terraform/main.tf index c87585cd54..2dd489f1de 100644 --- a/devops/terraform/main.tf +++ b/devops/terraform/main.tf @@ -4,6 +4,8 @@ provider "azurerm" { storage_use_azuread = true } +data "azurerm_client_config" "current" {} + # Resource group for TRE core management resource "azurerm_resource_group" "mgmt" { name = var.mgmt_resource_group_name @@ -29,9 +31,32 @@ resource "azurerm_storage_account" "state_storage" { allow_nested_items_to_be_public = false shared_access_key_enabled = false + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.tre_mgmt_encryption[0].id] + } + } + lifecycle { ignore_changes = [tags] } } +resource "azurerm_storage_account_customer_managed_key" "state_storage_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.state_storage.id + key_vault_id = local.key_store_id + key_name = var.kv_mgmt_encryption_key_name + user_assigned_identity_id = azurerm_user_assigned_identity.tre_mgmt_encryption[0].id + + depends_on = [ + azurerm_role_assignment.kv_mgmt_encryption_key_user, + azurerm_key_vault_key.tre_mgmt_encryption[0] + ] +} + + + # Shared container registry resource "azurerm_container_registry" "shared_acr" { name = var.acr_name @@ -68,3 +93,26 @@ EOF enabled = true } } + +# Key Vault for encryption keys +resource "azurerm_key_vault" "encryption_kv" { + count = var.enable_cmk_encryption && var.external_key_store_id == null ? 1 : 0 + name = var.encryption_kv_name + resource_group_name = azurerm_resource_group.mgmt.name + location = azurerm_resource_group.mgmt.location + enabled_for_disk_encryption = true + sku_name = "standard" + tenant_id = data.azurerm_client_config.current.tenant_id + enable_rbac_authorization = true + purge_protection_enabled = true + + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_role_assignment" "current_user_to_key_vault_crypto_officer" { + count = var.enable_cmk_encryption && var.external_key_store_id == null ? 1 : 0 + scope = azurerm_key_vault.encryption_kv[0].id + role_definition_name = "Key Vault Crypto Officer" + principal_id = data.azurerm_client_config.current.object_id +} + diff --git a/devops/terraform/variables.tf b/devops/terraform/variables.tf index 155193098c..238bc2f26e 100644 --- a/devops/terraform/variables.tf +++ b/devops/terraform/variables.tf @@ -23,3 +23,27 @@ variable "acr_name" { type = string description = "Name of ACR" } + +variable "enable_cmk_encryption" { + type = bool + description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" + default = false +} + +variable "external_key_store_id" { + type = string + description = "ID of external Key Vault to store CMKs in (only required if enable_cmk_encryption is true)" + default = null +} + +variable "encryption_kv_name" { + type = string + description = "Name of Key Vault for encryption keys, required only if external_key_store_id is not set (only used if enable_cmk_encryption is true)" + default = null +} + +variable "kv_mgmt_encryption_key_name" { + type = string + description = "Name of Key Vault Encryption Key for management resources (only used if enable_cmk_encryption is true)" + default = "tre-mgmt-encryption" +} diff --git a/docs/tre-admins/customer-managed-keys.md b/docs/tre-admins/customer-managed-keys.md new file mode 100644 index 0000000000..414bf0870a --- /dev/null +++ b/docs/tre-admins/customer-managed-keys.md @@ -0,0 +1,21 @@ +# Enabling Customer-managed keys for TRE resources + +You can enable customer-managed keys (CMK) for supporting resources in Azure TRE. + +!!! warning + Currently Azure TRE only supports CMK encryption for resources in the TRE core. + CMK encryption is not supported for the rest of the resources such as those deployed by a TRE workspace. + + +When enabled, CMK encryption provides an additional layer of encryption control for supported Azure resources within the TRE by allowing you to manage and control the encryption keys used to protect your data. + +To enable CMK encryption, set `enable_cmk_encryption: true` in the developer settings section of your `config.yaml` file. + +For more information about CMKs, see [Use customer-managed keys with Azure Storage encryption](https://learn.microsoft.com/azure/storage/common/customer-managed-keys-overview). + +## Key Vault configuration +The CMKs for Azure TRE can be stored in either a Key Vault deployed by TRE itself, or in an external Key Vault provided by the user. + +To have TRE create and manage its own Key Vault for storing CMKs, specify the `ENCRYPTION_KV_NAME` parameter in the `config.yaml` file. + +Alternatively, to use your own existing Key Vault, provide the `EXTERNAL_KEY_STORE_ID` parameter pointing to your Key Vault resource ID. diff --git a/docs/tre-admins/environment-variables.md b/docs/tre-admins/environment-variables.md index c35b945b46..04395b9ec9 100644 --- a/docs/tre-admins/environment-variables.md +++ b/docs/tre-admins/environment-variables.md @@ -12,6 +12,8 @@ | `MGMT_STORAGE_ACCOUNT_NAME` | The name of the storage account to hold the Terraform state and other deployment artifacts. | | `TERRAFORM_STATE_CONTAINER_NAME` | The name of the blob container to hold the Terraform state *Default value is `tfstate`.* | | `ACR_NAME` | A globally unique name for the Azure Container Registry (ACR) that will be created to store deployment images. | +| `EXTERNAL_KEY_STORE_ID` | The ID of the external Key Vault to store CMKs in. Should not be set if `ENCRYPTION_KV_NAME` is set and only required if `ENABLE_CMK_ENCRYPTION` is true. | +| `ENCRYPTION_KV_NAME` | The name of the Key Vault for encryption keys. Should not be set if `EXTERNAL_KEY_STORE_ID` is set and only required if `ENABLE_CMK_ENCRYPTION` is true. | | `ARM_SUBSCRIPTION_ID` | *Optional for manual deployment. If not specified the `az cli` selected subscription will be used.* The Azure subscription ID for all resources. | | `ARM_CLIENT_ID` | *Optional for manual deployment without logged-in credentials.* The client whose azure identity will be used to deploy the solution. | | `ARM_CLIENT_SECRET` | *Optional for manual deployment without logged-in credentials.* The password of the client defined in `ARM_CLIENT_ID`. | @@ -42,7 +44,7 @@ | `FIREWALL_SKU` | Optional. The SKU of the Azure Firewall instance. Default value is `Standard`. Allowed values [`Basic`, `Standard`, `Premium`]. See [Azure Firewall SKU feature comparison](https://learn.microsoft.com/en-us/azure/firewall/choose-firewall-sku). | | `APP_GATEWAY_SKU` | Optional. The SKU of the Application Gateway. Default value is `Standard_v2`. Allowed values [`Standard_v2`, `WAF_v2`] | | `CUSTOM_DOMAIN` | Optional. Custom domain name to access the Azure TRE portal. See [Custom domain name](custom-domain.md). | - +| `ENABLE_CMK_ENCRYPTION` | If set to `true`, customer-managed key encryption will be enabled for all supported resources. | ## For authentication in `/config.yaml` | Variable | Description | diff --git a/mkdocs.yml b/mkdocs.yml index edd9c4da74..4526800b85 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -140,6 +140,7 @@ nav: - Upgrading Resources Version: tre-admins/upgrading-resources.md - Configuring Airlock Reviews: tre-admins/configure-airlock-review.md - Supported Clouds: tre-admins/supported-clouds.md + - Customer Managed Keys: tre-admins/customer-managed-keys.md - Custom Domain Name: tre-admins/custom-domain.md - Development: # Docs related to the developing code for the AzureTRE