9.4. Using Permissions in Your Service

In this section, we return to the unfinished design of the parameter service (ParameterService) from Chapter 4 and see how permissions can be used in this service.

9.4.1. Checking Permissions

Recall that the parameter service allows other bundles to access their parameter settings and handles the task of persistently saving them to and restoring them from a file. Because we used the service factory technique, each bundle can read, create, or modify its own parameters. However, oftentimes an administrative bundle may want to configure the parameters on behalf of another bundle. For instance, the administrative bundle can set the sender, recipient, and mail host parameters for the mailer example, or the printer parameter for the print service, avoiding the expediency of using system properties, as we did earlier.

Based on this requirement, we need to define another service for the parameter bundle, the ParameterAdmin service:

package com.acme.service.param;
import java.util.Properties;
import java.io.IOException;
/**
 * This service allows an administrative caller to get or set
 * parameters on behalf of another bundle.
 */
public interface ParameterAdmin {
   /**
    * Stores a parameter set for a bundle.
    * @param bundleLocation the location of the bundle.
    * @param props the parameter set.
    * @exception java.io.IOException if saving to the property file
    *       fails.
    * @exception java.security.SecurityException if the caller does
						*       not have AdminPermission and the Java runtime supports
						*       permissions.
    */
   public void set(String bundleLocation, Properties props)
      throws IOException;

   /**
    * Retrieves the parameter set for a bundle.
    *
    * @exception java.security.SecurityException if the caller does
    *       not have AdminPermission and the Java runtime supports
    *       permissions.
    */
   public Properties get(String bundleLocation)
      throws IOException;
}

With this service added, the parameter bundle has two types of client bundles:

  1. The “ordinary” bundles, which access their own parameters

  2. The administrative bundles, which can access anyone's parameters

To distinguish the latter from the former, the parameter bundle must check their permissions; otherwise, chaos ensues if just about anyone can change anyone else's parameters. Figure 9.2 shows the new use scenario for the parameter bundle.

Figure 9.2. The two types of client bundles for the parameter bundle. The “ordinary” client bundles are on the left. They each have their own copy of ParameterService produced by the service factory. The administrative bundle is on the right. It can access any bundle's persistent property file directly through the ParameterAdmin service.


The following is the ParameterAdmin service implementation:

package com.acme.impl.param;
import org.osgi.framework.AdminPermission;
// other imports
class ParameterAdminImpl implements ParameterAdmin {
   private static AdminPermission adminPermission =
      new AdminPermission();
   ...
   private ParameterStore store;

   ParameterAdminImpl(BundleContext ctxt, ParameterStore store) {
      ...
      this.store = store;
   }

   public void set(String loc, Properties props)
      throws IOException
    {
      SecurityManager sm = System.getSecurityManager();
						if (sm != null) {
						sm.checkPermission(adminPermission);
						}
       Bundle b = getBundle(loc); // given its loc, find the bundle
       ...
       store.save(b, props);
   }

   public synchronized Properties get(String loc)
      throws IOException
   {
      SecurityManager sm = System.getSecurityManager();
						if (sm != null) {
						sm.checkPermission(adminPermission);
						}
       Bundle b = getBundle(loc); // given its loc, find the bundle
       ...
       return store.load(b);
   }
   ...
}

As shown by the implementation code, we check the caller for AdminPermission before we proceed to retrieve or store the properties for a given bundle. An “ordinary” bundle would fail this check, causing SecurityException to be thrown, because it does not have AdminPermission.

As a rule, we perform the permission checking only when a security manager has been installed. This makes it possible for the bundle to run successfully if we do not specify -Djava.security.manager on the command line when launching the framework and therefore choose not to enforce security at all.

What remains to be done is to instantiate a ParameterAdmin service in the activator and register it:

...
public void start(BundleContext ctxt) {
   ParameterStore store = new ParameterStore(ctxt);
   ...
   ParameterAdmin paramAdmin = new ParameterAdminImpl(ctxt, store);
   ctxt.registerService("com.acme.service.param.ParameterAdmin",
      paramAdmin, null);
}
...

Unlike ParameterService, the ParameterAdmin service is registered as an ordinary service, not a service factory.

The parameter bundle needs to be granted the following permissions:

...
grant codeBase "file:/home/joe/bundles/param.jar" {
   permission org.osgi.framework.ServicePermission
      "com.acme.service.param.*", "register";
   permission org.osgi.framework.PackagePermission
      "com.acme.service.param", "import,export";
   // we need to call Bundle's getLocation method, which
   // requires AdminPermission
   permission org.osgi.framework.AdminPermission;
}

The framework automatically grants the parameter bundle access permission to its own data directory, which is used to maintain property files for all client bundles.

9.4.2. Performing Privileged Actions

With the ParameterAdmin service in place, we turn to developing its client, the administrative bundle. A variety of designs are possible. Let's provide a Web interface using a servlet, which interacts with the ParameterAdmin service to set or retrieve parameters for any bundle in the framework. In this section, we look at only the code pertaining to permissions; the complete code listing can be found in Appendix A, section A.3.2.

The administrative bundle does the following to set properties for a bundle with a location found in bundleLoc:

ServiceReference ref = bundleContext.getServiceReference(
   "com.acme.service.param.ParameterAdmin");
if (ref != null) {
   ParameterAdmin admin =
      (ParameterAdmin) bundleContext.getService(ref);
   Properties props = new Properties();
   props.put("printer","kontakt");
   admin.set(bundleLoc, props);
}

To be able to perform these operations, the administrative bundle must be granted the following permissions:

grant "file:/home/joe/bundles/admin.jar" {
   permission org.osgi.framework.ServicePermission
      "com.acme.service.param.ParameterAdmin", "get";
   permission org.osgi.framework.PackagePermission
      "com.acme.service.param", "import";
   permission org.osgi.framework.AdminPermission;
}

Not surprisingly, AdminPermission must be granted to the administrative bundle.

Install and activate the admin.jar bundle, and point your browser to its URL:

http://localhost:8080/admin/parameters

You will see a list of currently installed bundles, each identified by its location, as shown in Figure 9.3.

Figure 9.3. A list of installed bundles by the administrative bundle whose parameters can be configured


Click on the link to the Log bundle to configure its parameters. You can modify an existing property in place, or create a new property by filling in the empty input fields (Figure 9.4).

Figure 9.4. Setting parameters for the Log bundle using the administrative bundle


So far so good. Unfortunately, when you initiate the parameter-setting operation, you are confronted with the following messages:

java.security.AccessControlException: access denied
   (java.io.FilePermission /home/joe/jescache/bundle1/data/2 read)
   at java.security.AccessControlContext.checkPermission
      (AccessControlContext.java, Compiled Code)
at java.security.AccessController.checkPermission
   (AccessController.java, Compiled Code)
at java.lang.SecurityManager.checkPermission
   (SecurityManager.java, Compiled Code)
at java.lang.SecurityManager.checkRead
   (SecurityManager.java, Compiled Code)
at java.io.File.exists(File.java, Compiled Code)
at com.acme.impl.param.ParameterStore.save
   (ParameterStore.java, Compiled Code)
at com.acme.impl.param.ParameterAdminImpl.set
   (ParameterAdminImpl.java, Compiled Code)
at com.acme.admin.param.AdminServlet.doPost
   (AdminServlet.java, Compiled Code)
...

At a glance, the exception messages appear to complain that the ParameterAdmin code does not have FilePermission to read the data area belonging to its own bundle! However, we specifically mentioned that the framework will grant this permission. What has gone wrong? It has to do with the algorithm used by the Java 2 Runtime Environment to enforce access control for a thread that crosses multiple protection domains.

In the framework, all classes of a bundle belong to the same protection domain because they share the same origin and are granted the same set of permissions. If class Foo belongs to bundle A, and class Bar belongs to bundle B, then Foo and Bar belong to different protection domains. When a method of Bar calls a method of Foo, this execution thread traverses two domains. To determine whether the execution is authorized to perform an action, Java runtime environment not only checks whether the class of the current domain has the permission, but also traces the calling stack to ensure all the preceding callers have the permission as well. This is to prevent a less privileged class from acquiring the permissions of a more privileged class by calling into that class, which would cause a security breach.

Stack trace in Figure 9.5 illustrates exactly such a situation: The execution goes from the class in the administrative bundle (com.acme.admin.param .AdminServlet.doPost) into the parameter bundle (com.acme.impl.param .ParameterAdminImpl.set). Although the parameter bundle has the needed FilePermission to read its own data area, its caller—the administrative bundle—does not. As a result, we failed the file permission check when we attempted to verify whether the property file already existed.

Figure 9.5. The stack status when the administrative bundle called the parameter bundle, and the read access to the latter's data area was denied.


Analogously, a bank teller has permission to access any customer's account. If he obeys whoever walks up to the counter and executes transactions as he's told, then anybody, authorized or not, can initiate random actions through him, which is clearly unacceptable.

However, although this strategy is secure, it is overly conservative. A bank teller can render privileged services that a customer is never permitted to do herself. For example, only a cashier can issue a money order. In other words, under some circumstances, it is okay for the cashier to execute a transaction under his own permission even though the customer does not have that permission. What are these circumstances? The cashier must first verify the customer's bank card or credentials to ensure that she is not an imposter and is thus entitled to the service.

Now the relationship between the administrative and the parameter bundles is quite similar: The latter has the permission to access its own data area, but the former does not. However, the parameter bundle has already checked the caller for AdminPermission, so it knows that the caller is authorized and it is safe to carry out the file operation on the caller's behalf. Therefore, the ParameterAdmin service needs to assert its own permissions when it calls ParameterStore's save method with the doPrivileged construct introduced in the Java 2 Platform:

...
try {
  AccessController.doPrivileged(new PrivilegedExceptionAction() {
						public Object run() throws IOException {
      ...
      FileOutputStream fos = new FileOutputStream(paramFile);
      props.save(fos, "...");
      fos.close();
      return null;
   });
						}  catch (PrivilegedActionException e) {
						throw (IOException) e.getException();
						}
					

The same construct should be wrapped around the file operations in ParameterStore as well. Because the file operations may potentially raise IOException, we need to implement the PrivilegedExceptionAction interface with an anonymous inner class. We can implement PrivilegedAction if the privileged code block does not throw checked exceptions. Other variations of using the doPrivileged construct are not discussed further.

A final note on the administrative bundle is that its servlet should perform authentication by implementing HttpContext's handleSecurity method. See “Performing Basic Authentication” on page 181 for one way of doing this. In a real deployment situation, without authenticating a request from a browser, all our efforts in permission enforcement would be in vain, because anyone could perform configurations by accessing the administrative servlet's URL. As the saying goes, “the security of the entire system is as strong as its weakest link.”

In our examples so far we have determined whether a class is secure based exclusively on its origin. Classes can also be digitally signed by a principal (a person or a corporation) to establish their trustworthiness. JDK provides security tools to assist in key management, and JAR file signing and verification. keytool is used to generate public/private key pairs and self-signed certificates. It also maintains a key store file as the key database. jarsigner is used to sign a JAR file using the private key from the key store and can verify that a signed JAR file has not subsequently been tampered with in any way.

When classes are digitally signed, we can grant permissions based on who are the signers. For example, our policy file may have the following entry:

grant codeBase "file:jes_path/bundles/log.jar", signedBy "Sun" {
   permission java.util.PropertyPermission "*", "read,write";
};

Good tutorials are available on how this is done at http://java.sun.com/docs/books/tutorial/security1.2/index.html.

Earlier we examined a design pattern of using delegation and callbacks. Specifically, we gave the example of a caller that passes a ContentTransformer object to NewsService's getNews method to have its result customized. Let's explore this scenario a bit further to reveal a security issue concerning privileged actions.

Suppose the service for the pager named Fetcher needs to read a system property to determine which tags should be removed and is granted the permission to access the system property. It would implement its ContentTransformer as follows:

ContentTransformer ct = new ContentTransformer() {
   public String[] getTagsForRemoval() {
      String tagstr = System.getProperty("pager.tags.removal");
      String[] tags = null;
      // 'tagstr' contains comma separated tags;
      // extract each and add them to the array 'tags'.
      return tags;
   }
};
InputStream src = newsService.getNews(ct);

As it calls the getNews method, SecurityException is thrown, complaining that the access to the system property is denied. The calling stack is shown in Figure 9.6.

Figure 9.6. The stack status of the callback


As is evident from Figure 9.6, the callback takes a “round-trip” from the fetcher's domain to the news service's domain and back. Because the news service's domain is inserted into the call stack, its lack of necessary permissions leads to denied access.

The solution is that the callback implementation should assert its own permissions. Thus, ContentTransformer's implementation should wrap the system property access code in a doPrivileged block.

Does applying doPrivileged in callbacks constitute any security risk? Generally, no, because when the fetcher passes the ContentTransformer object to NewsService, its very intention is to invite the latter to call it back. Therefore, the callback implementation knows clearly who the caller is going to be and trusts it fully.

This situation applies not only to interservices calls, but also to any context involving callbacks, particularly when you program an OSGi event listener or a service factory in your bundle. In these cases, consider using doPrivileged, because the framework will call your listener or service factory, but it may not have the required permissions.

9.4.3. Creating Your Own Permission Types

When it comes to customizing security policy, you have the following choices:

  1. “Subclassing” the default SecurityManager and overriding the checkPermission method

  2. Providing your own implementation of the Policy class

  3. Creating your own permission types

The first two options are only available to administrators, not to you as a bundle programmer, because there is only one security manager installed and one policy object instantiated in the entire Java virtual machine, and changes to them affect all bundles in the framework. Therefore, let's see how you can define your own permission type correctly through an example.

Suppose your service insists on not being called during certain operational hours in a day. Let's define WorkHourPermission that takes a time span. For example,

WorkHourPermission w = new WorkHourPermission("9-17");

means holder of the permission is allowed access from 9 AM to 5 PM. The semantics of the permission are implemented by its PermissionCollection's implies method. To understand this, you must understand how checkPermission works.

We learned that when a class is loaded, it is put into a protection domain, which has a set of permissions. Suppose the class is granted FilePermission ("/tmp/-", "read,write") for the first time. FilePermission's newPermissionCollection method is called to create FilePermissionCollection for holding permissions of the same type, and the first granted permission is added to the collection. Subsequently, if another FilePermission("/etc/-", "read") is granted to the same class, it is again added to the existing FilePermissionCollection.

Assume that the class above is permission checked as follows:

if (sm != null) {
   Permission requestedPermission =
      new FilePermission("/tmp/junk.txt", "write");
   sm.checkPermission(requestedPermission);
}

checkPermission calls FilePermissionCollection's implies method. What's being determined is whether the set of file permissions granted to the class implies the requestedPermission. The implication is true if any one permission in the permission collection implies the requested permission. In other words, the following evaluations take place:

FilePermission("/tmp/-", "read,write") →
   FilePermission("/tmp/junk.txt", "write") = true
FilePermission("/etc/-", "read") →
      FilePermission("/tmp/junk.txt", "write") = false

Because the class is permitted to read or write any files below /tmp and only to read files under /etc, it is certainly allowed to write a particular file junk.txt under /tmp. Therefore, the permission check succeeds, and the class is given the green light to perform the file operation.

With an understanding of the principle behind permission checking, let's proceed with our WorkHourPermission definition:

import java.security.*;
import java.util.*;

public final class WorkHourPermission extends BasicPermission {
   private int startHour;
   private int endHour;

   public WorkHourPermission(String name) {
      super(name);
      String h = name.trim();
      if (h.equals(""))
            throw new IllegalArgumentException("Work hour cannot be empty");
      String startHourStr = h;
      String endHourStr   = h;
      int dashPos = h.indexOf('-'),
      if (dashPos == 0) {
            startHourStr = "0";
            endHourStr   = h.substring(dashPos+1);
      } else if (dashPos == h.length()-1) {
            startHourStr = h.substring(0, dashPos);
            endHourStr   = "23";
      } else if (dashPos > 0) {
            startHourStr = h.substring(0, dashPos);
            endHourStr   = h.substring(dashPos+1);
      }
      // the following will throw NumberFormatException
      // if the work hour is not a legal integer string and
      // cannot be parsed
      this.startHour = Integer.parseInt(startHourStr);
      this.endHour   = Integer.parseInt(endHourStr);
      if (this.startHour > this.endHour)
         throw new IllegalArgumentException(
            "start hour cannot be after end hour");
      if (this.startHour >23 || this.startHour <0 ||
         this.endHour >23 || this.endHOur<0)
         throw new IllegalArgumentException(
            "start or end hour not 0-23 inclusive");
   }

   public WorkHourPermission(String name, String action) {
      this(name);
   }

   public boolean implies(Permission p) {
      if (!(p instanceof WorkHourPermission))
            return false;
      WorkHourPermission that = (WorkHourPermission) p;
      return (this.startHour <= that.startHour &&
         this.endHour >= that.endHour);
   }

   public String getActions() {
      // no action associated with this permission
       return "";
   }

   public boolean equals(Object obj) {
      if (obj == this)
            return true;
      if (! (obj instanceof WorkHourPermission))
            return false;
      WorkHourPermission that = (WorkHourPermission) obj;
      return (this.startHour == that.startHour &&
         this.endHour == that.endHour);
   }

   public int hashCode() {
      return this.startHour << 5 | this.endHour;
   }

   public PermissionCollection newPermissionCollection() {
      return new WorkHourPermissionCollection();
   }
}

We extend java.security.BasicPermission because it takes a name and an action, which maps to the policy file syntax in a straightforward way. This allows us to grant easily WorkHourPermission in the policy file.

We use the name parameter to specify the time span. Therefore, "8-18" means 8 AM to 6 PM inclusive, "-12" means at or before 12 PM, and "9-" means at or after 9 AM. The string parsing is done in the constructor. Because permission class implementation has serious security consequences, we must take extra care to ensure it is watertight: We must prevent an invalid time parameter from being entered, and we must always ensure that the two permission instances are of the same WorkHourPermission type in the equals and implies methods. We have no use for the action parameter, thus its canonical name is an empty string as returned by the getActions method.

For one WorkHourPermission to imply another, the former's time span must be wider than the latter. For example, a 9-17 WorkHourPermission implies those with the following time spans: 9, 17, 12, 12–14, 9–10, and 11–17, to name a few.

We know that granted permissions are put into a collection for WorkHourPermissions and the collection's implies method is the one used in actual permission checking. Thus we construct the collection in WorkHourPermission's newPermissionCollection method, and define the WorkHourPermissionCollection class as follows:

final class WorkHourPermissionCollection
   extends PermissionCollection
{
   private Vector perms;

   public WorkHourPermissionCollection() {
      perms = new Vector(11);
   }

   public void add(Permission permission) {
      if (! (permission instanceof WorkHourPermission))
            throw new IllegalArgumentException(
               "invalid permission: " + permission);
      if (isReadOnly())
            throw new SecurityException("attempt to add a " +
               "Permission to a readonly PermissionCollection");
      perms.addElement((WorkHourPermission) permission);
   }

   public Enumeration elements()
   {
      return perms.elements();
   }

   public boolean implies(Permission permission) {
      if (! (permission instanceof WorkHourPermission))
            return false;
      WorkHourPermission whp = (WorkHourPermission) permission;
      for (int i=0; i<perms.size(); i++) {
            WorkHourPermission p =
               (WorkHourPermission) perms.elementAt(i);
            if (p.implies(whp))
               return true;
      }
      return false;
   }
}

The all-important implies method enumerates all WorkHourPermissions granted to the class and checks to see whether the requested WorkHourPermission is implied by at least one of them. If so, the permission check succeeds; otherwise, it fails.

Equipped with the new permission class, we can grant WorkHourPermission to classes with the following entry in the policy file:

grant ... {
   // The class has permission to use a service 9 AM-5 PM only
   permission WorkHourPermission "9-17";
};

And during any hour of the day, the caller can be checked with the following segment of code:

SecurityManager sm = System.getSecurityManager();
if (sm != null) {
   Calendar cal = Calendar.newInstance();
   int currentHour = cal.get(Calendar.HOUR_OF_DAY);
   String hourStr = Integer.toString(currentHour);
   sm.checkPermission(new WorkHourPermission(hourStr));
}

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

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