Kerberos delegation and Service Identity in WCF

Scope:

Step-by-step walkthrough to establish a secure connection between a WCF Client and Service by using a Kerberos token. This example will focus on message security, though WCF also offers transport security. We will establish mutual authentication between service and client, using the wsHttpBinding.

Kerberos delegation will only have to be granted to the Service, not to the Client. We can choose to run the Service under a NetworkService or Localsystem ‘service’ account, or under a ‘domain’ account (domain\user). The Service will be self-hosted (serviceHost).

Requirements:

.NET 3.5

.Windows Domain with Active Directory

.Domain Administrator access
Tools:

SetSPN.exe, Klist.exe, Kerbtray.exe

SetSPN.exe makes it easy to define service principal names for computers or user accounts in the active directory. The other 2 tools can be useful to inspect the kerberos tickets on the machine where you will run the WCF Service. They can be downloaded from the microsoft site, in one of their Resource toolkits for windows servers

Running the Service under a Service Account

1. The Service

Code:

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceProcess;
using System.ServiceModel.Description;

namespace WCFWithKerberos
{
    class Program
    {
        static void Main(string[] args)
        {
            // create the service host
            ServiceHost myServiceHost = new ServiceHost(typeof(Echo));

            // create the binding
            WSHttpBinding binding = new WSHttpBinding();
            binding.Security.Mode = SecurityMode.Message;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

            // disable credential negotiation and establishment of the security context
            binding.Security.Message.NegotiateServiceCredential = false;
            binding.Security.Message.EstablishSecurityContext = false;

            // Creata a URI for the endpoint address
            Uri httpUri = new Uri("http://yamata:8000/Echo");

            // Create the Endpoint Address with the SPN for the Identity
            EndpointAddress ea = new EndpointAddress(httpUri, 
                              EndpointIdentity.CreateSpnIdentity("HOST/yamata.marbie.net:8000"));
            /* works with
             *  setspn -a HOST/yamata.marbie.net:8000 yamata
             */

            // Get the contract from the IEcho interface
            ContractDescription contract = ContractDescription.GetContract(typeof(IEcho));

            // Create a new Service Endpoint
            ServiceEndpoint se = new ServiceEndpoint(contract, binding, ea);

            // Add the Service Endpoint to the service
            myServiceHost.Description.Endpoints.Add(se);

            // Open the service
            myServiceHost.Open();
            Console.WriteLine("Listening...");
            Console.ReadLine();

            // Close the service
            myServiceHost.Close();
        }
    }

    public class Echo : IEcho
    {
        public string Reply(string message)
        {
            Console.WriteLine(message);
            return message;
        }
    }

    [ServiceContract]
    public interface IEcho
    {
        [OperationContract]
        string Reply(string message);
    }
}

Configuration

‘Yamata’ is the name of the machine I will be running the Service on.

Now we need to grant Kerberos delegation to this machine. For this, we will connect to the Domain Server and ‘Manage users and computers in Active Directory’. Browse to the computer name (yanaga in my case), in the ‘Computers’ tree, rightclick to select ‘properties’, select the ‘Delegation’ tab. For simplicity, we will Thrust this computer for delegation to any service, but you can tighten it to the service of type ‘HOST’, and limit it to specific ports (8000 in our example) if you want.

Next, we need to add the service principal name (SPN) we used in CreateSPNIdentity (HOST/yamata.marbie.net:8000) to the computer. On the domain controller, we can exete in a dosbox the following command:

setspn -a HOST/yamata.marbie.net:8000 yamata

Verify that there are no double entries, with the command: setspn -L yamata

Double entries will break the kerberos authentication.

The next important thing we need to do is make sure this kerberos ticket is propagated to the machine where we want to run the service. Most often, this can be done by -on the machine where we will run the service- lock the screen, and unlock it by typing in the password. This should cause a refresh of the kerberos tickets. Klist.exe or Kerbtray.exe can offer you insight on the list of tickets on your machine. The entry SPN we added should be in this list. If you use Kerbtray, you can check if the ‘delegate’ checkbox is checked dor this entry. It should be. If it is not, the ‘Kerberos Delegation’ right has not been granted to the machine for this Service.

Now, we can launch the Servide under a service account, we will use the LocalSystem account on yamata in this example. To do this, we execute the following command in start| Run:

at HH:mm /i cmd.exe

where we replace HH:mm with the current computer time + 1 minute. Wait a minute and a dos box pops up. This box will not run under your local computer user account, but under LocalSystem. In this dos box, we startup the WCFWithKerberos.exe

2. The Client

Code

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceProcess;
using System.ServiceModel.Description;

namespace Client
{
    class Client
    {

        private ChannelFactory factory;
        private WCFWithKerberos.IEcho echoClient;
        private WSHttpBinding myBinding;
        private EndpointAddress ea;

        public Client()
        {
            // Create the biding
            myBinding = new WSHttpBinding();
            myBinding.Security.Mode = SecurityMode.Message;
            myBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

            // disable credential negotiation and the establishment of a security context
            myBinding.Security.Message.NegotiateServiceCredential = false;
            myBinding.Security.Message.EstablishSecurityContext = false;

            // Create the endpoint address and set the SPN identity.
            // The SPN must match the identity of the service's SPN
		ea = new EndpointAddress(new Uri("http://yamata:8000/Echo"), EndpointIdentity.CreateSpnIdentity("HOST/yamata.marbie.net:8000"));
            /* works with
             *  setspn -a HOST/yamata.marbie.net:8000 yamata
             */

        }

        static void Main(string[] args)
        {
            Client c = new Client();
            c.Open();
            c.echoClient.Reply("test");
            c.Close();
        }

        public void Open()
        {
            // Create the proxy during runtime
            factory = new ChannelFactory(myBinding,ea);
            echoClient = factory.CreateChannel();
        }

        public void Close()
        {
            ((IChannel)echoClient).Close();
        }

    }
}

Configuration

It doesn’t matter under which account or where you run the client, as long as we run it under an account or on a workstation that is properly logged in into the domain (received valid kerberos ticket). Open a dos box and launch the Client.exe

The Service should echo ‘test’ in the console and the client should exit without output or errors. Even when we disabled credential negotiation and security context establishment, we have connected our Client to the Service on a secure way by letting WCF handle all the authentication hassle through the delegation of Kerberos.
Running the Service under a Domain Account

Configuration


There is nothing that needs to be done on the code side, if we re-use the same SPN.

We need to remove the SPN entry from the computer on the active directory, with the SetSPN tool:

setspn -d HOST/yamata.marbie.net:8000 yamata

Now we add the SPN to the domain account.

setspn -a HOST/yamata.marbie.net:8000 marbie\dreezst

where ‘dreezst’ is the domain user and ‘marbie’ is the domain name, under which I will be running the Service.

Additionally, we need to go to the Active Directory again, browse to the user ‘dreezst’, and grant kerberos delegation.

Next, we need to update our kerberos tickets on our Service machine by locking and unlocking the Computer again. Use Klist or Ktray to inspect your kerberos tickets and see that the SPN entry is there and updated. With Ktray, verify the delegation checkbox.

Now, we need to run the service as our domain account. In a dos box, enter: runas /user:marbie\dreezst WCFWithKerberos.exe, and enter the password for the domain user.

Once the Service is up and listening, launch the client from any machine under any (logged in) account.

The difference here is that we are running the Service under a domain account, which will probably be more likely in a production environment where you typically have domain accounts that are configured as Service accounts to run these services.

Advertisements

12 Responses to “Kerberos delegation and Service Identity in WCF”

  1. itai Says:

    I followed your instructions but I keep getting the following exception:
    “The athentication modes using Kerberos do not support the impersonation level ‘Delegation’. Specify identification or impersonation.
    Parameter name: tokenImpersonationLevel”.

    Do you have any idea?

    • marbie Says:

      Yes, this is typically the error you get when you have not or incorrectly granted delegation of impersonation rights to the machine(+process) that hosts the WCF service in the Active directory.

  2. Tosha Says:

    Hi,
    Is it possible to make kerberos delegation to any user?

    Example: Domain A has an IIS with a Web Application published: MyApp.com.
    Access to this app should be: Defined User’s from a trusted secure network (not from the same Domain and no trusts between) should start the MyApp.com (example with UPN as http header) and be able to make a SSO to MyApp.com!

    My Idea: The WCF Service on IIS where the MyApp.com is published takes the UPN Argument and makes a kerberos delegation to this User (provisioned as Shadow User to Domain A) :)!

    Is this possible?

    Thanks a lot..
    Tosha

    • marbie Says:

      If there’s no trust between the domains it will not work.
      In this particular case i would prefer to use certificates for client and service to establish mutual authentication.

  3. Matt Poland Says:

    I’ve always configured my delegating service connections using an SPN that starts with “HTTP”, I noticed you use “HOST”. In the WCF world, when would you use one versus the other? My guess would be based on the identity of the running service host: HOST = Local Machine, HTTP = Network Service?

    • marbie Says:

      The way i understood it, it depends on where you host your WCF services. If it is self-hosted, it will be running as a svchost.exe process and HOST should be used. If you host it in IIS i think HTTP should be used.

  4. Fun with WCF, SharePoint and Kerberos – well it looks like fun with hindsight - But it works on my PC! Says:

    […] I started from the WCF kerberos sample on Marbie’s blog. I modified the programmatic binding in the webpart to match this […]

  5. Henry Motu Says:

    Ohhhh!!!
    Thank you very much!!!
    I’m seeking this tutorial very long time) Because have some problems with passing user account to wcf service throw tcp.net.
    Now it works!

  6. sanket Says:

    I did delegation for my service and configured SPN. but the klist does not display my service SPN there. In what format it shoud appear over there?

    • marbie Says:

      matching my example:

      HOST/yamata.marbie.net:8000

      so pending on self-hosted or iis-hosted service it can be HTTP/yourmachine:port or HOST/yourmachine:port

  7. Wayne Barker Says:

    Just a side note.

    I accidentally deleted my server SPN for example setspn -d HOST/yamata.marbie.net yamata and not HOST/yamata.marbie.net:8000 yamata

    This resulted that i had to rebuild my VM. if you delete the server SPN while on a network you will get disconnected from your domain controller and you will loose trust with the DC.

    It is best to use another type like your application name instead of host when doing self hosted.

    setspn -a MyApp/yamata.marbie.net marbie\dreezst

  8. Wayne Clifford Barker Says:

    Blogged about Transport Delegation and WCF. Your site helped. thanks.

    http://www.waynecliffordbarker.co.za/wcf-tcp-transport-security-with-delegation/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: