Ok, this is difficult for me because I haven't fully digested the Module code yet.

An OSGi / Jini client would already know the Service API it wanted to find. So the Service API Bundle the client depends on would already be installed in the client node. It would just be a matter of discovering the correct service and Reggie does discriminate between API with different versions.

The really tricky part is finding the correct ClassLoader to unmarshall your classes into and the right ClassLoader to put your reflective proxy. Hint: It has to go into the Service API interface ClassLoader, otherwise there are issues with visibility. Java has the Package version spec, which might be used to assist, since a client could potentially have multiple versions of a Service API, but different bundles can have identical packages.

OSGi has some nice features like Permissions required by a bundle being packaged in the bundle. OSGi's Security Infrastructure predates the Java 1.4 dynamic permission security model, since it supported the various flavours of embedded java and personal profile, which is now simply Java CDC.

OSGi's security differs from the Java 1.4 Permission model in it's BundleClassLoader and PermissionCollectionWrapper, which enables Permission's to be dynamically added or removed. PermissionCollectionWrapper is passed to the static (pre Java 1.4) constructor of the ProtectionDomain. PermissionCollectionWrapper remains mutable after the ProtectionDomain's construction. It can't be used in the ProtectionDomain dynamic constructor, since this would cause it to be replaced by a Permissions instance when performing a policy merge.

It's important to remember that while Permission's can be added and removed from OSGi's ProtectionDomain's, true revocation of permission's doesn't occur, client code having obtained security sensitive object references still have them after permission has been removed, so can continue performing privileged actions, although it can't perform any new privileged actions. I believe the dynamic Permission's in OSGi were made dynamic to allow remote policy updates.

OSGi has a ConditionalPermissionAdmin service which allows permission checks to be delayed, in the case where a number of checks are performed.

I've done some work on an InternetSecurityManager, I've made sure that the DelegatePermission's work with OSGi ProtectionDomains as well, so the OSGi ProtectionDomains can be intermingled with the Dynamic Java 1.4 style ProtectionDomain's that Jini utilises to enable DynamicPolicy's. The catch is that the ConditionalPermissionAdmin delayed permission checks won't work, since that requires OSGi's SecurityManager.

I had given this some serious thought, but had concluded that it would be easier to treat Service API as fixed and non changing, this would not prevent OSGi from being a Jini client or service, however it wouldn't force others to adopt OSGi either. I proposed Extended Service API to enable evolution of Service API similar to the practice of extending java Interfaces. This is suitable when one party wants to extend the Service API but doesn't control the spec.

I'd figured that the proxy can install as many dependency's as it wants in its own ClassLoader, provided we minimise the classes visible in parent class loaders to the Service API, Jini and Java Platforms.

Since all implementations communicate using the Service API, Jini and Java platform classes, this makes it possible to isolate all the library and package versions of all the implementations, each to their own child ClassLoader visibility.

So clients can still be implemented in OSGi, and updated dynamically, but OSGi isn't concerned with the classes in proxy ClassLoaders, since the client and proxy only cooperate using the Service API and can't see each other. To add Service API in a client node, we would need a way to add new Service API to the Service API ClassLoader, with potentially many ProtectionDomains for many CodeSources containing Service API.

I thought the lost codebase problem could be solved by cooperative CodeSource caching or something similar, based on the jar file message digest.

Cheers,

Peter.

Michal Kleczek wrote:
Folks,

The discussion about trust and solving deserialization DoS issues brought me to the idea of annotating classes with Modules.

On the other hand Peter is working on ClassLoader / class identity issues.
I tried to think about it and came up with an idea that a Module can express that it depends on other Modules so that if there is a dependency that is shared between two modules classes loaded from this dependency preserve their ClassLoader:

interface Module {
  Module[] getDependencies();
  //... class loading methods
}

We would have to implement a ClassLoader structure that is not hierarchical but allows loading classes from dependencies.

BUT IT IS ALREADY DONE!!! And it is done well. It is called OSGI.
How can we leverage this?

1. Let's annotate classes with objects implementing:
interface BundleSource extends ReferentUuid {
  Iterable<? extends BundleSource> getDependencies();

  //no more multiple urls - we are a bundle
  //we can either provide everything in our bundle
  //or require dependencies to be installed
  InputStream open();
}

2. As in my original idea - we prepare BundleSources with a ProxyPreparer so that we know BundleSource is trusted before.

3. Let's implement a JiniBundle:
public class JiniBundle implements BundleActivator {

  //our context
  private static BundleContext bundleContext;

  //we need a package admin instance to find Bundles that
  //were used to load classes
  //have to check if it is really needed or we can safely assume
  //that ClassLoaders implement BundleReference
  private PackageAdmin packageAdmin;

  //our cache of BundleSources
  private static Map<Bundle, BundleSource> bundleSourceCache =
    new WeakHashMap();

  //used by MarshalOutputStream to annotate classes
  //will return null if we don't know the BundleSource
  public static BundleSource getSourceOf(Class c) {
    return bundleSourceCache.get(
      
packageAdmin.getExportedPackage(class.getPackage().getName()).getExportingBundle());
  }

  //now the difficult part :)
//cache of installed BundleSources
  private static Map<BundleSource, Reference<? extends Bundle>>
    installedBundles = new WeakHashMap();

  //used by MarshalInputStream to resolve classes
  //
  public static Class loadClass(
    BundleSource source, String name, ClassLoader defaultLoader) {
    if (source == null) {
      return defaultLoader.loadClass(name);
    }
    try {
      makeSureInstalled(source).loadClass(name);
    }
    catch (RuntimeException e) {
      throw e;
    }
    catch (Exception e) {
      throw new ClassNotFoundException();
    }
  }

  private static Bundle makeSureInstalled(BundleSource source) {
      Bundle b = installedBundles.get(source);
      if (b == null) {

        //prepare
        source = bundleSourcePreparer.prepare(source);

        //make sure dependencies are installed
        for (final BundleSource dependency : source.getDependencies()) {
          makeSureInstalled(dependency);
        }
//install
        Bundle b = bundleContext.installBundle(
          source.getReferentUuid().toString(), source.open());
//cache
        bundleSourceCache.put(b, source);
        installedBundles.put(source, new WeakReference(b));
      }
      return b;
  }

  //osgi activation follows...

}

Further details to be thought out:
1. Especially important is handling of lost codebase problem. OSGI implicitly imports exported packages so it can happen that the service interface is loaded from a bundle that was not installed by JiniBundle - so there is no BundleSource associated with it. 2. Similar to lost codebase problem is that OSGI container can load a class from a different bundle than the one we would want to so the codebase will not be lost but will be different than the original one. 3. Sure there are many more - and the question is whether OSGI is the best choice (maybe classworlds or NetBeans platform is better)

But in the end - would it be a nirvana? :)

Michal


Reply via email to