Tuesday, January 13, 2009

'Scuse Me While I Geek Out or How To Set Up WCF With Windows Message Security In a Load Balanced Environment, Using wsHTTP Binding

I am a computer geek. My job is very diverse so I get to delve in to all sorts of fun topics. My office is basically a Windows shop, so some of the things I deal with are Windows OSes, IIS, Load Balancing, Web Security, and Development.

I don't like to come in on nights and weekends, so I have set up our environment such that all vital web sites and services live on multiple Windows 2008 servers behind a F5 BigIP Local Traffic Manager. Enter in the DotNet Framework 3.0, 3.5, and 3.5 SP1, and the wonderful world of the Windows Communication Foundation (WCF).

Unlike previous versions of IIS, IIS 7 was specifically built with WCF in mind, and the ideal platform for any IIS hosted WCF Services. What's better is that in our environment, anything that goes on our IIS servers, are load balanced.

Before we get in to that, let's first talk a little about security. First it is my opinion that no unauthorized person should be able to connect to ANY web site, especially if that web site is in a corporate intranet. Therefore you need to have some form of user authentication on anything that hits your web server. My preference is to use Windows Authentication, because, well you have a domain for a reason, and I don't want to mess with creating any separate user databases. What's more is that I like to use Windows Kerberos so that I don't have to worry about authentication over multiple hops.

So how do you do you set up IIS to use Kerberos? It is easy, but a bit involved. First EVERYTHING involved must be trusted for delegation, and I do mean everything (your client account, the IIS server, any third tier server, and the database server all must be trusted for delegation in Active Directory).
Second if you are using a DNS name, this post is about load balancing, so I assume you are, you need to create a Security Principal Name (SPN) for your DNS name. Since we are load balancing, you will need to pick some service account, trusted for delegation of course, and create the SPN for the DNS name with that account.
If you are going to connect to a database server with your client credentials, you will need to create a SPN for that specific instance of the database server. This will need to be done under a domain account. Before you even ask, yes the account must be trusted for delegation.
Finally, all web sites that you want to secure with Kerberos need to have Windows Authentication enabled. Also in IIS, if you want to do ASP.NET impersonation, that must also be set to enabled.
You should be Kerberosing around the room now.

All that said, we go back to setting up WCF service to live in the load balanced environment with windows security. Remember all that stuff I said about Kerberos? All of that applies to web sites and traditional web services, NOT to WCF. WCF handles its own security, so we have to do things just a bit differently. If you are using a web site to call your WCF service, you will need everything that I mentioned above working, as WCF will still use Kerberos to authenticate the client user.
For IIS hosted WCF to work the way I will describe, you will first need to violate one of the rules for Kerberos Authentication in IIS, you will have to enable Anonymous Authentication and disable Windows Authentication on the service virtual directory in IIS. WCF will still be using Kerberos, it just hates it when IIS steals its glory.

I am not going to go in to WCF bindings and their pros and cons. I want to use Kerberos security, so I use the wsHTTP binding.
Setting up the your client and the WCF service now simply becomes a shell game of setting the right configuration settings in the system.servicemodel section of your service and client configuration files.
To get WCF to use Kerberos we set the security mode to "Message." We then have to set the message security to use Windows. Easily done. Set the clientCredentialType equal to "Windows" in both the transport and message sections.
In its default state, the client using wsHTTP will attempt to create a stable session with its service. This is NOT what we want. Remember, we are in a load balanced environment, and we could switch servers at any time. Stateful connections will error out when the client makes a connection with Server1, then is balanced over to Server2 on the next request. We have to disable this so we set the establishSecurityContext="false" and negotiateServiceCredential="false." This forces WCF to authenticate every connection it makes, and prevents the WCF service from forming a stateful relationship with the client.

The last thing that you need to do is to update your client configuration file to use the proper credential. Since you are now doing an authenticated call for each connection you set the identity tag to servicePrincipalName. This account should be the trusted for delegation account that you used to set up the application pool for your service.
The rest of the client configuration should be such that you can connect to your service.

So what do the config files look like? I am glad you asked.
Service config:



<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBinding">
<security mode="Message">
<message establishSecurityContext="false" negotiateServiceCredential="false" />
</security>
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="mexBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="200" maxConcurrentSessions="200" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="mexBehavior" name="YourService">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpBinding" name="EndPoint" contract="Your Contratct" />
</service>
</services>
</system.serviceModel>




Client config:



<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="EndPoint" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="false"
algorithmSuite="Default" establishSecurityContext="false" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="Your End point.svc"
binding="wsHttpBinding" bindingConfiguration="EndPoint"
contract="Your Contract" name="EndPoint">
<identity>
<servicePrincipalName value="Your user@yourdomain.com" />
</identity>
</endpoint>
</client>
</system.serviceModel>




That's it! Several weeks of work and many headaches later, we finally have a stable WCF service that is load balanced.

3 comments:

NattoNinja said...

Why yes, my blog is cool... What are your thoughts on plural marriage, by the way?

Daniel said...

Doesn't plural marriage contradict your stated preferences for security and load balancing?

We run our network on 220 or 221 volts - whatever it takes.

NattoNinja said...

Nah. Think of it as an Active/Active wife cluster.