Create a confidential compute avd session host with Terraform part 2

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.

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.

When we check out the OS disk we can see the encryption type. Here we can see it’s using a customer managed key.

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

Leave a Reply

Your email address will not be published. Required fields are marked *