Yet on BizTalk Impersonation With WCF Adapters
Scenario
Last year I wrote a post on how using BizTalk Server 2006 R2/2009 and Protocol Transition to impersonate the original caller when invoking a downstream service that uses the Windows Integrated Security. Recently, one customer posed the following question to my colleague, Tim Wieman:
Can I create a WCF Send Port that is able to impersonate a statically defined domain user, other than then the service account of the host instance process, when calling a downstream WCF service that exposes a BasicHttpBinding/WsHttpBinding endpoint configured to use the Transport security mode ?
The answer is:
- Yes, when using the Basic or the Digest authentication scheme.
- No, when using the Windows or NTLM client credential type.
To verify this constraint, you can proceed as follows:
- Open the BizTalk Administration Console.
- Create a new WCF-BasicHttp or WCF-WsHttp Static Solicit-Response Send Port
- Click the Configure button.
- Select the Security tab.
- Choose the Transport security mode.
At this point, if you select the Basic or Digest transport client credential type from the corresponding drop-down list:
- You can click the Edit button in the User name credentials section, as highlighted in the picture below.
- You can specify the Username and Password of the account that the Send Port will impersonate when invoking the target WCF service. As an alternative, you can leverage the Single Sign-On to redeem a ticket and pick a user at runtime from a certain affiliate application.
Instead, if you select the Ntlm or Windows transport client credential type from the corresponding drop-down list, the Edit button in the User name credentials section is greyed out, as shown in the picture below:
So at this point some of you might ask yourselves:
How can I impersonate a statically-defined user, different from the service account of the host process running my WCF Send Port, when invoking an underlying WCF service that uses the Transport security mode along with the Ntlm or Windows authentication scheme?
The answer is straightforward, you can achieve this objective using the WCF-Custom adapter and writing a custom WCF channel. Indeed, I didn’t create a new component from scratch, I just used grabbed some code from MSDN, and extended the component I wrote one year ago for my previous post on BizTalk and Protocol Transition . In particular, I made the following changes:
- I extended the following components:
- InspectingBindingExtensionElement
- InspectingBindingElement
- InspectingChannelFactory
- InspectingRequestChannel
- to expose two additional properties:
- WindowsUserName: gets or sets the domain account in the form of DOMAIN\Username that the Send Port will impersonate at runtime.
- WindowsUserPassword: gets or sets the password of the domain account.
- Then I extended the WindowsUserPositionEnum type and therefore the WindowsUserPosition property to include a new mode called Static. As a consequence, the custom channel at runtime will retrieve the client credentials in a different way depending on the value of the WindowsUserPosition property exposed by the InspectingBindingExtensionElement component:
- Context: username will be read from the message context property identified by the ContextPropertyName and ContextPropertyNamespace properties exposed by the InspectingBindingExtensionElement. In this case, the environment must be properly configured to use Protocol Transition. See my previous post for more details.
- Message: username will be read from the message using the XPath expression contained in the WindowsUserXPath property. Even in this case, the environment must be properly configured to use Protocol Transition. See my previous post for more details.
- Static: the custom channel will use the client credentials contained in the WindowsUserName and WindowsUserPassword to impersonate the corresponding domain account before invoking the downstream WCF service. Note: this pattern requires the component to uses the client credentials to invoke the LogonUser function at runtime, but it does not require to configure the BizTalk environment for Protocol Transition.
- I finally extended the InspectingRequestChannel and InspectingHelper classes to support the new Static mode. In particular, the custom channel at runtime performs the following steps to impersonate a given domain account declaratively defined in the WCF Send Port configuration:
- It calls the LogonUser static method exposed by the InspectingHelper class which in turn invokes the LogonUser Windows function which returns a token handle.
- The channel creates a new WindowsIdentity object using the constructor that accepts the user token returned by the previous call.
- Then, it invokes the Impersonate method exposed by the WindowsIdentity object.
- The channel invokes the underlying channel.
- In the finally block, when the call is complete, the channel invokes the Undo method on the WindowsImpersonationContext object returned by the Impersonate method, an then it invokes the CloseHandle static method exposed by the InspectingHelper class which in turn invokes the CloseHandle Windows function.
For your convenience, I report below the new code for the InspectingRequestChannel and InspectingHelper classes (I purposely omitted parts for ease of reading):
InspectingHelper class
|
InspectingRequestChannel class
|
Test Case
To test my component, I created the following test case:
WinForm driver application submits a new request to a WCF-NetTcp Request-Response Receive Location
- The Message Agent submits the incoming request message to the MessageBox (BizTalkMsgBoxDb).
- The inbound request is consumed by a Solicit Response WCF-Custom Send Port. This latter uses a Filter Expression to receive all the documents published by the Receive Port hosting the WCF Receive Location.
- The inbound message is mapped to the request format by the downstream HelloWorldService web service. This latter is hosted by IIS and exposes a single WsHttpBinding endpoint.
- The WCF-Custom Send Port impersonates the user statically defined in the Port configuration and invokes the underlying HelloWorldService WCF service that in the scenario is hosted by a separate Application Pool (w3wp.exe) on the same IIS instance.
- The HelloWorldService WCF service returns a response message.
- The incoming response message is mapped to the format expected by the client application.
- The transformed response message is published to the MessageBox.
- The response message is retrieved by the Request-Response WCF Custom Receive Location which originally received the request call.
- The response message is returned to the client WinForm application.
The following picture shows the binding configuration of the WCF Send Port used to communicate with the HelloWorldService.
Finally, the picture below reports the trace captured during a test run.
Conclusions
As I explained in one of my recent posts, using WCF extensibility points allows you customize in-depth the default behavior of BizTalk WCF Adapters. In particular, the WCF-Custom Adapter provides the possibility to specify the customize the composition of the binding and hence of the channel stack that will be created and used at runtime to communicate with external applications.
In this article we have seen how to exploit this characteristic to workaround and bypass a constraint of WCF Adapters. As usual, I had just a few hours to write the code and write the article, so should you find an error or a problem in my component, please send me an email or leave a comment on my blog, thanks!
You can find the new version of the code here.
Comments
Anonymous
April 15, 2010
Thanks for this information, it will help allot for future work, this has given me an idea to expose promoted properties to the adapter if needed :) Cheers RomikoAnonymous
April 15, 2010
The comment has been removedAnonymous
February 11, 2011
Hi, First off, this is amazing! Question, is it possible for this concept to be use in conjunction with wcf custom sqlbinding??? I'm unsure whether or not I'm missing a configuration or something, but I haven't been able to get this to work. Using the InspectingBehaviorExtensionElement, it appears that the adapter is using sql authentication instead of windows authentication as I'm receiving a transmission failure "Login failed for user…",see below. Is there a configuration I can set that would use windows authentication? Any help would be greatly appreciated. Jeff Microsoft.ServiceModel.Channels.Common.ConnectionException: Login failed for user ''. The user is not associated with a trusted SQL Server connection. ---> System.Data.SqlClient.SqlException: Login failed for user ''. The user is not associated with a trusted SQL Server connection. at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) at System.Data.SqlClient.SqlInternalConnectionTds.CompleteLogin(Boolean enlistOK) at System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, Boolean ignoreSniOpenTimeout, Int64 timerExpire, SqlConnection owningObject) at System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(StrAnonymous
February 13, 2011
The comment has been removedAnonymous
February 13, 2011
The comment has been removedAnonymous
February 13, 2011
Hi Jeff, did you try to specify the full domain account name? I mean "DomainNameUsername"? If the answer is yes, you could try one of the following solutions: a) You could configure domain B to trust users configured on domain A. b) You could use NTLM pass-through authentication. AFAIR, the only thing you have to do in this case is creating the user account U on both domains, A and B, with the same name and password. At this point, you can simply use the user U defined on domain A to access the SQL Server located in the domain B using Windows authentication. See support.microsoft.com/.../102716 for more information on NTLM pass-through authentication.Anonymous
February 14, 2011
Paolo, Yes I'm specifying the domain. So I built an app to test the cross domain impersonation. Using the logon type LOGON32_LOGON_NEW_CREDENTIALS tests were successful as the app was able to reach across domains and return data. This is the case for all resources, file, service, database, and etc. At this point I'm thinking that accounts are properly configured… What I'm not quite sure about is what the sqlAdapter is doing behind the scenes… as it appears to be doing something totally different when going after data on a different domain versus going after data on the same domain… wow, this is blowing my mind. Thoughts?Anonymous
February 14, 2011
Mmm, the only suggestion I can give you at this point is to use Reflector to disassemble and examine the code of the SqlAdapter (Microsoft.Adapter.Sql.dll). Unfortunately, I can't do it in first person because I'm very busy at the moment, sorry about that! :-( Using Reflector Pro, you could even debug through the SQL Adapter code, even if you don't have the source code or pdb files!Anonymous
February 14, 2011
Hi, Thanks a lot for your assistance. It's greatly appreciated! I'll take a look at Reflector, and maybe it'll lead to a resolution. I'll let you know my findings. JeffAnonymous
August 24, 2011
Hi, Thank you for the great solution – It was exactly what I needed. I use it to implement impersonation of statically-defined user in BizTalk send port when send a request to SharePoint hosted service with NTLM authentication. Just a quick question – Is it possible to hide the password in the binding, It is not acceptable to store it in plain text? Or is it possible to get credentials from SSO application which for me is the better solution? SonyaAnonymous
November 29, 2011
Hi Paolo! Thanks a lot for this post/information. It's very helpfull. I was trying to resolve a similar scenario (a WCF-basicHttp receive location in BizTalk that needs to "translate" credentials to a backend's web service being called via the WCF-custom send adapter). For testing purposes I used a similar scenario as you did (I published a Web Service in the same IIS as BizTalk simulating the backend logic). I used the "Context" mode of your binding (so, the BTS.WindowsUser variable was used) and all works fine! But when I used the backend's web service a problem appears. According to your Debugview debug information: "The HTTP request is unauthorized with client authentication.........". If instead of using the "Context" mode I used the "Static" mode the call to the backend's web service works fine. Could you help me with this issue please? Regards, Diego P.D.: The called web service is not hosted in an IIS. It's hosted in a UNIX.Anonymous
November 29, 2011
Hi Diego, is the backend service using the Windows Integrated Security despite the fact it runs in a UNIX system? When using the Context mode, did you properly set the ContextPropertyName and ContextPropertyNamespace to refer to the WindowsUser or any other context property? I assume the answer is yes, but just in case. ;) Can you stop the Send Port to suspend the message and check that the WindowsUser context property has the expected value? Are you running the WCF receive location in a trusted host? Did you configure the WCF receive location to use the Windows Integrated Security? ;) I know, lots of questions, but I need to have the full picture to provide an answer. In any case I strongly suggest you to attach the BizTalk process running the send port and debug through my custom channel to see what's going on and above all to read the value of variables at runtime! Ciao, PaoloAnonymous
November 30, 2011
Hi Paolo, Thank you very much for your answer! Most of your questions are YES. - ContextPropertyName and Namespace are correctly set to read the BTS.WindowsUser (the Debugview help me to confirm that your custom channel is obtaining the information OK). - The BTS.WindowsUser is available (unenlisting the Send Port or using the Debugview confirm that the property is promoted correctly). - The Receive Location is a WCF-basicHttp running on a trusted host and using TransportCredentialOnly (TransportType = Windows). IIS is configured to use Windows Integrated Security. - The backend Web Service only accepts Kerberos (could the problem be here?). What any information I could bring you in order to help me with this issue? Thanks!!!!!, DiegoAnonymous
November 30, 2011
Mmm... it seems that you did everything well. :) When using the Static mode instead of the Context mode, do you use the same Windows user? Did you configure Protocol Transition and Contrained delegation as explained in the articles referenced in the first post of the series? Ciao, PaoloAnonymous
December 21, 2011
Hi again Paolo, I'm having no luck with this. I'm still having problems (KDC_ERR_WRONG_REALM and KDC_ERR_PREAUTH_FAILED Kerberos errors). Those errors made me think that some configuration was wrong (or missed) regarding the server (not a BizTalk problem). However, it seems that all the Active Directory stuffs (computer and IIS App Pool Domain User) are correct because I hosted in the BizTalk server's IIS a simple WCF service that calls the backend web service and all goes fine. I have doubts regarding some things explained in your article:
- The userId used by the IIS App Pool need to be trusted for delegation in AD, right...?
- What about the UserId associated to the Receive Location (SOAP or WCF) Isolated Trusted Host Instance. Could this user be the same as the IIS App Pool? Or it need to be a different one and also be configured in AD for trusted delegation?
- What about the userId associated to the Send Port Host Instance. This Host Instance is not necessary to be trusted in BizTalk? No AD trusted for delegation UserId is necessary?
- The MessageBox server need to be added in AD as trusted for delegation server? It's also need to create a SPN for it?
- Active Directory configuration: the BizTalk server and the IIS App Pool UserId are configured as "Trust this computer/user for delegation for any service (Kerberos only)". Or it's need by BizTalk scenarios set other option? What about "Services to which this account can present delegated credentials"? Should I need to add the backend server there? During these last weeks I learn a lot of Kerberos Delegation..., but I couldn't make it work with BizTalk! Any help I'll really appreciate it. Regards, Diego
Anonymous
December 22, 2011
When I said "...because I hosted in the BizTalk server's IIS a simple WCF service that calls the backend web service and all goes fine." I wanted to say "...because I hosted in the BizTalk server's IIS a simple WCF service that impersonates the user and calls the backend web service with the original credentials, and all goes fine.". Regards, DiegoAnonymous
January 08, 2012
Hi Diego, sorry for the delayed answer, but as you can easily understand, I was on vacation during Xmas time. To answer your questions, the userId used by the IIS App Pool needs to trusted for delegation as well as the userId used by the trusted isolated host instance running the WCF and/or SOAP receive location. To be safe, all the domain accounts used by the BizTalk application should be trusted for delegation, include the service account of the host instance running the Send Port. I don't think I created a SPN for the SQL Server instance running the MessageBox, but only because everything was running on the same machine when I built the demo (BizTalk, SQL, AD), but creating a SPN for SQL Server it's not only a good practice, but in some cases a necessary step. Finally, AFAIR, I n my demo I used the "Trust this computer/user for delegation for any service (Kerberos only)" option. I hope this can help you to solve your issues, also because I'm not really a security guru. ;) Ciao, PaoloAnonymous
June 23, 2014
HI Paulo, Thanks for the excellent blog. I am trying to implement it and getting an error as below, "System.ServiceModel.Security.MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'Negotiate,NTLM'. ---> System.Net.WebException: The remote server returned an error: (401) Unauthorized. I have specified "authenticationscheme"=ntlm in "httptransport" binding element. I could see that the impersonation is happening correctly from debugview. Any thoughts...did i miss somethingAnonymous
June 23, 2014
The comment has been removed