Configuring Kerberos Delegation with .NET Remoting 2.0 [Rick Rainey]


Version 2.0 of the .NET Framework supports the use of ‘secure channels’.  These are powerful new features but they can be extremely challenging to setup.


For example, I recently was tasked with trying to get an application to use Kerberos delegation in a double hop scenario and found that while my application was actually working, it was not using Kerberos end-to-end.  It started off using Kerberos and on the 2nd hop defaulted to NTLM authentication.  This discussion illustrates how to make this work using Kerberos on both hops between 2 machines.


 


Rather than explain what Kerberos authentication and delegation is I will instead refer you to these links:


 


Exploring Kerberos, the Protocol for Distributed Security in Windows 2000


http://www.microsoft.com/msj/0899/kerberos/kerberos.aspx


 


Kerberos Authentication in Windows Server 2003


http://www.microsoft.com/windowsserver2003/technologies/security/kerberos/default.mspx


 


If you’re wondering about secure channels and delegation, the best source is from our patterns and practices group (see below).  While this document is dated, it has been revised for v2.0.  Where I hope to add value in this post is with regard to the Kerberos delegation configuration. 


 


Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetch11.asp


 


 


Solution


Attached is a sample .NET 2.0 Remoting solution that demonstrates how to use Kerberos delegation between two servers.  The solution consists of 3 projects:


 



  1. RemObj.dll – This is the remote object hosted on the server.  It is a C# class library.

namespace RemObj


{


    public class RemoteObject : MarshalByRefObject


    {


        public void Process()


        {


            IPrincipal cp = Thread.CurrentPrincipal;


            IIdentity cid = cp.Identity;


 


            Console.WriteLine(“\nName = “ + cid.Name);


            Console.WriteLine(“IsAuthenticated = “ + cid.IsAuthenticated);


            Console.WriteLine(“AuthenticationType = “ + cid.AuthenticationType);


        }


 


        public override object InitializeLifetimeService()


        {


            return null;


        }


    }


}


 



  1. RelObj.dll – This is the remote object hosted on the relay server that makes the call into RemObj.  It is a C# class library.

namespace RelObj


{


    public class RelayObject : MarshalByRefObject


    {


        public void Process()


        {


            IPrincipal cp = Thread.CurrentPrincipal;


            IIdentity cid = cp.Identity;


 


            Console.WriteLine(“\nName = “ + cid.Name);


            Console.WriteLine(“IsAuthenticated = “ + cid.IsAuthenticated);


            Console.WriteLine(“AuthenticationType = “ + cid.AuthenticationType);


 


            RemObj.RemoteObject ro = new RemObj.RemoteObject();


            ro.Process();


        }


 


        public override object InitializeLifetimeService()


        {


            return null;


        }


    }


}


 



  1. Program.Exe – This is an all in one application that can run as the server, relay server, or client.  It is a C# console application.

namespace Program


{


    class Program


    {


        // Display registered authentication modules.


        private static void displayRegisteredModules()


        {


            // The AuthenticationManager calls all authentication modules sequentially


            // until one of them responds with an authorization instance.  Show


            // the current registered modules.


            IEnumerator registeredModules = AuthenticationManager.RegisteredModules;


            Console.WriteLine(“\r\nThe following authentication modules are now registered with the system:”);


            while (registeredModules.MoveNext())


            {


                Console.WriteLine(“\r \n Module : {0}”, registeredModules.Current);


                IAuthenticationModule currentAuthenticationModule = (IAuthenticationModule)registeredModules.Current;


                Console.WriteLine(“\t  CanPreAuthenticate : {0}”, currentAuthenticationModule.CanPreAuthenticate);


            }


        }


 


        static void Main(string[] args)


        {


            displayRegisteredModules();


 


            Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);


 


            RemotingConfiguration.Configure(string.Format(“{0}.config”, args[0]), true);


 


            IPrincipal cp = Thread.CurrentPrincipal;


            IIdentity cid = cp.Identity;


 


            Console.WriteLine(“\nName = “ + cid.Name);


            Console.WriteLine(“IsAuthenticated = “ + cid.IsAuthenticated);


            Console.WriteLine(“AuthenticationType = “ + cid.AuthenticationType);


 


            if (args[0].Equals(“client”))


            {


                RelObj.RelayObject ro = new RelObj.RelayObject();


                Console.WriteLine(“About to call RelayObject.Process()”);


                Console.WriteLine(“Press Any Key…”);


                Console.ReadKey();


                ro.Process();


 


                cp = Thread.CurrentPrincipal;


                cid = cp.Identity;


 


                Console.WriteLine(“\nName = “ + cid.Name);


                Console.WriteLine(“IsAuthenticated = “ + cid.IsAuthenticated);


                Console.WriteLine(“AuthenticationType = “ + cid.AuthenticationType);


            }


            else if ((args[0].Equals(“relay”)) || (args[0].Equals(“server”)))


            {


                Console.WriteLine(“Press ENTER to exit: “);


                Console.ReadLine();


            }


            else


                Console.WriteLine(“Error: Invalid switch”);


        }


    }


}


 


To install the solution, just copy the assemblies and config files (client.config, relay.config, server.config) to each machine. 


 


Note: your config file settings (such as IP addresses, user names, and SPNs ) will need to be changed to match your environment.


 


To run the solution, open two command prompts on the client / server machine.  In one command window type ‘program server’.  This will launch the application as a server and load the server.config file settings.  In the other command window, type ‘program client’.  This will launch the application as a client and load the client.config file settings.  On the Relay Server, open a command prompt and type ‘program relay’.  This will launch the application as the relay server and load the relay.config file settings.  Finally, go back to the command prompt on the client / server machine where you have the client application running and press ENTER.  This will cause the client to call the RelObj.Process method on the Relay Server which in turn will call the RemObj.Process method back on the client / server machine.  The Process method in both objects displays the authenticated user and how the user was authenticated.


 


The physical hardware setup looks like this:


 


 



The Domain Controller (DC) is the same machine that client application and server application will run on.  In my test lab environment, this machine was a Windows Server 2003 SP1 machine.


 


The Relay Server is just a member server in the domain.  In my test lab environment, this machine was a Windows XP SP2 machine.


 


I created two domain users in the Active Directory: juser1 and juser2.  Both these users need to be trusted for delegation and make sure that the “Account is sensitive and cannot be delegated” is not checked.  For example,


 



 


Also, each computer needs to be trusted for delegation.  For example,


 



 


 


NOTE: In the following steps replace your domain name with the lab-assigned domain that I have here, which is A-BENGILDOMAIN3.


 


The key to making this work correctly is the servicePrincipalName (SPN) setting in the config file.  In an unmanaged environment, we typically use setspn.exe to get the SPN.  This typically looks something like ‘HOST/2B17C.A-BENGILDOMAIN3.LAB’.  While using this SPN works in a native DCOM solution, it does not work in a managed .NET Remoting solution.  Therefore, it is imperative that you use the following format for the servicePrincipalName in this case:


 


‘JUSER2@A-BENGILDOMAIN3’   // Again, this will be different depending on your environment; user(s) and domain name


 


For example, the application config file for program.exe would look like this:


<configuration>


  <system.runtime.remoting>


    <application>


      <client>


        <wellknown type=RelObj.RelayObject,RelObj url=tcp://


          <ipaddress of=“” relay=“” server=“”>


            :8123/relobj” />


          </client>


      <channels>


        <channel ref=tcp port=8123 secure=true tokenImpersonationLevel=Delegation servicePrincipalName=juser2@a-bengildomain3/>


      </channels>


    </application>


    <customErrors mode=Off/>


  </system.runtime.remoting>


</configuration>


 


Notice how the domain part of the name differs from the DNS name as shown above.  For example, instead of


 


‘A-BENGILDOMAIN3.LAB’


 


it is


 


‘A-BENGILDOMAIN3’


 


Other settings in the config files such as ‘secure’ and ‘tokenImpersonationLevel’ are necessary but pretty much self explanatory.  The SPN setting is then one that needs special attention because it is not what you would expect if you had done this before in a native DCOM environment.


 


The application config file for RelObj.dll would look like this:


<configuration>


  <system.runtime.remoting>


    <application>


      <channels>


        <channel name=server ref=tcp port=8123 secure=true tokenImpersonationLevel=Delegation servicePrincipalName=juser1@A-BENGILDOMAIN3 authenticationMode=IdentifyCallers/>


      </channels>


      <service>


        <wellknown mode=Singleton type=RelObj.RelayObject,RelObj objectUri=relobj />


      </service>


      <client>


        <wellknown type=RemObj.RemoteObject,RemObj url=tcp://


          <ipaddress of=“” client=“”/server>:8124/remobj” />


        </client>


    </application>


    <customErrors mode=Off/>


  </system.runtime.remoting>


</configuration>


 


Application config file for RemObj.dll would look like this:


<configuration>


  <system.runtime.remoting>


    <application>


      <channels>


        <channel ref=tcp port=8124 secure=true/>


      </channels>


      <service>


        <wellknown mode=Singleton type=RemObj.RemoteObject,RemObj objectUri=remobj />


      </service>


    </application>


    <customErrors mode=Off/>


  </system.runtime.remoting>


</configuration>


 


 


The application output will tell you whether or not this is working end-to-end.  If you see that the Client, Server, and Relay instances output Kerberos then you have configured it successfully.  For example, the output should look something like this on all three:


 


Name = <some user name>


IsAuthenticated = True


AuthenticationType = Kerberos


Comments (3)

  1. One of the common question we have in remoting forums is how to configure Kerberos delegation with .Net…

  2. BryanK says:

    A few issues here:

    a) You will probably need to know whether you’re authenticating to a UPN (which maps to a domain user in AD; the remoting server must be running as that user) or an SPN (which maps to a domain computer in AD; the remoting server must be running as LocalSystem or NetworkService on that computer) before you can set up the config files correctly.  The reason an SPN won’t work here is because both the RelObj and RemObj remoting servers are running as a domain user, not because remoting is inherently different from DCOM.

    b) In case it isn’t obvious from the previous item: HOST/2B17C.A-BENGILDOMAIN3.LAB is an SPN, and JUSER2@A-BENGILDOMAIN3 is a UPN.  UPNs look like email addresses (they use @ to separate the user name from the domain), while SPNs use a / to separate the service-identifier (and port, if specified in the SPN) from the domain.

    c) I don’t think it matters whether the full DNS domain is specified in the UPN that you use or not (I believe JUSER2@A-BENGILDOMAIN3.LAB should have worked).  However, you probably should use the same UPN as the one that’s registered in AD to the user account.  One way to find out what this UPN is, is to fire up ADSI Edit (or any other LDAP query tool), and look at the userPrincipalName attribute on the appropriate user account object.

    d) Trusting the computer for delegation should *not* be required in this case (plus it reduces security).  The only accounts that should have that box checked are the accounts that the middle-tier server(s) run under; nothing else should need it.  If the relay server ran as LocalSystem or NetworkService, then the computer account of the machine that it ran on would need to be trusted, but not otherwise.  (At least that’s how it works in our 2000-level domain.)

    e) The "account is sensitive and cannot be delegated" setting needs to be cleared on the account of the user running the client program, not necessarily any other user.  (Although in a multiple-delegation setup with two relay servers, the first relay server’s account may require that flag to be off.  I don’t think so, though, because the first relay server is going to delegate the real user’s account, not its own.)

    f) DCOM seems to act differently because DCOM (by default) runs your server as LocalSystem or NetworkService, not a domain user.  This can be changed in the Component Services snap-in, though, if you wanted a DCOM server to run as a domain user.

    In short: It appears that the real issue here is the fact that the remoting config file attribute name is servicePrincipalName.  It’s not actually an SPN in all cases; only when the remoting server is running as LocalSystem or NetworkService (i.e. it’s a service).  Otherwise, the remoting server is running as a domain user, and you need to use a UPN.

  3. memerson says:

    I eventually got this to work in my environment, but there were a few points that were not obvious and it took me a LONG time to get everything working.  Most of the information is available, but it is difficult to find and distributed.

    We have a Windows 2003 AD domain and in order to see the Delegation tab on user properties in Active Directory Users and Computers, you need to first create a SPN for that user.  So if you are setting up the user FooService for your relay service to run under and it will be running on the server RelayServer, you need to set up an SPN on you AD server using the command:

    setspn -A TCP/RelayServer FooService

    Once this is done you can then select "Trust this user for delegation to any service (Kerberos only)" on the delegation tab for the user FooService in the AD user manager.

    The servicePrincipalName value in the client config file then needs to be set to "FooService@MyDomain"

    The above example needs to have impersonate="true" in the channel definition for the relay and the server.

    In order for impersonation to work when connecting to SQL Server you need to enable kerberos authentication in SQLServer.  This link explains how to do that

    http://support.microsoft.com/kb/319723.