Item 66: Use GuardedObject to provide access control on objects





Item 66: Use GuardedObject to provide access control on objects

So you've read Item 62 and decided to make use of the underlying platform security model to help build authorization into the system (and perhaps you've gone so far as to embrace JAAS for role-based authorization, as described in Item 63). You've created custom permission types, and you've either established a custom Policy object or written the policy file (java.policy) to reflect the appropriate assignment of permissions. Now you're starting to write the necessary tests for permission ownership before allowing sensitive operations to take place, but you run into a small snag: you've got an object that wraps a sensitive operation, and it needs to travel across the wire to a recipient whose security context may be different from your own.

Consider the problem: in a role-based security environment, a Person object may require certain permissions to be held by the requesting principal (in other words, the user on whose behalf the code is currently executing) before allowing the call to go through. For example, recent U.S. legislation declares that certain sensitive personal data like social security numbers can't be accessed by just anybody. This is a perfect place for a JAAS-based permission check:






public class Person

{

  private String ssn;



  // . . .



  public String getSSN()

  {

    AccessController.checkPermission(

      new SensitiveDataAccessPermission("ssn",

                                        "read"));

    return ssn;

  }

}


In this particular scenario, only this method requires the check, so the cost of making the security stack-walk check is justified.

But what about the situation where even just simple access to the object itself requires permission? For example, in the JDK library itself, any sort of file I/O should be governed by a permission, which means that, in theory, each and every method call on the file-based object (FileInputStream, FileOutputStream, and so on) should verify that all of the ProtectionDomain instances on the calling thread have the appropriate FilePermission in their security context. Under the guidelines noted previously, then, every method in those classes should do the permission check to ensure that the permission is present.

Unfortunately, that's going to "have issues."

The problem is that while the JDK goes to great lengths to minimize the performance impact of the security stack walk, the basic fact is that every ProtectionDomain established on the thread will need to be checked, and each of that ProtectionDomain's permissions will need to be checked to see if it implies the FilePermission demanded. This is hardly an inexpensive operation, and to do this on every FileOutputStream.write or FileInputStream.read call is going to kill any kind of performance we might desire.

The JDK solves this problem by realizing that if every operation requires the security permission, it makes sense to require it once, at the time of object creation, and thereafter assume that since the caller had to have the permission to create it, they must still have the permission required in order to call the desired methods on it. So, the security check is done once, in the object constructor, and thereafter left out of the other methods.

Unfortunately, this otherwise efficient way to avoid multiple (usually redundant) security checks fails in a very specific and all-too-common scenario—serialization. Remember, as Effective Java [Bloch, Item 54] points out, the serialized form of an object effectively presents the class with another constructor, since the process of deserialization means building an object up from a byte array rather than from formal Java arguments.

This becomes dangerous when the object, constructed in a security context that has the appropriate permissions, is serialized and sent to another security context (either across the wire or simply serialized to disk and deserialized back again) that isn't supposed to be able to use this object. But because the security check was already passed in the constructor, and the constructor will never be invoked again for this object (in fact, it can't, by the laws of Java), the object is being used by code that shouldn't be able to use it.

As if this weren't enough to consider, there's also the basic premise that the caller will always have the same permissions in its access control context from the moment the object is constructed to its destruction. In the days before JAAS, this might have been true when the object remained entirely in the same JVM, as long as the object wasn't serialized and deserialized by code in a different ProtectionDomain. Now that JAAS is an integral part of the JVM, however, this isn't a fair assumption, and code that makes access control checks needs to take that into account, either within the library classes themselves or in the client code that uses them.

For these reasons, when you want to make sure that only callers with appropriate access use an object, use the java.security.GuardedObject to provide access control. It ensures that access control is checked at least once, by forcing callers to go through a method call to gain access to the protected object.

Using a GuardedObject is much like using its cousins, SignedObject (see Item 64) and SealedObject (see Item 65): create the object to be guarded (which should be Serializable, but doesn't have to be, unless you want to pass it across the wire), and pass it along with the "guarding" object into the constructor of the GuardedObject class. The guarding object itself must be of a type that implements the Guard interface, which requires a single method, checkGuard, to be implemented; this method will be used to verify that the caller has access. Note that even custom Permission objects are acceptable guards, by virtue of the base Permission class that implements Guard and implements the checkGuard method to obtain the SecurityManager and call checkPermission on it.






public class Person { . . . }



public class PersonPermission extends Permission

{ . . .  }



void foo()

{

  Person p = new Person();



  GuardedObject go =

    new GuardedObject(p,

                      new PersonPermission("read"));

}


The protected object (Person, in this case) doesn't have to be Serializable, but of course if we want to send the GuardedObject over the wire, it will need to be.

Access to the sensitive object can be achieved only by calling getObject on the GuardedObject, which in turn calls the guarding object's checkGuard method. Assuming the guarding object is a Permission type, the caller's security context is checked, rather than assuming that the creator's security context continues to exist.






GuardedObject go = getGuardedObject();

Person p = (Person)go.getObject();

  // At this point, if the caller of getObject()

  // doesn't have PersonPermission in its

  // ProtectionDomain, a SecurityException

  // is thrown and access will be denied


Again, as with SignedObject and SealedObject, we can make it marginally easier to work with GuardedObject by subclassing it and creating a customized getObject method that wraps getObject but returns a strongly typed reference to the sensitive object.

This gives us the ability to ensure that a security context check will occur before the recipient of the GuardedObject can access the sensitive object in question. Once again, this can be particularly powerful in the form of Message-Driven Beans or simple JMS consumers, since now we can ensure that the caller has the appropriate security context to access the contents of an object even across the wire; for example, we might take the caller's current JAAS security context (the established Subject) and pass that as part of the message itself:






Message msg = queueSession.createObjectMessage();



Person p = new Person(...);

GuardedObject guardedObj =

  new GuardedObject(p, new PersonPermission("read"));



Subject s = loginContext.getSubject();

  // Obtain the Subject out of the authenticated

  // LoginContext



msg.setObjectProperty("subject", s);

msg.setObject(p, guardedObj);



queueSender.send(msg);


Then, when the message is received, we can extract the Subject, obtain the GuardedObject, and dig the Person back out:






ObjectMessage objMsg =

  (ObjectMessage)queueReceiver.receive();



Subject subject =

  (Subject)objMsg.getObjectProperty("subject");

GuardedObject guardedObj =

  (GuardedObject)objMsg.getObject();



Subject.doAs(new PrivilegedExceptionAction()

  {

    public Object run()

      throws Exception

    {

      Person p = (Person)guardedObj.getObject();

        // An access control check is made here; if

        // the Subject doesn't have PersonPermission,

        // a SecurityException is thrown



      // Use p as appropriate; if you got here, you

      // obviously have the necessary permissions

    }

  });


Of course, once the object has been retrieved and stored into a Person reference, no further security checks will be performed when calling into the Person methods—this permits the "one security check" optimization desired but ensures that the check still occurs.

Note, as well, that if the holder of the Person reference decides to hand it out to other callers running in a separate security context, the original problem remains—unfortunately, there simply is no way to prevent this without a major change in the Java language and platform. Assuming you have followed the advice of Item 1 and built components instead of objects, you can create a dynamic proxy behind the interface exposed to clients that stores the actual object in a GuardedObject and uses getObject each time to forward the call on.

Doing this, of course, more or less eradicates the "one security check" optimization that we were looking for in the first place, but remember that this can be done selectively based on factors controlled at runtime, such as knowing whether the object will be used in the same security context. (At the same time, you can gain a certain measure of knowledge by implementing serialization hooks, as described in Item 71, to serialize the actual data in a GuardedObject. Just be careful to avoid infinite recursion as the GuardedObject gets serialized, which in turn serializes the protected object, which is to say, the object you wanted serialized in the first place.)


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows