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.