Showing posts with label Kerberos. Show all posts
Showing posts with label Kerberos. Show all posts

Wednesday, February 23, 2011

Did you know? You can be someone else

I often have conversations with people regarding accessing resources as another configured users, eg you may want to connect to a local / remote SQL server as a named windows user. You may want to access files or interact with a web based api.
Some people store the username and password in a config file. Well, you don't need to Smile
You need the “Act as part of operating system” security policy .
You can just call (yes, this is a test):
[TestMethod]
public static void SimpleCallAsUser()
{
string yourUser = @"Someone"; // Change me

WindowsIdentity wi = new WindowsIdentity(yourUser);
WindowsImpersonationContext imp = wi.Impersonate();
string currentUser = Environment.UserName;
Assert.AreEqual(yourUser, currentUser, true, "Could not impersonate");            

imp.Undo();
}


If you need to call remote servers or access remote shares, then the account running the code needs delegation configuring. Select the Account in AD and … (Note you do not need todo this if you accessing resources on the Local Machine)



image

Thursday, March 11, 2010

Protocol Transition with BizTalk

 

Background

A customer had a problem; they wanted a web service that was called by user UserA to run a BizTalk orchestration and at the end of the orchestration another web service would be called and they wanted that to be executed in the context of the caller.
image 

Fig 1: Illustration of problem

Above, the user token is lost when we enter the BizTalk orchestration. BizTalk will call any external services as the BizTalk process account. Setting up Kerberos will not fix this. This problem is not the typical double hop issue.

Workaround with Trusted sub system 

This problem can be tacked with the trusted subsystem model. The caller (Biztalk) sets the username in a header and the receiver acts as this user. Note that Biztalk does not call the endpoint using the callers credentials; It merely passes the username as a string. The web services must trust that if they are called from the BizTalk server the user parameter is correct. The web services are using API’s that allow impersonation. The effort to get this working is placed on both BizTalk and the endpoint, we have to pass the username around and we have to act as this user on the receiver.

For example
User calls service a as user UserA
BizTalk gets the username by using BTS.WindowsUser.
BizTalk calls the endpoint and passes the username.
The APIs that are used in the backend system behind the endpoint impersonate UserA (Note; no operating system level impersonation is done)

To make the above secure, the endpoint only accepts calls from the BizTalk server and it trusts the claims it makes.

Workaround with Constrained delegation

Another way to tackle this problem is to use constrained delegation. The works as follows :
User calls service a as user UserA

BizTalk gets the username by using BTS.WindowsUser.
BizTalk calls the endpoint and inserts the username in the message context.
A WCF adapter intercepts message and impersonates the user at the operating system level. (We are still on the BizTalk server at this point)
The receiving WCF endpoint is now running in the context of the user.
Why is this safe? How can you just impersonate a user? Without the password? Well you can and it’s safe (please read )

Trusted subsystem vs Constrained delegation

A simple way to think of this is, with Constrained delegation you put the effort into the BizTalk and the infrastructure hosting the BizTalk. With trusted sub system you put the effort into the receiver.

Please note, that the term “trusted sub system” can be used to describe other solutions to similar security related problems.

If you OWN and control all the endpoints then I would consider using the trusted sub system model. Your dev team may find this easier to get working and does not rely on setting up your domain. Saying that, once you do get this working you will probably find it easier to add more systems along the way.

How to

The solution that has been built by following these steps can be found on codeplex. I tend to-do things the TDD way (as much possible) this goes for BizTalk too. I tend not to drop files to test things, I hit web services and expect things to get returned. This step by step guide will go through creating the web services that call BizTalk.

Prerequisites
  • Visual Studio 2008.
  • BizTalk 2009 Installed and working.
  • Domain Admin privileges.
  • Access to setspn. (Comes with windows 2003 support tools, I think it’s built into 2008 r2)
  • An account to delegate to (just ask a colleague if you can impersonate them).
  • Windows 2008 R2 (That's what I used, you can get this working with other versions but things may not be exactly the same).
  • A Windows 2008 R2 Server to host the final WCF service. (To test properly, this NEEDS to be a separate server or VM)
Step One (Setup Delegation)

running as a domain admin run the following SETSPN commands
Setup for BIZTALK server running as DOM\BizTalkUser




setspn –A HTTP/BIZTALKSERVER DOM\BizTalkUser 
setspn –A HTTP/BIZTALKSERVER.FQDN DOM\BizTalkUser




Setup for WCF server running as network system.















setspn –A HTTP/WCFSERVER WCFSERVER 
setspn –A HTTP/WCFSERVER.FQDN WCFSERVER




Note, .FQDN is the full qualified domain name, eg server.domain.local





On the BIZTALK server ensure the account has the “act as part of operating system” right.





Using AD enable constrained delegation from the user DOM\Biztalk to WCFSERVER over HTTP. Edit settings on the Biztalk server. You will need to do this for the account. If you do not see the delegate options on the account this means the spns have not been created.





image 


Step Two (Create the solution)

Create a blank solution in Visual Studio 2008 called LetsDoProtocolTransition. Add it empty tests project to this. Call this Tests (or what ever you wish)







Step Three (create EndPoint system and test constrained delegation works)


We will be creating a WCF web service and some tests to call this. One test will impersonate the other will not.



  • Select Add New Web Site, select WCF Service, set location to HTTP and call this http://localhost/WCFStartBiztalk 


  • Delete Service.cs and IService.cs.


  • Using add new item, select WCF Service, call this NameService


  • Open INameService, remove



    [OperationContract] 
    void DoWork();



  • Add



    [OperationContract] 
    string WhatsLoginName();



  • Implement, just have it return “” for now as we are doing this the TDD way :)


Build and add a reference to this service from your test project. Enter WCFBackEndSystem in the namespace.


  • Add the following settings to your tests (right click on project, select settings to get to this screen) :

    image


  • Add a test file to your test project, name this file WCFBackEndTest.cs add the following code to your test :
    [TestMethod]
    public void SimpleCall()
    {
    NameServiceClient client = new NameServiceClient();
    Assert.AreEqual(WindowsIdentity.GetCurrent().Name, client.WhatsLoginName(), true);
    }



  • Run the tests and you will get Assert.AreEqual failed. Expected:<DOM\User>. Actual:<>.


  • Implement the backend code :



    public string WhatsLoginName() 
    {
    return OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name;
    }



  • Run your test and it should pass.


  • Add a new tests with the following code
    [TestMethod]
    public void SimpleCallAsUser()
    {
    WindowsIdentity wi = new WindowsIdentity(Tests.Properties.Settings.Default.remoteUPN);
    WindowsImpersonationContext imp = wi.Impersonate();
    NameServiceClient client = new NameServiceClient();


    Assert.AreEqual(Tests.Properties.Settings.Default.remoteUser , wi.Name, true,
    "Could not impersonate, make sure you can act as part of operating system");
    Assert.AreEqual(Tests.Properties.Settings.Default.remoteUser, client.WhatsLoginName(), true,
    "Call to service failed, delegation not working");
    imp.Undo();
    }



  • Run your test and it should pass.


  • If you get Call to service failed, then you don't have delegation setup correctly. Or…. you have problems with SPNs, you may need to get out ADSI edit have a look around.


  • The above tests should work, we are not calling a remote server but we are proving that part of this works.


  • Copy the WCF service to the remote server and create a new test that looks like
    [TestMethod]
    public void RemoteCall()
    {
    NameServiceClient client = new NameServiceClient();
    client.Endpoint.Address = new EndpointAddress(Settings.Default.WCFService);
    Assert.AreEqual(WindowsIdentity.GetCurrent().Name, client.WhatsLoginName(), true);
    }

    [TestMethod]
    public void RemoteCallAsUser()
    {
    NameServiceClient client = new NameServiceClient();
    client.Endpoint.Address = new EndpointAddress(Settings.Default.WCFService);

    WindowsIdentity wi = new WindowsIdentity(Settings.Default.remoteUPN);
    WindowsImpersonationContext imp = wi.Impersonate();

    Assert.AreEqual(Settings.Default.remoteUser, wi.Name, true, "Could not impersonate, make sure you can act as part of operating system");
    Assert.AreEqual(Settings.Default.remoteUser, client.WhatsLoginName(), true, "Call to service failed, delegation not working");

    imp.Undo();
    }



if everything is setup correctly you should get all greens.

Step Four (The BizTalk bits)

For this part, lets assume the following understanding of biztalk :


  • How to create a biztalk orchestration that is created by calling a SOAP web service.


  • How to consume a WCF service.


  • How to setup the ports for the above service.


I assume that you may not have done the following :


  • Passed calling user name to a header


  • Installed a WCF extension


  • Editing the custom binding


Our incoming message is the same as the outgoing to keep this simple. This message has the CallerName, BizTalk Username and the WCF Service Name. They should all match. The XML will look like :

<ns0:userDetails callerClaim="DOM\ExampleUser" biztalkClaim="DOM\ExampleUser" wcfClaim="DOM\ExampleUser" xmlns:ns0="LetsDoProtocolTransition" /> 





When I create these types of POC I tend to put exception handling into BizTalk and have it write out details using Debug.WriteLine. You can then run debug view and see the messages.





This also means if things don't work you don't get things stuck in any BizTalk queues so this makes redeploying easier. Any outgoing ports are setup with zero retries for the same reason.





Your final orchestration will look like this:








image 
When you are wiring up the WCF endpoints use Custom Bindings as we will be editing these.





We won’t be adding the custom WCF behaviour yet, we will get this working without. The WCF services will return the BizTalk process account.





Publish the orchestration as a web service with the address http://localhost/LetsDoProtocolTransition





For now, just get BizTalk calling your WCF endpoint. Don't worry about passing the user. Look at the next section for the test used to test BizTalk.



Step Five (The Tests)



  • Add a ASMX reference to http://localhost/LetsDoProtocolTransition


  • Call this BizTalkService with this code



    [TestMethod]
    public void CallBiztalk()
    {
    //
    // TODO: Add test logic here
    //
    BizTalkService.LetsDoProtocolTransition_Protocol_Transition_Orchestration_Port_1 client;
    client = new Tests.BizTalkService.LetsDoProtocolTransition_Protocol_Transition_Orchestration_Port_1();
    client.Credentials = CredentialCache.DefaultCredentials;

    WindowsIdentity wi = new WindowsIdentity(Tests.Properties.Settings.Default.remoteUPN);
    WindowsImpersonationContext imp = wi.Impersonate();

    var userDetails = new Tests.BizTalkService.userDetails();
    userDetails.callerClaim = WindowsIdentity.GetCurrent().Name;
    userDetails.biztalkClaim = "";
    userDetails.wcfClaim = "";

    client.Operation_1(ref userDetails);

    Trace.WriteLine("You are\t\t\t\t: " + userDetails.callerClaim);
    Trace.WriteLine("BizTalk thinks are\t\t\t: " + userDetails.biztalkClaim);
    Trace.WriteLine("The WCF Services thinks you are\t: " + userDetails.wcfClaim);

    Assert.IsTrue( string.Equals(userDetails.callerClaim,userDetails.biztalkClaim ,StringComparison.CurrentCultureIgnoreCase ), string.Format( "BizTalk does not agree that you are who you say you are. You Say {0}, BizTalk Says {1}", userDetails.callerClaim,userDetails.biztalkClaim ));
    Assert.IsTrue(string.Equals(userDetails.callerClaim, userDetails.wcfClaim , StringComparison.CurrentCultureIgnoreCase), string.Format("WCF does not agree that you are who you say you are. You Say {0}, BizTalk Says {1}", userDetails.callerClaim, userDetails.wcfClaim));

    imp.Undo();
    }



  • Run the test, it will fail as the WCF service will be running as the BIZTALK user.











Step Six (Adding the WCF Custom Channel)



You will need to download this from codeplex, download the project and add it to your solution. Build it (make sure it goes in the GAC).








Open your machine.config (if you running 64bit then depending on your biztalk setup you may need to edit the 64bit version). Locate










<system.serviceModel>
<extensions>







And add










<add name="protocolTransition" type="Microsoft.BizTalk.Rangers.ProtocolTransition.WCFCustomChannel.InspectingBehaviorExtensionElement, Microsoft.BizTalk.Rangers.ProtocolTransition.WCFCustomChannel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=874a60d7e5a4dd9b" />






To the behaviorExtensions node and bindingElementExtensions, this section of the config will look something like :








image











You can now use the protocol extension with WCF. This is done with a custom binding.


















Step Seven (Testing the WCF Binding)






To make testing of the WCF custom binding less effort we are going to setup the channel / binding with code. This mean you don't need to edit the config file and break the other tests. With real code I would use the config file so it would be less ‘hard coded’.











  • Add the following code :



    [TestMethod]
    public void WCFCustomChannelTest()
    {

    var textMessageEncoding = new TextMessageEncodingBindingElement();
    var protocolTransition = new InspectingBindingElement();
    var httpTransport = new HttpTransportBindingElement();

    textMessageEncoding.MessageVersion = MessageVersion.Soap11;
    httpTransport.AuthenticationScheme = System.Net.AuthenticationSchemes.Negotiate;
    protocolTransition.UserNameOverride = Settings.Default.remoteUPN ;

    CustomBinding binding = new CustomBinding(
    textMessageEncoding,
    protocolTransition,
    httpTransport
    );

    var channelFactory = new System.ServiceModel.ChannelFactory<INameService>(binding);
    INameService client = channelFactory.CreateChannel(new EndpointAddress(Settings.Default.WCFService));

    Assert.AreEqual(Settings.Default.remoteUser, client.WhatsLoginName(), true);
    }






  • If you run debug view you will see trace information.




    image


  • or double click on the test and you will see :




    image







We have now proved that our WCF Extension is intercepting the call and impersonating a user.










Step Eight (Getting it working from Biztalk)






To get this to work from BizTalk we need to modify the port that biztalk sends the message through. Assuming you have got the BizTalk bits working.











  • Create an XML file that contains the following (naming it CustomBindingConfig.config) :



    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
    <system.serviceModel>
    <client>
    <endpoint address="http://<YOURSERVER>/WCFBackendSystem/NameService.svc" binding="customBinding" bindingConfiguration="customBinding" contract="BizTalk" name="WcfSendPort_NameService_BasicHttpBinding_INameService_Custom" />
    </client>
    <bindings>
    <customBinding>
    <binding name="customBinding">
    <textMessageEncoding messageVersion="Soap11" />
    <protocolTransition ContextPropertyName="WindowsUser" ContextPropertyNamespace="http://schemas.microsoft.com/BizTalk/2003/system-properties" />
    <httpTransport authenticationScheme="Negotiate">
    <extendedProtectionPolicy policyEnforcement="Never" />
    </httpTransport>
    </binding>
    </customBinding>
    </bindings>
    </system.serviceModel>
    </configuration>



  • In your send ports, select the WCF one



  • image





  • Select configure, select import (on the import/export tab).


  • Import the xml config file created above.


  • You should see the following binding details , ok these




    image


  • Run your tests, you should get all greens.




    image


  • The output of the biztalk tests should be something like :




    image


  • As you can see we are running tests as wisdomv6 but all the checks return the user that we are impersonating.




What have we just done?



In simple terms we have extended WCF to allow it to impersonate a user before it calls the endpoint. We have configure our domain to allow impersonation. We have passed calling user to the WCF EndPoint in BizTalk.

Friday, November 20, 2009

Solving the Double Hop Issue

With a Batch File

I have come across many blogs and websites that go into great detail on how to solve the double hop issue. I have shared these with my colleagues and customers they all think that they seem too complex, and are therefore do not want to use Kerberos.

Solving the Double Hop Issue with Kerberos is not complex.
This diagram illustrates the problem.



This is taken from a very good blog on the same subject which goes into way more detail…

What you need to fix it

  • I would recommend running services as a DOMAIN USERs rather than network service or local users.
  • (“services” meaning Application Pools for web sites and SQL services)
  • Service Principal Names.
  • Local Machine Rights granted to the Service Accounts.
    • Act as part of operating system
    • Impersonate user
  • Settings in AD for the Machines to allow delegation.
  • Settings in AD for the Service Accounts to allow delegation.

 

What does this mean

A Service Principal Name (SPN) is the name by which a client uniquely identifies an instance of a service. For example HTTP/Servername is the SPN for a web site at Servername. MSSQLSvc/ServerName:1234 is the SPN for a SQL Service running on Servername on Port 1234 (see http://msdn.microsoft.com/en-us/library/ms677949(VS.85).aspx for more information on service principal names).

Local Machine rights are managed with Local Security Policy (found under Administrative Tools) .

Delegation is to pass someone’s user credentials to another process or server.

Both AD settings are edited with the MMC Snap-in “Active Directory Users and Computers”. You need to allow both the service users and the machine to delegate.

Some tools from the web


You need :
  • Found on the windows resource kit
    • NTRIGHTS.EXE (Set local machine rights)
    • SETSPN.exe
  • Installed with IIS
    • adsutil.vbs
  • On codeplex
    • machinedelegation.vbs
    • userdelegation.vbs
  • Below and on codeplex
    • Enable.bat

The Batch File


@REM ==================================
@REM Setup VARS
@REM ==================================

set MACHINE1=DBLHOP
set MACHINE2=DBLHOPSVR

set FQDN=domain.local

set USERACCOUNT=DOMAIN\ServiceAccount
@REM WEBID is the WEBSITE ID, THE Default website is 1
set WEBID=1

@REM ==================================
@REM Use adsutil to config IIS to use kerberos
@REM ==================================

cscript C:\inetput\adminscripts\adsutil.vbs set w3svc/WebSite/root/NTAuthenticationProviders Negotiate

@REM ==================================
@REM Use SETSPN to create the SPNs
@REM ==================================

setspn -A HTTP/%MACHINE1% %USERACCOUNT%
setspn -A HTTP/%MACHINE1%.%FQDN% %USERACCOUNT%

setspn -A HTTP/%MACHINE2% %USERACCOUNT%
setspn -A HTTP/%MACHINE2%.%FQDN% %USERACCOUNT%

@REM ==================================
@REM Use NTRIGHTS to enable Act as part of operating system and
@REM Impersonate a client after authentication
@REM ==================================

ntrights +r SeTcbPrivilege -u %USERACCOUNT% -m \\%MACHINE1%
ntrights +r SeTcbPrivilege -u %USERACCOUNT% -m \\%MACHINE2%

ntrights +r SeImpersonatePrivilege -u %USERACCOUNT% -m \\%MACHINE1%
ntrights +r SeImpersonatePrivilege -u %USERACCOUNT% -m \\%MACHINE2%


@REM ==================================
@REM Use VB Scripts to give the user the delegate right
@REM ==================================

cscript userdelegation.vbs %USERACCOUNT% enable

@REM ==================================
@REM Use VB Scripts to give the machine the delegate right
@REM ==================================

cscript machinedelegation.vbs %MACHINE1%
cscript machinedelegation.vbs %MACHINE2%

@REM ==================================
@REM Use IISRESET to restart IIS on both servers
@REM ==================================

iisreset %MACHINE1%
iisreset %MACHINE2%

Really good external websites

blog foo
ASP.NET Applicaiton to help

More Info

Diagonal