Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure Virtual Desktop Workspace Service #1865

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions templates/workspace_services/avd-aad/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dockerfile.tmpl
11 changes: 11 additions & 0 deletions templates/workspace_services/avd-aad/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
WORKSPACE_ID=__CHANGE_ME__
ID=__CHANGE_ME__
AZURE_LOCATION=__CHANGE_ME__
LOCAL_ADMIN_NAME="adminuser"
VM_SIZE="Standard_D2s_v3"
VM_COUNT="1"
VM_LICENSE_TYPE="Windows_Client"

ARM_CLIENT_ID=__CHANGE_ME__
ARM_CLIENT_SECRET=__CHANGE_ME__
ARM_TENANT_ID=__CHANGE_ME__
13 changes: 13 additions & 0 deletions templates/workspace_services/avd-aad/Dockerfile.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM debian:buster

# PORTER_MIXINS

ARG BUNDLE_DIR

COPY . $BUNDLE_DIR

WORKDIR $BUNDLE_DIR

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1

RUN az bicep build --file ./bicep/main.bicep --outfile main.json
27 changes: 27 additions & 0 deletions templates/workspace_services/avd-aad/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# azure-virtual-desktop-bicep

AzureAD-Joined session hosts requires the following:
- Non-overlapping private IP space
- Permissions to grant users the "Virtual Machine User Login" role at the resource group level for to login

## Resources

### Learning Bicep

- [Define resources with Bicep and ARM templates](https://docs.microsoft.com/azure/templates)
- [Bicep modules](https://docs.microsoft.com/azure/azure-resource-manager/templates/bicep-modules)
- [Iterative loops in Bicep](https://docs.microsoft.com/azure/azure-resource-manager/bicep/loop-resources#resource-iteration-with-condition)

### Specific Azure Resource Definitions in Bicep

- [Bicep reference: Microsoft.DesktopVirtualization/hostPools](https://docs.microsoft.com/azure/templates/microsoft.desktopvirtualization/hostpools?tabs=bicep)
- [Bicep reference: Microsoft.DesktopVirtualization/applicationGroups](https://docs.microsoft.com/azure/templates/microsoft.desktopvirtualization/applicationgroups?tabs=bicep)
- [Bicep reference: Microsoft.DesktopVirtualization/workspaces](https://docs.microsoft.com/azure/templates/microsoft.desktopvirtualization/workspaces?tabs=bicep)
- [ARM reference: Microsoft.DesktopVirtualization@2021-07-12](https://github.com/Azure/bicep-types-az/blob/main/generated/desktopvirtualization/microsoft.desktopvirtualization/2021-07-12/types.md)
- [ARM template for VM creation](https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/armtemplates/Hostpool_12-9-2021/nestedTemplates/managedDisks-galleryvm.json)

## Some notes

The `Microsoft.DesktopVirtualization` namespace isn't well documented yet, so I recommend you reference the [REST API docs](https://docs.microsoft.com/rest/api/desktopvirtualization/) to determine which API versions you should be using.

To research common VM extension error messages, see [Understand common error messages when you manage virtual machines in Azure](https://docs.microsoft.com/troubleshoot/azure/virtual-machines/error-messages).
17 changes: 17 additions & 0 deletions templates/workspace_services/avd-aad/bicep/bicepconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"analyzers": {
"core": {
"verbose": false,
"enabled": true,
"rules": {
"no-hardcoded-env-urls": {
"level": "warning",
"excludedhosts": [
"schema.management.azure.com",
"wvdportalstorageblob.blob.core.windows.net"
]
}
}
}
}
}
117 changes: 117 additions & 0 deletions templates/workspace_services/avd-aad/bicep/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
targetScope = 'subscription'

param id string
param workspaceId string
param treId string
param tags object = {}
param localAdminName string = 'adminuser'
param vmSize string = 'Standard_D2as_v4'

param vmCount int = 1
param deploymentTime string = utcNow()
@secure()
param passwordSeed string = newGuid()

var shortWorkspaceId = substring(workspaceId, length(workspaceId) - 4, 4)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use skip() instead of substring()

var shortServiceId = substring(id, length(id) - 4, 4)
var workspaceResourceNameSuffix = '${treId}-ws-${shortWorkspaceId}'
var serviceResourceNameSuffix = '${workspaceResourceNameSuffix}-svc-${shortServiceId}'

var deploymentNamePrefix = '${serviceResourceNameSuffix}-{rtype}-${deploymentTime}'

resource workspaceResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' existing = {
name: 'rg-${workspaceResourceNameSuffix}'
}

resource workspaceKeyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' existing = {
name: 'kv-${workspaceResourceNameSuffix}'
scope: workspaceResourceGroup
}

// Doubling up the unique string with the same seed does not increase password entropy,
// but it guarantees that there will be at least three character classes present in the password
// to meet operating system password complexity requirements
// This could be enhanced by specifying a second, different seed GUID
var localAdminPasswordGenerated = '${uniqueString(passwordSeed)}_${toUpper(uniqueString(passwordSeed))}'

var secrets = [
{
secretValue: passwordSeed
secretName: '${shortServiceId}-${deploymentTime}-localadminpwdseed'
}
{
// Generate a new password for the required local VM admin
secretValue: localAdminPasswordGenerated
secretName: '${shortServiceId}-${deploymentTime}-localadminpwd'
}
]

// Persist the new password in the workspace's Key Vault
module keyVaultSecrets 'modules/keyVaultSecret.bicep' = [for (secret, i) in secrets: {
scope: workspaceResourceGroup
name: '${replace(deploymentNamePrefix, '{rtype}', 'Secret')}-${i}'
params: {
workspaceKeyVaultName: workspaceKeyVault.name
secretValue: secrets[i].secretValue
secretName: secrets[i].secretName
}
}]

resource workspaceVirtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' existing = {
scope: workspaceResourceGroup
name: 'vnet-${workspaceResourceNameSuffix}'
}

module hostPool 'modules/hostPools.bicep' = {
scope: workspaceResourceGroup
name: replace(deploymentNamePrefix, '{rtype}', 'AVD-HostPool')
params: {
name: serviceResourceNameSuffix
tags: tags
location: workspaceResourceGroup.location
hostPoolType: 'Pooled'
}
}

module applicationGroup 'modules/applicationGroup.bicep' = {
scope: workspaceResourceGroup
name: replace(deploymentNamePrefix, '{rtype}', 'AVD-ApplicationGroup')
params: {
name: serviceResourceNameSuffix
tags: tags
location: workspaceResourceGroup.location
hostPoolId: hostPool.outputs.id
}
}

module workspace 'modules/workspace.bicep' = {
scope: workspaceResourceGroup
name: replace(deploymentNamePrefix, '{rtype}', 'AVD-Workspace')
params: {
name: serviceResourceNameSuffix
tags: tags
location: workspaceResourceGroup.location
applicationGroupId: applicationGroup.outputs.id
}
}

module sessionHost 'modules/sessionHost.bicep' = {
scope: workspaceResourceGroup
name: replace(deploymentNamePrefix, '{rtype}', 'AVD-SessionHosts')
params: {
name: serviceResourceNameSuffix
tags: tags
location: workspaceResourceGroup.location
localAdminName: localAdminName
localAdminPassword: localAdminPasswordGenerated
subnetName: 'ServicesSubnet'
vmSize: vmSize
vmCount: vmCount
vnetId: workspaceVirtualNetwork.id
hostPoolName: hostPool.outputs.name
hostPoolRegToken: hostPool.outputs.token
deploymentNameStructure: deploymentNamePrefix
}
}

output connection_uri string = 'https://aka.ms/wvdarmweb'
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
param name string
param tags object
param location string
param hostPoolId string

resource applicationGroup 'Microsoft.DesktopVirtualization/applicationGroups@2021-03-09-preview' = {
name: 'ag-${name}'
location: location
tags: tags
properties: {
applicationGroupType: 'Desktop'
hostPoolArmPath: hostPoolId
}
}

output id string = applicationGroup.id
36 changes: 36 additions & 0 deletions templates/workspace_services/avd-aad/bicep/modules/hostPools.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
param name string
param tags object
param location string
@allowed([
'Personal'
'Pooled'
])
param hostPoolType string

param baseTime string = utcNow('u')

var expirationTime = dateTimeAdd(baseTime, 'PT48H')

resource hostPool 'Microsoft.DesktopVirtualization/hostPools@2021-03-09-preview' = {
name: 'hp-${name}'
location: location
tags: tags
properties: {
hostPoolType: hostPoolType
loadBalancerType: 'BreadthFirst'
preferredAppGroupType: 'Desktop'
maxSessionLimit: 999999
startVMOnConnect: false
validationEnvironment: false
customRdpProperty: 'drivestoredirect:s:0;audiomode:i:0;videoplaybackmode:i:1;redirectclipboard:i:0;redirectprinters:i:0;devicestoredirect:s:0;redirectcomports:i:0;redirectsmartcards:i:1;usbdevicestoredirect:s:0;enablecredsspsupport:i:1;use multimon:i:1;targetisaadjoined:i:1'
registrationInfo: {
expirationTime: expirationTime
token: null
registrationTokenOperation: 'Update'
}
}
}

output id string = hostPool.id
output name string = hostPool.name
output token string = string(hostPool.properties.registrationInfo.token)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
param workspaceKeyVaultName string
param secretName string
@secure()
param secretValue string

resource localAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
name: '${workspaceKeyVaultName}/${secretName}'
properties: {
value: secretValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
param name string

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
name: 'rbac${name}'
properties: {
roleDefinitionId: 'string'
principalId: 'string'
principalType: 'string'
description: 'string'
condition: 'string'
conditionVersion: 'string'
delegatedManagedIdentityResourceId: 'string'
}
}
Loading
Loading