Context checking is a more advanced security mechanism that can perform selective filtering of incoming requests. The context is an arbitrary object provided by the client and used by the server to decide whether or not to allow the request.
Filtering and context checking are performed in between the communicator server and the MBean server. The mechanism relies on two objects called the MBeanServerForwarder and the MBeanServerChecker.
The MBeanServerForwarder allows the principle of stackable MBean servers. An MBeanServerForwarder implements the MBeanServer interface and one extra method called setMBeanServer. Its function is to receive requests and forward them to the designated MBean server.
The setMBeanServer method of a communicator server object allows you to specify the MBean server which fulfills its requests. By chaining one or more MBeanServerForwarder objects between a communicator server and the actual MBean server, the agent application creates a stack of objects which may process the requests before they reach the MBean server.
The MBeanServerChecker is an extension of the forwarder which forces each request to call a -checker method. By extending the MBeanServerChecker class and providing an implementation of the checker methods, you can define a policy for filtering requests before they reach the MBean server. As shown in the following table, checker methods apply to groups of MBeanServer methods.
Table 11-1 Filter Method Granularity for Context CheckingFilter Method | MBean Server Operations Filtered |
---|---|
checkAny |
Every method of the MBeanServer interface |
checkCreate |
All forms of the create and registerMBean methods |
checkDelete |
The unregisterMBean method |
checkInstantiate |
All forms of the instantiate method |
checkInvoke |
The invoke method which handles all operation invocations |
checkNotification |
Both addNotificationListener and removeNotificationListener |
checkQuery |
Both queryMBeans and queryNames |
checkRead |
All methods which access but do not change the state of the agent: getAttribute, getAttributes, getObjectInstance, isRegistered, getMBeanCount, getDefaultDomain, getMBeanInfo, and isInstanceOf |
checkWrite |
The setAttribute and setAttributes methods |
As a request passes through a stack of MBean servers, the checker methods are called to determine if the request is allowed. In order to identify the manager that issued a request, the checker may access the operation context of the request.
The operation context, or just context, is an object defined by the manager who seeks access through a context checker. It usually contains some description of the manager's identity. The only restriction on the context object is that it must implement the OperationContext interface. The context object is passed from the connector client to the connector server and is then associated with the execution of a request. Conceptually, this object is stored in the user accessible context of the thread which executes the request.
All methods in the MBeanServerChecker class may access the context object by calling the protected getOperationContext method. The methods of the context checker then implement some policy to filter requests based on the context object, the nature of the request, and the data provided in the request, such as the attribute or operation name.
The following diagram shows the paths of two requests through a stack of MBean server implementations, one of which is stopped by the context checker because it doesn't provide the correct context.
Only connectors fully support the context mechanism. Their connector clients expose the methods that allow the manager to specify the context object. Existing protocol adaptors have no way to specify a context. Their requests may be filtered and checked, but their context object will always be null.
This functionality may still be used to implement a filtering policy, but without a context object, straightforward manager identification is not possible. However, a proprietary protocol adaptor could define some mapping to determine a context object that could be accepted by the filters.
An agent wanting to implement context checking first needs to extend the MBeanServerChecker class. This class retrieves the context object and decides whether any given operation is allowed.
import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.QueryExp; import com.sun.jdmk.MBeanServerChecker; import com.sun.jdmk.OperationContext; public class ContextChecker extends MBeanServerChecker { // Constructor public ContextChecker(MBeanServer mbs) { super(mbs); } // Implementation of the abstract methods of the // MBeanServerChecker class: for each of the specific // checks, we just print out a trace of being called. [...] protected void checkWrite( String methodName, ObjectName objectName) { System.out.println("checkWrite(\"" + methodName + "\", " + objectName + ")"); } protected void checkQuery( String methodName, ObjectName name, QueryExp query) { System.out.println("checkQuery(\"" + methodName + "\", " + name + ", " + query + ")"); } [...] /** * This is where we implement the check that requires every * operation to be called with an OperationContext whose * toString() method returns the string "nice". */ protected void checkAny( String methodName, ObjectName objectName ) { System.out.println("checkAny(\"" + methodName + "\", " + objectName); OperationContext context = getOperationContext(); System.out.println(" OperationContext: " + context); if (context == null || !context.toString().equals("nice")) { RuntimeException ex = new SecurityException(" Bad context: " + context); ex.printStackTrace(); throw ex; } } } |
Then the agent application then needs to instantiate its context checker and stack them in between the communicator servers and the MBean server. Each communicator server would have its own stack, although filters and context checkers may be shared. The agent performs the stacking inside a synchronized block because other threads may try to do stacking simultaneously.
// Create MBeanServer // MBeanServer server = MBeanServerFactory.createMBeanServer(); /* Create context checker. The argument to the constructor is * our MBean server to which all requests will be forwarded */ ContextChecker contextChecker = new ContextChecker( server ); [...] // Create HTTP connector server /* Add the context checker to this HTTP connector server. * We point it at the context checker which already points * to the actual MBean server. * It is good policy to check that we are not sidetracking * an existing stack of MBean servers before setting ours. */ synchronized (http) { if (http.getMBeanServer() != server) { System.err.println("After registering connector MBean, " + "http.getMBeanServer() != " + "our MBeanServer"); System.exit(1); } http.setMBeanServer(contextChecker); } |
Finally, the manager operation defines a context object class and then provides a context object instance through its connector client.
/* In this example, the agent checks the OperationContext of each operation by examining its toString() method, so we define a simple implementation of OperationContext whose toString() is a constant string supplied in the constructor */ class StringOperationContext implements OperationContext, Cloneable { private String s; StringOperationContext(String s) { this.s = s; } public String toString() { return s; } public Object clone() throws CloneNotSupportedException { return super.clone(); } } // the contextName must be provided on the command line OperationContext context = new StringOperationContext(contextName); [...] // The context is set for all requests issued through // the connector client; it may be changed at any time connector.setOperationContext(context); |
The ContextClient and ContextAgent applications in he examplesDir/Context directory also demonstrate the use of stackable MBean servers and context checking through the HTTP connector.
If you have not done so already, compile all files in this directory with the javac command. For example, on the Solaris platform with the Korn shell, you would type:
$ cd examplesDir/Context/ $ javac -classpath classpath *.java |
If the agent and client applications are not already running from the previous example, type the following commands in separate windows:
$ java -classpath classpath ContextAgent |
$ java -classpath classpath ContextClient |
The classpath should include the current directory (.) for both applications because they rely on classes that were compiled in this directory.
Press <Enter> in the client application to trigger another set of requests.
The agent window displays the output of the ContextChecker class. We can see that the checkAny method verifies the "nice" context of every request and that the other checkers just print out their name, providing a trace of the request.
Stop both applications by typing <Control-C> in each of the windows.Restart both applications, but specify a different context string for the client:
$ java -classpath classpath ContextAgent |
$ java -classpath classpath ContextClient -context BadToTheBone |
This time we see the result of a context that is not recognized. The agent raises a java.lang.SecurityException which is propagated to the client who then exits.
Press <Control-C> in the agent window to stop the ContextAgent application.