In the past few weeks, I’ve been working on this new security scheme for web services and didn’t find what I needed in one place. That’s mostly why this took a couple of weeks! So, I wanted to write down some of the major lessons learned to help future-me, and potentially others that might run across this post.
I had a requirement to have a WCF client that consumed a Kerberos Token Profile v1.1 protected web service, over SSL, and use sign-only. In other words, it should be transport-only encryption, to reduce overhead and redundancy.
What made this complicated is that the server is side is an IBM DataPower appliance. The good thing about DataPower is that you can configure every minute detail of a web service or the security that it uses. The problem in this case is there wasn’t a “turnkey” solution for Kerberos Token Profile on DataPower, which means you kind of have to hand-make the endpoint on that side. To be able to do that, we needed to find out what a proper request and response looked like.
So, my approach here was to create a simple server and client in WCF. From there, we could see what a proper request and response looks like. Just to have this all written down in one place, let me cover hopefully all the major things you’d need.
Active Directory is a proprietary directory service from Microsoft, but under the covers, it implements open standards like Kerberos and LDAP. In this case, since we’re crossing platforms, we need to speak the common language of Kerberos, for authentication. Kerberos works by the client passing credentials to a Kerberos Domain Controller (KDC), the DC validates the credentials and returns a Ticket Granting Ticket (TGT). The client then uses this ticket to request access to a service, which in turn it gets a Service Granting Ticket (SGT). “Services” are setup ahead of time as a Service Principal Name (SPN) and can be associated with multiple accounts, ideally the service accounts that this service will run as. This book helped me understand this stuff much better
The client then connects to the actual service and includes the SGT with the request. On the server-side, that SGT can be validated only by the service account that the service is associated with. For example, let’s say you have a service account called ContosoService-ClaimService and an SPN of http/www.contoso.com/claimService – then in Active Directory (or your Kerberos server), you’d associate that SPN with that service account. Then, on the http://www.contoso.com server, you’d run the claimService application as the specified service account. So when the client connects with an SGT, your application validates it against Active Directory, but will only validate if you are connecting as the service account (as I understand it).
To be clear, I’m no Kerberos expert, this is just what I’ve gathered from this experience. So in short, you’ll NEED a service account, you’ll need an SPN set up, and the server side of this will need to run as this service account. If any of this is incorrect, please leave comments or send me e-mail!!
The only exception here is that if the server is a non-Microsoft technology and if that technology can’t speak Kerberos. In these cases, instead of the server-side running as the service account, the server uses a “keytab” file to validate the user. How does that work? I have no idea. But below, I’ll explain what we did to get this working, if it helps!
WCF server and client, using KTP:
Going back to our original proof-of-concept, we need to get a WCF server and client talking to each other using KTP. For the purpose of this post, I’ll use the fake name “Contoso” for a domain name and let’s assume this is a web service hosted on www.contoso.com/claimService. To create an end-to-end example purely in WCF, there are a few things you need – and you can’t do this without Active Directory or some other Kerberos server:
- Create a service account – let’s call it ContosoService-ClaimService (a service account is a regular user account that typically is restricted and can’t log on locally – used for running server-side applications)
- Associate an SPN with this service account – on an AD server, run: setspn -A http/www.contoso.com/claimService ContosoService-ClaimService
- Run the application as the service account – this part was a little surprising to me. You can’t use <identity impersonate=”true”… in the web.config and impersonate the service account (even with that ASP compatibility attribute) – it just didn’t work. The process itself, must run as the service account. This means that if this is hosted in IIS, the application must run in it’s own AppPool, and that AppPool needs to run as the service account. I couldn’t find a way for this to work any other way.
Before I go on, there are two key troubleshooting things that helped a lot. First, you can turn up Kerberos logging in the windows Event Viewer by following the directions here. This produced a couple of decent error messages that helped us troubleshoot. I made this setting on my workstation, not on the server. Also “kerblist.exe” (included in the Windows Server Resource Kit) can be used to display or flush the cache of Kerberos tickets that the current process holds.
OK, so assuming you have a service account, the SPN is associated with it, and the server is running as that service account, next, we configure WCF. Luckily, that’s the easy part! On the server-side, here’s a canonical example of the minimum config that’s needed, using our example:
<endpoint address="http://www.contoso.com/claimService" binding="wsHttpBinding"
<servicePrincipalName value="http/www.contoso.com/claimService" />
Next, you generate a client proxy (you’d need to have metadata turned on first though) – and that should be it. The client that is generated, knows that it is connecting to a Kerberos server, so the WCF run-time takes care of all of that TGT/SGT business. So the client is the easiest part of all.
From what I gather, if the “server” side of a service can’t run as a service account like in the case of DataPower, then it must use a keytab file. I’m not really clear on what it is, other than a facility with which the server can validate that a clients SGT is valid. Using Active Directory, you’d generate a keytab file by running this command:
ktpass -out output.keytab -princ http/www.contoso.com/claimService -mapuser Service-ClaimService@contoso.COM -mapOp set -pass ServiceAccountPasswordGoesHere -CRYPTO RC4-HMAC-NT -PTYPE KRB5_NT_PRINCIPAL -KVNO 255
Kerberos Token Profile over SSL:
Now, testing this on my workstation using Cassini was easy, everything was http. The requirements however were to just use KTP for authentication and signing, and NOT to encrypt. We wanted to use SSL for that. You might guess that you could set <security mode=”Transport” instead of the default of “Message”. As it turns out, you can’t. You also can’t use https with mode set to “Message”.
So after some research, I found what the customBinding version of wsHttpBinding is, and started working on a customBinding from there. I spent may a day or two trying every infinite combination of settings. Eventually, I started to go simpler and simpler until I came up with the working config:
<security authenticationMode="Kerberos" />
Two lines of config is all you need. Leave it to me to over-complicate things! So, the last thing we need to do is have the service do signing instead of the default of sign+encrypt. That’s accomplished on the server-side, in the ServiceContract attribute, on the interface:
public interface IClaimService
and that’s about it. Once we had a WCF server and client, we could inspect a proper request and response. From that, we could get the DataPower side all set and got an end-to-end solution working. Hope this helps!