diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa46af3c5..5d3288c92a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,11 @@ ENHANCEMENTS: * Storage accounts should use infrastructure encryption ([#4001](https://github.com/microsoft/AzureTRE/issues/4001)) * 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), [#4144](https://github.com/microsoft/AzureTRE/issues/4144)) +* Add partial (core resources only) support for customer-managed keys ([#4141](https://github.com/microsoft/AzureTRE/issues/4142), [#4144](https://github.com/microsoft/AzureTRE/issues/4144)) * Update the Azure CLI version to 2.67.0 in dev container and vmss ([#4157](https://github.com/microsoft/AzureTRE/pull/4157)) * Move Github PR bot commands into main documentation ([#4167](https://github.com/microsoft/AzureTRE/pull/4167)) * Block Authentication with keys to CosmosDB SQL account ([#4175](https://github.com/microsoft/AzureTRE/pull/4175)) +* Add support for customer-managed keys encryption in base workspace ([#4161](https://github.com/microsoft/AzureTRE/pull/4161)) 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/core/terraform/locals.tf b/core/terraform/locals.tf index 0b8bc5dfc7..d8620aeb5f 100644 --- a/core/terraform/locals.tf +++ b/core/terraform/locals.tf @@ -42,5 +42,5 @@ locals { 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 + 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) : "" } diff --git a/core/terraform/resource_processor/vmss_porter/cloud-config.yaml b/core/terraform/resource_processor/vmss_porter/cloud-config.yaml index b86b4c883d..ace477b132 100644 --- a/core/terraform/resource_processor/vmss_porter/cloud-config.yaml +++ b/core/terraform/resource_processor/vmss_porter/cloud-config.yaml @@ -61,6 +61,8 @@ write_files: OTEL_RESOURCE_ATTRIBUTES=service.name=resource_processor,service.version=${resource_processor_vmss_porter_image_tag} OTEL_EXPERIMENTAL_RESOURCE_DETECTORS=azure_vm LOGGING_LEVEL=${logging_level} + ENABLE_CMK_ENCRYPTION=${enable_cmk_encryption} + KEY_STORE_ID=${key_store_id} ${rp_bundle_values} - path: /etc/cron.hourly/docker-prune # An hourly cron job to have docker free disk space. Running this frquently diff --git a/core/terraform/resource_processor/vmss_porter/data.tf b/core/terraform/resource_processor/vmss_porter/data.tf index 8ae856343f..53d58f452d 100644 --- a/core/terraform/resource_processor/vmss_porter/data.tf +++ b/core/terraform/resource_processor/vmss_porter/data.tf @@ -32,6 +32,8 @@ data "template_file" "cloudconfig" { firewall_sku = var.firewall_sku logging_level = var.logging_level rp_bundle_values = local.rp_bundle_values_formatted + enable_cmk_encryption = var.enable_cmk_encryption + key_store_id = var.key_store_id } } diff --git a/core/terraform/resource_processor/vmss_porter/main.tf b/core/terraform/resource_processor/vmss_porter/main.tf index 7b51a4fd34..64a9133501 100644 --- a/core/terraform/resource_processor/vmss_porter/main.tf +++ b/core/terraform/resource_processor/vmss_porter/main.tf @@ -218,7 +218,7 @@ resource "azurerm_role_assignment" "keyvault_vmss_role" { 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" + role_definition_name = "Key Vault Crypto Officer" principal_id = azurerm_user_assigned_identity.vmss_msi.principal_id } diff --git a/core/terraform/resource_processor/vmss_porter/variables.tf b/core/terraform/resource_processor/vmss_porter/variables.tf index 9537cd79c5..c71c01efb6 100644 --- a/core/terraform/resource_processor/vmss_porter/variables.tf +++ b/core/terraform/resource_processor/vmss_porter/variables.tf @@ -88,7 +88,6 @@ variable "enable_cmk_encryption" { 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" { diff --git a/core/version.txt b/core/version.txt index be379744b8..eec2a4dd5e 100644 --- a/core/version.txt +++ b/core/version.txt @@ -1 +1 @@ -__version__ = "0.11.6" +__version__ = "0.11.7" diff --git a/docs/tre-admins/customer-managed-keys.md b/docs/tre-admins/customer-managed-keys.md index 414bf0870a..711ac50015 100644 --- a/docs/tre-admins/customer-managed-keys.md +++ b/docs/tre-admins/customer-managed-keys.md @@ -3,7 +3,7 @@ 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. + Currently Azure TRE only supports CMK encryption for resources in the TRE core and Base Workspace. CMK encryption is not supported for the rest of the resources such as those deployed by a TRE workspace. diff --git a/resource_processor/_version.py b/resource_processor/_version.py index e94731c0f2..f8c6ac7fea 100644 --- a/resource_processor/_version.py +++ b/resource_processor/_version.py @@ -1 +1 @@ -__version__ = "0.9.4" +__version__ = "0.9.5" diff --git a/resource_processor/shared/config.py b/resource_processor/shared/config.py index 8da0ee2ab0..ddc7b94d92 100644 --- a/resource_processor/shared/config.py +++ b/resource_processor/shared/config.py @@ -25,6 +25,8 @@ def get_config() -> dict: config["aad_authority_url"] = os.environ.get("AAD_AUTHORITY_URL", "https://login.microsoftonline.com") config["microsoft_graph_fqdn"] = os.environ.get("MICROSOFT_GRAPH_FQDN", "graph.microsoft.com") config["firewall_sku"] = os.environ.get("FIREWALL_SKU", "") + config["enable_cmk_encryption"] = os.environ.get("ENABLE_CMK_ENCRYPTION", "false") + config["key_store_id"] = os.environ.get("KEY_STORE_ID", None) try: config["number_processes_int"] = int(config["number_processes"]) diff --git a/templates/workspaces/base/parameters.json b/templates/workspaces/base/parameters.json index 0e9e74165a..fa261465ca 100755 --- a/templates/workspaces/base/parameters.json +++ b/templates/workspaces/base/parameters.json @@ -147,6 +147,18 @@ "source": { "env": "AZURE_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "enable_cmk_encryption" + } + }, + { + "name": "key_store_id", + "source": { + "env": "key_store_id" + } } ] } diff --git a/templates/workspaces/base/porter.yaml b/templates/workspaces/base/porter.yaml index 6e2971ad82..75ee4374a1 100644 --- a/templates/workspaces/base/porter.yaml +++ b/templates/workspaces/base/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-base -version: 1.6.1 +version: 1.7.0 description: "A base Azure TRE workspace" dockerfile: Dockerfile.tmpl registry: azuretre @@ -116,6 +116,12 @@ parameters: default: true - name: arm_environment type: string + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: app_role_id_workspace_owner @@ -183,6 +189,8 @@ install: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: ${ bundle.parameters.enable_airlock } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -225,6 +233,8 @@ upgrade: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: ${ bundle.parameters.enable_airlock } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -290,6 +300,8 @@ uninstall: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: ${ bundle.parameters.enable_airlock } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspaces/base/terraform/airlock/storage_accounts.tf b/templates/workspaces/base/terraform/airlock/storage_accounts.tf index 2729e0d544..0bcea7c812 100644 --- a/templates/workspaces/base/terraform/airlock/storage_accounts.tf +++ b/templates/workspaces/base/terraform/airlock/storage_accounts.tf @@ -20,6 +20,14 @@ resource "azurerm_storage_account" "sa_import_approved" { bypass = ["AzureServices"] } + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge( var.tre_workspace_tags, { @@ -75,6 +83,14 @@ resource "azurerm_storage_account" "sa_export_internal" { bypass = ["AzureServices"] } + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge( var.tre_workspace_tags, { @@ -122,6 +138,14 @@ resource "azurerm_storage_account" "sa_export_inprogress" { # 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] + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -192,6 +216,14 @@ resource "azurerm_storage_account" "sa_export_rejected" { bypass = ["AzureServices"] } + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge( var.tre_workspace_tags, { @@ -247,6 +279,14 @@ resource "azurerm_storage_account" "sa_export_blocked" { bypass = ["AzureServices"] } + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + tags = merge( var.tre_workspace_tags, { @@ -296,3 +336,18 @@ resource "azurerm_role_assignment" "api_sa_data_contributor" { role_definition_name = "Storage Blob Data Contributor" principal_id = data.azurerm_user_assigned_identity.api_id.principal_id } + +resource "azurerm_storage_account_customer_managed_key" "sa_encryption" { + for_each = var.enable_cmk_encryption ? { + "sa_import_approved" = azurerm_storage_account.sa_import_approved, + "sa_export_internal" = azurerm_storage_account.sa_export_internal, + "sa_export_inprogress" = azurerm_storage_account.sa_export_inprogress, + "sa_export_rejected" = azurerm_storage_account.sa_export_rejected, + "sa_export_blocked" = azurerm_storage_account.sa_export_blocked + } : {} + + storage_account_id = each.value.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/templates/workspaces/base/terraform/airlock/variables.tf b/templates/workspaces/base/terraform/airlock/variables.tf index a1a5909d38..70e4b1a101 100644 --- a/templates/workspaces/base/terraform/airlock/variables.tf +++ b/templates/workspaces/base/terraform/airlock/variables.tf @@ -24,4 +24,16 @@ variable "tre_workspace_tags" { } variable "arm_environment" { type = string -} \ No newline at end of file +} +variable "enable_cmk_encryption" { + type = bool +} +variable "key_store_id" { + type = string +} +variable "encryption_identity_id" { + type = string +} +variable "kv_encryption_key_name" { + type = string +} diff --git a/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf b/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf index df888ebc37..89c44962d6 100644 --- a/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf +++ b/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf @@ -23,6 +23,14 @@ resource "azurerm_storage_account" "app_insights" { cross_tenant_replication_enabled = false tags = var.tre_workspace_tags + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [var.encryption_identity_id] + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -34,6 +42,14 @@ resource "azurerm_storage_account" "app_insights" { lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } +resource "azurerm_storage_account_customer_managed_key" "app_insights_stg_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.app_insights.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/templates/workspaces/base/terraform/azure-monitor/variables.tf b/templates/workspaces/base/terraform/azure-monitor/variables.tf index 9b9f4845dc..1171350646 100644 --- a/templates/workspaces/base/terraform/azure-monitor/variables.tf +++ b/templates/workspaces/base/terraform/azure-monitor/variables.tf @@ -37,3 +37,15 @@ variable "tre_resource_id" { variable "enable_local_debugging" { type = bool } +variable "enable_cmk_encryption" { + type = bool +} +variable "key_store_id" { + type = string +} +variable "encryption_identity_id" { + type = string +} +variable "kv_encryption_key_name" { + type = string +} diff --git a/templates/workspaces/base/terraform/cmk-encryption.tf b/templates/workspaces/base/terraform/cmk-encryption.tf new file mode 100644 index 0000000000..954bfd2361 --- /dev/null +++ b/templates/workspaces/base/terraform/cmk-encryption.tf @@ -0,0 +1,37 @@ +resource "azurerm_user_assigned_identity" "encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + resource_group_name = azurerm_resource_group.ws.name + location = azurerm_resource_group.ws.location + tags = local.tre_workspace_tags + + name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" + + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_role_assignment" "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.encryption_identity[0].principal_id +} + +resource "azurerm_key_vault_key" "encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + + name = local.kv_encryption_key_name + key_vault_id = var.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/templates/workspaces/base/terraform/locals.tf b/templates/workspaces/base/terraform/locals.tf index 9de7b57b56..37a8263266 100644 --- a/templates/workspaces/base/terraform/locals.tf +++ b/templates/workspaces/base/terraform/locals.tf @@ -8,4 +8,5 @@ locals { tre_id = var.tre_id tre_workspace_id = var.tre_resource_id } + kv_encryption_key_name = "tre-encryption-${local.workspace_resource_name_suffix}" } diff --git a/templates/workspaces/base/terraform/storage.tf b/templates/workspaces/base/terraform/storage.tf index 0946ac6f12..5992d88d7c 100644 --- a/templates/workspaces/base/terraform/storage.tf +++ b/templates/workspaces/base/terraform/storage.tf @@ -9,6 +9,14 @@ resource "azurerm_storage_account" "stg" { cross_tenant_replication_enabled = false // not technically needed as cross tenant replication not supported when is_hns_enabled = true tags = local.tre_workspace_tags + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.encryption_identity[0].id] + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -131,3 +139,13 @@ resource "azurerm_private_endpoint" "stgdfspe" { subresource_names = ["dfs"] } } + +resource "azurerm_storage_account_customer_managed_key" "stg_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + storage_account_id = azurerm_storage_account.stg.id + key_vault_id = var.key_store_id + key_name = local.kv_encryption_key_name + user_assigned_identity_id = azurerm_user_assigned_identity.encryption_identity[0].id + + depends_on = [azurerm_key_vault_key.encryption_key] +} diff --git a/templates/workspaces/base/terraform/variables.tf b/templates/workspaces/base/terraform/variables.tf index 567d468ede..42eecbad69 100644 --- a/templates/workspaces/base/terraform/variables.tf +++ b/templates/workspaces/base/terraform/variables.tf @@ -122,3 +122,16 @@ variable "workspace_owner_object_id" { variable "arm_environment" { type = string } + +variable "enable_cmk_encryption" { + type = bool + default = false + description = "Enable CMK encryption for the workspace" +} + +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 +} + diff --git a/templates/workspaces/base/terraform/workspace.tf b/templates/workspaces/base/terraform/workspace.tf index 14f4786e07..2e6b36d173 100644 --- a/templates/workspaces/base/terraform/workspace.tf +++ b/templates/workspaces/base/terraform/workspace.tf @@ -56,6 +56,10 @@ module "airlock" { short_workspace_id = local.short_workspace_id airlock_processor_subnet_id = module.network.airlock_processor_subnet_id arm_environment = var.arm_environment + enable_cmk_encryption = var.enable_cmk_encryption + key_store_id = var.key_store_id + kv_encryption_key_name = local.kv_encryption_key_name + encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption_identity[0].id : null depends_on = [ module.network, ] @@ -76,6 +80,10 @@ module "azure_monitor" { azure_monitor_ods_opinsights_dns_zone_id = module.network.azure_monitor_ods_opinsights_dns_zone_id azure_monitor_agentsvc_dns_zone_id = module.network.azure_monitor_agentsvc_dns_zone_id blob_core_dns_zone_id = module.network.blobcore_zone_id + enable_cmk_encryption = var.enable_cmk_encryption + key_store_id = var.key_store_id + kv_encryption_key_name = local.kv_encryption_key_name + encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption_identity[0].id : null enable_local_debugging = var.enable_local_debugging depends_on = [ module.network,