My diary of software development

Posts tagged ‘Web Service’

Finally! An 4.0 XBAP can call a Windows Authenticated WCF Service! Part 2

In the first part to this subject, I wrote about the problem I ran into, the hotfix from Microsoft, the hotfix’s failure, and finally Microsoft’s promise to fix the bug in .Net 4.0. Now that 4.0 is in RC mode and proved itself to fix this problem, I’m going to take a look at the source code to see just how it did fix this problem. But before I look at the 4.0 source code, I’m going to look at the 3.5 pre-hotfix source code and then the 3.5 post hotfix source code to get a feel for what Microsoft was doing which caused my problem. By the way, I’m using RedGate’s Reflector product to look at the source code.   

.Net 3.5 Service Pack 1, Pre-Hotfix

Here is the exception stack thrown when my problem first surfaced in the 3.5 pre-hotfix era:

Pre-Hotfix Exception

 The exception came from HttpChannelFactor.GetConnectionGroupName():   

 

[SecurityTreatAsSafe, SecurityCritical]
private string GetConnectionGroupName(HttpWebRequest httpWebRequest, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken)
{
    if (this.credentialHashCache == null)
    {
        lock (base.ThisLock)
        {
            if (this.credentialHashCache == null)
            {
                this.credentialHashCache = new MruCache<string, string>(5);
            }
        }
    }
    string inputString = TransferModeHelper.IsRequestStreamed(this.TransferMode) ? "streamed" : string.Empty;
    if (AuthenticationSchemesHelper.IsWindowsAuth(this.AuthenticationScheme))
    {
        httpWebRequest.UnsafeAuthenticatedConnectionSharing = true;
        inputString = this.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
    }
    inputString = this.OnGetConnectionGroupPrefix(httpWebRequest, clientCertificateToken) + inputString;
    string str3 = null;
    if (!string.IsNullOrEmpty(inputString))
    {
        lock (this.credentialHashCache)
        {
            if (!this.credentialHashCache.TryGetValue(inputString, out str3))
            {
                byte[] bytes = new UTF8Encoding().GetBytes(inputString);
                str3 = Convert.ToBase64String(this.HashAlgorithm.ComputeHash(bytes));
                this.credentialHashCache.Add(inputString, str3);
            }
        }
    }
    return str3;
}

The GetConnectionGroupName() function is attempting to set the UnsafeAuthenticatedConnectionSharing property and according to the MSDN reference for the property, an application must have unrestricted web permission to set it. However I used the Intranet Zone permission set for my XBAP which did not have the web permission security privlege and so this is why the above exception was thrown. I did not dig into the actual source code when this problem arose last summer, I simply opened a ticket with Microsoft because I figured that I should be able to call a WCF service secured with Windows Integrated security from an XBAP. After a few days, I received feedback from Microsoft that they had released a hotfix.   

.Net 3.5 Service Pack 1, Post-Hotfix

Here is how Microsoft fixed the above problem in the hotfix. The below code is the same HttpChannelFactory.GetConnectionGroupName() function shown above except that it has the hotfix changes in it:    


[SecurityCritical, SecurityTreatAsSafe]
private string GetConnectionGroupName(HttpWebRequest httpWebRequest, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken)
{
    if (this.credentialHashCache == null)
    {
        lock (base.ThisLock)
        {
            if (this.credentialHashCache == null)
            {
                this.credentialHashCache = new MruCache<string, string>(5);
            }
        }
    }
    string inputString = TransferModeHelper.IsRequestStreamed(this.TransferMode) ? "streamed" : string.Empty;
    if (AuthenticationSchemesHelper.IsWindowsAuth(this.AuthenticationScheme))
    {
        if (!httpWebRequestWebPermissionDenied)
        {
            try
            {
                httpWebRequest.UnsafeAuthenticatedConnectionSharing = true;
            }
            catch (SecurityException)
            {
                httpWebRequestWebPermissionDenied = true;
            }
        }
        inputString = this.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
    }
    inputString = this.OnGetConnectionGroupPrefix(httpWebRequest, clientCertificateToken) + inputString;
    string str3 = null;
    if (!string.IsNullOrEmpty(inputString))
    {
        lock (this.credentialHashCache)
        {
            if (!this.credentialHashCache.TryGetValue(inputString, out str3))
            {
                byte[] bytes = new UTF8Encoding().GetBytes(inputString);
                str3 = Convert.ToBase64String(this.HashAlgorithm.ComputeHash(bytes));
                this.credentialHashCache.Add(inputString, str3);
            }
        }
    }
    return str3;
}

The httpWebRequestWebPermissionDenied flag is checked to guard against setting the UnsafeAuthenticatedConnectionSharing property. So if this flag is set, the code will not try to set the connection sharing property. However, where is the httpWebRequestWebPermissionDenied flag set? It is set to true if an exception is thrown in the above code on line 25 and it is initialized to false in the constructor but the only other place it is set is in the OnOpen() function:    


protected override void OnOpen(TimeSpan timeout)
{
    if (this.IsSecurityTokenManagerRequired())
    {
        this.InitializeSecurityTokenManager();
    }
    if (this.AllowCookies)
    {
        this.cookieContainer = new CookieContainer();
    }
    if (!httpWebRequestWebPermissionDenied && (HttpWebRequest.DefaultMaximumErrorResponseLength != -1))
    {
        int num;
        if (this.MaxBufferSize >= 0x7ffffbff)
        {
            num = -1;
        }
        else
        {
            num = this.MaxBufferSize / 0x400;
            if ((num * 0x400) < this.MaxBufferSize)
            {
                num++;
            }
        }
        if ((num == -1) || (num > HttpWebRequest.DefaultMaximumErrorResponseLength))
        {
            try
            {
                HttpWebRequest.DefaultMaximumErrorResponseLength = num;
            }
            catch (SecurityException exception)
            {
                httpWebRequestWebPermissionDenied = true;
                if (DiagnosticUtility.ShouldTraceWarning)
                {
                    DiagnosticUtility.ExceptionUtility.TraceHandledException(exception, TraceEventType.Warning);
                }
            }
        }
    }
}

 

This function uses pretty much the same logic to set the httpWebRequestWebPermissionDenied flag. If a SecurityException is thrown, then the flag is set to true. That seems like a quick and dirty way to set the permission flag’s value and I guess it works but it seems to me that it should be set during class initialization by reading the XBAP’s associated manifest file to see if that permission is in the XBAP’s permission set. I looked at the .Net 4.0 code to see if the flag was initialized in some way but found that it uses the same logic to figure out if the XBAP doesn’t have web permission. Strange, I thought there’d be a more elegant way.    

However ugly the above hotfix is, it did get the code past that point and on down the callstack. But farther down in the callstack another exception was thrown:    

Post-Hotfix Exception

This next exception was thrown because SecurityUtils.AppendWindowsAuthenticationInfo() called WindowsIdentity.GetCurrent():    


[SecurityCritical]
internal static string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel)
{
    if (IsDefaultNetworkCredential(credential))
    {
        using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
        {
            SecurityIdentifier user = identity.User;
            return (inputString + "\0" + user.Value + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
        }
    }
    return (inputString + "\0" + NetworkCredentialHelper.UnsafeGetDomain(credential) + "\0" + NetworkCredentialHelper.UnsafeGetUsername(credential) + "\0" + NetworkCredentialHelper.UnsafeGetPassword(credential) + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
}

However, in order to call WindowsIdentity.GetCurrent, the app must have SecurityPermissionFlag.ControlPrincipal as the WindowsIdentity.GetCurrent() method demands it:    


[SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlPrincipal)]
public static WindowsIdentity GetCurrent()
{
    return GetCurrentInternal(TokenAccessLevels.MaximumAllowed, false);
}

Since my clickonce XBAP did not have this security, this Demand caused another exception.    

.Net 4.0

The .Net 4.0 code was the same as the hotfix code for dealing with the UnsafeAuthenticatedConnectionSharing property problem. However it handled the WindowsIdentity.GetCurrent() somewhat differently. Here is the SecurityUtils.AppendWindowsAuthenticationInfo() function in .Net 4.0:  

</pre>
 

[SecurityCritical]
internal static string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel)
{
    if (IsDefaultNetworkCredential(credential))
    {
        string str = UnsafeGetCurrentUserSidAsString();
        return (inputString + "\0" + str + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
    }
    return (inputString + "\0" + NetworkCredentialHelper.UnsafeGetDomain(credential) + "\0" + NetworkCredentialHelper.UnsafeGetUsername(credential) + "\0" + NetworkCredentialHelper.UnsafeGetPassword(credential) + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
}

 

The logic for this version of SecurityUtils.AppendWindowsAuthenticationInfo() is about the same as the logic in .Net 3.5 except for the call to the new member UnsafeGetCurrentUserSidAsString() on line 11. The code to this new member function is shown below:

[SecurityCritical, SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)]
private static string UnsafeGetCurrentUserSidAsString()
{
    using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
    {
        return identity.User.Value;
    }
}

On line 1 of the above code there is an Assert for the ControlPrincipal flag, the same flag which WindowsIdentity.GetCurrent() demands. This Assert will satisfy the stack walk caused by the Demand and no exception will be thrown. So that is how Microsoft fixed this bug in .Net 4.0. 

 

Advertisements

Finally! An 4.0 XBAP can call a Windows Authenticated WCF Service! Part 1

The Problem

Last year, I was on a project where I designed a solution with an XBAP and an IIS hosted WCF data service. The problem came out because the WCF service was secured with Windows integrated authentication and the XBAP, being a partial trust application, did not have the appropriate privileges needed to call the necessary CLR methods needed to package and issue a call to the secured WCF service. 

The first hint of the problem came out when I got a security exception from calling the WCF service from inside the XBAP. The security exception was because the WCF service proxy was trying to set the HttpWebRequest.UnsafeAuthenticatedConnectionSharing property. However, unrestricted web permission is required to set that property and the XBAP, which was running with the Intranet zone privleleges, did not have that permission. Here is the stack trace of the exception:

I opened a ticket with Microsoft concerning this problem and after a few days, received hotfix 959546 which was supposed to address this problem:

This hotfix did address the problem and allowed the code to proceed past the point of setting the HttpWebRequest.UnsafeAuthenticatedConnectionSharing property but a new exception was thrown higher in the callstack:

It took quite a bit of time after I first opened the ticket, worked throught he hotfix, and before Microsoft was able to clearly address this issue and by this time in my ticket lifecycle, I was working with an escalation engineer. After reporting the above problem to him, I received the below email email in which he indicating that this bug was to be fixed in .Net 4.0:

So at this point, I’m left with a problem: I can’t use the WCF service proxy to call a secured WCF service so what should I do? Should I re-implement the WCF data service as a 2.0 web service? ~~SHIVER!!~~. Grasping at straws, I decided to try using a 2.0 web service proxy to call my secured WCF data service and see what would happen. What happened was that it worked! So I released this project as an XBAP, a secure WCF data service, and a 2.0 web service proxy inside the XBAP to call the secure data service.

And that brings us to today. As you can see from the dates in the email chain above, this all happened last summer and today the .Net framework release 4.0 is in RC mode so I decided to see if this problem was fixed in 4.0. To try out this fix, I setup two quick hyper-v VMs:

  1. Win7 + Visual Studio 2010 RC
  2. 2008 R2 + IIS 7.5

To model the environment of last year’s project, I wrote a quick WCF service and an XBAP to call the service. I ran the XBAP and sure enough, everything worked just fine in 4.0, the XBAP was able to call the WCF service which was being hosted on IIS 7.5 using Windows Integrated Authentication. Just for kicks, I regressed the build’s target framework back to .Net 3.5 and ran it again just to see what would happen. What happened was I got the same exception I did last summer, so that proved to me there were no shenanigans going and that nogthing had crept in to my problem domain (you never know after a year long wait) as the outcome of my proofs were what I expected.

After going through this test with 4.0, I became curious about how exactly Microsoft fixed this problem. To satisfy this curiosity, I’ve decided to write a second part of this post and take a look at the version 3.5 CLR code using Reflector to see what exactly the code looks like that was causing this problem. And then I’ll take look at the 4.0 CLR code to see how the problem was fixed.

 

 

Why SvcUtil creates duplicate named classes when generating the proxy – Part 2

Part 1 of this post is here.

I wanted to see what the SOAP body looked like between these two UploadDetails classes. SvcUtil generated one UploadDetails class in the global namespace and the other class in a namespace which I called ‘Bubba’ and both had to be serialized to XML for the SOAP body, depending on which one I used in the generated proxy client call. I was able to get the generated SOAP body of the message by using Fiddler. The global namespace UploadDetails XML is shown below with lines 15-52 removed:

Global Namespace UploadDetails

The Bubba namespace UploadDetails XML is shown below with lines 14-41 removed:

Bubba Namespace UploadDetails

So how was it that I was able to choose which UploadDetails class I wanted to send? To explain that, I’ll explain the steps I went through at the beginning of this odyssey.

When I used Visual Studio’s ‘Add Service Reference’ to generate a proxy for this partner’s WSDL, the generated proxy put both UploadDetails classes in the global namespace. This resulted in a name collision compile time error so I decided to use the command line SvcUtil which did seperate the UploadDetails classes between 2 namespaces. One UploadDetails class was placed in the global namespace and the other in our partner’s namespace which I’ll refer to as Partner.A.B.C.D. SvcUtil generated the proxy client to accept the global namespace UploadDetails which left the Partner.A.B.C.D.UploadDetails class just hanging around with nothing referencing it. But I found out that if I ran SvcUtil with the /namespace option and told it to assign the classes in the partner’s namespace Partner.A.B.C.D to a namespace I called ‘Bubba’, then the SvcUtil generated the proxy client to accept the Bubba namespace UploadDetails. And that is how I was able to run the proxy client with both namespace UploadDetails classes to see how they were serialized to XML. Below is a screen show which summarizes the important points of this. It shows the class diagrams for each UploadDetails and the key differences in the serialized XML for both classes:

Serialization Differences

So basically, an object[] array is serialized into <data> elements and a List<object> is serialized into <anyType> elements. Which is strange because I thought it’d be the other way around but I double checked it and those are the correct elements. I know that WCF uses the DataContractXMLSerializer by default. I could have used the /serializer parameter to SvcUtil to have it use the XmlSerializer which would have given me more control over how exactly the object was serialized to XML but I stopped at this point.

In my first post I indicated that I was getting a timeout error when calling this partner’s web service. Lo and behold, the partner sent me an updated WSDL which fixed that problem. Apparently their first WSDL was in error.

After getting the new WSDL and being able to successfully call the web service, I checked to see which set of XML would be accepted and found that they wanted the XML generated from the global namespace UploadDetails. I got an exception when I sent the other UploadDetails. Since at this point, I could call the partner’s web service and since I had many more things to work on, I chose to stop pursuing why SvcUtil generated two classes.

In summary, SvcUtil generated 2 classes with the same name: UploadDetails. I was able to direct the namespace each class was generated into by using the /namespace parameter which also drove which class the generated proxy client would accept. I fed both namespace classes into the web service call to see which one the web service would accept and found that it accepted the global namespace UploadDetails. Their web service did not accept the XML generated from serializing the Bubba (or the Partner.A.B.C.D) namespace UploadDetails.

Why SvcUtil creates duplicate named classes when generating the proxy – Part 1

I appended ‘Part 1’ to this post when I started it because I know good and well that I’m not going to figure this out in one post.

On my current project, I need to communicate with a web service provided by a new partner company. I do not have access to the code behind this web service (and in a perfect world, I wouldn’t need to) and after my initial conversations with them, I got the feeling that they wouldn’t be very amenable to assisting me. So, here I am with a URL to their WSDL and SvcUtil which creates duplicate class names during proxy generation. I should say also that the web service times out when I call it but that is a seperate issue that I’m tracking down with this partner.

One duplicate class is a class named UploadDetails. When I added a service reference to my Visual Studio 2008 project, it generated duplicate class names in the Reference.cs file for all +20 data classes. The specifc class that I’m working with is called UploadDetails but I could work with any of their +20 classes to try and figure this out, I simply chose the UploadDetails class.

The duplicately named classes are all generated in the same namespace when I use the ‘Add Service Reference’ in Visual Studio. I’m not speaking of XML namespaces, I’m speaking of  the C# namespaces. However when I generate the proxy by using SvcUtil from the command line, it creates one UploadDetails class in the global namespace and one UploadDetails class in the partner’s namespace (which I will refer to as the Bubba namespace) so there are no name collision problems. Since I have more control over the options in the SvcUtil command line, I am going to use it to figure this out.

Here are the class diagrams of the 2 UploadDetails classes. One class was generated in the default namespace and one class in the Bubba namespace:

UploadDetails Class Diagrams

 Therer are several things to note about the similarities and differences in these 2 classes:

  1. The Bubba.UploadDetails class implements IExtensibleDataObject and the other class does not.
  2. The Fields and Properties of the 2 classes are the same except for the ones used to implement IExtensibleDataObject.
  3. The Delete, Insert, and Update properties are of different data types in the 2 classes.
    1. The global namespace UploadDetails class properties are an object array.
    2. The Bubba.UploadDetails class properties are of types which inherit from List<object>. These types are classes named Delete, Insert, and Update and are shown at the bottom of the above image.

In my next post I’m going to see what the XML SOAP body looks like when I use each of these 2 UploadDetails classes. I think they’ll both serialize to the same XML but I want to see for myself.

Does Kerberos authentication affect SQL Server connection pooling? Part 2

The Problem

In my first post on this subject, I wrote about a problem that occurs in the ADO.Net connection pool when a .Net application authenticates its callers to SQL Server using their Kerberos ticket. I am now going to present a solution to this problem and an actual implementation of the solution.

Most applications today are n-tiered applications with a data tier which accesses the database on behalf of the frontend user. The n-tiered application uses Kerberos so that the frontend user’s identity is passed through the application’s tiers to the data tier and used to authenticate the user to the database.

Since the data tier’s database connection is established under the credentials of each frontend user, the application receives two important benefits: data auditing accuracy and a tighter more focused security model. SQL Server audit trails are able to record data operations and tie them to each individual user. In addition, each user’s operations are performed under their specific database permission set.

 

The problem with this pattern is in the connection pool. The ADO.Net connection pools are indexed by the user and by the database so in the above example, there would be 3 connection pools. One pool for User1, one pool for User2, and one pool for User3 which drastically affects the application’s scalability since a database connection would have to be created for each user.

The Solution

One solution to this problem is to use Microsoft’s Trusted Subsystem pattern. In this pattern, the data tier would authenticate to the database using its own service account and operate against the database on behalf of each frontend user. By using this pattern, the connection pooling problem is solved because the data tier’s service account is the only account which authenticates to the database. However, there is at least one drawback to using the data tier’s service account to authenticate to the database:  The SQL Server auditing module can no longer tie the actions it records to the individual frontend users. In addition, the data tier’s service account would need enough permissions in the database to accommodate all users from the least privileged user to the most privileged user.

One way around the audit problem is to not use the SQL Server auditing module and replace it with one at the data tier level. Since the data tier gets the Kerberos ticket, it knows who is requesting the data and can therefore write the audit trail entries itself. But why re-invent the wheel when SQL Server has a tried and true enterprise level auditing system?

In order to solve both the audit trail and the permissions problem, the Trusted Subsystem approach should be used and the data tier’s service account should be granted impersonate privileges on each frontend user account. Each time the service account accesses the database for a user, it should issue an Execute As Login=xxx statement, perform the necessary operations, and then issue a Revert statement. This way, the SQL Server auditing module can accurately record operations per user and the database operations the service account performs are constrained by each user’s permissions.

 

Solution Implementation

In order to prove this solution in a way as close to the real world as possible, I setup an environment with the following virtual machines in a development domain I named POPEDEV:

Host Operating System Role
DC1 2008 R2 x64 Domain controller – POPEDEV domain
WEB1 2008 R2 x64 IIS
WEB2 2008 R2 x64 IIS
WEB3 2008 R2 x64 IIS
SQL1 2008 R2 x64 SQL Server 2008 (running under Network Service)
WORK1 Win7 x86 Development and user workstation

 

My test involved a web service to return a set of products from a SQL Server Products table. Each of the web services had a service operation named GetProducts(). I created three web services: Hop1, Hop2, and BDE. The client called Hop1.GetProducts() which called Hop2.GetProducts() which called BDE.GetProducts(). I created three web services and therefore three hops to exercise the Kerberos ticket delegation process. The Kerberos ticket which was created on the workstation would be passed to Hop1 and then to Hop2 and finally to BDE.

I deployed the following web services to each web server:

Host Web Service Application Pool Identity Role
WEB1 Hop1 Network Service Proxy web service to call Hop2.
WEB2 Hop2 Network Service Proxy web service to call the BDE.
WEB3 BDE POPEDEV\BDE Business Data Engine. This is the data tier.

 

The client application on WORK1 would call the Hop1 web service on WEB1 which would do nothing except call the Hop2 web service on WEB2. The Hop2 web service would do nothing except call the BDE web service on WEB3. The BDE web service would be my data tier and would connect to the database as a trusted subsystem to return the requested data to Hop2 which would return the data to Hop1 which would return the data to the client on WORK1.

I wrote my web services in WCF and configured them to use the WsHttpBinding with message security and Kerberos authentication.

 

For my testing, I created 5 domain users: USER1 – USER5 and wrote the client application to be able to impersonate one of those 5 domain users before calling Hop1. This way, I could simulate any of the 5 users being the frontend user. In addition, I created the domain user POPEDEV\BDE which would be the data tier’s service account user.

Constrained Delegation

When the Kerberos ticket is passed from one the Hop1 web service to the Hop2 web service, Hop1 is basically delegating to Hop2 the user’s request for data. By the same token, when Hop2 called the BDE and passed it the Kerberos service ticket, it was delegating to the BDE web service. However, the ability to delegate from one machine/service default ability in Active Directory.  Each machine/service must be given explicit delegation permissions.

This is called constrained delegation and is a security feature of Active Directory. The opposite would be open delegation where, when a process received a user’s identity in a Kerberos ticket, could call any other service local or remote on that user’s behalf. This could present a whole host of security problems if an attacker were able to launch a service within the organization and induce users to call it.

So I needed to setup the delegation settings on WEB1, WEB2, and WEB3 to be able to delegate to WEB2, WEB3, and SQL Server service as shown below:

 

In the screen shot above, I gave WEB1 permission to delegate to the service HTTP/web2.popedev.com because the HOP2 web service on WEB2 was running under the Network Service account.


Delegating to the BDE Web Service

The last set of delegation permissions was for the Hop2 web service on WEB2 to be able to delegate to the BDE web service on WEB3. This wasn’t quite as straight forward as permitting WEB1 to delegate to WEB2 because the BDE web service was running under the service account POPEDEV\BDE.

First I added the service principal name HTTP/web3.popedev.com to the POPEDEV\BDE account:

Next I enabled WEB2 to delegate to that same service name:

 

Running the WhoAmI Test

The first test I wanted to perform was to verify that the Kerberos ticket was being passed between the web servers and would make it all the way to the BDE web service.

I created a method named WhoAmI() in each web service which would return the current thread, windows, and other identities. It returned 4 identities as follows:

  • ServiceSecurityContext.Current.PrimaryIdentity.Name
  • ServiceSecurityContext.Current.WindowsIdentity.Name
  • WindowsIdentity.GetCurrent().Name
  • Thread.CurrentPrincipal.Identity.Name

If the Kerberos service ticket was being passed from web service to web service, then these four methods should return the name of the frontend user (e.g. POPEDEV\User2). Except that the BDE web service, which was not impersonating its users to the SQL Server would return POPEDEV\BDE for the Windows identity because POPEDEV\BDE was its service account identity.

In addition, I ran NetMon on WEB1 and WEB2 to watch for Kerberos packets as another verification step.

I ran my console tester and the opening menu asked which operation I wanted to run. I chose option 0, the WhoAmI operation.

After choosing the WhoAmI operation, the console tester asked which user to impersonate. I chose POPEDEV\User4.

After choosing which user to impersonate, the console tester called the WhoAmI method on Hop1. In the screen shot above, you can see that the Hop1 and Hop2 web services see the user as POPEDEV\User4. The BDE web service shows the windows identity as POPEDEV\BDE and the thread identity as POPEDEV\User4.

The next screen shot shows the NetMon captures with the packet filter set to watch for Kerberos frames. As you can see, WEB1 requested http/WEB2.POPEDEV.COM and WEB2 requested http/WEB3.POPEDEV.COM which is exactly what I would have expected.

WEB1 NetMon trace:

WEB2 NetMon trace:

After seeing the results of the WhoAmI test and the captured frames in NetMon, I was confident that Kerberos was being used to authenticate all the way to the BDE web service.

Running The Full Test

For the full test, I wanted to prove that any user could request data from the data tier and the audit trail would record the select operation under that user and I wanted to prove that the data tier was using a single connection pool.

The data which the BDE web service returned was a set of products in a table I named Products. Here is a sample of what the BDE needed to run in order to return the products correctly:

Execute as Login= ‘Popedev\User3’

select * from Product

revert

The first line ‘Execute as…’ enabled the BDE to run the select statement underneath the identity of the frontend user whose identity came across in the Kerberos ticket. Remember that the BDE connected as its service account so that a single connection pool would be used. In addition, the BDE web service needed to run the SQL underneath the frontend user’s identity so that the audit trail would be accurate and that the SQL operations would be run under the frontend user’s security.

The last line ‘Revert’ reverted the connection back to the BDE web service’s service account identity.

Here is a shot of the current database connections before I ran the test. There are 2 connections to the master database and 1 connection to the tempdb database. After running the test, I should see a new connection to my test database which I named kerbtest.

I ran the console tester and told it I wanted operation 1 – GetProducts and to impersonate POPEDEV\User3.

The console tester next asked whether I wanted to use LINQ or ADO.Net to get the products. I chose LINQ and it returned 7 products.

The SQL Server activity monitor now shows process 54 which was my BDE web service’s connection. Notice that process 54 is under the BDE web service’s service account POPEDEV\BDE and not POPEDEV\User3 who is the frontend user in my test.

This next screen shot shows the audit trail for session 54. Notice the SESSION_SERVER_PRINCIPAL_NAME is POPEDEV\BDE because that is who opened connection 54. The DATABASE_PRINCIPAL_NAME is POPEDEV\User3 because that is who the console tester was running under.

I then ran my test again but this time I told the tester to impersonate POPEDEV\User5 and to use ADO.Net instead of LINQ:

Here is a screen shot of the SQL Server activity monitor. Notice that there is still one session – 54:

The next screen shot shows the audit trail. The 2 entries reflect session id 54 as excepted and the DATABASE_PRINCIPAL_NAME reflects POPEDEV\User5 and POPEDEV\User3. In addition, the statement is different because I used LINQ the first time and ADO.Net the second time.

Figuring out why SAS BI Web Services for .Net doesn’t work – Part 3

It’s been well over a week since I wrote about battling the SAS BI Web Services for .Net beast and a lot has changed. A week or so ago I was pretty sure that I had gotten it installed and was dealing with problems created by configuration issues. I have since found out that I did not have it installed correctly and through the course of the last 10 days SAS sent me 3 additional plan files, the last of which still didn’t work correctly.

I’ll recap what I’m doing. I’m trying to get SAS BI Web Services for .Net installed and working so that I can work on the bigger picture of my project which is to document how to call these web services from a .Net web application. Installing SAS is like building the bay bridge from a bucket (a really big one) of nuts and bolts and cables and other stuff. The full installation for all of the SAS components encompasses dozens and dozens of CDs. So you install SAS in almost any permutation of components but the specific permutation which I need is the one which enables me to use SAS BI Web Services for .Net. The ‘plan’ file which I spoke of above is basically the blueprint to the bridge. It is an XML file which is fed into the SAS installer to tell it which components to install in what order and how to configure them.

Dissecting a SAS installation plan file

I’m still trying to get a working plan file from SAS. I have a ticket open with them to get a plan file with which I can install SAS BI Web Services for .Net plus all the other needed components. But to date, I haven’t received a working plan file. I am now in receipt of the 5th plan file but it doesn’t work and so I’m currently awaiting the 6th one. SAS does not support user modifications of a plan file, they only support plan files from SAS Support or a dedicated account rep. Well, I don’t have a dedicated account rep and I’m still waiting on a good plan file from SAS so last week I set about creating my own. And I succeeded. But I’m not telling SAS because with all of the complexity of SAS, I may have missed something so I’m going to wait on a blessed and ordained plan file from SAS rather than my own creation.

I took a look at the XML plan file to see what it looked like and I found that the plan file’s contents really don’t look that complicated; there are 3 basic types of information in it:

  1. Component section. These are sections which identify a component (e.g. SAS Web Infrastructure Client) and give it a unique ID within the plan file.
  2. The properties section of a component section. Properties are sequentially numbered within the whole plan file so if a new component section is inserted; all properties in the entire file need to be renumbered. These properties for the most part have identical names and values between sections. There are a couple of components which have different props but overall, they’re the same for each component section.
  3. References to other components. This seems to be the way dependencies between components are defined. Here is an example of the first 3 types of information:
    1. Plan1
  4. Some components are hierarchically children of other components. I *think* that is because the parent component is dependent on the child but then again, number 3 above seemed to indicate.  Here is an example:
    1. Plan2

I now have a working installation of SAS BI Web Services for .Net but, like I said, I’m keeping my ticket open with SAS until I get an official one from SAS because I probably missed something.

Figuring out why SAS BI Web Services for .Net doesn’t work Part 2

Well, in my last post, I basically couldn’t get the WebServiceMaker.asmx service to work. I now have it working and the problem was because it was not installed properly. You see, SAS is like a bucket of Lego bricks. It contains dozens and dozens of modules and services which can be installed in what seems like endless permutations of arrangements. These arrangements are defined by an XML ‘plan file’ used by the SAS deployment wizard and  SAS support had sent me a couple of plan files which ‘appeared’ to work but did not. There are also some pre-defined plan files provided by the installation download which didn’t build the exact Lego castle I needed but got close. So, I created my own plan file from the plan files provided by SAS and these pre-defined ones. I got everything installed fine and I am able to call the services within WebServiceMaker.asmx without trouble but, given that there is precedence in the world of SAS for gettting an installation with no errors but not necessarily working, I worry.
 
I still can’t call ListWebServices in the WebServiceMaker.asmx file from my .Net console app but the Enterprise Manager on the SAS server can call it without trouble. I verified this using netmon on the server. Right now I’m at the point where I can deploy a SAS Stored Process as a web service using the Enterprise Manager and then attempt to call it from my .Net console app. When I called my test SAS web service, I got this error:
System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: There was an error due to configuration of the middle tier.
 
Oh great. Now what? I thought for sure there was some issue with security (which seems to be plaguing me with the ListWebServices call). I poked around online a bit but didn’t really find anything so I decided to fire up Fiddler (such a wonderful tool) and see what my SOAP request and response looked like. Now I have some more information because in the SOAP response I see this information in the SOAP fault:
 
<Fault code=”4000″ xmlns=”http://support.sas.com/xml/namespace/biwebservices/webservicemaker-9.2“>
 <Exception message=”Folder path ‘/Users/sasadm/My Folder’ was not found or you may lack permission to view some part of the path.” />
</Fault>

 

 Well, that *kind of* makes sense. I created my stored process in the above SAS folder and the name of the folder leads me to believe there is one for every user of SAS. I was signed on as SASADM when I created the stored process and I sent the user sasadm in the Username token but maybe the identity attempting to run the service is other than sasadm.
 
What to do, what to do….
 
I tried calling the service without providing a WSE UsernameToken and got this response:
Could not establish a connection to the SAS server on the requested machine. Verify that the SAS server has been started with the -objectserver option or that the SAS spawner has been started. Verify that the port Combridge is attempting to connect to is the same as the port SAS (or the spawner) is listening on.