Using Code Security Models

Code security models are logical frameworks for designing and implementing application security. They define the conditions under which certain actions might, or might not, be taken in code. The .NET Framework provides three code security models, which can be combined within a single application or service to implement the appropriate security for your application:

  • CLR role-based security

  • .NET code access security

  • .NET Enterprise services role-based security

The first two are new to the .NET Framework, while the third is an adaptation of the COM+ role-based security model that might already be familiar to you. In this section, we discuss each in turn.

CLR Role-Based Security

CLR role-based security (sometimes called .NET role-based security) grants permissions based on the identity of the user running the code and the roles to which that user is assigned are assigned. It is often used to check whether a specific Windows user is authorized to access a particular system or network resource.

The two primary objects used in this security model are the Identity and Principal objects. An Identity object contains information about the identity of the user (such as their user ID) and the authentication provider used to determine and verify that identity, as well as some Boolean fields that can be checked to see whether the identity represents an Anonymous, Guest, or System user. The available Identity types are as follows:

  • FormsIdentity

  • GenericIdentity (for custom authentication methods)

  • PassportIdentity

  • WindowsIdentity (which allows for impersonation)

A Principal object contains an Identity object as well as information about the roles for which the user with that identity is authorized. The available Principal types are as follows:

  • GenericPrincipal (based on an identity that does not correspond to a Windows user)

  • WindowsPrincipal (based on an identity that is a Windows user)

  • CustomPrincipal (application-defined)

To select which of these types of Principal objects is used in CLR role-based security checks, you would use the SetPrincipalPolicy method of the current application domain.

In addition to these objects, there is the concept of a role, which is often thought of as corresponding to the user’s role(s) in the organization. Because users can be assigned multiple roles within an organization, they can also be assigned multiple roles in CLR role-based security. For Windows Principal objects, the Windows groups to which the user is assigned are considered to be their roles. This enables an administrator to add and delete users from application roles simply by changing the users’ Windows group memberships. When specifying a Windows group in the context of role-based security, the group must be specified with its fully qualified name. For example, a local group called Friends on a machine named Linda would have a fully qualified name of Linda\Friends. The built-in groups such as Administrators and Users would have fully qualified names of BUILTIN\Administrators and BUILTIN\Users, respectively. You could also refer to them in your code as Linda\Administrators or Linda\Users, hard-coding the machine name. However, using the generic BUILTIN designation instead enables the code to be deployed to any machine, with the security checks performed against the groups defined on the current machine.

In role-based security, the code evaluates whether the current Principal object is in a specific role (or is a specific user), and allows or disallows certain functionality based on the result of the check. For example, a class that returns employee data might return the employee’s social security number if called by an application running with a Principal object that is assigned the role of PersonnelAdministrator. But it might return a string of asterisks in place of the social security number if called by an application whose Principal object is assigned the role of NetworkAdministrator.

The .NET Framework provides several ways to perform this role membership verification, including these three:

  • Imperatively demanding the permission corresponding to the role

  • Imperatively verifying that the user is in a role, by using the IsInRole method

  • Declaratively demanding the permission corresponding to the role

  • You will look at each of these options next.

Imperatively demanding the permission corresponding to the role or user identity is performed in much the same way as any other imperative permission demand. If the Demand method is not satisfied, an exception is thrown. In addition to using this method to demand membership in a particular role, you can also use it to check the current user’s identity. To do this, instead of using the following:

Dim RolePermission as New PrincipalPermission _       (Nothing, "BUILTIN\Administrators", True)

you would use a principal permission declaration, such as this:

Dim UserPermission as New PrincipalPermission _       ("CORPDOMAIN\Linda", Nothing, True)

For example, to demand that the principal is a member of the group BUILTIN\Administrators, you would use code like that in Listing 9.3.

Listing 9.3: Imperative Demand of Role Membership via Permission

start example
Imports System Imports System.Threading Imports System.Security.Principal Imports System.Security.Permission Public Class RoleExample Private Sub CheckRole() Dim RolePermission as New PrincipalPermission _       (Nothing, "BUILTIN\SystemOperators", True) Try    RolePermission.Demand() Catch    ' handle exception here End Try End Sub End Class
end example

Imperatively verifying whether a particular principal belongs to a role is done via a method call to the Principal object’s IsInRole function, using code like that in Listing 9.4.

Listing 9.4: Imperative Query of Role Membership via Principal.IsInRole

start example
Imports System Imports System.Threading Imports System.Security.Principal Public Class RoleExample Private Sub CheckRole() AppDomain.CurrentDomain.SetPrincipalPolicy _ (PrincipalPolicy.WindowsPrincipal) If (Thread.CurrentPrincipal.IsInRole _    ("BUILTIN\SystemOperators")) then    ' perform action for users in SystemOperators group Else    ' perform action for users not in SystemOperators End If End Sub End Class
end example

Note that in this listing, we supply a string consisting of the fully qualified role (or Windows group) name, and that a failure returns a Boolean False rather than throwing an exception as with permission demands. You also have the option of specifying some built-in group names by using predefined enumerated values, such as WindowsBuiltInRole.SystemOperator and WindowsBuiltInRole.Administrator.

Finally, as with other types of permissions, you can use the declarative attribute syntax to demand that the caller’s identity have a specific role. An example of this is shown in Listing 9.5.

Listing 9.5: Declarative Demand of Role Membership via Permission

start example
Imports System Imports System.Threading Imports System.Security.Principal Imports System.Security.Permissions Public Class RoleExample <PrincipalPermissionAttribute(SecurityAction.Demand, _     Role = "BUILTIN\SystemOperators")> _ Private Sub CheckRole()    ' perform whatever actions require SystemOperators role End Sub End Class
end example

start sidebar
Real World Scenario—Developing under the Administrator Account

Suppose you are a developer writing a serviced component that will be accessed by many accounts payable data-entry operators in your organization to update vendor information in a centralized database. You develop the component, test it, and turn it over to the quality assurance person on your team.

Within a day, you see an e-mail message from the quality assurance person, addressed to all project team members, noting that the component is generating an exception when invoked by the data-entry application. You test it again on your computer, just to be sure, and then send back the infamous developer reply that causes quality assurance and end-user personnel the world over to wince: “It works for me; I don’t know why it’s failing for you.” Several hours later, after you’ve paid a personal visit to the QA person, seen the code fail, and had him come back to your office with you so that he could log in on your Visual Studio .NET–equipped workstation and you could trance through the code, you discover that his account is missing a permission required by your component. Because you had been developing under an account in the Administrators group, which had been granted that permission by default, you had not noticed that it would be necessary to add this permission to typical application user accounts.

Actually, you were fortunate, because the code went through a quality assurance department. Instead, you could have found yourself working with an end user—possibly on the other side of the globe, in a time zone whose work hours perfectly overlap with your usual sleep hours—to troubleshoot the failure.

The solution is for developers to make a habit out of developing and testing under a user account with “normal” system privileges (those that apply to the Everyone group, for example) rather than Administrator, so that these problems could often be detected and avoided earlier in the development cycle.

Many developers see logging into an account with Administrator privileges and doing all of their development from that account to be the path of least resistance. However, this practice has often led to code that fails out in the field when run by non-Administrators or that results in hastily updated installation instructions requesting (usually in very small print, in the middle of a paragraph) that all users be assigned certain high-level Windows permissions manually, so that the code will run correctly for them. Discovering and solving permission-related problems during development, rather than during external testing or live production use, contributes favorably to application usability and quality.

end sidebar

Exercise 9.1 gives you hands-on experience in the use of role-based security. Because the exercise demonstrates the effect of testing whether the user is in the Administrators built-in group, you should ideally have access to at least one user who is in that group, and at least one who is not.

Exercise 9.1: Using CLR Role-Based Security

start example
  1. Create a new Visual Studio .NET project by using the Windows Application project template. Name this project RoleBasedExample.

  2. In the Solution Explorer, right-click the project name and choose Add Reference. In the Add Reference dialog box, select System.Security.

    click to expand

  3. Switch to the Code View for the form and add the following Imports statements to the top of the module as follows, to support working with CLR role-based security:

     Imports System Imports System.Threading Imports System.Security.Principal
  4. Add the following controls to the form, with the following properties set:

    • Control Type: Button

    • Name: btnCheckRole

    • Text: Check Result

    • Control Type: Textbox

    • Name: txtResult

    • Text: (blank)

      • Your form should look something like this:

  5. Add code to the button’s Click event to check whether the user running the application is a member of the Administrators built-in group:

    Private Sub btnCheckRole_Click (ByVal sender as _          System.Object, ByVal e As System.EventArgs) _          Handles btnCheckRole.Click    AppDomain.CurrentDomain.SetPrincipalPolicy _         (PrincipalPolicy.WindowsPrincipal)    If Thread.CurrentPrincipal.IsInRole _         ("BUILTIN\Administrators") Then         txtResult.Text = "User is a member of Administrators"    Else         txtResult.Text = "User is NOT a member of Administrators"    End If      End Sub 

  6. Save, build, and run the application. Click the Check Result button on the form. The application will report whether the current user is a member of the Administrators group.

  7. Close the application.

  8. Log off that user account and log onto the other account (in Administrators if your original account was not in the group, or not in the group if your original account was).

  9. Run the application again. Once more, the application will report whether the current user is a member of Administrators.

  10. Close the application.

end example

.NET Code Access Security

Prior to the advent of code access security, all code run under the same user ID ran with the same permissions, regardless of the origin or trustworthiness of the code. The reality of the component- and network-oriented computing world we live in today is that all code is not equally trustworthy. You have no way of knowing whether someone has maliciously modified the code located on an Internet server you don’t control. You might not want intranet-based code accessing certain privileged resources of your local PC (such as its hard disk). That third-party class library might have latent bugs waiting to be discovered. Or a certain software publisher might be known for calling Beta version 3, Release 1.0. Any of these pieces of code might be calling your code and might be able to lure it into performing some action you never anticipated. For example, suppose you wrote a component that displays the most recent 100 lines in one of several log files and can optionally delete lines from the files. The name of the log file is a parameter passed to the component. You might want to allow local intranet-based callers of this component access to full functionality, but restrict the functionality available to Internet-based callers. That is, you might want to allow Internet-based callers to view the most recent 100 lines of only one of the log files but might want to deny them permission to use the deletion function.

Code-access security facilitates restricting the operation of code in these scenarios and more, based on what the CLR knows about the calling code. Code-access security is implemented by combining .NET permissions with the concepts of evidence, security policies, and code groups.

Evidence

.NET code-access security complements user and role-based security mechanisms by granting permissions to managed code based on evidence. Evidence is information identifying the code and its origin. Common types of evidence considered when evaluating (or administering) code-access security are listed in Table 9.9.

Table 9.9: Selected Evidence Used by Code Access Security

Evidence

Description

ApplicationDirectory

Directory containing the application

Publisher

Publisher (Authenticode signature) of the application

Site

Website of origin for an assembly that was loaded directly from a website

StrongName

Strong name of the assembly (as generated by sn.exe –k)

Url

URL of origin for the assembly (note that Site and Url are relevant only for applications run directly from a website, not those downloaded and then run locally)

Zone

Security zone from which the code originates (Trusted Sites, LocalIntranet, etc.)

Security Policies

How is this evidence connected to the permissions assigned to managed code? Permissions are granted via .NET security policies, which are administered by the .NET Framework Configuration tool (see Figure 9.7). This is accessed by choosing Start Ø Settings Ø Control Panel Ø Administrative Tools Ø Microsoft .NET Framework Configuration, in Windows 2000, or by using command-line utilities such as caspol.exe.

click to expand
Figure 9.7: The Microsoft .NET Framework Configuration tool

When a demand is made for a permission, the evidence is run through the security policy. A permission set is produced, which can be searched for the permission being demanded.

.NET provides four levels of policy, so that application behavior and security options can be customized at the appropriate granularity, and applications can be configured to behave differently from enterprise to enterprise or from machine to machine. The available policy levels are listed in Table 9.10.

Table 9.10: .NET Security Policy Levels

Level

Description

Enterprise

Affects all machines in an organization

Machine

Affects all users on the machine

User

Affects all appdomains in programs run by that user (user or administrator controlled)

ApplicationDomain

Affects a single appdomain (programmer controlled, not persisted to disk nor visible in the configuration tools that are used to maintain the other policy levels)

Each policy level has several components: a named PermissionSet collection, a code group hierarchy, and a list of assemblies. The named PermissionSet collection includes system-supplied permission sets such as those listed in Table 9.5, and additional user-defined permission sets. When policy settings at different levels conflict, the most restrictive setting takes effect. For example, if a machine-level policy disallows access to the system environment settings, and a user-level policy allows access to the environment, assemblies to which both of those policies apply will not have permission to access the system environment settings.

Code Groups

Within each security policy level are one or more code groups. These code groups (such as All_Code—) are used to indicate the evidence that must be present in order for the permissions listed for that code group to be granted to the assembly. They are used to organize and simplify permission assignments in much the same way as Windows groups are used to simplify permission assignments to multiple users. Code groups are organized into hierarchies, and each code group has a membership condition (a defined list of the evidence that must be present for code to be considered a member of that group), a permission set name, and additional attributes. If the membership condition is satisfied, then the rights listed in the named permission set are granted to the code.

In Exercise 9.2, you will explore code access security. By default, code originating locally is fully trusted. In order to create a situation in which some code access permissions will fail, you will create a code group specific to this assembly, by setting the code group’s membership criteria to be “those assemblies having a hash code (that is, a statistically unique identifier) equal to the hash code of this assembly.” Then you will assign that code group Internet rather than FullTrust. This will be sufficient to create conditions under which a demand for Write access to files on drive C: will fail, because the Internet permission set allows only restricted access to the local disk.

Exercise 9.2: Using Code-Access Security

start example
  1. Create a new Visual Studio .NET project by using the Windows Application project template. Name this project CodeAccessSecurityExample.

  2. In the Solution Explorer, right-click the project name and choose Add Reference. In the Add Reference dialog box, select System.Security.

  3. Switch to the Code View of the form and add Imports statements to the top of the module as follows, to support working with code access security permissions:

     Imports System Imports System.Security Imports System.Security.Permissions 
  4. Add an additional Imports statement and assembly attribute following those Imports statements, to support strong-naming:

     Imports System.Reflection <Assembly: AssemblyKeyFile("C:\myKey.snk")>
  5. Add the following controls to the form, with the following properties set:

    • Control Type: Button

    • Name: btnCheckFileIOPermission

    • Text: Check Permission

    • Control Type: Textbox

    • Name: txtResult1

    • Text: (blank)

    • Control Type: Textbox

    • Name: txtResultBoth

    • Text: (blank)

      • Your form should look something like this:

  6. Add code to the button’s Click event handler to perform two permission checks. You'll verify that the code can access a single file, c:\Ex92a.txt, for writing, and you'll verify that the code can access both c:\Ex92a.txt and c:\Ex92b.txt for writing. The following example demonstrates how to issue the Demand method for multiple permissions by combining them via the Union method:

    Private Sub btnCheckFileIOPermission_Click(ByVal sender as _           System.Object, ByVal e As System.EventArgs) _           Handles btnCheckFileIOPermission.Click   Dim WritePermission1 As New FileIOPermission _       (FileIOPermissionAccess.Write, "C:\Ex92a.txt")   Dim WritePermission2 As New FileIOPermission _       (FileIOPermissionAccess.Write, "C:\Ex92b.txt")   Try       WritePermission1.Demand()       txtResult1.Text = "Demand of WritePermission1 succeeded."   Catch       txtResult1.Text = "Demand of WritePermission1 failed."   End Try   Try       WritePermission1.Union(WritePermission2).Demand()       txtResultBoth.Text = "Demand of both permissions succeeded."   Catch       txtResultBoth.Text = "Demand of both permissions failed."   End Try End Sub 

  7. Open a Visual Studio .NET command prompt and navigate to the C:\ directory. Use the strong-name utility to generate a key pair:

     C:\> sn.exe -k myKey.snk 
  8. Save, build, and run the application. Click the Check Permission button on the form. You should see results like the following, which report that both permission demands succeeded:

  9. Close the running application.

  10. Open the .NET Framework Configuration tool by choosing Start Ø SettingsØ Control Panel Ø Administrative Tools Ø Microsoft .NET Framework Configuration in Windows 2000 Professional. Create a new code group and assign it the appropriate permissions. To do this, expand Runtime Security Policy Ø Machine Ø Code Groups Ø All_Code.

  11. Right-click All_Code and choose New. Name the new code group CheckPerm and click the Next button.

    click to expand

  12. Choose Hash as the condition type, so you can easily set permissions that will apply to only a single assembly.

  13. Select the SHA1 hashing algorithm, click Import, and browse to the assembly (EXE file) for this project, which should be in the project’s \bin directory.

  14. Click the Open button. The hash code should now be displayed. Before continuing, verify that your code group is configured as in the next graphic , and then click Next.

    Note: Your hash code will vary from the one displayed in the following graphic.

    click to expand

  15. Select the Internet permission set to restrict what the app can do.

    click to expand

  16. Click Finish.

  17. Right-click the CheckPerm node and choose Properties.

  18. On the General tab, select the check box labeled This Policy Level Will Only Have The Permissions From The Permission Set Associated With This Code Group to restrict this assembly’s permissions to only those specified here. Then click OK.

    click to expand

  19. Run the application again. You should see results like the following, which indicate that both demands failed:

  20. Save and close your project in Visual Studio .NET.

end example

.NET Enterprise Services Role-Based Security

In addition to the role-based security implemented at the CLR level, .NET provides a second role-based security mechanism. This one is inherited from COM+ and defined in the System.EnterpriseServices namespace, which includes COM+ functionality for .NET Framework–based applications, as discussed in Chapter 2, “Creating and Managing Serviced Components.”

This .NET Enterprise Services role-based security mechanism provides compatibility with legacy code, as well as an easy way to implement role-based security when roles are not defined as Windows groups. In this security model, roles are independently defined for each application, with each role representing a logical grouping of Windows groups and users that is meaningful to the application. Role names do not need to be unique across components, nor do they need to correspond to Windows group names. For example, both the QueryAPVendor and the QueryARCustomer components can define a Supervisors role, and each can include a different set of users and groups. The Supervisors role in the Accounts Payable application might include only Accounts Payable supervisors, and the Supervisors role in the Accounts Receivable application might include only Accounts Receivable supervisors. These roles and the list of Windows groups and users participating in them are stored in the COM+ catalog.

The CLR’s role-based security can be extended to implement security based on criteria other than Windows group membership by using the GenericPrincipal object to manually code your own security checks. However, you should consider using the facilities built into .NET Enterprise Services instead of inventing your own application-specific role-based security. .NET Enterprise Services already allow for checking role assignments that do not correspond to Windows groups, and include useful features such as the Component Services tool (see Figure 9.8), which can be used to view and maintain the role memberships.

click to expand
Figure 9.8: The Component Services tool

This tool is accessed by choosing Start Ø SettingsØ Control Panel Ø Administrative Tools Ø Component Services in Windows 2000 Professional.

In the Component Services tool, navigate down the tree in the left-hand pane and select the COM+ application whose roles you wish to configure. You can perform the following actions:

  • Add and remove roles recognized by that application, using the Roles node

  • Add and remove Windows groups and users from any role, using the Users node under that role

To use .NET Enterprise Services features, including role-based security, your component must derive from the ServicedComponent base class. Security-related methods are available in the System.EnterpriseServices.ContextUtil class. To check whether .NET Enterprise Services’ role-based security is enabled, check the value of the Boolean ContextUtilt.IsSecurityEnabled property. The calling user’s role membership can be checked either imperatively, via the ContextUtil.IsCallerInRole method, or declaratively, via attributes.

For example, to imperatively verify that the calling user is in the role HRstaff, you might use the code in Listing 9.6.

Listing 9.6: .NET Enterprise Services Role-Based Security

start example
Imports System Imports System.EnterpriseServices Public Class EnterpriseRoleExample    Inherits ServicedComponent Private Sub CheckRole()    If (ContextUtil.IsSecurityEnabled) Then     If (ContextUtil.IsCallerInRole ("HRstaff")) Then                       ' perform whatever actions require HRstaff role        End If      End If End Sub End Class
end example

Alternatively, you can check the .NET Enterprise Services role membership declaratively. To require the caller to be in the HRstaff role, simply notate the assembly or method with a security role attribute: SecurityRoleAttribute. The first parameter of this attribute is the name of the role, and the second parameter is a Boolean indicating whether the built-in group Everyone is automatically added to the members included in that role. For example:

<SecurityRoleAttribute("HRstaff", False)> _ Private Sub CheckRole …



MCAD/MCSD(c) Visual Basic. NET XML Web Services and Server Components Study Guide
MCAD/MCSD: Visual Basic .NET XML Web Services and Server Components Study Guide
ISBN: 0782141935
EAN: 2147483647
Year: 2005
Pages: 153

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