![]() |
Show Changes |
![]() |
Edit |
![]() |
|
![]() |
Recent Changes |
![]() |
Subscriptions |
![]() |
Lost and Found |
![]() |
Find References |
![]() |
Rename |
| Search |
History
| 4/5/2006 10:40:48 AM |
| -71.211.188.249 |
![]() |
List all versions |
Role-based security has been evolving on the Windows platform since the first release of Windows NT. Using roles, the operating system can make determinations, such as whether a process is privileged, by checking the security context for a group called BUILTIN\Administrators. The operating system makes decisions based on this logical role (such as whether to let you install services or device drivers). After installing the operating system, you get to choose who will assume this role by adding them to the Administrators group.
Microsoft Transaction Services (MTS) and COM+ tried to make role-based security palatable for application developers, providing a simple role-based authorization infrastructure for COM servers. The goal was to enable a trusted subsystem model for multitier server applications, where an application server is trusted by back-end resources to authorize requests. By performing authorization early, you avoid having to delegate client credentials to back-end servers. Delegation wasn’t such a viable option before Windows Server 2003 because it could not be constrained in space (see WhatIsDelegation for more details).
If you've been looking for a general-purpose authorization solution in the middle tier, your search may very well be over.
Authorization Manager (commonly known as AzMan) is a general-purpose role-based security architecture for Windows. AzMan is not tied to COM+, so it can be used in any application that needs role-based authorization, including ASP.NET Web apps or Web Services, client-server systems based on .NET Remoting, and so on. As of this writing, Authorization Manager is available only on Windows Server 2003, service pack 4 for Windows 2000, and is slated to ship in a future service pack for Windows XP.
There are two parts to AzMan: a runtime and an administration UI. The runtime, provided by AZROLES.DLL, exposes a set of COM interfaces used by applications that employ role-based security. The administration UI is an MMC snap-in that you can try out by running AZMAN.MSC or by adding the Authorization Manager snap-in to your MMC console of choice. Note that you'll need to install the Windows Server 2003 Administration Tools Pack 2 in order to administer AzMan on older platforms such as Windows XP and Windows 2000.

Figure 49.1 Authorization Manager
The first thing you'll notice when you run the AzMan admin tool shown in Figure 49.1 is that it's much more complex than what COM+ offered. You no longer have only roles and role assignments. You now have low-level operations that can be assigned to tasks, which can then be assigned to roles. Tasks can include other tasks, and roles can include other roles. This hierarchical approach helps cap the seemingly unbounded set of roles needed in today's complex applications.
Here's how tasks and roles are created. The application designer defines the entire set of low-level operations that are considered security sensitive. The designer then defines a set of tasks that map onto those operations. Tasks were designed to be understandable by business analysts, so a given task is always composed of one or more low-level operations. If a user is granted permission to perform a task, he or she is granted permission to all operations in it. As an example, a task named "Submit purchase order" might consist of the following operations: "Acquire next PO number", "Enqueue PO", and "Send notification." Of course, you could always simply map each task to a single operation to keep things as simple as possible, but the flexibility of separating tasks and operations is available if you need it.
Once the tasks and operations are defined, you can start coding and include calls in the AzMan runtime anytime a sensitive operation needs to be performed. This call is IAzClientContext.AccessCheck, and I'll show an example of its usage shortly.
At deployment time, the application setup program sets up an AzMan store either as part of Active Directory or in a simple XML file, and installs the basic low-level operations and tasks. The administrator uses the AzMan snap-in to see definitions and descriptions of the tasks for the application. She then defines roles that make sense for her organization. Just as a task is defined as a set of low-level operations, roles are usually defined as a set of tasks. The administrator can then assign users and groups to those roles. In fact, the administrator's main job in maintaining the application from here on out will be adding and removing users from roles as people join or leave the company or change job titles.
So far, I've focused on the application developer and the administrator, but there may actually be a third person helping with the deployment: the business logic scripter. Every task can have a script associated with it. The idea here is to find dynamic security decisions typically made through calls to IsCallerInRole and move them out of application code and into a place where an administrator can make changes to an application's security policy without having to modify and recompile code.
Let's look at an example. Imagine you're building a system to manage a company library. You need to be able to manage the book inventory, check books in and out, and so on. You use AzMan to implement role-based security.
First, you need to make a list of the sensitive operations that appear in your design:
Note that a couple of operations are sensitive to information that you'll only have at runtime. For instance, when attempting to read a patron's history, the application must provide contextual information indicating whether the user is trying to access his own history or that of someone else. While prototyping, you can use the AzMan snap-in to add these operations to a simple XML store. Figure 49.1 shows what this looks like.
If you want to try following along on your own, run AZMAN.MSC, and make sure you're in developer mode via the Action | Options menu. Create a new store in an XML file, then create a new application in it. Next, add the operations one by one, giving them a name and a unique integer that represents the operation number. This number is how the application developer identifies operations in calls to AccessCheck. Note that in naming operations I've encoded the names with the prefix "op." This is simply to avoid naming collisions later when creating tasks and roles, because these names all come from the same pool and must be unique.
The AzMan snap-in operates in two modes: developer and administrator. In administrator mode, you don't have the option of creating stores or applications and you aren't allowed to muck with the low-level operation definitions that the application code relies on. Frankly, nothing stops the system administrator from entering developer mode and doing these things, but the point is that in administrator mode the number of options in the UI is reduced to simplify the administrator's life and to help avoid mistakes.

Figure 49.2 Task definitions
The next step is to define a set of tasks that map to these low-level operations so the administrator will have an easy job defining roles. Because you kept your list of operations simple, you can define a single task for each one. There's a good reason to keep things simple unless you absolutely need more complexity, and it has to do with business logic scripts—but more on that later. For now, let's define a list of tasks that are basically the same as my operations. Figure 49.2 shows what this looks like in AzMan when you edit a task definition.
It's time to switch hats and pretend that you're the administrator deploying the application. Switch to administrator mode under the Action | Options menu and note how the GUI changes: You're no longer allowed to edit the low-level operations for the application. Go ahead and add roles for the application as follows. Patrons should be allowed to browse the catalog, place holds, and review their history. Clerks should be able to do all that Patrons can do, with the addition of checking books in and out. Managers should be able to do all that Clerks can do, but also add and remove books from the inventory.
One way to simplify things here is by nesting roles. For example, Clerk can be defined in terms of the Patron role, with the addition of the "Check out book" and "Check in book" tasks, as I've shown in figure 49.3. Try doing that in COM+!

Figure 49.3 Nesting roles
The last thing the administrator needs to do is make these abstract roles concrete by adding real users to them. To do this, select the Role Assignments folder and choose the "Assign Roles" action. Note that a role doesn't actually become active in an application until it's been added to this folder. For example, the IAzApplication.Roles property only returns the collection of roles that have been added to the Role Assignments folder, as opposed to all roles that have been defined. Once a role has been assigned, right-click it to add either Windows users and groups or application groups that you previously defined in your AzMan store. I'll describe application groups later in this item.
Once you have got some operations and tasks defined, you can start implementing access checks in code. The first thing you need to think about is authentication. If you can use some built-in Windows plumbing, like the Web server's support for Kerberos authentication (What Is Kerberos), you can get a token for the client (What Is a Token). This is by far the most natural way to use AzMan because a token contains all the groups a user is in, making it fast to map that user onto a set of AzMan roles. If, on the other hand, you're using a form or X.509 certificate to authenticate the user, you won't have a token. Rather, you'll have only the user's name. This doesn't mean you can't use AzMan or even that you'll have to write more code. But it does mean that it'll be more expensive, since the AzMan runtime will have to look up the user's groups manually. This incurs round-trips to domain controllers.
The first thing the app needs to do is initialize the AzMan runtime, point it to the store it plans to use, and drill down to the application where the authorization settings live. For now, let's use a simple XML-based store:
AzAuthorizationStore store = new AzAuthorizationStoreClass();
store.Initialize(0, @"msxml://c:\MyStore.xml", null);
IAzApplication app = store.OpenApplication("Corporate Library Application", null);
To build this application, the project needs to reference the AzMan interop assembly, which can be found in \Microsoft.NET\Framework\AuthMan.
Now that the application is bootstrapped, when a new client is authenticated you need to construct a representation of its security context. The context is a lot like a token in that it caches role mappings for a user.
IAzClientContext ctx = app.InitializeClientContextFromToken(htoken, null);
Where do you get the client's token? Well, that depends on what type of app you're writing. For example, here's some C# code from an ASP.NET page that obtains the token. In this case, web.config specifies the authentication mode as Windows, and IIS has been configured to require integrated Windows authentication.
WindowsIdentity id = (WindowsIdentity)User.Identity; IntPtr htoken = id.Token;
If you only know the client's name and don't have access to its token, try to figure out if there's a token that you can get your hands on because it's is the most authoritative way to discover groups for a client. It's also the fastest way, as I mentioned earlier. If you're sure that there's no token for the client available to you, then use this alternate method to initialize a context from an account name in the form domain\user. This call may incur round-trips to discover domain groups, so expect that it will take some time to execute.
IAzClientContext ctx = app.InitializeClientContextFromName(name, null);
Once you have a client context, you can run an access check. This call takes a number of arguments, but for now I'll keep things simple. Let's say you're implementing a function that adds a book to the inventory. I defined "Add book to inventory" as operation number five, so the code might look like what's shown in Figure 49.4.
// always define constants or enums for your ops!
const int opAddBookToInventory = 5;
const int NO_ERROR = 0;
// later in the code...
object[] operations = { opAddBookToInventory };
object[] scopes = { "" };
object[] results = (object[])
ctx.AccessCheck(nameOfBook,
scopes, operations,
null, null, null, null, null);
int result = (int)result[0];
if (NO_ERROR == result) {
// access is granted
}
else {
// access was denied, result holds error code
}
Figure 49.4 Performing an access check
The first argument, nameOfBook, is a string used if you've got runtime auditing turned on. It identifies the object on which you're performing the operation, so you should always provide some meaningful information here. I've used the default value for the second argument, scopes, which I'll explain a bit later. The third argument is where you list one or more operations that you want to test. The result is an array that's always the same size as the operations array, with an integer status code for each operation that indicates whether access is granted or denied. Zero indicates that the access check succeeded, and the client identified by the context is allowed to perform the specified operation. Any other value indicates failure (generally what you'll see is number five, which is ERROR_ACCESS_DENIED).
The AzMan runtime interface isn't strongly typed. It uses VARIANTs for most of its arguments. This allows classic ASP programmers who use scripting languages to use AzMan, but it means that programmers who use strongly typed languages like C# and Visual Basic .NET may make some mistakes when calling AccessCheck that won't be caught until runtime. For example, the operations array must be of type object[], not int[], but the compiler won't complain if you pass an int[] because the actual type of the argument is object. This bit me when I was first learning this API, and it took me a while to figure out exactly how to write the code to avoid getting runtime errors due to parameter type mismatches. I've heard rumors that eventually there will be a managed interface to AzMan, but until that happens, you might want to write a strongly typed wrapper around AccessCheck to avoid getting bitten. The following code shows an example that also simplifies the most common way you'll call the function.
public class AzManWrapper {
public static int AccessCheck(
IClientContext ctx,
string objectName,
int operation) {
object[] results = (object[])ctx.AccessCheck(objectName, scopes, ops,
null, null, null, null, null);
return (int)results[0];
}
}
With a wrapper, you can provide your own overloads of AccessCheck to handle more sophisticated situations where other optional arguments are necessary. Using a wrapper for this function in particular should save you a lot of grief and will reduce clutter in your application code. You could even use this wrapper to convert AccessCheck failures into exceptions instead of returning a status code, along the lines of IPermission.Demand. Don't go crazy and wrap the whole suite of interfaces, though, since this function is really the only one that's tricky to call.
One thing you might be wondering at this point is whether you can use AzMan without Windows accounts to represent users. The runtime was designed with this in mind, although you need to define custom security identifiers (SIDs) for each user, which isn't terribly difficult, and you must call an alternate method to initialize a client security context—namely, InitializeClientContextFromStringSid. The biggest hurdle is that you won't be able to use the AzMan snap-in to manage your stores, which are pretty tightly coupled to Windows users and groups. For more details on how to approach this, see (McPherson).
I want to back up for a moment and discuss the structure of the authorization store a bit. First of all, you have two choices for storing your authorization settings: You can drop the entire store into an XML file, or you can host it in Active Directory. I would strongly recommend using Active Directory for production apps, as it provides a lot more functionality and often better performance than a simple XML file.
If you have a test domain in a lab that you can play with, try bringing up the AzMan snap-in and creating a new store in Active Directory with a distinguished name like this: "CN=MyStore, CN=Program Data, DC=MyDomain, DC=com," replacing "MyDomain" with your own. To see what objects AzMan created in Active Directory, use a tool like adsiedit.exe, an MMC snap-in that you can install from your Windows Server 2003 CD by running SUPPORT\TOOLS\SUPTOOLS.MSI. Create an application in the store and bring up the new application's property page. You'll notice that there are Security and Auditing tabs that aren't present when you're using a simple XML file.
In Active Directory stores, you can delegate responsibility for administering individual applications within a store, and you can audit changes to the store at a very detailed level. With an XML file, you're limited to securing the file itself with NTFS permissions and auditing. Currently, runtime auditing is only supported if the store is housed in the directory, and auditing is tremendously important to most applications. If Active Directory is available, I strongly urge you to house your AzMan stores in it because it's the best place for hosting security policy on Windows.
A single store can house multiple applications. Each application has its own namespace for operations, tasks, and roles. Be aware of concurrency issues if you share a store among multiple applications, because stores don't yet support concurrent editing. If you think there's a chance two administrators might be editing a single store at the same time, you need to provide some external locking to serialize access to the store; otherwise, it might become corrupted. The AzMan snap-in doesn't provide this, and until it does you'll be better off limiting the contents of each store to avoid concurrent editing. The simplest solution is to limit each store to housing a single application.
Each application can also define multiple scopes, which is an advanced feature of Authorization Manager that I recommend only to people who have studied AzMan further and absolutely need this extra level of complexity. Scopes allow you to have different authorization settings for different parts of your application. For example, in a large Web application, roles might be assigned one way under a certain subdirectory; under a different subdirectory, they might be assigned differently or a totally different set of roles might be defined.
Scopes can be convenient in this case because they can share the low-level operation definitions and maybe even some tasks, roles, and application groups. Unfortunately, they can also be confusing and easy to misuse. For example, when you call AccessCheck the second argument is where you specify the scope you want to use for the check. If the user is providing the scope name, perhaps via the URL in the request, you'd better be sure that the name is canonicalized before you pass it to AccessCheck; otherwise, you may allow clever users to fool you into using a weaker scope by encoding the name in an unexpected way. If you're new to this type of attack, you should read the chapter on canonicalization in (Howard and LeBlanc 2002). To learn more about advanced features like scopes, see (McPherson).
There's a nifty feature in AzMan called application groups. In large organizations, it can be a real pain to get new groups added to the directory for your application to use. In fact, if only your application needs a particular group definition, you might be out of luck when an overworked domain administrator refuses to add yet another entry to his already barely manageable list of groups. In this case, application groups can save you. At the store, application, or scope level, you can define groups of users and assign a logical group name to them. You can then use these application groups in role assignments.
AzMan provides two types of application groups basic and LDAP query. The basic groups are a lot like the groups in Active Directory but with a twist: You can define both included and excluded members. For example, you can define a group called EveryoneButBob, as I did in Figure 49.5. The benefit is increased functionality and convenience. The drawbacks are the increased number of CPU cycles required to determine membership in the application group and the memory required to store the membership list in the application group, so use this feature with care. If you like the exclusion feature shown in Figure 49.5, you can still get it by using domain groups as members in your application groups, thus reducing the size of the membership list AzMan needs to keep in memory.

Figure 49.5 Allow everyone but Bob
LDAP query groups are an expensive but nifty feature of AzMan. Here you can use LDAP query syntax to define a group of users who are similar in some way. Here's how you might define the set of engineers who are at least 21 years old:
(&(age>=21)(memberOf= CN=eng,DC=foo,DC=com))
Regardless of the type, basic or LDAP query, the administrator can use application groups as alternative ways of assigning users to roles.
For cases where static grants aren't enough, your application can provide extra context in the form of variables and object references to AccessCheck. This allows a script writer to add business logic using JScript or VBScript without having to change and recompile the application. For example, the "Read patron history" task defined earlier could be supplied with extra context, perhaps the set of roles in which the user is a member, and a Boolean indicating whether the client is accessing her own history or someone else's. This would enable you to write a script that permits Managers to review any Patron's history but restricts normal Patrons to reviewing only their own. A script such as the one shown in Figure 49.6 could be written for this task.
' always start by being pessimistic
AzBizRuleContext.BusinessRuleResult = false
isManager = false
roles = AzBizRuleContext.GetParameter("roles")
for each role in roles
if "Manager" = role then
isManager = true
exit for
end if
next
if isManager then
' Managers are allowed to review
' any patron's history
AzBizRuleContext.BusinessRuleResult = true
else
' anyone else is allowed to review
' their own history
self = AzBizRuleContext.GetParameter("self")
AzBizRuleContext.BusinessRuleResult = self
end if
Figure 49.6 Performing an access check
To associate the script shown with the "Read patron history" task, bring up the definition page for the task, browse to a file containing this script, specify the language as VBScript, and then press the "Reload Rule into Store" button.
To support the script shown in Figure 49.6, you'll need to pass a few more arguments to any access check that involves the "Read patron history" task. Because you've kept things simple and only defined one task per operation, this means you can provide this context whenever you ask about the corresponding operation. Here's a snippet of code that shows how you would provide these extra context variables for a script writer:
bool self = _userIsCheckingOwnHistory();
object[] operations = { opReviewPatronHistory };
object[] scopes = { "" };
object[] varNames = { "roles", "self" };
object[] varValues = { ctx.GetRoles(""), self };
object[] results = (object[])
ctx.AccessCheck(nameOfPatronHistory,
scopes, operations,
varNames, varValues,
null, null, null);
AzMan is a bit picky about the order of the varNames and varValues array. You must order varNames alphabetically, as I did. The varValues array must then provide the corresponding value for each named parameter in varNames, which is pretty obvious. If you want to get even fancier, you can use the last three arguments of AccessCheck to pass in named object references. This will expand the object model that the script writer sees beyond the default AzBizRuleContext object. I'll leave you to experiment on your own with that feature and address some of the challenges you'll run into if you decide to support scripting.
The first odd thing you'll probably notice about scripts is that they're defined at the task and role level, not at the operation level. But application programmers perform access checks and provide context variables for individual operations, so how does the script writer know what variables will be available for a given task? Clearly it's up to the developer to document this carefully on a per-operation basis. One strategy is to keep things simple and always pass the same context variables no matter what operation is being performed. This certainly simplifies things for a script writer. In my library example, I was careful to define one task per operation so I could customize the context variables for each task. Remember, however, that administrators can define new tasks when running in admin mode. What would happen if the systems administrator were to define a new task that incorporated two operations that each provided different context variables? Just try to keep things simple and document carefully how scripts should be written in order to avoid these nasty situations.
Another thing to watch out for when writing scripts is that the results of scripts are cached in the client context object for efficiency. Throw away the client context and you throw away the cache. This is good to know because some scripts might be dependent on external data that can change over time.
Be aware that scripts are designed to qualify the tasks or roles to which they're attached. Say, for example, that Alice is a member of roles R1 and R2. Role R1 is directly granted permission to perform operation X. Role R2 is granted the same permission, but this grant is qualified by a script. When Alice tries to perform operation X, AzMan doesn't even bother running the script in role R2 because operation X is already statically granted through role R1. Thus scripts cannot be used to deny permissions outright.
Scripts can only be used to decide whether a particular task or role should be considered in an access check. Just because a scripted task or role is omitted because its corresponding script evaluated to false doesn't mean there isn't an entirely different task or role that still grants the operation being tested. (McPherson) provides a very detailed description of how the runtime implements an access check. You'll find this in the Performance section. For anyone serious about using AzMan, I suggest you study this section carefully.
Runtime auditing of access checks is an important feature that's available only if you're using an Authorization Manager store within Active Directory. If you want to enable this feature, right-click the application, choose Properties, and turn it on through the Auditing tab. At runtime, the account your server process runs under is important: It must be granted the Generate Audits privilege. The built-in accounts Local Service, Network Service, and SYSTEM all have this privilege by default. Finally, note that the server machine must have auditing of object access turned on for these audits to be captured in the security event log.
What you'll see after enabling auditing is that each call to AccessCheck results in an audit entry, where the object name in the entry is whatever string you passed as the first argument to AccessCheck. The operation's friendly name is shown in the audit along with the client's name. If the check succeeded, a successful access is logged; otherwise, you see a failed access in the log. Whether you see both success and failure audits depends on what level of object access auditing you enabled via the Windows security policy.
Authorization Manager is an important tool for building secure systems on Windows. It expands the idea of role-based security popularized by MTS and COM+, but can be used by any server application, not just COM-based servers. Authorization Manager strives to help you centralize security logic into a concise security policy that can be stored in Active Directory, and it provides a simple API for performing access checks. Runtime auditing satisfies a longtime need as well.
Authorization Manager has loads of features, and part of your job when writing secure code is to figure out what subset of those features your application requires. Finally, remember to keep things as simple as possible to avoid opening security holes.
1 The content of this item previously appeared in MSDN Magazine and is included in this book by permission.
2 Download from http://www.microsoft.com/downloads/details.aspx?familyid=c16ae515-c8f4-47ef-a1e4-a8dcbacff8e3&displaylang=en.
Keith's first book-in-a-wiki. If you would like to read the book online or order a physical copy to throw at annoying coworkers, surf to the HomePage. Please note that due to overwhelming wikispam, this particular wiki is no longer editable.
About FlexWiki.
Recent Topics