views:

448

answers:

2

I'm having a problem at work in that our app which uses WCF for SSO is failing when using selective authentication for a domain trust when communicating with the other domain. This is on Server 2k8R2 machines, at a full 2k8R2 functional level for both of their domains (this is a test system, because we have a customer that wants to deploy this type of thing).

Basically, we have two domains, call them A and B. When we do a full two-way EXTERNAL (not forest) trust between the domains, the application works fine (after putting the users in appropriate groups on the other domain of course). Then we flipped the relationship from "Domain-wide" authentication to "Selective Authentication." According to some doc we've read, we need to add the users to each computer's entry in AD directly, and give them the "Allowed to authenticate" permission.

It didn't work.

Further, then we saw somewhere ELSE that implied that we had to also give them the same permissions on the DCs themselves. So that was done. Again, failure.

The exception thrown by the application is as follows (I dumped it to a file)

A call to SSPI failed, see inner exception. Stacktrace: 
Server stack trace: 
   at System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade(Stream stream, SecurityMessageProperty& remoteSecurity)
   at System.ServiceModel.Channels.StreamSecurityUpgradeInitiatorBase.InitiateUpgrade(Stream stream)
   at System.ServiceModel.Channels.ConnectionUpgradeHelper.InitiateUpgrade(StreamUpgradeInitiator upgradeInitiator, IConnection& connection, ClientFramingDecoder decoder, IDefaultCommunicationTimeouts defaultTimeouts, TimeoutHelper& timeoutHelper)
   at System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.SendPreamble(IConnection connection, ArraySegment`1 preamble, TimeoutHelper& timeoutHelper)
   at System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.DuplexConnectionPoolHelper.AcceptPooledConnection(IConnection connection, TimeoutHelper& timeoutHelper)
   at System.ServiceModel.Channels.ConnectionPoolHelper.EstablishConnection(TimeSpan timeout)
   at System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
   at System.ServiceModel.Channels.ServiceChannel.EnsureOpened(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
(A couple of calls in our own code below here)
InnerException: System.Security.Authentication.AuthenticationException: A call to SSPI failed, see inner exception. ---> System.ComponentModel.Win32Exception: Unknown error (0xc0000413)
   --- End of inner exception stack trace ---
   at System.Net.Security.NegoState.ProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.NegotiateStream.AuthenticateAsClient(NetworkCredential credential, String targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel)
   at System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade(Stream stream, SecurityMessageProperty& remoteSecurity) 

So, basically that's it. Some type of "AuthorizationException" is occurring. As I said, it doesn't happen when it's a domain-wide two-way trust, so we think we're missing some setting for what to "re-enable" to make this work. Does anybody know trusts well enough to point us in the right direction for what to enable for selective authentication and make SSO/SSPI work for this? It DID work already on the domain-wide, but what do we have to open up to make it work for selective (and preferrably for ONLY the users we want it to work for)?

Thanks.

A: 

After a call to Microsoft, this was resolved: we hadn't put the "Allowed to Authenticate" permission on a number of the users running services on the host machines. The documentation all referred to having to give your clients that permission on the machines you are accessing (including the DC in some cases), but said nothing about also having to add your remote clients to the "security" tab of the host user running the services.

So I'll summarize with some contrived user, domain, and machine names, as well as one actual service account which exists by default.

"A" domain: The domain where the client is trying to connect from "B" domain: This is the domain where a machine is hosting the service being connected to by a user in domain A. Client@A: user from the A domain connecting to a service in the B domain. ServiceAccount@B: user from the B domain that is hosting the WCF service that Client@A is connecting to. krbtgt@B: this is a built-in user who's description is "Key Distribution Center Service Account". It will be under Active Directory Users and Computers under "Users" as long as you have "Advanced Features" enabled under the "view" menu. It won't show up at all unless you have done that. B-DC: The domain controller of domain B B-host: The host machine for the WCF service we're connecting to.

So, to let Client@A connect to the WCF service being run on B-host by user ServiceAccount@B by using SSPI/Windows authentication, on an external trust with Selective Authentication, you need to do the following:

  1. Open up AD Users & Computers and under the "view" menu enable "Advanced Features"
  2. Find the B-DC object (under Domain Controllers by default, but could be elsewhere if you've moved it) and open up the "security" tab. The tab won't exist unless you've enabled Advanced Features in step 1. Add the Client@A user to the group or user names, and make sure that "read" and "allowed to authenticate" are checked for that user.
  3. Find the B-host object (under Computers by default, but could be elsewhere if you've moved it) and open up the "security" tab. The tab won't exist unless you've enabled Advanced Features in step 1. Add the Client@A user to the group or user names, and make sure that "read" and "allowed to authenticate" are checked for that user.
  4. Find the krbtgt@B user under "Users". The account itself will not be visible without "advanced features" enabled. Go to the security tab and add the Client@A user with the "allowed to authenticate" and "read" permissions checked.
  5. Find the serviceaccount@B user and do the same as you did for krbtgt.

NOW it should work. It's weird you need all that just to run a windows-authenticated (so not certificates) WCF connection across the boundary like that, but there you go. You need to add your remote user to FOUR different objects in AD to get it to work. Presumably adding the remote user to some domain-local groups in domain B that had the right permissions would also work, but that's not tested yet.

Edit: and then get krbtgt overwritten by the PDC Emulator with the rights on AdminSDHolder in under an hour. Every hour.

Basically, krbtgt is super-protected. You have to alter AdminSDHolder, and it propogates out a lot. The command to add JUST "allowed to authenticate" is:

dsacls "[DN of object]" /g "[groupname]:ca;allowed to authenticate"

Look up the "dsacls" tool if you want more info on what it does. It's pretty handy for both altering the access control lists, and printing them out. Better than the GUI in fact.

Anyways, this actually works now. There's the FULL answer.

Kevin
A: 

Kevin,

I have a question for you. You mentioned that one can make WCF calls across two trusted domains by putting users in appropriate groups.

"Basically, we have two domains, call them A and B. When we do a full two-way EXTERNAL (not forest) trust between the domains, the application works fine (after putting the users in appropriate groups on the other domain of course)."

When I make cross domain WCF calls. The domains are two-way EXTERNAL trusted with "domain-wide authentication", I'm getting "SecurityNegotiationException" (SSPI authentication failed).

Can you please explain / provide a reference for details about the groups in which users need to be added ?

Thanks in advance, Rishikesh

Rishikesh
The groups I was referring to were the groups that were able to connect and authorize in the first place. Basically, we are taking something that usually always lives in the same domain, and putting it in two, so if a user was part of a group on one side, they were in the corresponding group on the other side. As for what they need to be a part of for SSPI to work, read my checked answer. But don't try and be part of the krbtgt user unless your app requires it. For some reason our app does, but most shouldn't need to change that one.
Kevin
Ok. Now I understand. Thanks for elaborating.Yes, the steps you mentioned in your answer must be specific to "selective authentication" case. I'm still figuring out ways to get it working in my scenario.
Rishikesh