Security in COM

[Previous] [Next]

Now that I've covered the essentials of Windows security, I can describe the security features that are specific to COM+. COM+ security comes in two forms: declarative and programmatic. As you'll see, both types are based on an important abstraction called a role.

Declarative security puts the responsibility of enforcing security policies on the shoulders of system administrators. When an application is deployed, an administrator decides who gets access to an application. As the security requirements for the application change over time, the administrator can address them without having to write or rewrite any security-related application code. This also lessens the need to recompile and redistribute DLLs. As with most other aspects of COM+ administration, you can configure COM+ security using the Component Services administrative tool or through the COM+ Admin object model.

In COM+, you can augment declarative authorization checks with programmatic security. In certain situations, programmatic authorization checks can provide greater control because you can add conditional logic. You should also note that while Windows security provides many declarative auditing features, these aren't supported by COM+. If you want to your application to audit the actions of its users, you must resort to programmatic security. However, the good news is that writing the code to perform customized auditing is quite easy.

Roles and Authorization

One of the central figures in the COM+ security model is the role. Let's look at why the COM+ team decided to base all authorization on roles. Earlier in the chapter, I described the practice of nesting groups within groups. A common motivation for nesting one group within another is to map several organizational units to a single security profile. For example, you might decide to nest the Sales group and the Marketing group in the DatabaseReader group. When you configure a set of access permissions for the DatabaseReader group, these permissions are also implicitly extended to the Sales group and the Marketing group.

Using multiple levels of grouping can simplify security administration in larger organizations because it reduces the number of accounts for which you must configure access permissions. It can also eliminate the need to reconfigure access permissions when users and groups are added or removed. The role-based model of COM+ security is based on these same principles. However, instead of nesting groups within groups, you nest groups within roles.

I must point out that COM+ roles are fundamentally different from groups. COM+ roles are defined within the scope of a COM+ application, whereas groups are defined in an accounts database and are thus associated with a specific authority. Roles are identified using strings, while groups are identified using SIDs. The COM+ security model decouples roles from SIDs to provide greater flexibility.

At design time, you define a set of roles. A role is a set of users in a COM+ application who share a common set of access permissions. Each role has a descriptive name and defines a security profile for a specific type of user. For example, you might design an application that includes three roles: Reader, Writer, and Manager. The authorization checks that you set up (either declaratively or programmatically) are always conducted in terms of roles. Roles are a comfortable abstraction for designers and programmers to work with, and they eliminate the need to be concerned about accounts (and their SIDs) during the development process.

At deployment time, an administrator maps a set of users and groups to the roles in your application, as shown in Figure 11-9. Note that a user or group can be added to multiple roles. Likewise, each role is likely to have many different users in its membership. The many-to-many relationship between users and roles creates a valuable degree of flexibility.

click to view at full size.

Figure 11-9. You use roles during the development process to define security profiles and configure authorization checks. At deployment time, an administrator maps a set of users and groups to the roles defined in a COM+ application.

One big benefit of the role-based model is that you can preconfigure access permissions for the entire application in the development environment. Access permissions are always specific to roles, and roles are defined within the scope of the COM+ application. Thus, role-based access permissions have no dependencies on SIDs. This is a key point. The use of roles in the COM+ security model eases deployment of a single COM+ application in multiple production environments.

When you distribute a COM+ application with a preconfigured security policy, you should include documentation that explains the semantics of each role. This information will be important to the administrators who map sets of users and groups to the roles you've defined.

If you're designing a secured application that you intend to deploy in many different production environments, you should also consider limiting the number of roles and minimizing the complexity of their semantics. Consider an application that defines 27 roles and contains four or five pages of documentation to describe each one. How many administrators will want to read through 100 pages of documentation when they put the application into production? Remember that your security policies can't be enforced as you intend unless an administrator can figure out how to properly map users and groups to your roles.

Configuring Declarative Security

Let's take a quick look at the steps you must take to set up declarative security checks for a COM+ server application. To configure and enforce role-based authorization checks, you must complete the following steps:

  1. Enable authorization checking by selecting the Enforce Access Checks For This Application check box on the Security tab of the application's Properties dialog box.
  2. On the Security tab, select component-level security (the default) or process-level security.
  3. On the Security tab, set the authentication level and impersonation level.
  4. Create a set of roles in the server application.
  5. Add a role to the membership of each component, interface, and method to which you want to grant access.
  6. Map user and group accounts to the roles you've created. (An administrator usually does this at deployment time.)

When you create a new COM+ server application, declarative authorization checks are disabled by default. You enable them by selecting the Enforce Access Checks For This Application check box on the Security tab of the server package's Properties dialog box. This is the application-wide switch to turn on security. When security is turned off, just about anyone who can be authenticated to access your computer can create objects and execute methods.

COM+ applications also have a Security Level attribute that by default is configured for component-level checks as well as process-level checks. You can adjust this attribute to disable component-level checks in favor process-level checks only. In most cases, you'll want to stick with the default setting. In just a bit, I'll describe what happens when you configure an application to use only process-level checks. For now, let's assume that you're going to stick with component-level security checks.

Designing roles is the hard part. Once you decide which roles to use, it's easy to add them to a COM+ application. In the Component Services administrative tool, you just right-click an application's Roles folder and choose New and then Role. Note that you can add roles and configure role-based authorization checks using the COM+ Admin Objects as well.

Once you add the appropriate roles to a COM+ application, you can configure a set of declarative authorization checks. This is where you configure which users get access permissions to various methods. You can add role-based authorization checks at the method level, the interface level, and/or the component level.

Every component has a ComponentAccessChecksEnabled attribute, which is on by default. This means that once security is enabled, callers are denied access unless they're in an authorized role associated with the method they're attempting to execute. If the caller isn't in one or more of the roles that are authorized to call the method, COM+ returns a standard "Access denied" error to the caller.

Figure 11-10 shows how the roles available to a method appear in its Properties dialog box. You must decide whether to configure access permissions at the component level, interface level, or method level. Each approach can be beneficial in certain situations, and you can mix and match all three. If you assign a role at the interface level, the role is implicitly associated with all its methods. If you assign a role at the component level, the role is implicitly associated with all its interfaces and all its methods. The design of your components will typically influence whether you use broad strokes (component-level checks) or fine-grained strokes (method-level checks). Note that method-level checks are a welcome addition in COM+. MTS doesn't support declarative checks at the method level, so developers must resort to programmatic checks more often.

Figure 11-10. The Component Services administrative tool makes it easy to add roles and configure role-based authorization checks.

Enforcing Authorization at Runtime

When a COM+ server application with security enabled isn't running, a client faces two initial barriers to activating an object and calling a method. The client must have the appropriate launch permissions as well as the appropriate process-wide access permissions.

COM+ automatically configures these two access permissions behind the scenes. It constructs a DACL by taking the SIDs from the union of all roles defined in the application. Note that the SID for the system identity is added to this DACL as well. At runtime, the system compares the SIDs in the client's token to the SIDs in the DACL. If the client's user account is explicitly or implicitly associated with at least one role, it has both launch permissions and access permissions. However, if the client doesn't map to a at least one role, it can't launch the application or execute a method call.

When the COM+ runtime determines that the client is trying to do something for which it doesn't have authorization, it fails the call and returns the well-known HRESULT E_ACCESSDENIED. If the client is a Visual Basic application, the Visual Basic runtime catches the E_ACCESSDENIED HRESULT and raises a trappable "Access denied" error with error number 70.

If you understand how the DACL influences runtime authorization checking, you can understand what happens in a COM+ application when security is disabled. COM+ prepares a NULL DACL for the application's launch permissions and process-wide access permissions. The NULL DACL indicates that all SIDs have implicit access permissions. In other words, anyone can launch the application and anyone can execute method calls into the application.

You should note that users who can't be authenticated by your server might have problems executing method calls even if you've disabled the application-wide security switch. If you want to allow unauthenticated users to execute methods, you must lower the application's authentication level to None. Note that running at an authentication level of None usually involves setting up a client-side AppID so that you can also adjust the client application's low-water mark to None (as described earlier in the chapter).

When an application has security enabled and you adjust the Security Level attribute to conduct only process-level checks, COM+ performs a single application-wide authorization check when a call enters the boundaries of the application. It does this using the DACL that I just described. Note that when an application is configured in such a manner, you have little granular control. If the client's token contains at least one SID that maps to any SID associated with any role in the application, the client has complete access to the entire application.

In most cases, you should leave the Security Level attribute at its default setting, which tells COM+ to run the authorization checks you've configured at the method, interface, and component levels. Once again, the COM+ runtime uses a simple algorithm to determine whether a call should succeed. It verifies that the caller is in at least one role associated with the method (or its interface or component). If the caller is in one or more of those roles, the call succeeds. If the caller isn't in an authorized role, the COM+ runtime fails the call by returning an "Access denied" error to the client.

Now let's look at a more complicated scenario. Let's say that component A and component B are both configured in a server application that has authorization checking enabled. A client application creates an object from component A, which in turn creates an object from component B, as shown in Figure 11-11. Let's say that the client application is running under the identity of a user account, which is in a role that's authorized to access component A. However, the user account isn't in a role that's authorized to access component B. What happens if the client attempts to execute a method on the component A object, which then attempts to execute a method on the component B object? The call will succeed because no intra-application security checks are implemented. You must understand the implications of this example when you design your security policies.

click to view at full size.

Figure 11-11. COM+ runs authorization checks only when a call crosses an application boundary.

Let's look at a minor variation on the example in Figure 11-11. If you install component A in a server application and you install component B in a library application, there will automatically be a security check between them. This, of course, assumes that both applications have security checks enabled. However, you should be careful not to get too carried away with configuring security on components that aren't used directly by clients. Components that aren't used by clients directly to create objects should be configured to deny all access. They should, however, be designed to trust other components in their application that call methods on behalf of authorized clients.

Calls between server processes can also be tricky. For example, what happens if a client application running as Bob makes a call to server application A, which then makes a call to server application B (as shown in Figure 11-12)? You might expect server application B to perform its security checks using Bob's security credentials, but this isn't the case. COM+ performs its security checks from hop to hop rather than from end to end. You must understand the difference between the direct caller, which is the party one hop up the call chain, and the original caller, which is the party at the top of the call chain. COM+ always performs its security checks using the credentials of the direct caller. Note that COM+ propagates Bob's identity downstream to server application B but that this contextual information should be used only for auditing purposes.

In this example, Bob is the original caller. The principal account serving as the identity for server application A is the direct caller. Server application B conducts its security checks using the direct caller. This security scheme requires server application B to trust that server application A has verified that Bob is authorized to do what he's attempting to do.

click to view at full size.

Figure 11-12. COM+ runs role-based authorization checks when a call enters the boundaries of an application that has security enabled. However, security checks are always conducted using the token of the direct caller rather than the original caller.

As you can see, you must be careful. When you give a role access permissions to a component, you typically give the members of the role implicit authorization to access any resources that the component uses. These resources often include other components, databases, Web servers, and file servers. Once again, the model encourages you to perform authorization checks as early as possible and to avoid relying on authorization checks that are conducted downstream from the initial point of authentication.

Programmatic Security

COM+ offers programmatic security to complement and extend what you can do using declarative security. You can write your own security checks using the ObjectContext interface or the SecurityCallContext interface. The two interfaces have some overlapping functionality, and in many cases either one will suffice.

Let's look at a quick example of how to obtain and use a SecurityCallContext reference in a method implementation. This example checks whether the method is executing in a secured application; if not, the call fails.

 ' Define standard HRESULT for access-denied errors. Const E_ACCESSDENIED = &H80070005 Dim scc As SecurityCallContext Set scc = GetSecurityCallContext If scc.IsSecurityEnabled() Then ' Conduct secured operation. Else ' Raise an access-denied error to caller. Err.Raise E_ACCESSDENIED, _ "MyDll.MyClass.MyMethod", _ "Access denied" End If 

IsSecurityEnabled tells you two things. First, it tells you whether the current call is running in a COM+ application that has security enabled. Second, it tells you whether the component has been configured to run authorization checks at the component, interface, and method levels. If you want your code to run only in a secured COM+ application, you can use the code in the previous example to raise an "Access denied" error back to the caller in the event that the application isn't secured. Note that the example uses a standard HRESULT when raising an error back to the client. This is the same HRESULT that COM+ uses when it fails a declarative authorization check.

You can also write code to test whether the caller belongs to a particular role. The IsCallerInRole method accepts a single string parameter for the role you want to test. The method returns True if the caller's user account or one of the user's group accounts maps to this role. You have to watch out because IsCallerInRole always returns True when it's called in an application in which security has been disabled. Some programmers make a habit of calling IsCallerInRole together with IsSecurityEnabled, as shown here:

 Dim scc As SecurityCallContext Set scc = GetSecurityCallContext If scc.IsSecurityEnabled() And _ scc.IsCallerInRole("Manager") Then ' Conduct a secured, manager-only operation. Else Err.Raise E_ACCESSDENIED, _ "MyDll.MyClass.MyMethod", _ "Access denied" End If 

You have to choose whether it's necessary to call IsSecurityEnabled together with IsCallerInRole. The benefit of always calling these methods together is that you can prevent a user from conducting a sensitive operation in the case where a COM+ application hasn't been properly configured. The benefit of calling IsCallerInRole without IsSecurityEnabled is that it allows an administrator to turn off your programmatic authorization checks simply by turning off declarative authorization checks. The ability to turn off all authorization checks can be important when the administrator needs to perform diagnostic testing on an application.

As you can see, IsCallerInRole lets you conduct custom authorization checks on a method-by-method basis. This gives you more control than declarative security because your authorization checks can contain conditional logic. For example, you can write the SubmitOrder method so that only users in the role of Manager can submit a purchase order if the request exceeds $5,000, as shown in the following code:

 Const E_ACCESSDENIED = &H80070005 Dim scc As SecurityCallContext Set scc = GetSecurityCallContext Dim Amount As Currency ' Determine the purchase amount. Amount = GetAmount() ' Check for Manager role on any amount that exceeds $5,000. If Amount > 5000 Then If Not scc.IsSecurityEnabled() Or _ Not scc.IsCallerInRole("Manager") Then ' (caller <> manager) AND (amount > $5,000) ObjCtx.SetAbort ' Abort transaction if one is running. Dim ErrDesc As String ErrDesc = "Manager required when amount exceeds $5,000" Err.Raise E_ACCESSDENIED, , ErrDesc End If End If ' Now conduct secured operation. 

Note that COM+ generates an mtsErrCtxRoleNotFound error if you call IsCallerInRole in a COM+ application that has security enabled and you pass a role name that doesn't exist.

Both COM+ and MTS support all the security-related functionality exposed by the ObjectContext interface. However, the SecurityCallContext interface is new to COM+. IsSecurityEnabled and IsCallerInRole are supported by both interfaces and work in the same way. However, the SecurityCallContext interface supplies the IsUserInRole method as well as several collections with information about upstream callers; these aren't available through the ObjectContext interface.

You can use the IsUserInRole method to conduct a customized authorization check on an upstream caller. For example, this code checks whether the original caller is in the role of Manager:

 Dim oc As ObjectContext Set oc = GetObjectContext() Dim scc As SecurityCallContext Set scc = GetSecurityCallContext() Dim OriginalCaller As String OriginalCaller = oc.Security.GetOriginalCallerName() If scc.IsSecurityEnabled() And _ scc.IsUserInRole(OriginalCaller, "Manager") Then ' Conduct manager-only operations. Else Err.Raise E_ACCESSDENIED, _ "MyDll.MyClass.MyMethod", _ "Access denied" End If 

Although the IsUserInRole method might be tempting, you should use it sparingly. Unlike its cousin the IsCallerInRole method, IsUserInRole typically requires a round trip to a domain controller. From a design perspective, the use of IsUserInRole runs counter to the practice of conducting authorization checks as close to the user as possible. If you find yourself using this method too often, you should consider redesigning your application to conduct authorization checks further upstream.

Adding customized auditing code

In addition to allowing you to add conditional authorization checks, COM+ programmatic security also provides contextual information about the identity of upstream clients. You can retrieve identity information about both the object's creator and the object's caller. (A COM+ application must be configured for component-level access checks in order for the following examples to work.)

Even though the Security property exposed by the ObjectContext interface contains information about both the creator and the caller, they're typically one and the same. In fact, the contextual information associated with the creator is something that was created for MTS and is now considered deprecated in COM+. You should be concerned only with the name of the caller. The following code retrieves the original caller's user account name:

 Dim oc As ObjectContext Set oc = GetObjectContext() Dim Caller As String Caller = oc.Security.GetOriginalCallerName() 

The main reason that COM+ provides the account and authority names of upstream callers is to provide contextual information for customized auditing. You can conduct an audit within a method implementation by writing the name of the original caller to the Windows event log or to a database.

In some situations, you might want to audit users when they successfully complete a transaction. In other situations, you might want to audit the times when authenticated users try to perform operations for which they aren't authorized. For example, if you determine through a programmatic authorization check that a user is attempting to do something sneaky, you can write the following code to log that fact in the Windows event log:

 Dim Caller As String, LogMsg As String Dim oc As ObjectContext Set oc = GetObjectContext Caller = oc.Security.GetOriginalCallerName() LogMsg = Caller & " has attempted to look at everybody's salary." App.LogEvent LogMsg, vbLogEventTypeError 

This example uses the Security property of the ObjectContext interface. The property's GetOriginalCallerName method returns the caller's account name in the Authority\Principal format, as in MyDomain\KeithB. The Security object also exposes the GetDirectCallerName, GetDirectCreatorName, and GetOriginalCreatorName methods. As I mentioned earlier, GetDirectCreatorName, and GetOriginalCreatorName are considered deprecated under COM+ and should not be used. In most cases, it's the name of the original caller that you want to audit.

The technique I've just shown using the ObjectContext interface is the easiest way to get information about upstream callers. Using the SecurityCallContext interface is a little more complicated but can provide more detailed information, including the name of every upstream caller in the call chain.

A SecurityCallContext object provides an Item collection with several useful elements, as shown in Table 11-3. You can use this collection to determine the number of upstream callers as well as the application's minimum authentication level. It also lets you get at information about the original caller and the direct caller. When you want information about a particular caller, you must use the SecurityIdentity interface. This interface is defined in the COM+ Services type library.

Table 11-3 Elements of the SecurityCallContext Object's Item Collection

Item Type
NumCallers Integer
MinAthenticationLevel Integer
Callers SecurityCallers reference
DirectCaller SecurityIdentity reference
OriginalCaller SecurityIdentity reference

A SecurityIdentity object provides an Item collection with the elements shown in Table 11-4. You can use this object to determine the caller's account name and SID as well as the SSP, authentication level, and impersonation level used to make the call. Here's a simple example that retrieves the account name and authentication level of the original caller:

 Dim scc As SecurityCallContext Set scc = GetSecurityCallContext() Dim Caller As SecurityIdentity Set Caller = scc.Item("OriginalCaller") Dim UserName As String, AuthLevel As Integer UserName = Caller.Item("AccountName") AuthLevel = Caller.Item("AuthenticationLevel") 

Table 11-4 Elements of the SecurityIdentity Object's Item Collection

Item Type
SID Binary array
AccountName String
AuthenticationService Integer
AuthenticationLevel Integer
ImpersonationLevel Integer

COM+ also lets you examine each caller in the logical call chain, which is known as the causality. (See Chapter 6.) For example, what happens if a client application calls into one server application, which calls into a second, which calls into a third, which calls into a fourth, and so on? COM+ flows contextual information about each caller down the call chain. The server application that is furthest downstream can obtain contextual information about the caller's identity at each hop.

To get at this information, you must retrieve the Callers item from the SecurityCallContext interface. This item is an object reference of type SecurityCallers. A SecurityCallers object provides a collection with a SecurityIdentity reference for each caller. Here's an example that inspects each caller in the causality:

 Dim scc As SecurityCallContext Set scc = GetSecurityCallContext() Dim Callers As SecurityCallers Set Callers = scc.Item("Callers") Dim Caller As SecurityIdentity Dim Item As Integer For Item = 0 To (Callers.Count - 1) Set Caller = Callers.Item(Item) ' Access identity of caller. Next Item 

Once again, I must reiterate that COM+ exposes all this contextual information about upstream callers for auditing purposes. If you need to call IsUserInRole to conduct an authorization check on a caller in the middle of the causality, you should probably rethink your security scheme and move your authorization checks to the point where the user is actually authenticated.



Programming Distributed Applications with COM+ and Microsoft Visual Basic 6.0
Programming Distributed Applications with Com and Microsoft Visual Basic 6.0 (Programming/Visual Basic)
ISBN: 1572319615
EAN: 2147483647
Year: 2000
Pages: 70
Authors: Ted Pattison

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net