Skip to content

Commit

Permalink
Add CMK support for core resources (#4149)
Browse files Browse the repository at this point in the history
* added management key vault

* added cmk for vmss and storage accounts

* add default value for variables

* add CMK for cosmos accounts

* move tre-encryption key from mgmt to core

* fix order of creation for encryption key

* add cmk for the state store in mgmt

* add support for external KV

* revert CMK for cosmos - not working, need to redo this

* refine comments and files names

* remove redundant space

* add space

* upper case in comment

* revert cosmos tags

* update changelog + core version

* remove unused var

* remove redundant variable

* remove redundant variables

* add check for enable_cmk_encryption for the key_store_id variable in tf

* bugfix: remove redundant data keyword

* add enable_cmk_encryption check in module variables

---------

Co-authored-by: Matthew Fortunka <[email protected]>
  • Loading branch information
yuvalyaron and Matthew Fortunka authored Nov 26, 2024
1 parent f40dee6 commit 38bd432
Show file tree
Hide file tree
Showing 26 changed files with 507 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
7 changes: 7 additions & 0 deletions config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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__

Expand Down Expand Up @@ -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"
Expand Down
16 changes: 16 additions & 0 deletions core/terraform/airlock/airlock_processor.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 82 additions & 0 deletions core/terraform/airlock/storage_accounts.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@


# 'External' storage account - drop location for import
resource "azurerm_storage_account" "sa_import_external" {
name = local.import_external_storage_name
Expand All @@ -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"
})
Expand Down Expand Up @@ -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
Expand All @@ -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"
})
Expand Down Expand Up @@ -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
Expand All @@ -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"
})
Expand All @@ -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" {
Expand Down Expand Up @@ -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"
})
Expand Down Expand Up @@ -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
Expand All @@ -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"
})
Expand Down Expand Up @@ -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
}
21 changes: 21 additions & 0 deletions core/terraform/airlock/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
}
16 changes: 16 additions & 0 deletions core/terraform/appgateway/staticweb.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions core/terraform/appgateway/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
}
16 changes: 16 additions & 0 deletions core/terraform/azure-monitor/azure-monitor.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions core/terraform/azure-monitor/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
}
38 changes: 38 additions & 0 deletions core/terraform/cmk_encryption.tf
Original file line number Diff line number Diff line change
@@ -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
]
}
6 changes: 6 additions & 0 deletions core/terraform/data.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions core/terraform/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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<scheme>[^:/?#]+):)?(?://(?P<fqdn>[^/?#:]*))?(?::(?P<port>[0-9]+))?(?P<path>[^?#]*)(?:\\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?", 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
}
Loading

0 comments on commit 38bd432

Please sign in to comment.