Partially Trusted Services

In .NET 3.0, the only way for a service to execute in partial trust was to explicitly permit only the permissions required for it to operate, and implicitly deny all other permissions. One way of achieving that is to apply the matching permission attributes with the SecurityAction.PermitOnly flag. Consider the service in Example D-7.

Example D-7. Using permission attributes for a partially trusted service

[SecurityPermission(SecurityAction.PermitOnly,Execution = true)]
[UIPermission(SecurityAction.PermitOnly,
              Window = UIPermissionWindow.SafeTopLevelWindows)]
class MyService : IMyContract
{
   public void MyMethod(  )
   {
      Form form = new TestForm(  );
      form.ShowDialog(  );
   }
}

The service requires permission to execute (as does all managed code) and permission to display safe windows. Stacking multiple permit-only permission attributes on a class yields at runtime a single permission set that .NET uses to allow only those permissions and actively deny all others, by installing a dedicated stack-walk modifier. Even if the assembly the service resides in (as well as the app domain) grants the service full trust, all other permissions will be denied. For instance, if the service tries to perform an operation such as opening a file, that will trigger a security exception because the demand for file I/O will encounter the stack-walk modifier, which will actively deny having that permission. All the service can do is execute in its virtual sandbox and display safe windows. Not only that, but .NET will change the window caption and display a warning tag on the displayed form to inform the user that the application is partially trusted, as seen in Figure D-1.

A window displayed from partially trusted code

Figure D-1. A window displayed from partially trusted code

Instead of specifying the permissions as attributes, you can list them in a permission set XML file and provide that file's name to the PermissionSetAttribute, as shown in Example D-8.

Example D-8. Using a permission set file for a partially trusted service

<!-- MyServicePermissions.xml -->
<PermissionSet class = "System.Security.PermissionSet">
   <IPermission class = "System.Security.Permissions.SecurityPermission"
                        Flags = "Execution"
   />
   <IPermission class = "System.Security.Permissions.UIPermission"
                        Window = "SafeTopLevelWindows"
   />
</PermissionSet>

[PermissionSet(SecurityAction.PermitOnly,File = "MyServicePermissions.xml")]
class MyService : IMyContract
{
   public void MyMethod(  )
   {
      Form form = new TestForm(  );
      form.ShowDialog(  );
   }
}

Note that the permission set file is used only at compile time, not during deployment. The compiler will fuse the permitted permissions into the class metadata, as with individual attributes. If the file is not present, the build will fail.

AppDomainHost

There are several problems with the attribute-based approach to partially trusted services. First, every time there is a change in what the service does—or, for that matter, in any of the downstream classes it uses—you will need to modify the stack of attributes or the permission set file. The service is therefore coupled to those downstream classes, and the maintenance cost is increased.

Second, because the permissions are part of the service definition, there is no way to use the service with different permissions as a product of the hosting environment. That is, you cannot dynamically give it more or fewer permissions.

Third, and most important, it is unlikely that most services will actually go to the trouble of analyzing the security permissions required for them to operate and meticulously reducing their granted permissions in order to run with the least possible privileges and permissions. If anything, it is the host that should be concerned about what exactly the service it loads is up to, and yet the host by default has no ability to affect the service's permissions. WCF's ServiceHost hosts all services in full trust. It would be better for the host, as well as for the service's own protection, to allow the host to grant the service whichever permissions it deems appropriate.

To that end, I wrote the class AppDomainHost, defined in Example D-9.

Example D-9. The class AppDomainHost

public class AppDomainHost : IDisposable
{
   //Create new app domain in full trust
   public AppDomainHost(Type serviceType,params Uri[] baseAddresses);
   public AppDomainHost(Type serviceType,string appDomainName,
                        params Uri[] baseAddresses);

   //Create new app domain with specified permission set
   public AppDomainHost(Type serviceType,PermissionSet permissions,
                        params Uri[] baseAddresses);
   public AppDomainHost(Type serviceType,PermissionSet permissions,
                        string appDomainName,params Uri[] baseAddresses);
   public AppDomainHost(Type serviceType,StandardPermissionSet standardSet,
                        string appDomainName,params Uri[] baseAddresses);

   //Additional constructors that take standard permission set,
   //permission set filename, and an existing app domain

   public void Open(  );
   public void Close(  );
   public void Abort(  );

   //More members
}

AppDomainHost can be used just like the WCF-provided ServiceHost:

AppDomainHost host = new AppDomainHost(typeof(MyService));
host.Open(  );

The difference is that AppDomainHost will host the provided service type in a new app domain, not in the app domain of its caller. The new app domain name will default to "AppDomain Host for", suffixed with the service type and a new GUID. You can also specify a name for the new app domain:

AppDomainHost host = new AppDomainHost(typeof(MyService),"MyService App Domain");
host.Open(  );

The new app domain is created with full trust by default. However, placing the service in a separate app domain is the key to managing partially trusted services. AppDomainHost allows you to provide the permissions for the new app domain. For example, to host this service:

class MyService : IMyContract
{
   public void MyMethod(  )
   {
      Form form = new TestForm(  );
      form.ShowDialog(  );
   }
}

with the same permissions as in Example D-7, you would write:

PermissionSet permissions = new PermissionSet(PermissionState.None);
permissions.AddPermission(new SecurityPermission(
                                                SecurityPermissionFlag.Execution));
permissions.AddPermission(new UIPermission(
                                          UIPermissionWindow.SafeTopLevelWindows));
AppDomainHost host = new AppDomainHost(typeof(MyService),permissions);
host.Open(  );

You can also use a permission set file, but unlike with Example D-8, the file is needed only at runtime and can be modified post-deployment. You can also specify one of the standard named permission sets.

A single host process can have as many instances of AppDomainHost as required, each hosting the same or a different service type and each with any arbitrary set of permissions, as shown in Example D-10.

Example D-10. Hosting the same service type with different permissions

//Default is service with full trust
AppDomainHost host0 = new AppDomainHost(typeof(MyService),
                      "Full Trust App Domain",new Uri("net.tcp://localhost:6000"));
host0.Open(  );

//With just enough permissions to do work
PermissionSet permissions1 = new PermissionSet(PermissionState.None);
permissions1.AddPermission(new SecurityPermission(
                           SecurityPermissionFlag.Execution));
permissions1.AddPermission(new UIPermission(
                           UIPermissionWindow.SafeTopLevelWindows));

AppDomainHost host1 = new AppDomainHost(typeof(MyService),permissions1,
                "My Partial Trust App Domain",new Uri("net.tcp://localhost:6001"));
host1.Open(  );


//With not enough permissions to do work
PermissionSet permissions2 = new PermissionSet(PermissionState.None);
permissions2.AddPermission(new SecurityPermission(
                           SecurityPermissionFlag.Execution));

AppDomainHost host2 = new AppDomainHost(typeof(MyService),permissions2,
                     "Not enough permissions",new Uri("net.tcp://localhost:6002"));
host2.Open(  );


//Using one of the named permission sets
AppDomainHost host3 = new AppDomainHost(typeof(MyService),StandardPermissionSet.
Internet,
                      "Named permission set",new Uri("net.tcp://localhost:6003"));
host3.Open(  );

Implementing AppDomainHost

There are two parts to implementing AppDomainHost: creating a new app domain and injecting a service host instance into it, and then having the service host (and the service instance) execute in partial trust. To create a service host instance and to activate it in a separate app domain, I wrote the class ServiceHostActivator, shown in Example D-11.

Example D-11. The ServiceHostActivator class (partial)

class ServiceHostActivator : MarshalByRefObject
{
   ServiceHost m_Host;

   public void CreateHost(Type serviceType,Uri[] baseAddresses)
   {
      m_Host = new ServiceHost(serviceType,baseAddresses);
   }
   public void Open(  )
   {
      m_Host.Open(  );
   }
   public void Close(  )
   {
      m_Host.Close(  );
   }
   public void Abort(  )
   {
      m_Host.Abort(  );
   }
   //Rest of the implementation
}

ServiceHostActivator is a simple wrapper around a standard WCF-provided ServiceHost instance. ServiceHostActivator derives from MarshalByRefObject, so AppDomainHost can call it across the app domain boundary. The CreateHost( ) method encapsulates constructing a new ServiceHost instance. The rest of the methods of ServiceHostActivator just forward the remote calls to the underlying host instance. AppDomainHost offers several overloaded constructors. These constructors all call each other (see Example D-12), even creating a new app domain along the way. Eventually, the public constructors end up using a protected constructor that takes the service type, the new app domain instance, the permission set for the new domain, and the base addresses.

Example D-12. Implementing AppDomainHost (partial)

public class AppDomainHost : IDisposable
{
   ServiceHostActivator m_ServiceHostActivator;

   public AppDomainHost(Type serviceType,params Uri[] baseAddresses) :
                        this(serviceType,"AppDomain Host for "+serviceType+"
                        "+Guid.NewGuid(  ),baseAddresses)
   {}

   public AppDomainHost(Type serviceType,string appDomainName,
                        params Uri[] baseAddresses) : this(serviceType,
                        new PermissionSet(PermissionState.Unrestricted),
                        appDomainName,baseAddresses)
   {}

   public AppDomainHost(Type serviceType,PermissionSet permissions,
                        string appDomainName,params Uri[] baseAddresses) :
                        this(serviceType,AppDomain.CreateDomain(appDomainName),
                        permissions,baseAddresses)
   {}

   //More constructors

protected AppDomainHost(Type serviceType,AppDomain appDomain,
                           PermissionSet permissions,Uri[] baseAddresses)
   {
      string assemblyName = Assembly.GetAssembly(
                                            typeof(ServiceHostActivator)).FullName;
      m_ServiceHostActivator = appDomain.CreateInstanceAndUnwrap(
                              assemblyName,typeof(ServiceHostActivator).ToString(  ))
                                                           as ServiceHostActivator;

      appDomain.SetPermissionsSet(permissions);

      m_ServiceHostActivator.CreateHost(serviceType,baseAddresses);
   }
   public void Open(  )
   {
      m_ServiceHostActivator.Open(  );
   }
   public void Close(  )
   {
      m_ServiceHostActivator.Close(  );
   }
   public void Abort(  )
   {
      m_ServiceHostActivator.Abort(  );
   }
   void IDisposable.Dispose(  )
   {
      Close(  );
   }
}

The protected constructor of AppDomainHost uses .NET Remoting to inject into the new app domain an instance of ServiceHostActivator, ending up with a Remoting proxy to it stored in the m_ServiceHostActivator member.

The new app domain is created with full trust by default. AppDomainHost uses the SetPermissionsSet( ) AppDomain extension method of CodeAccessSecurityHelper to install in the new app domain a new code-access security policy, permitting only the supplied permissions and denying the rest. Example D-13 shows the implementation of SetPermissionsSet( ).

Example D-13. Implementing SetPermissionsSet( )

public static class CodeAccessSecurityHelper
{
   public static void SetPermissionsSet(this AppDomain appDomain,
                                        PermissionSet permissions)
   {
      PolicyLevel policy = PolicyLevel.CreateAppDomainLevel(  );
      policy.RootCodeGroup.PolicyStatement = new PolicyStatement(permissions);
      appDomain.SetAppDomainPolicy(policy);
   }
   //More members
}

Assigning the permissions to the new app domain is as simple as creating a new security policy at the app domain level and calling the SetAppDomainPolicy( ) method of the AppDomain class:

public sealed class AppDomain : MarshalByRefObject,...
{
   public void SetAppDomainPolicy(PolicyLevel domainPolicy);
   //More members
}

Tip

A similar technique to Example D-13 is what ClickOnce uses to enforce the partial-trust execution of ClickOnce-deployed applications.

When you call the other methods of AppDomainHost, such as Open( ) or Close( ), the proxy to ServiceHostActivator calls to the other app domain and has it there open or close its host instance. Since the service instance will execute in the app domain that happened to open its host, the service will also execute under that app domain's security policy.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.152.198