lørdag den 26. maj 2012

Dynamics NAV - Multiple Service Tiers

updated 28-05: Small issued fixed
updated 12-06: I was dead wrong regarding SPN’s. Read more here

So you want to host several Navision Server Services on a single host.

Easy, first you read http://msdn.microsoft.com/en-us/library/dd301254.aspx
Seriously, I normally think of my self as one who got the whole delegation stuff figured out, but this on had me spinning for a while.

If a ran the service as Local System or Network Service, (and gave the NAV server computer account permission to delegate for MSSQLSvc to the SQL Server) everything worked but as soon as a ran the service as a Domain account (and gave that user account permission to delegate for MSSQLSvc to the SQL Server) everything worked locally on the NAV server, but when called from a client machine you would get an error saying it couldn’t authenticate to the SQL Server.

Oh my, turns out I was running my SQL server as a domain account, but some how, some where (and it can’t have been me, I swear) the MSSQLSvc was registered to the SQL Servers computer account, and/but not to the Domain Account the SQL Server was running as. so after deleting the SPN from the SQL server and adding it to the SQL Account, everything was working perfectly.

Well, almost.

you will probably come across this post http://blogs.msdn.com/b/nav/archive/2009/10/20/creating-a-web-service-manually-the-importance-of-what-name-you-give-it-and-a-few-small-things-to-remember.aspx and this post http://blogs.msdn.com/freddyk/archive/2009/08/05/multiple-service-tiers-sp1.aspx
Awesome reading by the way, but it doesn’t take into account you might not want to run everything as local system, and all the fun that comes with that.

Next problem, do you want to assign a new port for each NAV Service or share them ?
Managing several ports are fun, managing many many more ports must be so much more fun. not
So you fire up the NetTcpPortSharing service and suddenly nothing works for NAV, since the domain account doesn’t have permission to do Port sharing. Several post if you Google this, will tell you to uninstall .NET 4.0 *pffhhh*, nah, just open C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SMSvcHost.exe.config and add

<system.serviceModel.activation>
<net.tcp>
<allowAccounts>
<add securityIdentifier="S-1-5-11" />
</allowAccounts>
</net.tcp>
</system.serviceModel.activation>

Well, maybe not THAT specifically, this will allow any authenticated user to use port sharing, you might want to add each service account by SID. ( And restart the server every time you update it =| )


Next NAV will start complaining it cannot start a listener on net.tcp://+:7046/[something]/Service … This should fix that


netsh http add urlacl "url=http://+:7046/" user="NT AUTHORITY\Authenticated Users"
(replace NT AUTHORITY\Authenticated Users with the real account your running the service as)


Next the Web Service Service will fail with a similar error ( http://+:7047/ ) ..
Ahh well,
netsh http add urlacl "url=http://+:7047/" user="NT AUTHORITY\Authenticated Users"
will shut it up …. (replace NT AUTHORITY\Authenticated Users with the real account your running the service as)


So now everything is working, you can start the services, and can probably also connect using a locally installed client on the NAV server, but once you go to a client computer it will complain it cannot talk with the SQL Server.
Here is a dilemma. You cannot have a SPN registered twice in AD. (ok, you can but it will not work) lets imagine you have 2 NAV services one on
net.tcp://nav01:7046/navservice1/Service running with domain account “navservice1acct”
and one on
net.tcp://nav01:7046/navservice2/Service running with domain account “navservice2acct”


We know we need to register a DynamicsNAV/FQDN and NAVSERV_DynamicsNAV/FQDN for the account’s but if FQDN is the same, we have a duplicate and it wont work.
So create a CNAME for each service


$rec = Get-WmiObject -Namespace root\MicrosoftDNS -ComputerName $dnsserver -Class MicrosoftDNS_CNAMEType -Filter "ContainerName='$fzone' and OwnerName='$cname'"
if(!$rec){
Write
-Host "$cname not registrered in DNS, creating it now"
$text = "$cname IN CNAME $NAVServer"
#$rec = [WmiClass]('\\$dnsserver\root\MicrosoftDNS:MicrosoftDNS_CNAMEType')
$rec = [WmiClass]"\\$($dnsserver)\root\MicrosoftDNS:MicrosoftDNS_ResourceRecord"
$rec.CreateInstanceFromTextRepresentation($dnsserver, $fzone, $text) | Out-Null
}


And register the SPN’s for those. in my tests, the role based client will be using host SPN and not DynamicsNAV, not sure if that is related to me using cnames and/or using domain accounts for AX, but just be safe add all 3.
Turns out it was not even doing that. See part 2. Use this



$username = $NAVAdmin.Username
Start
-Process -FilePath 'C:\Windows\System32\setspn.exe' -ArgumentList ("-A $instancename/" + $NAVServer + ":7046 -U $username") -NoNewWindow -Wait
Start
-Process -FilePath 'C:\Windows\System32\setspn.exe' -ArgumentList ("-A $instancename/" + $cname + ":7046 -U $username") -NoNewWindow -Wait
Start
-Process -FilePath 'C:\Windows\System32\setspn.exe' -ArgumentList ("-A HOST/" + $cname + " -U $username") -NoNewWindow -Wait
Start
-Process -FilePath 'C:\Windows\System32\setspn.exe' -ArgumentList ("-A HTTP/" + $cname + " -U $username") -NoNewWindow -Wait


We want InstanceName\fqdn since that, is what Navision is using. Using CNames is still a good idear to handle the WebService, but for purely Navision, we could just stick with InstanceName\fqnd of nav server


Next you want to the Service account to allow delegation to the SQL server. If you want to use constrained delegation and restrict it to Kerberos, all you have to do is add the SPN you want to delegate too


$ADNAVAdminUser = Get-ADUser $NAVAdmin.Username
$SQLServerDelegate = @( ("MSSQLSvc/"+$NAVDBServer+":1433") )
$ADNAVAdminUser | Set-ADObject -Replace @{"msDS-AllowedToDelegateTo"=$SQLServerDelegate}

If, how ever you also want to support NTLM, you need to allow constrained delegation to any authentication protocol, and you need to add TRUSTED_TO_AUTH_FOR_DELEGATION to userAccountControl on the accounts


$NORMAL_ACCOUNT = 512
$DONT_EXPIRE_PASSWORD = 65536
$TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
$userAccountControl = ($NORMAL_ACCOUNT + $DONT_EXPIRE_PASSWORD + $TRUSTED_TO_AUTH_FOR_DELEGATION)

$ADNAVAdminUser = Get-ADUser $NAVAdmin.Username
$SQLServerDelegate = @( ("MSSQLSvc/"+$NAVDBServer+":1433") )
$ADNAVAdminUser | Set-ADObject -Replace @{"msDS-AllowedToDelegateTo"=$SQLServerDelegate; "userAccountControl"=$userAccountControl}

And to make it all easier, you might want to script updating a client config file too
Remember, the NAV client doesn’t support roaming profiles, so you need to override the config file anyway with "C:\Program Files (x86)\Microsoft Dynamics NAV\60\RoleTailored Client\Microsoft.Dynamics.Nav.Client.exe" -settings:"X:\NAV6\ClientUserSettings.config"
So why not place it somewhere everyone have access to, and keep it simple.
( I had a few issues getting Kerberos to work against the cname, but after reading http://blogs.msdn.com/b/nav_developer/archive/2009/08/17/troubleshooting-multi-machine-installations-of-nav-2009.aspx I tried adding DelegationInfo to the client config and setting it to DomainUser and that did the trick, after also adding a HOST SPN to the Service account running Navision )
( if you are using my cname trick, you probably also need to set ServicePrincipalNameRequired to False. This will also make the logon faster so might be a good idea no matter what ????. Since we have no way of telling the Navision Service what hostname to listen to and register it self as, it will always register it self with the hostname of the server. And since the client is connecting on, and expecting a reply for, the cname they cant do a full mutual authentication, based on hostname and you will get below error. )
( The remote server did not satisfy the mutual authentication requirement. )
image


[xml]$CustomSettings = Get-Content ($clientconfig)
(
$CustomSettings.configuration.appSettings.add | ?{$_.key -eq 'Server'}).value = $cname
(
$CustomSettings.configuration.appSettings.add | ?{$_.key -eq 'ServerInstance'}).value = $InstanceName
(
$CustomSettings.configuration.appSettings.add | ?{$_.key -eq 'ServerPort'}).value = "7046"
(
$CustomSettings.configuration.appSettings.add | ?{$_.key -eq 'AllowNtlm'}).value = "False"
(
$CustomSettings.configuration.appSettings.add | ?{$_.key -eq 'ServicePrincipalNameRequired'}).value = "False"
(
$CustomSettings.configuration.appSettings.add | ?{$_.key -eq 'ClientCredentialType'}).value = "Windows"
$CustomSettings.save($clientconfig)

2 kommentarer:

  1. Hi,

    Thanks for this post very helpful.

    i have a problem with calling nav webservice in 3 tiers architecture from a remote session.

    My nav web service is under domain account.

    when i try to call nav web service with soapui i have this message
    Fri Jul 11 12:37:06 CEST 2014:INFO:frcp00vpp1322:7047 requires authentication with the realm 'null'

    RTC work fine in 3 tiers architecture.

    i use proxy fiddler and it work , because fiddler can read kerberos tickets without problem.

    i must call my webservice with soapui.

    Have you any idea please?

    Thanks

    SvarSlet
    Svar
    1. I will need a bit more info, to give a good guess.
      But the first thing that springs to mind is, have you triede setting preauthenticate=true when calling the webservice ?

      Slet