Code Access Security: When Role-based Security Isn’t Enough
Ask any typical .NET developer about Code Access Security (CAS) and you’ve got the chance of hearing “Huh?” as the response. Most developers haven’t run into CAS at all—let alone in a way that would cause them to develop a deep understanding of it.
Ask your typical SharePoint developer about CAS and they’re likely to begin to shudder uncontrollably. Why is that? Well, SharePoint developers have been dealing with CAS since the day that SharePoint was released. Unlike ASP.NET, which makes the assumption of full trust—effectively neutralizing any impact that CAS will have on a standard .NET application—SharePoint starts with a minimal trust, which means most code will need to have a CAS policy applied to it in order to work.
Until.NET 2.0 switches the default from full trust, not much is likely to change on that front. But there has been one change in .NET that will encourage some Web developers to dig into CAS: new support for web parts. As Sharepoint developers know, Web parts are bits of code that can be plugged into application frameworks and be used to construct a visual interface. It is not unlike the way that user controls can be added to a web page except that web parts are designed to be moved and controlled at run time, not design time.
In this article I’ll explore what every web developer needs to know about CAS policies, and how they fit with both SharePoint and ASP.NET 2.0
Before I drop into the details of what CAS is, you need to understand its place in the security world, including what it is not and why it’s necessary. We are all familiar with Windows’ basic security structure, which allows or denies operation based on the user who is logged in. We’re also aware that all too often we’re running as administrators on our local machines because eventually every user runs into something they need to do that requires administrative privileges.
The problem, which has been demonstrated again and again by wave after wave of viruses and worms, is that any code that is run, intentionally or accidentally, by the user has all of their permissions. In the case of a user who is an administrator, that means it can do anything. This is a bad situation, not only because this effectively puts users in charge of what code to run on a case-by-case basis, opening the door for mistakes, but also because it’s intrusive to the user. Being prompted for access time and again as software is loading eventually becomes tiresome.
To complicate the problem, consider the case of a hosting company that is trying to make sure that several applications run on the same server without interfering with one another. There may be permissions that one application requires that they wouldn’t want to give users directly. Such is the case with SharePoint, which will only run on a machine where the user is qualified as an Administrator (or Power User). That’s more permissions than you would generally want to give to a hosting customer.
Enter CAS. CAS doesn’t approach security from the perspective of the logged in user and their role on the system. It augments role-based security; it doesn’t replace it. It approaches security from the perspective of a trust level for the code being run. In this way, users who log in to the system as administrators can still be restricted from performing certain actions that, under role-based security, would always be in their purview. The level of trust for a particular program, then, provides another way to secure a system—with a lesser permission set than the users have themselves.
Suddenly, you have a way to allow the user to directly delete files but to prevent an application—something that they downloaded from the Internet, perhaps—from doing any deletions. Web hosting companies have a way to allow SharePoint to access the entire system while limiting the customer’s access to only areas they should have access to.
How Does CAS Work?
CAS defines permissions sets—things that can be done by a set of code and membership conditions. To do this, CAS identifies and characterizes client code so the appropriate permissions for that code can be determined. So defining how you want code access security to work is all about defining what you want an application to be able to do—and not do—and then telling .NET how to figure out whether the code gets the permission set to work.
The next few sections connect the pieces of the system that define how CAS will make those determinations. After you have an understanding of how CAS settings are processed in an ASP.NET application, I’ll show you how to create your own custom CAS policy and watch it in action.
Defining CAS in the web.config
The first tool in configuring the CAS is the web.config. There are actually two areas that define CAS settings in the web.config file. Both of these areas are located within the <system.web> tag in the web.config file. The first section is a <securityPolicy> tag, which has no attributes and contains a set of <trustLevel> tags.
The <trustLevel> tag is the tag that creates an association between a friendly name for the security policy and the actual policy file. The attributes for the trustLevel tag are:
name—This is the friendly name for the policy file as it will be referred to below.
policyFile—This is the file name of the policy file. If the policy file is not located in the same directory as the web.config file it is a full path to the policy file including the file name.
SharePoint, for instance, defines two <trustLevel> tags with names of WSS_Medium and WSS_Minimal. These are the two out-of-the-box policy files that SharePoint provides. As mentioned above, ASP.NET does not, by default, configure CAS at all—the default is Full trust. So if you’re running ASP.NET without configuration you won’t find these entries.
The second part of the configuration for the web.config is a <trust> tag, which is also placed in the <system.web> tag. The <trust> tag takes two attributes. They are:
level—The level attribute corresponds to one of the <trustLevel> tag name attributes from above, or Full for full trust.
originUrl—This specifies the host name for permissions that are defined only for certain hosts. This attribute is optional.
The entire <system.web> section and the <trust> tag can be encapsulated in a <location> tag if you need to allow for different locations with different trust levels or if you want to make sure that no one with an application in a subdirectory can override your settings. You can find out more about the <location> element in the MSDN documentation at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/gngrflocationelement.asp.
Defining the Policy File
Using the two entries in the web.config file ASP.NET will load your CAS policy file. Now it’s time to put something in that file that ASP.NET can use. To do that you may want to look at the configuration files already installed on your system by the framework. You can look in %WINDIR%\Microsoft.Net\Framework\V2.0.50727\CONFIG. The web_lowtrust.config is a simple file that you can start with to get comfortable with the file format. The web_hightrust.config is a reasonably complicated file that can be useful when you’ve gotten comfortable with the basic structure and how the files fit together.
The basic structure of the file is as follows:
<configuration> <mscorlib> <security> <policy> <PolicyLevel> <SecurityClasses> <SecurityClass> … </SecurityClasses> <NamedPermissionSets> <PermissionSet> <IPermission> … </PermissionSet> … </NamedPermissionSets> <CodeGroup> <IMembershipCondition> … <CodeGroup /> … </CodeGroup> </PolicyLevel> </policy> </security> </mscorlib></configuration>The key parts of the file are the SecurityClasses, the NamedPermissionSets, and the CodeGroups. I’ll examine each one in detail in the following sections.
The SecurityClasses section is perhaps the most straightforward. It lists a set of <SecurityClass> tags for each of the kinds of available permissions. If you open up one of the CAS policy files from the framework directory, as mentioned above, you’ll see that there are several <SecurityClass> tags defined and that their format is very simple. They take only two attributes:
Name—the name of the class to load in processing the rest of the file. The class can be a permission, a membership condition, or a helper class as in the case of the NamedPermissionSet class.
Description—This is the full name of the assembly that the class is found in. This will include the version number, the culture, and public key token. No path information is typically given to the assembly because they are registered in the GAC.
The best thing to do for security class tags is to use the web_hightrust.config as a starting point and remove any security classes that you do not need. This will cover the NamedPermissionSet helper class, all of the membership conditions, and the out of box permissions
Named Permission Sets
Named permission sets allow you to collect up the various permissions that the code may need into a single set of permissions. This single set of permissions can then be referenced from the code groups that identify assemblies and grant them permissions. The <NamedPermissionSets> section contains a set of <PermissionSet> tags, each of which defines an individual named permission that the code group can refer to.
The basic format of the <PermissionSet> tag has the following attributes:
class—This indicates the class to be instantiated. By default, the class is NamedPermissionSet.
version—the version of the policy configuration. This has a value of “1” even in .NET 2.0.
Name—The name that will be used to refer to this permission set.
Description—The description for the policy set.
Unrestricted—An optional attribute that when set to “true” allows for unrestricted access. Typically this is only used with a Full trust permission set.
In each <PermissionSet> tag there may be zero or more <IPermission> tags, which associate the permission with the permission set. The <IPermission> tag has the following attributes:
class—This is the class from the <SecurityClasses> group above which bestows the permission. This value should precisely match the name attribute of one of the <SecurityClass> tags in the <SecurityClasses> tag.
version—As above, this attribute indicates the version and is always set to “1” even in .NET 2.0.
There are several other attributes that may be added to each of the <IPermission> tags based on the class being referenced. The attributes supported cannot be easily ascertained from the documentation of the classes themselves, however, the additional attributes and the permissions that they relate to can be found in the Patterns and Practices guide “How to: Use Code Access Security in ASP.NET 2.0” available at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/PAGHT000017.asp. Refer to Table 4 in the article (linked just above) to find a mapping of individual permission classes and attributes to the trust levels built into ASP.NET 2.0. In doing this the Table enumerates all of the options for the ASP.NET permissions classes.
The final section of the CAS policy file to address is the <CodeGroup> section. Unlike the other sections of the file, which were not hierarchical in nature, the <CodeGroup> tag can be nested inside itself. This is, in fact, how it is set up by default. The first <CodeGroup> tag specifies a membership condition that includes all assemblies but grants them no permission. Under this are all of the individual code groups. The basic format of the <CodeGroup> tag has the following attributes:
class—This is the class, from <SecurityClasses> above, which is associated with the <CodeGroup> tag. For the first, highest tag this is “FirstMatchingCodeGroup.” The FirstMatchingCodeGroup tells .NET to locate the first matching code group (child of this node) and apply only its permissions. All of the subsequent tags typically use “UnionCodeGroup.” This indicates that the permissions granted for all of the membership matches below should be granted to the assembly.
version—Version always contains “1” even if working with ASP.NET 2.0.
PermissionSetName—This is the name of the permission set (from above) to be granted to the code matching this code group.
Within the <CodeGroup> tag there should be one and only one <IMembershipCondition> tag. If you want a code group to apply to multiple assemblies that cannot be matched securely with one condition, simply put new <CodeGroup> tags in for each membership condition that you need. The IMembershipCondition is used to specify a few different kinds of matches. The basic types are listed below:
StrongNameMembershipCondition—This condition matches any code that was signed by a specific strong name key. It takes a PublicKeyBlob attribute, which specifies the public key that matches the private key used to sign the assembly.
PublisherMembershipCondition—This condition uses the Authenticode x.509v3 signature to determine whether the assembly can be trusted. This class takes the attribute of Certificate, which indicates the certificate to use for membership.
HashMembershipCondition—This condition matches code based on its hash. In other words, the hash of the assembly after it was compiled. This class takes two attributes, the HashValue and the HashAlgorithm.
SiteMembershipCondition—This condition matches code based on the site that it originated from. The Site attribute specifies the site to be matched, including wildcards.
ZoneMembershipCondition—This condition matches code based on where the code was run from, as defined by Internet Explorer. This membership type takes an attribute of Zone, which matches the Internet Explorer zone name that the code is coming from.
ApplicationDirectory—This condition matches assemblies based on their directory on the computer. The additional attribute, ApplicationDirectory, specifies the directory for the application to be matched.
UrlMembershipCondition—This condition matches based on testing the assembly’s URL. This membership condition takes an additional attribute of Url, which is used to provide the URL to match for.
AllMembershipCondition—This condition matches all code.
The basic format of the <IMembershipCondition> has at least two attributes:
class—Indicating the class name from above, which is to be used to determine membership.
version—As with other classes, always set to “1” even in .NET version 2.
Added to these two attributes are whatever additional attributes are necessary to specify the condition to be matched.
The complexity of Code Access Security and the fact that it’s not required by default for Web applications has made it something that few developers want to tackle, however, it is a tool that you can use to make your web applications more secure, whether you’re working in SharePoint or just ASP.NET.