mandag den 11. juni 2012

Citrix XenApp and ADFS 2.0 / Claims based authentication

Updated 12-06: forgot to add LDAP service SPN’s in PowerShell script.
This has been one of my little pet project for a long time. I knew I would get a few issues but I sure didn’t expect it to take more than 6 months to get working, but now things are starting to look bright.

So XenApp supports Claims based authentication, it does this by facilitating Kerberos authentication on the website and by some stuff I still don’t quite understand (several people have been mentioning Protocol Transition ) it ends up spawning a desktop/application on the XenApp server as the user. Several layers are part of this and several things can go wrong, so ill do a quick “how to” based on my initial findings.

Step one – Active Directory Federation Service
Get ADFS 2.0 or some other Secure Token Service Provider up and running. For this setup I will be assuming we use ADFS 2.0.
Next get ADFS 1.1 up and running. Its part of Windows Server 2008 R2
We need to configure ADFS 2.0 to support issuing Tokens to ADFS 1.1 .
We do that by first configuring a few Claims types, open the management console and go to Services –> Claim Description and add a new Claim Description.
Display name: AD FS 1.x UPN
Claim type: http://schemas.xmlsoap.org/claims/UPN
Description: The UPN of the user when interoperating with AD FS 1.1 or ADFS 1.0

image

Next add your ADFS 1.x server as a Relying Party on the ADFS 2.0 server. Go to Trust Relationships –> Relying Party Trusts and add a new Relying Party Trust
Choose to manually setup the Relying party, Choose AD FS 1.0 and 1.1 profile.
type path to your 1.X server ( for instance https://adfs11.domain.com/adfs/ls/ )
While troubleshooting I also added https://adfs11.domain.com/adfs/ but I am not sure its needed

Once done, setup Claim Rules for the new Relying Party. You want to at least send Name ID, UPN, Authentication method and Authentication time stamp.
image

image

image

image

Perfect, now head to your ADFS 1.x server, open Server Manager –> Roles –> ADFS –> Federation Service –> Trust Policy –> My Organization –> Account Store –> Active Directory. Right click it and choose disable. ( while testing it can be helpful to enable this and make sure everything works, while implementing ADFS on the Citrix site )
open Server Manager –> Roles –> ADFS –> Federation Service –> Trust Policy –> Partner Organizations –> Account Partners, and Add a new partner
Federation Service URI: http://adfs.wingu.dk/adfs/services/trust
Federation Service Endpoint URL: https://adfs.wingu.dk/adfs/ls/
(be very aware … Slash / in the end of URL, but not URI … )
Link to Token Signing Certificate on the ADFS 2.0 server
Choose “Federated Web SSO” and not “Federated Web SSO with Forest Trust”
Select “UPN Claim”, and then add all the domains you will be using for UPN’s.

Step two – Setup Citrix Web Interface
Citrix only supports using ADFS on 32 bit OS, so install Windows Server 2008 in 32bit version. Install Secure Gateway and Web Interface 5.4 or what ever version is the newest.
Open Server Manager and add Active Federation Service Role and choose to install “Windows Token-based Agent”
Open Server Manager –> Roles –> Web Server (IIS) –> Internet Information Services, Select [your server] and in the Feature View pane, choose “Federation Service URL” and type in “https://adfs10.domain.com/adfs/fs/federationserverservice.asmx
Open Services and make sure “AD FS Web Agent Authentication Services” is runningimage
Setup you Secure Gateway like normal, and then configure the Web Interface, Open “Citrix Web Interface Management” –> XenApp Web Sites, Create Site.
Choose “at Microsoft AD FS account partner” and enter return URL
https://xenapp.domain.com/Citrix/XenApp
Note, no / in the end
Once done, setup your site like normal, defining farm and so on.

Step 3, Add XenApp Web Interface as Relying Party on ADFS 1.x server
Open Server Manager –> Roles –> ADFS –> Federation Service –> Trust Policy –> My Organization –> Applications –> Add new application
Choose Windows NT Token-based Application
Give it a name, and add the return URL from XenApp Web Interface
https://xenapp.domain.com/Citrix/XenApp
Note, no / in the end
Choose UPN, and choose to enable the application.

Step 4, setting up Kerberos Delegation
First, make sure IIS is working properly on your XML Service machines. ( see http://support.citrix.com/article/CTX130480 )
This is the part that always goes wrong. Lets step back a little and see what is actually happening here.
A user hits https://xenapp.domain.com/Citrix/XenApp and is not authenticated. XenApp will not be handling authentication but leaves it to the Web Server. Web Server looks for a cookie on the client machine with a SAML token, if the user does not have it, it redirects the user to the Federation Service.
The ADFS 1.x service (who should have Active Directory disabled) forwards the user to its Federation Partner, the ADFS 2.0 server. The user gets authenticated and a SAML token gets issued and posted back to ADFS 1.x. It then issues a new Token and that gets posted back to the Citrix Website. This gets saved in a cookie in the users browser and ifssvc ( AD FS Web Agent Authentication Services ) now created an iPrincipal representing the user based on the UPN and sets the authenticated user to be this Principal. The Citrix Web Application now see the users as authenticated though Kerberos and does it’s “magic”. The Web App needs to talk to the XML service on one of the Citrix Servers, on behalf of the user enumerate a list of applications and desktops. Since the Principal is restricted to only be used locally, we need tell Active Directory that this server can “delegate” Kerberos tickets to other servers. We do that from Active Directory Users and Computers, find the Computer account of the WI server and go to the “Delegation” tab. When giving an (user or computer) account permission to delegate on behalf of another user, we can allow it to any computer (unconstrained delegation) or we can specify we will only allow this to specific services (run by a computer or user account) this is called Constrained Delegation. The Kerberos ticked created by the AD FS Web Agent Authentication Services is only allowed to be delegated onwards by means of Constrained Delegation, so we select that. Several documents on the internet will tell you to choose “Kerberos only” here, but since Citrix uses Kerberos Protocol Transition later in the process we need to select “Any protocol” here

image
Its important that there is an delegation for HTTP to the all XML Services, HOST will not be enough.
If everything worked, when a user logs on to the web interface they should now get a list of applications and desktops, but we are not done yet. When the user, for instance, launch a Desktop the user will now be logged on to the XenApp server with a Restricted Token and we therefor need to allow each Citrix XenApp server permission to Delegate to each service. As part of the Logon process, we at least need permission to delegate to the Active Directory Servers and what ever File Server is hosting the Profiles (if you use Roaming Profiles or Folder Redirection )

So, for every Citrix XenApp Server, you need to Delegate the following.
For every Domain Control, add delegation to Service CIFS and LDAP.
For your file server(s) , add delegation to Service CIFS
(not sure about this one, but just to be safe) For the SQL server or account running the SQL service, hosting your Citrix Store Database, add delegation to Service MSSQLSvc.
Lastly, the Citrix Servers need permission to talk to each other and it self, so add delegation to Service HOST, for every XenApp server, including it self.
Again, several documents say you need to delegate constrained delegation using Kerberos only. I have seen it working, but some times it is just not enough, so setting this to any protocol at least while testing should eliminate a few errors.

image

If you have a lot of servers, it might be worth scripting this. This is a part of the script I have been using while building a Proof of Concept environment. You can modify this to fit what ever yours look like, just do this by hand first, to make sure everything works, then you can modify this to generate something similar.

Import-Module 'activedirectory'

#Locate domain I am running in and choose a prefered domain controller

$ADInfo = Get-ADDomain
$DCprefix = "," + $ADInfo.distinguishedName.ToString()
$RandomDC = Get-ADDomainController
$PreferedDC = $RandomDC.HostName
$domainname = $RandomDC.Domain

Write
-Host -ForegroundColor Green "I am running in domain $domainname and will use $PreferedDC as Prefered Domain Controller"


# List of Citrix XenApp Servers.
#
Its important we get an HTTP entry for each XML Service, to the Web Interface, if its missing add on first, with somethign like
#
SetSPN -a HTTP/xentest01.int.wingu.dk xentest01
$XenAppServers = @('XEN01$', 'XEN02$', 'XEN03$', 'XEN04$', 'XEN05$', 'XEN12$', 'XEN13$', 'XENTEST01$', 'XENTEST02$')


# List of Xitrix XenApp Web Interface Servers
$XenAppWIServers = @('XENGW01$', 'XENGW02$')

# List of File Servers
$FileServers = @('FS01$')

# List of SQL Servers. IF SQL is running as a Domain account, type the name of the Account instead.
$SQLAccounts = @('sqlacct','SQLEXP01$')

# List of Microsoft Application Virtualization Servers ( AppV / SoftGrid )
$AppVServers = @('APPV01$','APPV02$')

# Any other servers or Service Accounts, you might just want to bulk import host for.
#
For now, we just copy everything.
$OtherServers = @('provacct', 'mail$', 'NAVI01$','NAVI01_NAV601_NAVI01', 'NAVI01_NAV602_NAVI01' )


# Computer Account - Any Protocol
$WORKSTATION_TRUST_ACCOUNT = 4096
$TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
$userAccountControl = $WORKSTATION_TRUST_ACCOUNT + $TRUSTED_TO_AUTH_FOR_DELEGATION


$delegates= @()
Write
-Host "***************************************************************"
Write
-Host "* Create delegates list for Citrix WebInterface servers"
Write
-Host "***************************************************************"
foreach($server in $XenAppServers){
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('servicePrincipalName') -Server $preferedDC
$servicePrincipalNames = $ADServer | Select -ExpandProperty servicePrincipalName
foreach($servicePrincipalName in $servicePrincipalNames){
if(($servicePrincipalName.ToLower() -like 'host*') -or ($servicePrincipalName.ToLower() -like 'http*')){
$delegates += $servicePrincipalName
}
}

}

foreach($server in $XenAppWIServers){
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('servicePrincipalName') -Server $preferedDC
$servicePrincipalNames = $ADServer | Select -ExpandProperty servicePrincipalName
foreach($servicePrincipalName in $servicePrincipalNames){
if(($servicePrincipalName.ToLower() -like 'host*') -or ($servicePrincipalName.ToLower() -like 'http*')){
$delegates += $servicePrincipalName
}
}

}

foreach($del in $delegates){ Write-Host $del }

foreach($server in $XenAppWIServers){
Write
-Host "Updating msDS-AllowedToDelegateTo and userAccountControl on $server"
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('msDS-AllowedToDelegateTo','userAccountControl') -Server $preferedDC
if($ADServer.userAccountControl -ne 16781312){
Write
-Warning "Updating $server to Trust for delegation with any protocol. Please restart it."
$ADServer | Set-ADObject -Replace @{"userAccountControl"=$userAccountControl} -Server $preferedDC
}

$ADServer | Set-ADObject -Clear "msDS-AllowedToDelegateTo" -Server $preferedDC
# For some reason, adding it all in one go, sometimes fails, so adding each entry one by one
foreach($del in $delegates){
$ADServer | Set-ADObject -add @{"msDS-AllowedToDelegateTo"="$del"} -Server $preferedDC
}
}

Write
-Host "***************************************************************"
Write
-Host "* Adding cifs/ldap and SQL to delegates list for Citrix servers"
Write
-Host "***************************************************************"

foreach($server in $SQLAccounts){
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('servicePrincipalName') -Server $preferedDC
$servicePrincipalNames = $ADServer | Select -ExpandProperty servicePrincipalName
foreach($servicePrincipalName in $servicePrincipalNames){
if( ($servicePrincipalName.ToLower() -like 'mssqlsvc*') -or ($servicePrincipalName.ToLower() -like 'host*') ){
$delegates += $servicePrincipalName
}
}
}

foreach($server in $ADInfo.ReplicaDirectoryServers){
$DC = Get-ADDomainController $server -Server $preferedDC
$SamAccountName = ($DC.Name + '$')
$ADServer = Get-ADObject -Filter {SamAccountName -eq $SamAccountName} -Properties @('servicePrincipalName') -Server $preferedDC
$servicePrincipalNames = $ADServer | Select -ExpandProperty servicePrincipalName
foreach($servicePrincipalName in $servicePrincipalNames){
if(($servicePrincipalName.ToLower() -like 'dfsr-*') -or ($servicePrincipalName.ToLower() -like 'ldap*')) {
$delegates += $servicePrincipalName
}
}

# CIFS services is not listed in servicePrincipalName, so add those manually
$delegates += ("CIFS/" + $DC.Name)
$delegates += ("CIFS/" + $DC.Name + "/" + $ADInfo.NetBIOSName)

$delegates += ("CIFS/" + $server)
$delegates += ("CIFS/" + $server + "/" + $ADInfo.NetBIOSName)
$delegates += ("CIFS/" + $server + "/" + $RandomDC.Domain)

# Not sure if this is needed, but i see Kerberos errors about it, when Kerberos logging is enabled
$delegates += ("ProtectedStorage/" + $DC.Name)
$delegates += ("ProtectedStorage/" + $DC.Name + "/" + $ADInfo.NetBIOSName)

$delegates += ("ProtectedStorage/" + $server)
$delegates += ("ProtectedStorage/" + $server + "/" + $ADInfo.NetBIOSName)
$delegates += ("ProtectedStorage/" + $server + "/" + $RandomDC.Domain)
}



foreach($server in $FileServers){
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('servicePrincipalName') -Server $preferedDC
$servicePrincipalNames = $ADServer | Select -ExpandProperty servicePrincipalName
foreach($servicePrincipalName in $servicePrincipalNames){
if($servicePrincipalName.ToLower() -like 'dfsr-*') {
$delegates += $servicePrincipalName
}
}
$delegates += ("CIFS/" + $ADServer.Name)
$delegates += ("CIFS/" + $ADServer.Name + "." + $RandomDC.Domain)
}

foreach($server in $AppVServers){
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('servicePrincipalName') -Server $preferedDC
$servicePrincipalNames = $ADServer | Select -ExpandProperty servicePrincipalName
foreach($servicePrincipalName in $servicePrincipalNames){
if( ($servicePrincipalName.ToLower() -like 'host*') -or ($servicePrincipalName.ToLower() -like 'softgrid*') ){
$delegates += $servicePrincipalName
}
}
}

foreach($server in $OtherServers){
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('servicePrincipalName') -Server $preferedDC
$servicePrincipalNames = $ADServer | Select -ExpandProperty servicePrincipalName
foreach($servicePrincipalName in $servicePrincipalNames){
#if($servicePrincipalName.ToLower() -like 'host*'){
$delegates += $servicePrincipalName
#}
}

}

foreach($del in $delegates){ Write-Host $del }

foreach($server in $XenAppServers){
Write
-Host "Updating msDS-AllowedToDelegateTo and userAccountControl on $server"
$ADServer = Get-ADObject -Filter {SamAccountName -eq $server} -Properties @('msDS-AllowedToDelegateTo','userAccountControl') -Server $preferedDC
if($ADServer.userAccountControl -ne 16781312){
Write
-Warning "Updating $server to Trust for delegation with any protocol. Please restart it."
$ADServer | Set-ADObject -Replace @{"userAccountControl"=$userAccountControl} -Server $preferedDC
}

$ADServer | Set-ADObject -Clear "msDS-AllowedToDelegateTo" -Server $preferedDC
# For some reason, adding it all in one go, sometimes fails, so adding each entry one by one
foreach($del in $delegates){
$ADServer | Set-ADObject -add @{"msDS-AllowedToDelegateTo"="$del"} -Server $preferedDC
}
}

Ingen kommentarer:

Send en kommentar