I recently setup and configured some new web servers for the new web application being rolled out. The application is an ASP.NET 2.0 application running on IIS6 and Windows 2003.
Our existing application this new application will replace runs in IIS5 and Windows 2000. The real differentiator is the legacy application is running on a Windows domain and our new application will not. This decision was made to remove the domain, domain management and the overhead incurred by having a domain in our web application.
The application runs two web servers in a cluster using Microsoft Network Load Balancing. Load Balancing does just what it says, balances the workload. This is not all it does, it allows us to take down one server for maintenance while not taking the application down from our clients. We have a third server that we call our Application Server that runs various “batch” applications for system maintenance. This server keeps all of our images for the web sites and since this is an eCommerce site we have thousands of product images. Instead of keeping each image on each server and having to maintain images in more than on location we chose to have a central location and have IIS use a share on our Application Server.
There is an inherent problem with this configuration. By default, IIS 5 runs under the ASP.NET worker process and the default App Pool in IIS6 runs under the NETWORK SERVICE user. Since we don’t have a domain there is a problem, all of the NETWORK SERVICE accounts are local accounts in each system and have an auto generated password. This causes a problem in our application when we try to upload images, create new directories or even check for the existence of a file on our web application.
I configured IIS with a virtual directory on another server, which is pretty straight-forward. We have a Windows share on the other server with permissions for the user we are using to “Connect As” from IIS. See the below how we indicate a share on another server:
We need to tell IIS what user we want to use to connect to this share:
I am using a user called AppFilesUser, which has to be setup prior on all servers to be used for sharing files, in our case 3. The user has to have the same password on all the servers as well. Our web applications display images from the AppFiles share, which work well with this configuration. The real problem comes into light when we perform certain administrative tasks from the web site that does a Directory.Exists, File.Exists or other Directory.* functions.
I started searching the Internet for possible solutions to my problem and was surprised there was very little posted about this situation. Even the trusty ASP.NET Forums showed little promise. Was I wrong in my assumption that this was a good way to go? Well, maybe but I was not planning on taking the easy way out and simply setup a domain.
Any time you come up with a solution to a problem you have to first understand the entire problem completely. I had said our web site was displaying images fine but when trying to do some simple management we were getting errors in our logs and things were not working.
The key to understanding what was actually going on relies on how .NET handles certain method calls to the Directory and File classes in particular. You see, when our web application simply requests an image, IIS just uses the virtual path we specified in our virtual directory to display the file. This worked for us the first time. The real problem is how IIS is handling the our calls to Directory.Exists() in our code. I assumed (yes, I know) IIS was handling the call the same way, through a virtual path but this is NOT the case at all. Looking at the Directory.Exists() and File.Exists()on MSDN it reveals the obvious problem, both methods use relative or absolute paths, not virtual. What this means is IIS is using the NETWORK SERVICE account to connect to the AppFiles share to do our business.
This way IIS is connecting is the real problem here and explains why viewing our images works but simple things like checking for the existence of a file does not. Since we are not in a domain and the NETWORK SERVICE accounts are all have unique passwords making the ability of the Windows to use pass-through authentication ineffective. The real solution here is to by-pass the NETWORK SERVICE user and create our own user. It’s pretty simple once you know how to do it and secure as well. Since Microsoft did away with the aspnet_wp.exe from IIS5 in IIS6 they decided to use a user who had very little privileges, NETWORK SERVICE. Since NETWORK SERVICE has so little privileges our user won’t need any special permissions either.
The steps below are all that is needed to solve this problem:
- Create new user on all servers– I called my user AdminWebUser and did this on all of our web servers in our cluster as well as the server actually doing the sharing.
- Make the new user a member of the IIS_WPG group – this was a key piece of the puzzle. The NETWORK SERVICE user is a member of this group with this group having the minimal but needed permissions.
- Assign read/write permissions to the share and the directory of the images to the IIS_WPG group– this is done on the server actually housing the images. Make sure the share and the directory have their permissions set. If IIS is not on this server then you can just assign the user you created in step 1 to the share and directory. I also make sure the flags are set so any directories in the path below this directory inherit these permissions.
- Create a new App Pool in IIS for this application– this is not required but since I have multiple applications on our servers I wanted to keep these changes isolated. It is not a bad idea at all to have separate app pools for each application so there is isolation between apps in order to so maintenance, etc. I won’t explain how to create an app pool here, it’s pretty self-explanatory or you can search on Microsoft’s web site for explicit directions.
- Change the identity used by the app pool to the new user created in step 1
- Change the app pool used by the web application to the one created in step 4 – this is important and must be done.
And that’s it! It took some time to figure all of this out, yes a bunch of trial and error. I hope I can save you some time and hair pulling.
UPDATE: After several days of running with this configuration I ran into a slight problem. I am accessing a web service from this web site and the instantation of the local web service proxy class kept failing with the following error in our application (not Windows Application Event log):
Unable to generate a temporary class (result=1). error CS2001: Source file ‘C:\WINDOWS\TEMP\z3wfukwd.0.cs’ could not be found error CS2008: No inputs specified
It turns out when changing the user our App Pool was running under there happens to be some services that use the temp directory. If you look on your Windows 2003 system you will see that NETWORK SERVICE has special rights to this directory instead of the IIS_WPG group, which I found strange. Once I added IIS_WPG to the permissions list for the temp directory and matched those of NETWORK SERVICE, the calls to the web service worked again.