So given a daemon process running in the middle tier (as Bob), and a thread in that process that is currently impersonating a client (Alice), when that thread makes an outgoing COM call to the back end, whose credentials will COM use to make the outgoing call? Figure 1 shows such a scenario, with Alice as the client, Bob as a daemon in the middle tier, and Charlie as a daemon on the back end. (The red boxes indicate the process token, and the red circle indicates the thread token.) Here's what happened to create this picture. When Alice called Bob, COM authenticated Alice and obtained a token for Alice for use on Bob's machine. (Technically, a pluggable security service provider does this on COM's behalf.) While servicing the call from Alice, Bob asked COM to place Alice's token on his thread by calling CoImpersonateClient. With his thread running as Alice, Bob then made a COM call to Charlie, intending to pass through Alice's identity in the hopes that Charlie would deal with access control. (This is very convenient for Bob!)
In this picture, COM has two choices for the call to Charlie. COM can choose to make the call as Alice or Bob because both the thread token (Alice) and the process token (Bob) are there for the picking. If COM were to take the natural course of action and choose the thread token, the call would fail miserably during the authentication handshake, at least under Windows NT 4.0. This is because the built-in authentication protocol (NTLM) doesn't support cross-host delegation of credentials. Under Windows 2000, there is a skosh of a chance that authentication might succeed if the system uses Kerberos, but there are still several knobs that need to be tweaked in the security database to make it work.
What it boils down to is that using the thread token isn't going to work in most cases, so COM simply ignores the fact that Bob's thread is impersonating Alice, and instead makes the call to Charlie using Bob's identity (which might surprise Bob, who was hoping Charlie would do access checking based on Alice's identity). On Windows NT 4.0, by default, outgoing COM calls use the process token because NTLM doesn't support more than one network hop with a client's credentials.
Access Control in Three-tier Systems
For most classic three-tier systems, a very effective way to perform access control is to do it as close to the client as possible while still being on a machine that isn't under the client's control. (In other words, it can't easily be compromised by a bad guy.) This generally means performing fine-grained access checks in the middle tier, which completely avoids the previous problem where I tried to delegate Alice's credentials. In fact, both COM+ and MTS have
built-in support for performing these access checks on a very fine-grained basis: per-class, per-interface, and in COM+, per-method. Performing access control in the middle tier has the benefit of offloading work from the back end, and increases the potential for sharing database connections from the middle tier to the back end.
With a Web application, Alice is making a call to Bob not via DCOM, but rather via HTTP, so IIS (not COM+) is the receptor of the request. IIS authenticates Alice and (typically) executes an ASP script on the middle-tier machine. Following this prescription for keeping access checks as close to the client as possible, it seems as though IIS should be tasked to perform access checks in this case. With that in mind, let's take a look at how IIS performs access control.
Access Control in IIS
IIS (both versions 4.0 and 5.0) has a very simple and consistent mechanism for dealing with access control. It simply passes the buck to whoever happens to be serving up the requested files. In other words, given an IIS thread servicing a request from Alice, that thread will simply impersonate Alice and attempt to open the requested file (or execute the requested isapi extension) while impersonating. The file system will complain if Alice hasn't been granted appropriate access permissions to the file in question. For anonymous clients, IIS impersonates a well-known account. (By default this is a local account called IUSR_MACHINE, where MACHINE is the computer name.) IIS actually prefers to use this anonymous account because it's less expensive than authenticating the client, and will do so unless IUSR_MACHINE doesn't have access permission to the file being requested, or if anonymous access has been disabled for the virtual directory in question. The important point to note is that IIS will always be impersonating someone when it processes an HTTP request.
To make IIS perform access control on your behalf, you simply customize the DACLs on each file the client might request. This is an incredibly coarse-grained solution, however. Clients will be either granted or denied access to the file as a whole. What if it's an ASP page, and you need to parse parameters in the URL to determine the client's intentions before you can perform any meaningful access checks? In this case, a strategy of simply placing DACLs on your ASP files is surely not going to cut it.
If the requested file is an ASP script and IIS is able to successfully open the script file while impersonating, the ASP isapi extension will carry on impersonating and execute the script. This gives you a chance to provide a more meaningful access control policy because IIS and ASP perform a handoff of the client's security information via the thread token, and you can do whatever sorts of access checks you like by peering into the thread token from ASP.
But let's be honest; that doesn't sound like a lot of fun. If you are using IIS and ASP as a gateway into COM+ (or MTS), then what you really want is another handoff, in this case from ASP to COM+. This will allow COM+ to perform its role-based access checks transparently, and will make you a much happier developer. Only one problem stands in the way. COM normally ignores the thread token in preference to the process token, which means that when you make a call from ASP to a local COM+ component, the client's identity seems to get dropped on the floor. Clearly the notion of using IIS as a gateway into COM+ breaks down if this happens. But before I describe the solution, I need to explain some infrastructure that's being added to COM in Windows 2000 to help address this issue.
Cloaking in Windows 2000
Windows 2000 is expected to provide a feature with an incredibly snazzy name (cloaking), but a really simple goal: to allow a COM client to specify that the thread token should not be ignored. By calling IClientSecurity::SetBlanket (or the shortcut, CoSetProxyBlanket), you can specify exactly how COM should use the thread token for subsequent calls through a particular proxy. Two mutually exclusive capability flags were added to COM in Windows 2000 to support this: EOAC_STATIC_CLOAKING and EOAC_DYNAMIC_CLOAKING. To make use of this feature, call SetBlanket to specify one of these flags (via the dwCapabilities parameter). When you do, you'll be selecting one of three different policies that determines how COM figures out which credentials to use for subsequent outgoing calls on that particular proxy. To adjust the process-wide default policy, you can also pass these flags to CoInitializeSecurity so that the policy will automatically be applied to all proxies. (You can always call SetBlanket to override the default policy on a per-proxy basis, however.)
Here's what the individual policies mean. The first policy is in force if you don't pass either of these capability flags, and it says that COM will always ignore the thread token, just like in the old days.
The second policy is static cloaking, which says that at the time you call SetBlanket, the blanket captures the current identity of the thread and uses that identity for all subsequent calls through that proxy (until the next call to SetBlanket). For example, if a thread in Bob's process happens to be impersonating Alice, and that thread called SetBlanket specifying a policy of static cloaking, all future outgoing calls through that same physical interface pointer will always use Alice's identity. The identity was captured at the SetBlanket call (or on the first call through the proxy if the process-wide cloaking policy was used). This means that even if the thread stops impersonating Alice, calls through that proxy will still go out as Alice. To revert the proxy to use Bob's identity, the thread must call SetBlanket again once it has stopped impersonating.
The third policy is dynamic cloaking, which says that all subsequent outgoing calls through the proxy will be sensitive to the current security context of the thread making the call. So if a thread in Bob's process impersonates Alice and makes a call, the call goes out as Alice. If the thread then impersonates Mary and makes a call, the call goes out as Mary. If the thread stops impersonating and makes a call, the call goes out as Bob.
Interestingly enough, even in Windows NT 4.0 something akin to static cloaking has been available for some time now. (I verified by experimentation that this feature exists at least as far back as Service Pack 3.) The feature behaves exactly like static cloaking except for two subtle differences. First, to turn it on you don't pass EOAC_STATIC_CLOAKING. You simply pass NULL for the pAuthIdentity parameter. In other words, you don't provide explicit credentials to SetBlanket. Second, the feature only works if the proxy points to a remote object and the thread token that calls CoSetProxyBlanket is not a network token. If the proxy points to a local object, SetBlanket will succeed, but COM will still use the process token.
In Windows 2000, cloaking works with proxies to local and remote objects. Clearly you should be careful if you choose to take advantage of this feature in Windows NT 4.0 because the feature behaves (and is invoked) so differently on Windows 2000 that your program will likely break unless you check the operating system version at runtime and plan accordingly. The rule of thumb to avoid getting bitten is to simply not call SetBlanket while impersonating, unless you are willing to deal with the issues I just mentioned. This feature cannot be used to solve the IIS gateway problem (because it doesn't work locally), but it's an important part of the COM cloaking story, so I wanted to mention it here.
Default Cloaking Policy in COM+ Apps
In Windows 2000, COM+ server applications are loaded by a system-provided program known as DLLHOST.EXE, and therefore the settings you used to specify via your good friend CoInitializeSecurity (including the process-wide cloaking policy) are no longer set this way; rather, they are configured via the COM+ catalog, and COM+ applies them implicitly. For instance, the application-wide authentication level is one very obvious setting stored in the catalog. This setting is obvious because you can control it via the COM+ user interfacejust ask for an application's properties and select the Security tab. However, the cloaking policy, while stored in the catalog, is not exposed via the COM+ user interface, but can be read and written via the catalog's scripting interface.
The following script enumerates through the applications installed on the local machine, displaying the AuthenticationCapability setting for each application (which includes the cloaking policy):
Set cat = CreateObject("COMAdmin.COMAdminCatalog")
Set apps = cat.GetCollection("Applications")
apps.Populate
For Each app In apps
caps = app.Value("AuthenticationCapability")
line = "0x" & Hex(caps) & Chr(9) & app.Name
WScript.Echo line
Next
Here are the results I received from Windows 2000 beta 3 (build 2072):
0x40 COM+ Utilities
0x40 COM+ IMDB Utilities
0x40 COM+ IMDB Proxy Connection Mgr
0x40 COM+ QC Dead Letter Queue Listener
0x40 IIS Utilities
0x40 IIS In-Process Applications
0x40 IIS Out-Of-Process Pooled Applications
0x40 Test App
0x2040 System Application
The dynamic cloaking bit is 0x40 and, as you can see, all the COM+ applications listed here have dynamic cloaking turned on by default. The application named Test App is one that I created from scratch to verify that this is indeed the default policy for COM+ applications. This directly affects isapi (and therefore ASP) applications.
Web Applications in Windows 2000
On Windows 2000, Web applications are hosted by an entity known as the Web Application Manager (WAM), which is simply a configured COM+ component that lives in an IIS-managed COM+ application. Take a look at the output from my script and you'll see a COM+ library application for hosting in-process Web apps and a server application for hosting pooled out-of-process Web apps. IIS itself (that is, INETINFO.EXE) and other isapi processes (these processes run under the guise of DLLHOST.EXE) naturally set up process-wide dynamic cloaking when calling CoInitializeSecurity.
Recall that the thread executing an ASP script is always impersonating either IUSR_MACHINE or the actual authenticated client, as I discussed earlier. This means that your ASP scripts will make outgoing COM calls using the original client's identity (via cloaking), and your COM+ components can therefore provide role-based access checks on your behalf. This is exactly what you want!
As a reminder, if you want to use this pass-through security model to hand off the client's identity to COM+, you'll want to co-locate your COM+ application on the same machine as the Web server receiving the request. (This configuration can of course be replicated on multiple middle-tier machines as you build a Web server farm.) If you try to call a remote object from ASP, you'll most likely be struck down immediately by the security Gods. Remember Figure 1; trying to use a client's credentials across two network hops isn't going to fly unless all the knobs are turned correctly to allow delegation of the client's credentials.
Once again I come back to the rule of keeping the access checks as close to the client as possible, and in this case it means performing access checks in the middle tier. Passing the buck (such as delegating security access checks) to the back tier is generally a bad practice, at least for classic three-tier Web-based applications, for the reasons I mentioned earlier. It usually won't work because of delegation issues, and even if it did, it would eliminate the benefit of database connection pooling in the middle tier.
Web Applications in Windows NT 4.0
The main difference back on Windows NT 4.0 is the lack of dynamic cloaking as a feature. IIS still works the same way; it impersonates its client and does its work while impersonating, thus passing the buck to the file system, and perhaps also to your ASP application that typically needs finer-grained access control. But when your ASP application makes a call through a proxy to a local COM object, as far as COM is concerned that call goes out using the process token, never using the thread token. Specifically, your COM object won't be able to use CoQueryClientBlanket or CoImpersonateClient to discover the identity of the client who made the HTTP request. If you try to discover the caller's identity this way, you'll see the process token from IISeither SYSTEM if your Web app runs in-process, or IWAM_MACHINE (where MACHINE is your computer name) if your Web app runs out-of-process. The original caller's identity gets dropped on the floor.
Or does it?
It turns out that MTS has an interesting feature that IIS uses to fix this difficult problem. (In all likelihood this feature was introduced to allow a smooth handoff from IIS to MTS.) Since the details of this feature are undocumented, I can only provide my own insights based on empirical evidence, and while I may not have all the details completely correct, the following description will help you gain a better understanding of security in your MTS-based Web applications.
When you first installed the Option Pack, you may have noticed a new alias (local group) with a mysterious name (MTS Impersonators) in User Manager. If your process token includes this alias, or if you are running in the System logon session (as a service, for instance), MTS works some interesting magic when threads in your process call IObjectContext::CreateInstance.
The first time an MTS object (A) calls IObjectContext::
CreateInstance (on B) while impersonating, the object context (at A) caches the thread token and all future method calls you make (from A) into local MTS server packages will appear to be using this cached identity, at least as far as MTS is concerned. COM will still report the calls coming in as using the process token, which makes it clear that MTS is doing some out-of-band work on your behalf to communicate the alternate identity. If you call ISecurityProperty::GetDirectCallerSID in a local MTS component being called from A, you'll see this behavior. Note that while COM and MTS disagree on the caller's identity, MTS performs all of its role-based access checks based on the MTS direct caller, not the COM caller. Hence, the overall effect is similar to the Windows 2000 behavior I described earlier (albeit less seamless).
|