Hi There,
Welcome to the 2nd part of my 2 serie blogpost about AVD on confidential virtual machines. In the first part I showed how to create the key vault, keyvault key and disk encryption set. You can check it here.
In this part I’ll focus on the deployment of a session host with Terraform.
Before we start lets see what already existing resources that we need.
- Log Analytics workspace
- storage account for boot diagnostics
- subnet for session hosts
- Key Vault
- Disk Encryption Set
- Existing host pool
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=3.67.0"
}
}
}
provider "azurerm" {
features {}
}
provider "azurerm" {
features {}
alias = "hub"
subscription_id = var.subscription_id_mgmt
}
provider "azurerm" {
features {}
alias = "prod"
subscription_id = var.subscription_id_prd
}
provider "azurerm" {
features {}
alias = "identity"
subscription_id = var.subscription_id_identity
}
provider "azurerm" {
features {}
alias = "avd"
subscription_id = var.subscription_id_avd
}
data "azurerm_storage_account" "bootdiag" {
name = "sthubjvnbootdiag01"
resource_group_name = "rg-hub-jvn-storage-01"
}
data "azurerm_log_analytics_workspace" "law" {
provider = azurerm.hub
name = "law-${var.env}-${var.prefix}-01"
resource_group_name = "rg-${var.env}-${var.prefix}-management-01"
}
data "azurerm_resource_group" "rg-sessionhosts" {
provider = azurerm.avd
name = "rg-${var.spoke}-${var.prefix}-${var.solution}-shared-sessionhosts-01"
}
data "azurerm_virtual_network" "avd-vnet" {
provider = azurerm.hub
name = "vnet-${var.spoke}-${var.prefix}-${var.solution}-we-01"
resource_group_name = "rg-${var.spoke}-${var.prefix}-${var.solution}-networking-01"
}
data "azurerm_key_vault" "avd-keyvault" {
provider = azurerm.avd
name = "kv-${var.spoke}-${var.prefix}-${var.solution}-80"
resource_group_name = "rg-${var.spoke}-${var.prefix}-${var.solution}-management-01"
}
data "azurerm_key_vault_secret" "loc-admin" {
name = "loc-admin"
key_vault_id = data.azurerm_key_vault.avd-keyvault.id
}
data "azurerm_virtual_desktop_host_pool" "hp" {
provider = azurerm.hub
name = "hp-${var.spoke}-${var.prefix}-${var.solution}-we-01"
resource_group_name = "rg-${var.spoke}-${var.prefix}-${var.solution}-backplane-01"
}
data "azurerm_disk_encryption_set" "des" {
provider = azurerm.avd
name = "des-prd-jvn-avd-50"
resource_group_name = "rg-prd-jvn-avd-management-01"
}
Now let’s take a look at some of the settings that we need to define that are very specific for confidential compute.
- vTPM enabled > is required
- secure boot > can be disabled but not recommended
- security encryption type for the os disk > DiskWithVMGuestState
- disk encryption set for the os disk that has been created already
- the vm sku for confidential compute > Standard_DC2as_v5
Now that we have all these prerequisites let’s have a look at the code for the session hosts itself. The code for the confidential compute is highlighted.
resource "azurerm_windows_virtual_machine" "vm" {
provider = azurerm.avd
name = "${var.vm_name}${count.index + 1}"
location = data.azurerm_resource_group.rg-sessionhosts.location
resource_group_name = data.azurerm_resource_group.rg-sessionhosts.name
size = var.vm_size
network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"]
count = var.vm_count
license_type = "None"
vtpm_enabled = "true"
secure_boot_enabled = "true"
identity {
type = "SystemAssigned"
}
admin_username = var.admin_username
admin_password = var.admin_password
source_image_reference {
publisher = var.image_publisher
offer = var.image_offer
sku = var.image_sku
version = var.image_version
}
os_disk {
name = "c-${var.vm_name}${count.index + 1}"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
security_encryption_type = "DiskWithVMGuestState"
secure_vm_disk_encryption_set_id = data.azurerm_disk_encryption_set.des.id
}
provision_vm_agent = true
boot_diagnostics {
storage_account_uri = data.azurerm_storage_account.bootdiag.primary_blob_endpoint
}
zone = (count.index % 3) + 1
tags = {
"Location" = "We"
"Costcenter" = "IT"
"Purpose" = "AVD Session Host"
"Environment" = "Prd"
}
}
resource "azurerm_network_interface" "nic" {
provider = azurerm.avd
name = "nic-01-${var.vm_name}${count.index}"
location = data.azurerm_resource_group.rg-sessionhosts.location
resource_group_name = data.azurerm_resource_group.rg-sessionhosts.name
count = var.vm_count
ip_configuration {
name = "ipc-${var.vm_name}${count.index}"
subnet_id = var.subnet_id
private_ip_address_allocation = "Dynamic"
}
tags = {
"Location" = "We"
"Costcenter" = "IT"
"Purpose" = "AVD Session Host Nic"
"Environment" = "Prd"
}
}
The final pieces of code are for the ADDS domain join and for registering the session host in the host pool.
resource "azurerm_virtual_machine_extension" "domain_join" {
count = var.vm_count
name = "${var.vm_name}-${count.index + 1}-domainJoin"
virtual_machine_id = azurerm_windows_virtual_machine.vm.*.id[count.index]
publisher = "Microsoft.Compute"
type = "JsonADDomainExtension"
type_handler_version = "1.3"
auto_upgrade_minor_version = true
settings = <<SETTINGS
{
"Name": "${var.domain}",
"OUPath": "${var.oupath}",
"User": "${var.domainuser}@${var.domain}",
"Restart": "true",
"Options": "3"
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"Password": "${var.domainpassword}"
}
PROTECTED_SETTINGS
lifecycle {
ignore_changes = [settings, protected_settings]
}
}
resource "azurerm_virtual_machine_extension" "registersessionhost" {
name = "registersessionhost"
virtual_machine_id = azurerm_windows_virtual_machine.vm[count.index].id
depends_on = [
azurerm_windows_virtual_machine.vm
]
publisher = "Microsoft.Powershell"
count = var.vm_count
type = "DSC"
type_handler_version = "2.73"
auto_upgrade_minor_version = true
settings = <<SETTINGS
{
"ModulesUrl": "${var.artifactslocation}",
"ConfigurationFunction" : "Configuration.ps1\\AddSessionHost",
"Properties": {
"hostPoolName": "${data.azurerm_virtual_desktop_host_pool.hp.name}"
}
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"properties" : {
"registrationInfoToken" : "${azurerm_virtual_desktop_host_pool_registration_info.avd_token.token}"
}
}
PROTECTED_SETTINGS
lifecycle {
ignore_changes = [settings, protected_settings]
}
}
Let’s see what we can see in the portal after deploying this session host.
![](https://johanvanneuville.com/wp-content/uploads/2023/11/image-38-1024x482.png)
You will see the security type is confidential. The option Integrity Monitoring isn’t active because currently Terraform doesn’t support this option. I opened a feature request for this on Github.
![](https://johanvanneuville.com/wp-content/uploads/2023/11/image-37-1024x666.png)
When we check out the OS disk we can see the encryption type. Here we can see it’s using a customer managed key.
![](https://johanvanneuville.com/wp-content/uploads/2023/11/image-39-1024x175.png)
![](https://johanvanneuville.com/wp-content/uploads/2023/11/image-40.png)
![](https://johanvanneuville.com/wp-content/uploads/2023/11/image-41.png)
There you go, an Azure Virtual Desktop session host running on confidential compute hardware. Using this feature will improve your security a lot.
As usual you can find the code on my Github here.
In case you have any questions about this blog post feel free to leave a comment or to contact me on my socials.
1 thought on “Create a confidential compute avd session host with Terraform part 2”