Support for Azure Resource Manager in XenApp & XenDesktop allows you to create and manage a catalog of virtual machines in the Azure cloud. In this blog post we will explore different ways of granting XenApp & XenDesktop access to your Azure subscription.

Azure Service Principals

Support for Azure Resource Manager (ARM) is encapsulated in a component known as the ARM Plugin and it is a standard feature of XenApp & XenDesktop. In order to provision machines in Azure, the ARM Plugin must be granted access to your Azure subscription via a service principal that has been assigned permissions to the relevant Azure resources. A service principal serves the same basic purpose as a user account: it provides the ARM Plugin with an Azure Active Directory identity; credentials for authentication and permissions on Azure resources. Just like user accounts, service principals are configured using Role Based Access Control (RBAC).

Depending on how permissions are defined, we classify service principals as:

  1. Subscription Scope Service Principals; or
  2. Narrow Scope Service Principals

Subscription Scope Service Principals

Subscription Scope Service Principals have Contributor permissions on all resources in the subscription which makes them easy to create and manage. In fact, Citrix Studio automates the process of creating Subscription Scope Service Principals or they can be created manually in PowerShell. Another benefit is that they allow the ARM Plugin to create Azure Resource Groups and completely automate the management of resources. The disadvantage is that the ARM Plugin may have permissions to resources in the subscription that are unrelated to the resources that the ARM Plugin is tasked with managing. This blog post describes how to connect to Azure Resource Manager in XenApp & XenDesktop using Subscription Scope Service Principals and includes a detailed description of the authentication process.

Using the Contributor role allows the ARM Plugin to create, delete, read and write all resources in the subscription, but permissions do not extend to objects in any Azure Active Directory nor are Subscription Scope Service Principals allowed to grant other users or service principals access to resources.

Narrow Scope Service Principals

Narrow Scope Service Principals allow the ARM Plugin access to a limited set of resources defined by you. Azure requires subscription scope permissions in order to create resource groups and the ARM Plugin is therefore unable to create resource groups when using Narrow Scope Service Principals. This means that, in addition to the service principals, you are required to provide a pool of resource groups for each catalog into which machines are to be provisioned.

At the time of writing, Citrix Studio does not support creating Narrow Scope Service Principals or catalogs using Narrow Scope Service Principals so both of these tasks must be performed using PowerShell. However, once a catalog has been created, it can be managed like any other catalog in Studio including adding and deleting machines. If at some point you wish to use an existing Narrow Scope Service Principal with a new pool of resource groups, you must explicitly add permissions to the service principal using PowerShell.

Defining Your Azure Subscription Access Requirements

The techniques and examples in the following sections are intended to demonstrate various options in a reasonably simple manner and may need to be adapted to your particular circumstances. Here are some guidelines to help you define your requirements:

Consider using a Subscription Scope Service Principal if:

  • you want the simplest management experience.
  • you want to avoid using PowerShell and manage everything in Citrix Studio.
  • your Azure subscription is dedicated to a single XenApp & XenDesktop service.
  • you are doing a proof of concept XenApp & XenDesktop installation.
  • your XenApp & XenDesktop administrators have contributor access at Azure subscription scope.

Consider using a Narrow Scope Service Principal if:

  • your Azure subscription is hosting multiple unrelated services.
  • your Azure administrators have different subscription permissions depending on their role.
  • your company has security standards that require access control at a fine-grained level.
  • you have an existing process for creating Narrow Scope Service Principals

It is also worth noting that you can create “child” subscriptions that are billed as part of your primary subscription and refer to the default Azure Active Directory in your primary subscription. This provides another mechanism for controlling access to unrelated resources.

Planning a Narrow Scope Service Principal Catalog

The key decision that must be made before creating a Narrow Scope Service Principal catalog is deciding how many resource groups are required to host the initial and future number of virtual machines. Due to a limitation in the Machine Creation Services that drives the ARM Plugin, it is not possible to add resource groups once a catalog has been created.

Citrix recommends provisioning one catalog per resource group pool.

The ARM Plugin will create the necessary infrastructure in each resource group consisting of storage accounts, security groups, network interfaces, virtual machines etc. Storage accounts are created on demand as needed when and if machines are added to the catalog. This means that the size of a catalog can grow to an upper limit set by the size of the resource group pool and Azure subscription quotas. Once a storage account has been created, it is not deleted until the catalog is deleted. Since any virtual machine can be deleted, it is possible to end up with empty storage accounts. This however is rare because virtual machines tend to be randomly distributed over the available storage accounts so machines have to be tediously selected by inspecting the content of storage accounts in order to deliberately empty a storage account.

Azure limits the number of virtual machines in a resource group to 800, but the ARM plugin uses a different measure. A standard Azure disk has a limit of 500 IO operations per second (IOPS) and a standard storage account has an IOPS limit of 20,000. For this reason, the ARM Plugin provisions no more than 40 machines to a storage account. This limit is currently applied to both standard and premium storage. In addition, the ARM Plugin will create no more than 19 storage accounts in a resource group.

The basic formula for calculating the number of resource groups based on the maximum number of machines is therefore:

Number of resource groups = ceiling( Maximum number of machines / (40 * 19) )

The ARM Plugin assumes that it has exclusive use of the resource group pool, i.e., there should be no user created resources in any of the specified resource groups.

Basics of Azure Role Based Access Control (RBAC)

Access to Azure resources are granted by assigning an RBAC role to a service principal at a certain scope where scope can be a subscription, a resource group or a specific resource. Resources are arranged in a containment hierarchy and the permissions defined by the role apply to all resources below the scope where it is applied. For example, a role applied on a subscription is applied to all resources in the subscription and a role applied to a resource group is applied to all resources contained in the resource group.

An implication of the Azure resource hierarchy is that only service principals with subscription scope permissions are allowed to create resource groups. This is not ideal because it prevents applications like the ARM Plugin from creating resource groups on demand to logical group and manage resources unless they have broad permissions on the full subscription.

A detailed description of access control in Azure can be found here.

Azure has a large selection of built in roles and additionally supports the definition of custom roles.

This article
shows how to create custom roles in PowerShell.

Creating a Subscription Scope Service Principal

For completenes, this example shows how to create a Subscription Scope Service Principal. The details can subsequently be to used to create an Azure connection in Citrix Studio by electing to use an existing service principal or an Azure connection can be created manually in PowerShell.

param(
[string]$applicationName = "SubscriptionScopeSP",
[Parameter(Mandatory=$true)][string]$applicationPassword,
[Parameter(Mandatory=$true)][string]$subscriptionId
)

$application = New-AzureRmADApplication -DisplayName $applicationName -HomePage "https://localhost/$applicationName" `
-IdentifierUris "https://$applicationName" -Password $applicationPassword

New-AzureRmADServicePrincipal -ApplicationId $application.ApplicationId

# Wait for the service principal to become available
Start-Sleep -s 60

New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $application.ApplicationId `
-scope "/subscriptions/$subscriptionId"

Write-Host ("Application ID: " + $application.ApplicationId)

Creating a Basic Narrow Scope Service Principal

In this section we will create the simplest possible Narrow Scope Service Principal where permissions are assigned at the resource group scope. In the next section we will look at how permissions can be further tightened by using custom roles.

The ARM Plugin needs permission to the following resources:

  1. The master image VHD
  2. The virtual network for the machines
  3. The resource groups into which the machines are to be provisioned.

To simplify the script, we make an assumption that Contributor access can be granted at resource group scope. This means that the ARM Plugin will have contributor permissions on the resource group where the image VHD is stored, the resource group that contains the virtual network and the resource group pool where the machines are to be provisioned.

param(
[string]$applicationName = "BasicNarrowScopeSP",
[Parameter(Mandatory=$true)][string]$applicationPassword,
[Parameter(Mandatory=$true)][string]$subscriptionId,
[Parameter(Mandatory=$true)][string[]]$resourceGroups
)

$application = New-AzureRmADApplication -DisplayName $applicationName -HomePage "https://localhost/$applicationName" `
-IdentifierUris "https://$applicationName" -Password $applicationPassword

New-AzureRmADServicePrincipal -ApplicationId $application.ApplicationId

# Wait for the service principal to become available
Start-Sleep -s 60

foreach ($rg in $resourceGroups)
{
  New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $application.ApplicationId `
  -scope "/subscriptions/$subscriptionId/resourcegroups/$rg"
}

Write-Host ("Application ID: " + $application.ApplicationId)

Creating a Narrow Scope Service Principal using Custom Roles

Azure comes with a large set of built in RBAC roles and we made use of the Contributor role in the previous section. As noted, that gave the ARM Plugin slightly broader permissions than strictly required. In this section, we will define a custom role and further tighten access. If desired, access can be completely locked down using additional custom roles and applying roles directly to the image and network resources.

Please note that the required permissions are subject to change as we continue to improve the ARM Plugin and add new features. For our example, we will use the permissions listed below and define a custom role for granting access to the virtual network and the master image at the resource group scope.

The master image VHD

For catalog creation:

  • Microsoft.Storage/storageAccounts/read
  • Microsoft.Storage/storageAccounts/listKeys/action

For Future Citrix Studio support:

  • Microsoft.Resources/subscriptions/resourceGroups/read

The virtual network for the machines

  • Microsoft.Network/virtualNetworks/read
  • Microsoft.Network/virtualNetworks/subnets/join/action

The resource groups into which the machines are to be provisioned

We could create another custom role with the following permissions, but to keep the example simple, we will continue to use the Contributor role for the machine resource groups. These resource groups should not contain any resources not created by the ARM plugin and using the contributor role makes it less likely that changes to the ARM plugin will require changes to the service principal.

  • Microsoft.Compute/virtualMachines/*
  • Microsoft.Network/networkInterfaces/*
  • Microsoft.Network/networkSecurityGroups/*
  • Microsoft.Resources/deployments/*
  • Microsoft.Resources/subscriptions/resourceGroups/read
  • Microsoft.Storage/storageAccounts/*
  • Microsoft.Storage/storageAccounts/listKeys/action

XenApp & XenDesktop Custom Access Roles

We create a new custom role by first defining it in JSON:

{
  "Name": "Citrix-Custom-Reader",
  "Description": "Grants access to Citrix XenDesktop images and virtual networks.",
  "Actions": [
    "Microsoft.Storage/storageAccounts/read",
    "Microsoft.Storage/storageAccounts/listKeys/action",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/join/action"
  ],
  "NotActions": [
  ],
  "AssignableScopes": [
    "/subscriptions/<YOUR-SUBSCRIPTION-ID>"
  ]
}

We can now create the role by referencing the JSON definition:

New-AzureRmRoleDefinition -InputFile citrix-custom-reader.json

Finally, we can use our new custom role when creating a service principal:

param(
[string]$applicationName = "NarrowScopeSP",
[Parameter(Mandatory=$true)][string]$applicationPassword,
[Parameter(Mandatory=$true)][string]$subscriptionId,
[Parameter(Mandatory=$true)][string[]]$machineResourceGroups,
[Parameter(Mandatory=$true)][string]$imageResourceGroup,
[Parameter(Mandatory=$true)][string]$networkResourceGroup
)

$application = New-AzureRmADApplication -DisplayName $applicationName -HomePage "https://localhost/$applicationName" `
-IdentifierUris "https://$applicationName" -Password $applicationPassword

New-AzureRmADServicePrincipal -ApplicationId $application.ApplicationId

# Wait for the service principal to become available
Start-Sleep -s 60

foreach ($rg in $machineResourceGroups)
{
  New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $application.ApplicationId `
  -scope "/subscriptions/$subscriptionId/resourcegroups/$rg"
}

New-AzureRmRoleAssignment -RoleDefinitionName Citrix-Custom-Reader -ServicePrincipalName $application.ApplicationId `
-scope "/subscriptions/$subscriptionId/resourcegroups/$imageResourceGroup"

New-AzureRmRoleAssignment -RoleDefinitionName Citrix-Custom-Reader -ServicePrincipalName $application.ApplicationId `
-scope "/subscriptions/$subscriptionId/resourcegroups/$networkResourceGroup"

Write-Host ("Application ID: " + $application.ApplicationId)

Creating XenApp & XenDesktop Azure Connections

While is it perfectly reasonable to create a XenApp & XenDesktop Azure connection in Citrix Studio using an existing service principal, it is equally reasonable to create the connection in PowerShell and the following example shows how. This is particulary the case as, at the time of writing, Studio does not support creating catalogs using connections based on a narrow scope service principal.

param(
[string]$connectionName = "AzureConnection",
[Parameter(Mandatory=$true)][string]$applicationId,
[Parameter(Mandatory=$true)][string]$applicationPassword,
[Parameter(Mandatory=$true)][string]$subscriptionId,
[Parameter(Mandatory=$true)][string]$subscriptionName,
[Parameter(Mandatory=$true)][string]$tenantId
)

Add-PsSnapin Citrix*

$customProperties = @"
<CustomProperties xmlns="http://schemas.citrix.com/2014/xd/machinecreation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <Property xsi:type="StringProperty" Name="AuthenticationAuthority" Value="https://login.microsoftonline.com/"/>
 <Property xsi:type="StringProperty" Name="ManagementEndpoint" Value="https://management.azure.com/"/>
 <Property xsi:type="StringProperty" Name="StorageSuffix" Value="core.windows.net"/>
 <Property xsi:type="StringProperty" Name="TenantId" Value="$tenantId"/>
 <Property xsi:type="StringProperty" Name="SubscriptionId" Value="$subscriptionId"/>
 <Property xsi:type="StringProperty" Name="SubscriptionName" Value="$subscriptionName"/>
</CustomProperties>
"@

$connection = New-Item -ConnectionType "Custom" -CustomProperties $customProperties -HypervisorAddress @("https://management.azure.com/") `
-Path @("XDHyp:\Connections\$connectionName") -Persist -PluginId "AzureRmFactory" -Scope @() `
-SecurePassword (ConvertTo-SecureString -AsPlainText -Force $applicationPassword) -UserName $applicationId

New-BrokerHypervisorConnection -HypHypervisorConnectionUid $connection.HypervisorConnectionUid

At this point you need to add resources to the connection which again can be done in Studio or in PowerShell.

Creating XenApp & XenDesktop Catalogs

The following example uses the Citrix PowerShell Snap-Ins to create a XenApp & XenDesktop catalog.

As Narrow Scope Service Principals do not allow the ARM Plugin to create resource groups, we must:

  1. Create a pool of resource groups.
  2. Assign the service principal permissions on all the resource groups in the resource group pool.
  3. List each resource group in the resource group in a custom property when creating the provisioning scheme.

The custom property is named ResourceGroups and the value is a comma separated list of resource group names. An example of how to define this custom property is shown below.

Note that only resource groups that are intended for machines should be listed in the custom property. The resource group(s) where the image and/or virtual network is located should not be included. If they are specified, the ARM plugin will attempt to provision machines into those resource group which will likely cause some unintended behaviour.

In this example, machines will be provisioned in two resource groups named xd-sales-1 and xd-sales-2.

Add-PsSnapin Citrix*

# The hosting unit name is the name of the Azure connection resources that should be used for this catalog
$hostingUnitName = "AzureHostingUnit"
$domain = "citrix.local"
$controllerAddress = ("ddc." + $domain)
$adminAddress = ($controllerAddress + ":80")
$catalogName = "catalog-name"
$network = "network-resource-group.resourcegroup\network-name"
$subnet = "subnet-name"
$serviceOffering = "Standard_A4"
$template = "image-resource-group.resourcegroup\imagestorage.storageaccount\images.container\image-name.vhd"

$customProperties = @"
<CustomProperties xmlns="http://schemas.citrix.com/2014/xd/machinecreation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Property xsi:type="StringProperty" Name="StorageAccountType" Value="Standard_LRS" />
    <Property xsi:type="StringProperty" Name="ResourceGroups" Value="xd-sales-1, xd-sales-2" />
</CustomProperties>
"@

$identityPool = New-AcctIdentityPool -AdminAddress $adminAddress -AllowUnicode -Domain $domain `
    -IdentityPoolName $catalogName -NamingScheme "vm-#" -NamingSchemeType "Numeric" -Scope @()
    
$brokerCatalog = New-BrokerCatalog -AdminAddress $adminAddress -AllocationType "Random" -IsRemotePC $False `
    -MinimumFunctionalLevel "L7_9" -Name $catalogName -PersistUserChanges "Discard" -ProvisioningType "MCS" -Scope @() `
    -SessionSupport "MultiSession"
    
$provScheme = New-ProvScheme -AdminAddress $adminAddress -CleanOnBoot -CustomProperties $customProperties `
    -HostingUnitName $hostingUnitName -IdentityPoolName $catalogName `
    -MasterImageVM "XDHyp:\HostingUnits\$hostingUnitName\image.folder\$template.vhd" `
    -NetworkMapping @{"0"="XDHyp:\HostingUnits\$hostingUnitName\\virtualprivatecloud.folder\$network.virtualprivatecloud\$subnet.network"} `
    -ProvisioningSchemeName $catalogName -Scope @() -SecurityGroup @() `
    -ServiceOffering "XDHyp:\HostingUnits\$hostingUnitName\serviceoffering.folder\$serviceOffering.serviceoffering"

Write-Host $provScheme

Set-BrokerCatalog -AdminAddress $adminAddress -Name $catalogName -ProvisioningSchemeId $provScheme.ProvisioningSchemeUid

Add-ProvSchemeControllerAddress -AdminAddress $adminAddress.com -ControllerAddress $controllerAddress -ProvisioningSchemeName $catalogName

At this point you can refresh the catalog page in Citrix Studio, add machines and manage machines as with any other catalog.