mandag den 18. juni 2012

Remote Desktop and Claimsbased authentication

I have had a wet dream for a long time, about implementing WIF/Claimsbased authentication into Windows Credential Provider/Remote Desktop. I don’t really care weather it could be though the RDP Website and/or directly on the Windows Server.

If we want to implement claims based authentication in corporation with remote desktop we need to consider what scenario this makes sense for, and how the logon process should be. Doing passive authentication in a browser, the user gets redirected to an identity provider and validated, has a SAML token issued and posted back to the website. Validation at the Identity Provider could also involve redirecting the user to yet another Identity provider (Microsoft Azure ACS, another ADFS 2.0 server, Google ID, Facebook and such) and the all this without the original website knowing anything about this, all it cares about is getting a SAML token from its trusted Identity Provider.

Doing Active Federation, lets say, from a Windows Application, we (could) ask the user how he wants to be validated and then though Kerberos, Certificates or Username/Password send the users credentials to the Identity Provider on behalf of the user and get’s back a SAML token. If the user want to logon though Windows Live ID in this scenario, we first need to ask the user for he's username/password ( this is where all alarm bells should go off for most security aware users ) and send this to login.live.com and by some kind of magic we get back a SAML token. We then ask out local Identity provider to Authorize us, using the SAML token from Windows Live ID and get back a SAML token and can continue the login process.

First of all, asking a user to type in a username and password for Facebook/windows Live ID/what ever on a “proprietary login page” is not good practice. Next, I doubt Microsoft/Facebook/whatever is allowing validating username and passwords though any kind of API, so the above will not work in any case.

So why is this even interesting, if its wrong ? Imaging the most common setup. Company A and Company B wants to collaborate and allow each other users access to certain resources. Instead of creating an Active Directory trust, Company A and B both install ADFS 2.0 and created a mutual trust between these. Now they can both add users to each others SharePoint 2010 and other Claims aware web applications. But what if they want to grant access to Microsoft CRM 2011, create Exchange mailbox's, allow access to Axapta 2009/2012 or use any other kind of application that rely on a Windows user in the Active Directory, then they need to map the User Principal name to locally created Active Directory users. How you do that, is beyond the scope of this post, but its doable and used many places.

Now image you want to allow users from Company A to access Company B’s Citrix XenApp or Remote Desktop servers. So User1 from Company A also have a User account in Company B, but he doesn’t know the password for that account. To give the whole Single Sing-on experience all he needs to know is the username and password to he’s own AD account in Company A.

You might think that implementing Claimsbased authentication on Remote Desktop WebApp is easy, and to be honest, it is. it just doesn’t work. The reason is, when you login to RDWeb the webpage loads an instance of your locally installed Remote Desktop client, though JavaScript/ActiveX and feeds it, the username and password you logged in with. So after implementing Claimsbased authentication in enabling it to use the c2wts service ( Claims to Windows Token Service ) you WILL get the correct list of application, but when ever you launch any of those, you will be prompted to login with a real windows username and password, and the whole concept falls apart. See this post

If you rely on Citrix XenApp you could implement the solution explained here, but that will not work with Remote Desktop. The solution explained there, will not work for user accessing the solution with PNAgent installed locally either ( PDA, Tablets, Thin Clients or what ever ) so an alternative solution will need to rely on replacing the Credential Provider ( called Gina pre windows vista ).

I came across this blog post and I really really wish I was better a coding C++ so I could create this my self. I can do hello world and maybe write to a text file, but this is just to much for my skillset. Google is not much help here, but you will probably come across www.pgina.org at some point.

This will not be completely what I wanted, but at least it’s a few steps in the right direction. Pgina is a cool, yet in some ways limited implementation of a Gina/Credential Provider that send events back to a Service written in .NET 4.0, you can then develop plugins that gets loaded into this service and react/modify things doing the login process. It come bundled with tons of cute add-ons that can validate users though LDAP/Radius/SQL or even IMAP/POP3 . When I say it is limited, I do that, from the perspective that it could have been nice if they had implemented support for asking for more than just username and password. a 3rd field for a passcode to do RSA Token or SMS 2 factor validation would have been a very nice touch, but for now lets just focus on Claimsbased authentication.

So just to test my theory I installed Pgina on a server and wrote a simple add-on to Pgina, that would take what ever the user types in, validate it against and Identity Provider, find the user based on the UPN claim in the local Active Directory and then log the user on.

image

Doing login, no matter what, it all have to end up with win logon getting a valid username and password. But in a world of claims based authentication we don’t have the password. God knows how Citrix manage to get a user logged on purely based on a Kerberos Ticket, and I would love to see this implemented in Pgina too, but a even better solution would be if someone with more brains than me, could write an Authentication Package as explained by Steve Syfuhs in he's blog post. But for now, we focus on the scenario where a user from Company A logs on to a Server inside Company B and the user have an account in the local AD with same UPN.
What I do here is validate what ever the user types in,
1) by asking for a SAML token from the remote Identity Provider.
2) If I get a token back, I extract the User Principal name and search the local Active Directory for such an user.
3) I then validate that the password the user typed is also the password the local Active Directory account, and if login fails, I reset the password and set the login password to this.

Since the context the login process run as, does not have permission to look up in the local AD and/or reset passwords, we also need to tell the PGina addon what username and password to use when talking to AD

image

Finally, since RDP will try to Negotiate security (the popup for credentials before you are connected) and we want PGina to handle this, you need to open Remote Desktop Session Host Configuration and set it to RDP Security Layer

image

And voila, we just logged on to Remote Desktop using Claims based authentication.

tirsdag den 12. juni 2012

Dynamics NAV - Multiple Service Tiers – Part 2

So I felt like a super hero figuring out all the stuff explained in Part 1 but after posting it, and testing some more I started getting weird errors, and stuff would stop working. While working on something else ( implementing Claims based authentication in Citrix XenApp ) I had enabled Kerberos Logging and I noticed this error

image

And I got this sweating feeling all over, like when you did something bad. So I quickly went to test it. First I tried opening the client, making sure AllowNtlm was set to false and ServicePrincipalNameRequired to true, and tried connecting to one of the Navision instance I had problems with.
It failed.
I added a SPN for the service account with instance name pointing to the server’s FQDN(not the cname as explained in the article) and restarted the service and tried again.


Bam, success … so I went to Google and searched for the documentation. Try opening http://msdn.microsoft.com/en-us/library/dd301254.aspx and search for SPN. Notice how it says

setspn -A InstanceName/FullyQualifiedDomainNameOfServer:Port Domain\User

How the freaking hell did I miss that ? I’ve seen that pages a billion times. Oh well, nothing wrong in learning something new once in a while.

Using a CName is still a good idea thou. If you want to use Kerberos when talking to the Web Service you still need a HTTP/HOST SPN Service registered and what I wrote in part 1 will still apply.

mandag den 11. juni 2012

ADFS 1.X and Custom STS

So while implementing Claims based authentication for Citrix XenApp the next natural step for me would be to update my Custom STS to issue the tokens directly instead of making the user “jump” between several websites, while logging on. When you configure the ADFS Token Based client you need tell the Web Server what ADFS Server to use, by pointing to a URL like https://admin.wingu.dk/adfs/fs/federationserverservice.asmx 

I am sure its doable to create a Web Service that can send what ever reply’s it expect, I’m also 99% sure all that can be done purely with ADFS 2.0 and the Token2Identity Service, but for now I don’t mind leaving the ADFS 1.1 server and just issue a token to that, and let it parse on it’s own token to the Citrix Web Interface. So I add my STS as an account partner and try logging in. After sorting out some URL’s and certificates, I got the first real error.

[ERROR] SamlViolatesSaml: No NameIdentifier

Hmm, I was 100% sure I’m was sending Name ID, but I start Fiddler2 and look at the token’s doing a login though my ADFS 2.0 server and compare with the token while logging on with my STS, and notice mine looks different. and it didn’t seem to have Name ID, so I added on, based on UPN (since I know that works with ADFS 2.0) in my code, and try again (with the code below but without setting Format). This time I get

SamlViolatesInterop: No NameIdentifier/@Format

Weird, Google wasn’t much help, but after playing around a bit I end up with this piece of code that works

Dim NameID As New Claim(ClaimTypes.NameIdentifier, UPN)
NameID.Properties(ClaimProperties.SamlNameIdentifierFormat)
= "http://schemas.xmlsoap.org/claims/UPN"
identity.Claims.Add(NameID)
but now I get this error


Crypto algorithm 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' not supported in this context


I always wondered how to change that, but never had the need, now I do. Google and I came up with this



scope.SigningCredentials = New X509SigningCredentials(_configuration.SigningCertificate, "http://www.w3.org/2000/09/xmldsig#rsa-sha1", "http://www.w3.org/2000/09/xmldsig#sha1")
Several people have been complaining their tokens was running out to fast, and I had not really had time to look into it, but since I was already updating the STS I might as well add that too. Turns out that was almost to easy. In you SecurityTokenService class, where you override GetScope and GetOutputClaimsIdentity, just override GetTokenLifetime too … here is how I did it while testing


Protected Overrides Function GetTokenLifetime(requestLifetime As Microsoft.IdentityModel.Protocols.WSTrust.Lifetime) As Microsoft.IdentityModel.Protocols.WSTrust.Lifetime
Return New Lifetime(Date.UtcNow, Date.UtcNow.AddHours(10))
End Function

Next project. Testing if I can completely rule out ADFS 1.x and run purely with WIF components.

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
}
}