Have we been doing codebase annotations wrong?
Could RMIClassLoader have been better conceived?
Could a simpler alternative be utilised instead?
For example, classes are resolved differently during deserialization
than how classes are resolved at runtime. At runtime a ClassLoader
delegates to its parent ClassLoader, or in the case of modular systems,
ClassLoader’s of imported modules. RMIClassLoader, doesn’t look to
resolve classes through ClassLoader hierarchies, but instead tries to
locate each ClassLoader directly based on an annotation.
The problem with this approach is not all ClassLoader’s provide codebase
annotations, and class resolution may be different at distinct nodes in
the network.
Currently codebase annotations may change when marshalling between
nodes, depending on where each class is resolved.
Refer to: http://sorcersoft.org/resources/jini/smli_tr-2006-149.pdf
A much simpler approach.
We can define the ClassLoader at each endpoint.
In JERI, a ServerEndpoint can be assigned a default ClassLoader, by
passing it as a parameter to its InvocationLayerFactory. The client
Endpoint’s default ClassLoader is the ClassLoader of its dynamic proxy
instance (the ClassLoader where the java.lang.reflect.Proxy dynamically
generated instance is loaded).
So if a service has a smart proxy, it’s codebase should be present in a
ClassLoader at both the ServerEndpoint and the client Endpoint, so the
default ClassLoader’s at both Endpoint’s contain that codebase.
The default ClassLoader at each Endpoint now has responsibility for
resolution of classes, no longer is RMIClassLoader required. In fact
codebase annotations no longer need to be annotated with every class in
the stream either.
But what about client parameter objects, or exported remote handback
objects passed as parameters, I hear you ask?
Simple, we use a marker interface, so these objects can identify
themselves to the stream, a bootstrap proxy can be provided by the
stream, that uses only local classes, present at both endpoints, the
original object can be stored into a MarshalledInstance and serialized
with the bootstrap proxy to the remote Endpoint, that allows the
originator to be authenticated, it’s codebase provisioned into a
ClassLoader which becomes the default loader of the MarshalledInstance
to deserialize the object in question. The identity key of the
ClassLoader will be a combination of the bootstrap proxy’s
InvocationHandler identity and it’s codebase annotation, so it can be
cached. If a remote object which is contained within the
MarshalledInstance, its JERI Endpoint will use the default ClassLoader
passed to the MarshalledInstance as its default loader.
Note: If we’re in a traditional java hierarchical ClassLoader system
(not modular), we’d want the current stream’s default loader to be the
parent loader of the resolved or provisioned ClassLoader passed to the
MarshalledInstance.
So now you don’t get codebase annotation loss, ServerEndpoint and client
Endpoint’s have ClassLoader’s with compatible class resolution. The
codebase annotation becomes a configuration concern of the service.
In addition, MethodConstraints can also be applied to exported objects
nested within other services. It can be passed using the stream context.
This ensures that minimum principal authentication, integrity and
confidentiality apply to all nested objects.
The good news is that most of the mechanisms are already present and
backward compatibility can be preserved allowing eventual migration.
Remember that a smart proxy can have no real server back end
communication at all (except for providing the codebase), it’s just an
object that gets serialized around different nodes, in this case the
bootstrap proxy is still used to provide the codebase annotation and as
trust verification.
How does trust work in this system?
Provided you still trust the bootstrap proxy’s service, after method
constraints that ensure confidentiality and minimum principal
authentication have been applied , it provides the codebase annotation,
and if integrity constraint is true, then the codebase scheme is checked
for integrity, or if it’s signed, the jar is validated by a provider.
(The signers can be anonymous and advised by the bootstrap proxy). You
now trust that the code will validate input during deserialization, and
if the de-serialized object implements RemoteMethodControl apply
MethodConstraints to it as well. The object bytes in serial form may
have originated from a third party (also with MethodConstraints applied,
but possibly not trusted by the original node), in any case it’s
important for the input to be validated during deserialization.
Note this system would also utilise a Service Provider Interface to
communicate with the bootstrap proxy and preferred classes can still be
supported, simply by using a PreferredClassLoader when loading the codebase.
This system also allows support for modular environments like OSGi to be
relatively simple when compared to RMIClassLoaderSpi. Additonally it
allows an OSGi node to interact with traditional nodes / services,
provided jar files have bundle manifests and the configured codebase
annotation string contains all required jar files, including
dependencies (the OSGi provider can ignore the dependencies, provided
the first jar is the proxy bundle).
I've currently got a prototype I'm working on if anyone's interested.
Regards,
Peter.