Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
24415b9
feat(kms): add Key Vault KMS encryption support for AKS clusters
xinWeiWei24 Nov 24, 2025
8a0655a
Remove dynamic variable in plan phase
xinWeiWei24 Nov 24, 2025
9e88fcf
Change the default value of key_vault_network_access to Public
xinWeiWei24 Nov 24, 2025
516f2c2
Use UserAssigned identity type when enable KMS
xinWeiWei24 Nov 24, 2025
ef02196
Add Key Vault Crypto Service Encryption User role to key and key vault
xinWeiWei24 Nov 24, 2025
6b352f1
Remove dynamic variable in plan stage
xinWeiWei24 Nov 24, 2025
579a4a3
Add wrapKey and unwrapKey
xinWeiWei24 Nov 24, 2025
602d782
Only add role to key vault
xinWeiWei24 Nov 24, 2025
7d029de
Delete key role dependancy
xinWeiWei24 Nov 24, 2025
c6e7275
Only wrap and unwrap
xinWeiWei24 Nov 24, 2025
13124a0
Add Key Vault Crypto User role to key and key vault
xinWeiWei24 Nov 24, 2025
a7608be
fix bugs
xinWeiWei24 Nov 24, 2025
fdf8720
Use resource_id and add purge_soft_delete_on_destroy feature
xinWeiWei24 Nov 25, 2025
546b335
Export key resource_id
xinWeiWei24 Nov 25, 2025
d6cbc8e
Disable recover_soft_deleted_key_vaults
xinWeiWei24 Nov 25, 2025
180c6ab
Generate key vault name randomly
xinWeiWei24 Nov 25, 2025
b0c57c8
Only generate lower characters
xinWeiWei24 Nov 25, 2025
8833d29
Add Key Vault Crypto Service Encryption User role to key vault key
xinWeiWei24 Nov 25, 2025
008eedf
fix bugs
xinWeiWei24 Nov 25, 2025
8f8ef17
Delete Key Vault Crypto User role to key in aks
xinWeiWei24 Nov 25, 2025
6b01a2d
Delete Key Vault Crypto User role for key
xinWeiWei24 Nov 25, 2025
dc6e192
Use new-pipeline-test.yml to test
xinWeiWei24 Nov 25, 2025
c4f3c2e
Use new-pipeline-test.yml to test aks-cli
xinWeiWei24 Nov 25, 2025
cad0e79
Use nap azure-complex to test
xinWeiWei24 Nov 25, 2025
7ac9eac
Revert "Use nap azure-complex to test"
xinWeiWei24 Nov 26, 2025
83f28c3
Revert "Use new-pipeline-test.yml to test aks-cli"
xinWeiWei24 Nov 26, 2025
586fa12
Revert "Use new-pipeline-test.yml to test"
xinWeiWei24 Nov 26, 2025
ef9e546
Update the comments
xinWeiWei24 Nov 26, 2025
006a034
Update the comments
xinWeiWei24 Nov 26, 2025
fc46ff6
Update command to apply in test_aks_aad
xinWeiWei24 Nov 26, 2025
1a8c1ca
Update modules/terraform/azure/main.tf
xinWeiWei24 Nov 26, 2025
d113022
Update modules/terraform/azure/main.tf
xinWeiWei24 Nov 26, 2025
eb0ac1b
Update modules/terraform/azure/main.tf
xinWeiWei24 Nov 26, 2025
698537c
Update modules/terraform/azure/aks-cli/main.tf
xinWeiWei24 Nov 26, 2025
199c1fb
Delete explicit depends_on
xinWeiWei24 Nov 26, 2025
641beec
Update key vault random string
xinWeiWei24 Nov 26, 2025
30b5bb3
Require managed_identity_name to be set when KMS is enabled
xinWeiWei24 Nov 26, 2025
67a5ecf
Use nap azure-complex to test
xinWeiWei24 Nov 25, 2025
3915675
Revert "Use nap azure-complex to test"
xinWeiWei24 Nov 26, 2025
e2d1484
Delete version from key-vault module
xinWeiWei24 Nov 27, 2025
0c1291e
Make key vaults config as a list
xinWeiWei24 Nov 27, 2025
12445bb
Use nap azure-complex to test
xinWeiWei24 Nov 27, 2025
2fc1655
Fix output bug
xinWeiWei24 Nov 27, 2025
082d7b0
Revert "Use nap azure-complex to test"
xinWeiWei24 Nov 27, 2025
3f702d5
Validate kms module ouput in aks/aks-cli module instread of on the main
xinWeiWei24 Dec 2, 2025
7766b51
Use nap complex to test
xinWeiWei24 Dec 2, 2025
68a12c8
Revert "Use nap complex to test"
xinWeiWei24 Dec 2, 2025
de82758
Move kms configs to an object
xinWeiWei24 Dec 2, 2025
e1fdc8c
Add README.md in key-vault module and update azure.tfvars template to…
xinWeiWei24 Dec 2, 2025
db77007
Use nap complex to test
xinWeiWei24 Dec 2, 2025
7ecc85b
Revert "Use nap complex to test"
xinWeiWei24 Dec 2, 2025
d2cc732
Reduce duplicate code
xinWeiWei24 Dec 4, 2025
c12f197
Merge branch 'main' into xinwei/support_kms
xinWeiWei24 Dec 4, 2025
3022e80
Change condition
xinWeiWei24 Dec 4, 2025
7bd00c0
Use nap complex to test
xinWeiWei24 Dec 3, 2025
5c7195f
Revert "Use nap complex to test"
xinWeiWei24 Dec 4, 2025
6622080
key-vault: update README.md
xinWeiWei24 Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion modules/terraform/azure/aks-cli/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ locals {
pool.name => pool
}

key_management_service = (
var.aks_cli_config.kms_key_vault_name != null &&
var.aks_cli_config.kms_key_name != null
) ? {
key_vault_id = try(
var.key_vaults[var.aks_cli_config.kms_key_vault_name].id,
error("Specified kms_key_vault_name '${var.aks_cli_config.kms_key_vault_name}' does not exist in Key Vaults: ${join(", ", keys(var.key_vaults))}")
)
key_vault_key_id = try(
var.key_vaults[var.aks_cli_config.kms_key_vault_name].keys[var.aks_cli_config.kms_key_name].id,
error("Specified kms_key_name '${var.aks_cli_config.kms_key_name}' does not exist in Key Vault '${var.aks_cli_config.kms_key_vault_name}' keys: ${join(", ", keys(var.key_vaults[var.aks_cli_config.kms_key_vault_name].keys))}")
)
key_vault_key_resource_id = try(
var.key_vaults[var.aks_cli_config.kms_key_vault_name].keys[var.aks_cli_config.kms_key_name].resource_id,
error("Specified kms_key_name '${var.aks_cli_config.kms_key_name}' does not exist in Key Vault '${var.aks_cli_config.kms_key_vault_name}' keys: ${join(", ", keys(var.key_vaults[var.aks_cli_config.kms_key_vault_name].keys))}")
)
} : null

kubernetes_version = (
var.aks_cli_config.kubernetes_version == null ?
"" :
Expand Down Expand Up @@ -48,6 +66,17 @@ locals {
])
)


kms_parameters = (
local.key_management_service == null || var.aks_cli_config.managed_identity_name == null ?
"" :
join(" ", [
"--enable-azure-keyvault-kms",
format("--azure-keyvault-kms-key-id %s", local.key_management_service.key_vault_key_id),
format("--azure-keyvault-kms-key-vault-network-access %s", var.aks_cli_config.key_vault_network_access)
])
)

subnet_id_parameter = (local.aks_subnet_id == null ?
"" :
format(
Expand Down Expand Up @@ -105,6 +134,7 @@ locals {
"--no-ssh-key",
local.kubernetes_version,
local.optional_parameters,
local.kms_parameters,
local.subnet_id_parameter,
local.managed_identity_parameter,
local.api_server_vnet_integration_parameter,
Expand Down Expand Up @@ -172,7 +202,9 @@ resource "terraform_data" "aks_cli" {
depends_on = [
terraform_data.enable_aks_cli_preview_extension,
azurerm_role_assignment.network_contributor,
azurerm_role_assignment.network_contributor_api_server_subnet
azurerm_role_assignment.network_contributor_api_server_subnet,
azurerm_role_assignment.aks_key_service_encryption_user,
azurerm_role_assignment.aks_kv_service_encryption_user
]

input = {
Expand Down Expand Up @@ -219,3 +251,17 @@ resource "terraform_data" "aks_nodepool_cli" {
])
}
}

# Grant Key Vault Crypto Service Encryption User role for KMS encryption
resource "azurerm_role_assignment" "aks_key_service_encryption_user" {
count = local.key_management_service == null || var.aks_cli_config.managed_identity_name == null ? 0 : 1
scope = local.key_management_service.key_vault_key_resource_id
role_definition_name = "Key Vault Crypto Service Encryption User"
principal_id = azurerm_user_assigned_identity.userassignedidentity[0].principal_id
}
resource "azurerm_role_assignment" "aks_kv_service_encryption_user" {
count = local.key_management_service == null || var.aks_cli_config.managed_identity_name == null ? 0 : 1
scope = local.key_management_service.key_vault_id
role_definition_name = "Key Vault Crypto User"
principal_id = azurerm_user_assigned_identity.userassignedidentity[0].principal_id
}
14 changes: 12 additions & 2 deletions modules/terraform/azure/aks-cli/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ variable "aks_cli_custom_config_path" {
default = null
}

variable "key_vaults" {
description = "Map of Key Vault configurations with keys"
type = map(any)
default = {}
}


variable "aks_cli_config" {
type = object({
role = string
Expand Down Expand Up @@ -59,11 +66,14 @@ variable "aks_cli_config" {
value = string
})), [])
})), [])
optional_parameters = optional(list(object({ # Refer to https://learn.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az-aks-create(aks-preview) for available parameters
optional_parameters = optional(list(object({ # Refer to https://learn.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az-aks-create(aks-preview) for available parameters
name = string
value = string
})), [])
dry_run = optional(bool, false) # If true, only print the command without executing it. Useful for testing.
kms_key_name = optional(string, null)
kms_key_vault_name = optional(string, null)
key_vault_network_access = optional(string, "Public")
dry_run = optional(bool, false) # If true, only print the command without executing it. Useful for testing.
})
}

58 changes: 56 additions & 2 deletions modules/terraform/azure/aks/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,34 @@ locals {
role_assignment_list = var.aks_config.role_assignment_list
subnets = var.subnets
dns_zone_ids = try([for zone_name in var.aks_config.web_app_routing.dns_zone_names : var.dns_zones[zone_name]], null)
key_management_service = (
var.aks_config.kms_key_vault_name != null &&
var.aks_config.kms_key_name != null
) ? {
key_vault_id = try(
var.key_vaults[var.aks_config.kms_key_vault_name].id,
error("Specified kms_key_vault_name '${var.aks_config.kms_key_vault_name}' does not exist in Key Vaults: ${join(", ", keys(var.key_vaults))}")
)
key_vault_key_id = try(
var.key_vaults[var.aks_config.kms_key_vault_name].keys[var.aks_config.kms_key_name].id,
error("Specified kms_key_name '${var.aks_config.kms_key_name}' does not exist in Key Vault '${var.aks_config.kms_key_vault_name}' keys: ${join(", ", keys(var.key_vaults[var.aks_config.kms_key_vault_name].keys))}")
)
key_vault_key_resource_id = try(
var.key_vaults[var.aks_config.kms_key_vault_name].keys[var.aks_config.kms_key_name].resource_id,
error("Specified kms_key_name '${var.aks_config.kms_key_name}' does not exist in Key Vault '${var.aks_config.kms_key_vault_name}' keys: ${join(", ", keys(var.key_vaults[var.aks_config.kms_key_vault_name].keys))}")
)
} : null
}
data "azurerm_client_config" "current" {}

resource "azurerm_user_assigned_identity" "aks_identity" {
count = local.key_management_service != null ? 1 : 0
location = var.location
name = "${local.name}-identity"
resource_group_name = var.resource_group_name
tags = var.tags
}

resource "azurerm_kubernetes_cluster" "aks" {
name = local.name
location = var.location
Expand All @@ -20,6 +45,13 @@ resource "azurerm_kubernetes_cluster" "aks" {
},
)
sku_tier = var.aks_config.sku_tier

# Wait for KMS role assignment to propagate
depends_on = [
azurerm_role_assignment.aks_key_service_encryption_user,
azurerm_role_assignment.aks_kv_service_encryption_user
]

default_node_pool {
name = var.aks_config.default_node_pool.name
node_count = var.aks_config.default_node_pool.node_count
Expand All @@ -45,7 +77,16 @@ resource "azurerm_kubernetes_cluster" "aks" {
dns_service_ip = var.aks_config.network_profile.dns_service_ip
}
identity {
type = "SystemAssigned"
type = local.key_management_service != null ? "UserAssigned" : "SystemAssigned"
identity_ids = local.key_management_service != null ? [azurerm_user_assigned_identity.aks_identity[0].id] : []
}

dynamic "key_management_service" {
for_each = local.key_management_service == null ? [] : [local.key_management_service]
content {
key_vault_key_id = key_management_service.value.key_vault_key_id
key_vault_network_access = var.aks_config.key_vault_network_access
}
}

dynamic "service_mesh_profile" {
Expand Down Expand Up @@ -138,10 +179,23 @@ resource "azurerm_role_assignment" "aks_on_subnet" {

role_definition_name = each.key
scope = var.vnet_id
principal_id = azurerm_kubernetes_cluster.aks.identity[0].principal_id
principal_id = local.key_management_service != null ? azurerm_user_assigned_identity.aks_identity[0].principal_id : azurerm_kubernetes_cluster.aks.identity[0].principal_id
}

resource "local_file" "save_kube_config" {
filename = "/tmp/${azurerm_kubernetes_cluster.aks.fqdn}"
content = azurerm_kubernetes_cluster.aks.kube_config_raw
}

resource "azurerm_role_assignment" "aks_key_service_encryption_user" {
count = local.key_management_service == null ? 0 : 1
scope = local.key_management_service.key_vault_key_resource_id
role_definition_name = "Key Vault Crypto Service Encryption User"
principal_id = azurerm_user_assigned_identity.aks_identity[0].principal_id
}
resource "azurerm_role_assignment" "aks_kv_service_encryption_user" {
count = local.key_management_service != null ? 1 : 0
scope = local.key_management_service.key_vault_id
role_definition_name = "Key Vault Crypto User"
principal_id = azurerm_user_assigned_identity.aks_identity[0].principal_id
}
9 changes: 9 additions & 0 deletions modules/terraform/azure/aks/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ variable "aks_aad_enabled" {
default = false
}

variable "key_vaults" {
description = "Map of Key Vault configurations with keys"
type = map(any)
default = {}
}

variable "aks_config" {
type = object({
role = string
Expand Down Expand Up @@ -151,6 +157,9 @@ variable "aks_config" {
web_app_routing = optional(object({
dns_zone_names = list(string)
}), null)
kms_key_name = optional(string, null)
kms_key_vault_name = optional(string, null)
key_vault_network_access = optional(string, "Public")
})

validation {
Expand Down
52 changes: 52 additions & 0 deletions modules/terraform/azure/key-vault/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
data "azurerm_client_config" "current" {}

resource "random_string" "kv_suffix" {
count = var.key_vault_config != null ? 1 : 0
length = 4
special = false
upper = false
numeric = true
}
resource "azurerm_key_vault" "kv" {
count = var.key_vault_config != null ? 1 : 0
name = "${lower(var.key_vault_config.name)}-${random_string.kv_suffix[0].result}"
location = var.location
resource_group_name = var.resource_group_name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
rbac_authorization_enabled = true

tags = var.tags
}

resource "azurerm_key_vault_key" "kms_key" {
for_each = var.key_vault_config != null ? {
for key in var.key_vault_config.keys : key.key_name => key
} : {}

name = each.value.key_name
key_vault_id = azurerm_key_vault.kv[0].id
key_type = "RSA"
key_size = 2048
key_opts = ["encrypt", "decrypt", "wrapKey", "unwrapKey"]

depends_on = [
azurerm_role_assignment.current_user_crypto_officer
]
}

# Grant current user/service principal Key Vault Crypto Officer role to create keys
resource "azurerm_role_assignment" "current_user_crypto_officer" {
count = var.key_vault_config != null ? 1 : 0
scope = azurerm_key_vault.kv[0].id
role_definition_name = "Key Vault Crypto Officer"
principal_id = data.azurerm_client_config.current.object_id
}

# Grant Key Vault Contributor role for purge operations
resource "azurerm_role_assignment" "kv_contributor" {
count = var.key_vault_config != null ? 1 : 0
scope = azurerm_key_vault.kv[0].id
role_definition_name = "Key Vault Contributor"
principal_id = data.azurerm_client_config.current.object_id
}
13 changes: 13 additions & 0 deletions modules/terraform/azure/key-vault/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
output "key_vaults" {
description = "Key Vault with all its keys and their IDs"
value = {
id = try(azurerm_key_vault.kv[0].id, null)
keys = {
for key_path, key in azurerm_key_vault_key.kms_key :
key.name => {
id = key.id
resource_id = key.resource_id
}
}
}
}
37 changes: 37 additions & 0 deletions modules/terraform/azure/key-vault/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
variable "resource_group_name" {
description = "Name of the resource group"
type = string
}

variable "location" {
description = "Azure region location"
type = string
}

variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}

variable "key_vault_config" {
description = "Key Vault configuration for AKS KMS encryption"
type = object({
name = string # Key Vault name
keys = list(object({
key_name = string # Encryption key name
}))
})
default = null

validation {
condition = (
var.key_vault_config == null ? true : (
length(var.key_vault_config.name) >= 3 &&
length(var.key_vault_config.name) <= 20 &&
length(var.key_vault_config.keys) >= 1
)
)
error_message = "Key Vault name must be 3-20 characters (total 24 after adding 4-char random suffix), and at least one key must be defined."
}
}
25 changes: 23 additions & 2 deletions modules/terraform/azure/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ locals {

aks_cli_custom_config_path = "${path.cwd}/../../../scenarios/${var.scenario_type}/${var.scenario_name}/config/aks_custom_config.json"

all_subnets = merge([for network in var.network_config_list : module.virtual_network[network.role].subnets]...)
all_subnets = merge([for network in var.network_config_list : module.virtual_network[network.role].subnets]...)
all_key_vaults = merge([for kv_name, kv in module.key_vault : { (kv_name) = kv.key_vaults }]...)
updated_aks_config_list = length(var.aks_config_list) > 0 ? [
for aks in var.aks_config_list : merge(
aks,
Expand Down Expand Up @@ -54,10 +55,17 @@ locals {
] : []

aks_cli_config_map = { for aks in local.updated_aks_cli_config_list : aks.role => aks }

key_vault_config_map = { for kv in var.key_vault_config_list : kv.name => kv }
}

provider "azurerm" {
features {}
features {
key_vault {
purge_soft_delete_on_destroy = true
recover_soft_deleted_key_vaults = false
}
}
}

module "public_ips" {
Expand Down Expand Up @@ -87,6 +95,16 @@ module "dns_zones" {
tags = local.tags
}

module "key_vault" {
for_each = local.key_vault_config_map

source = "./key-vault"
resource_group_name = local.run_id
location = local.region
key_vault_config = each.value
tags = local.tags
}

module "aks" {
for_each = local.aks_config_map

Expand All @@ -104,6 +122,7 @@ module "aks" {
network_policy = local.aks_network_policy
dns_zones = try(module.dns_zones.dns_zone_ids, null)
aks_aad_enabled = local.aks_aad_enabled
key_vaults = local.all_key_vaults
}

module "aks-cli" {
Expand All @@ -116,4 +135,6 @@ module "aks-cli" {
tags = local.tags
subnets_map = local.all_subnets
aks_cli_custom_config_path = local.aks_cli_custom_config_path
key_vaults = local.all_key_vaults
}

Loading