I am a Netherlands-born and based software developer, preferably in Artificial Intelligence and intensive instrument-based data-mining, who has been using JAVA almost from the start in 1996, following Pascal, C and Delphi. In 2002 I started with Eclipse and despite the steep learning curve, I have been happy to have followed this path, especially since DS, OSGI and other technologies have significantly improved the software. I curently also teach embedded technology, UML and C Kees has posted 4 posts at DZone. You can read more from them at their website. View Full User Profile

A Secure Broker in OSGI

04.01.2013
| 2009 views |
  • submit to reddit

This article discusses a way to use OSGI declarative services to implement a secure broker. I often want to secure my declarative services, so that these services are not accessible for any bundle, and while I was working on this, I realised that there were some other issues I was having with DS that I decided to tackle as well. The result is a secure broker that mediates between servers and clients, which has a footprint of give or take 30 kBytes, and has greatly improved the development time of DS in my projects. For example, the sample software   implements the following situation:

Figure 1: Reference Application

The server sends a message to a client and a petitioner, which are all present in a secure domain, or an assembly. The petitioner is also part of a second assembly, and forwards a petition to a provider, who responds by providing a service (a message). The four elements are distributed over three bundles (the client and provider have been put in one bundle for demonstration purposes). I had this up and running in less than half an hour, and that included working on the tooling for this article.

Introduction

Before I begin, I would like to say that I love declarative services (DS)! I have worked with Eclipse/OSGI before DS was around, and since it became commonplace I hardly have to deal with buddy-loading, dependency management and all kinds of other stuff that used to frustrate component-based development.

However, there is something in the implementation that keeps me getting confused (which is my shortcoming, no doubt) and it took me a long time to figure out what that was; the whole idea of having to drop a 'service' into the heart of the 'client' does not seem very intuitive, even though I can understand the implementation logic, but in my mind I always see the server 'out there' and the clients to be somewhere else. With DS it's just as if you can listen to your favourite music station because the DJ is in your basement! As a result, everytime I implement a DS, I loose a lot of time fighting the sense of what I find intuitive, and believe me, I have done this so often, that I get frustrated that it still is such a struggle!

I had been racking my mind about this problem when I stumbled upon another problem, which is not related to DS per se, but is exacerbated by the above issue. I am currently working on an open source framework, and I realised that in this case I do not want to allow any bundle to access the services I present through DS. If, for instance, I have a plugin that offers a folder location as a service, then I want to be sure that only trusted bundles can access it.

Then I started to get nightmares about the concepts behind 'clients', 'servers', 'listeners', 'services' and so on. With DS a 'service' (provider) basically provides one or more objects and 'clients' (often indirectly 'listeners' as well) can get access to those objects through a dedicated method, usually called notifySomething(..). Intuitively a listener could be a 'client' and the service the 'server'. However, I often find myself implementing what I call a petitioner. In this case, the service (povider) is passively waiting for another object to request whatever it has to offer, and does not actively distribute it.This, incidentally, is not how clients and servers are implemented in web aplications, even though at first glance you may think so. Indeed the client petitions certain resources by sending requests, but only after the server has first provided a web page to start the conversation (you could, of course, bring in that the client first has to enter the web page, but then I would respond that it is the user who is really doing the petitioning, and not the client application). I find that the petioner-provider pattern gives an extra level of security to the framework, as rogue bundles will have to actively find out what the providers have to offer, instead of providers putting it all out there for rogue bundles to analyse.

So I have been experimenting with a wrapper bundle around DS which addresses all the topics above, and for me it really has made development a great deal simpler. The wrapper bundle implements a broker and conforms to the whiteboard pattern . The bundle is only about 30 Kbytes, so it hardly puts a strain on an application. I thought I'd share it with the community and maybe get some discussions going on how to make things even better.

Architecture

Initially I started out with the architecture depicted in the following figure.


Figure 2: Initial Architecture

The idea was that services and clients could register themselves to the broker, and they would have to have some means to negotiate which clients would want to subscribe to which service. The two registries would ensure that services would not be transferred to clients, rather the broker would pass objects between them. After implementing this, I found out that DS became much more intuitive, and an additional advantage was that I could copy and paste the component.xml files and the Component class to every plugin that needed to provide or use services. However, then the nightmares started, and slowly it dawned to me that I had to get rid of the whole idea of 'clients', 'servers' and 'services' at implementation level; in fact, they were all one and the same! So I redesigned the broker as follows:

 
Figure 3: Redesigned Architecture

This alternative closely resembles people meeting at a community centre (the roundhouse). Attendees can register themselves (the red lines), and provide some information about their intentions. They may want to be an audience, or conversely they may want to contribute something. The Roundhouse takes this information and starts to match the attendees, after which they can directly communicate with each other. The Roundhouse is also the object that is passed to the various bundles that want to provide or use services (or send attendees). The Match  object is hidden from view, so Roundhouse is also the shield from the outside world.

In case you may be wondering what the benefit is of all this, I will first show what a typical bundle has to implement. Of course we need to add a component, a component.xml in the OSGI-INF folder of the bundle:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.mycompany.myapp.service">
   <implementation class="org.mycompany.myapp.service.RoundhouseComponent"/>
   <reference bind="setAttendeeService" cardinality="1..1" interface="org.condast.osgi.IAttendeeService" name="IAttendeeService" policy="static" unbind="unsetAttendeeService"/>
</scr:component>

Only the name and the class need to be adjusted for customisation for a specific bundle, the other entries are always the same. Don't forget to include the following to the MANIFEST.MF file:

Service-Component: OSGI-INF/component.xml

NOTE: Heed warning messages in the component editor and the build.properties file. These warnings usually mean that things will not work correctly.

The RoundhouseComponent object is also quite straightforward:

package org.mycompany.myapp.service

import org.eclipselabls.osgi.ds.broker.service.AbstractAttendeeProviderComponent;

public class RoundhouseComponent extends AbstractAttendeeProviderComponent {
    
    @Override
    protected void initialise() {
        super.addAttendee( MyServerAttendee.getInstance() );
        super.addAttendee( MyClientAttendee.getInstance() );
        super.addAttendee( MyServiceAttendee.getInstance() );
        ...
    }
}

As you can see, all it takes is one component to add all the clients, services, and servers you need! I tend to implement them as singletons so that they can be easily deployed where (and when) needed in the bundle.

Every bundle that wants to provide attendees has to implement these three steps, which significantly simplifies using declarative services.

Securing the Attendees

So far,  the broker allows attendees to enter the Roundhouse, but the attendees must provide some means to determine with which other attendees they want to hook up. It was mentioned earlier that the assembly of likeminded attendees is regulated by a Match object, and the matching is regulated by a Palaver object that the attendees have to bring along with them. The Palaver object (yes, this is loosely based on Pirates of the Carribean) can be considered an invitation to attend certain meetings in the Roundhouse, and is defined as follows:

package org.eclipselabls.osgi.ds.broker.service;

public interface IPalaver<T extends Object> {

  /**
    * The first check; only attendees with equal introductions get matched
    * @return
  */
  public String getIntroduction();

  /**
   * Give a token that allows other attendants to see if 
   * you belong to their congregation
   * @return
  */
   public T giveToken();

    /**
     * Within this group, a token needs to be passed to confirm the tie
     * between the attendants
    * @return
    */
    public boolean confirm( Object token );

   /**
    * If true, the attendee claims the crowd with which it gets matched.
    * If other attendees claim the same crowd, then their attendance is not
    * accepted.
    * @return
    */
    public boolean claimAttention();

   /**
    * set the flag for claiming attention
    * @param choice
    */
    public void setClaimAttention( boolean choice );
}

The security is based on a three pass check. As I consider most bundle implementers to be of good intent, the first pass is an introduction, which is a String that must be equal for all attendees of an assembly. With the introduction, you basically define a service that will be offered (or requested). So if a unique name is chosen for every service that you want to provide through DS, for instance based on the full plugin name, then other services should have no conflicts with your software. The first attendee that enters the Roundhouse will be made chairman of that assembly, regardless its function. If another attendee registers itself with the same introduction, then the real work begins. First, the new attendee must provide a token that is tested by the chairman, using the Palaver's confirm method. In a simple (non-secure) situation, the token could be the introduction, which is tested for equality. If a secure communication is required, more elaborate schemes could be implemented here. Typically these should be implemented at the level where the software becomes proprietary.

The last part of the Palaver object determines whether an attendee wants to claim full attention, which is typical for a server. Most communication between attendees is unicast or multicast (one server and zero or more clients), and so the attendee that will act as a server will usually want to claim the assembly. It is possible to implement a [n..m] relationship between servers and clients, but only one attendee in an assembly can claim attention! As an example, the following attendee provides a service.

package org.mycompany.myapp.service

import org.eclipselabls.osgi.ds.broker.service.AbstractPalaver;
import org.eclipselabls.osgi.ds.broker.service.AbstractProvider;

public class
MyServiceProvider extends AbstractProvider<String, ARequest, MyService> {

    private static MyServiceProvider attendee = new MyServiceProvider();
    private MyService service;

    private MyServiceProvider(){
        super( new Palaver());
    }

    public static MyServiceProvider getInstance(){
        return attendee;
    }

    public MyService getService(){
        return service;
    }

    public void setService( MyService service ){
        this.service = service;
    }

    /**
     * When a request is received from another attendee,
     * the service is provided, if available
     */
     @Override
     protected void onDataReceived( ParlezEvent<ARequest> event ){
          Arequest request = event.getData();
          if( request.getHeader().equals("Provide")){
               if( this.service != null )
                   this.provide( this.service );
           }
    }
}

class Palaver extends AbstractPalaver<String>{

    protected Palaver() {
        super("my.application.service.A");
    }

    @Override
    public String giveToken() {
        return scramble(super.getIntroduction());
     }

    @Override
    public boolean confirm(Object token) {
        return unscramble( super.getIntroduction(), token );
    }
}

The snippet above gives an impression of a possible provider. The Palaver object has access to certain scramble and unscramble methods that secure the assembly. The provider itself is quite straightforward, as all the hard work is done under the hood. If the provider becomes part of an assembly, then it waits for the onDataReceived() method to pass the service is contains. Note that AbstractPovider is annotated with three objects, the token, the request and the provided service:

AbstractProvider<Token, ARequest, MyService>

The snippet below gives an example of another bundle that provides the corresponding petitioner.

package org.mycompany.my.other.app.service
    import org.eclipselabls.osgi.ds.broker.service.AbstractPalaver;
    import org.eclipselabls.osgi.ds.broker.service.AbstractProvider;

    public class MyPetitioner extends AbstractPetitioner<String, ARequest, MyService>
    {
        private static MyPetitioner attendee = new MyPetitioner();

        private JxtaServiceContainerPetitioner() {
            super( new Palaver());
        }
        
        public static JxtaServiceContainerPetitioner getInstance(){
            returnattendee;
        }

    /**
      * Send a request for a certain service. Usually this does not need
      * to be overridden, as the method is called externally
      */
     @Override
    public void petition( Arequest request ) {
        super.petition( request );System.out.println("Request sent!!!");
    }

    /**
      * The requested service is received.
     */
    @Override
    protected void onDataReceived( ParlezEvent<MyService> event ) {
        super.onDataReceived( event );
        System.out.println("Service Received");
    }
}

class Palaver extends AbstractPalaver<String>{

    protected Palaver() {
        super("my.application.service.A");
    }

    @Override
    public String giveToken() {
        return scramble(super.getIntroduction());
    }

    @Override
    public boolean confirm(Object token) {
        return unscramble( super.getIntroduction(), token );
    }
}

As you can see, the petitioner is almost identical to the provider, with the exception of a petition(...) method that starts the communication. Another difference is that, under the hood, the provider sets claimAttention to true, while the petitioners set them to false. The default situation therefore expects one provider and zero or more petitioners.

Servers, Clients, Listeners, Providers and Petitioners

It had been mentioned in the introduction that the whole issue of clients, servers and what not would not be addressed at implementation level, and so far we have been mainly dealing with Attendees who register themselves at a RoundHouse and gather in assemblies. However, we can easily configure the attendees to serve the purposes described above. A few scenarios are depicted below:

Initiator

Responder

Cardinality

Description

Claim attention

Client

Server

[m..1]

The server offers a service to registered clients

server

Provider

Listener

[m..n]

Provider offers services to registered listeners

~provider

Petitioner

Provider

[m..n]

A petitioner requests a service offered by the provider

~provider

In the classic client-service model, the client initiates a communication by requesting a service, which the server then provides. The observer pattern that is often used in programming, usually follows a provider-listener scenario, where the listener registers itself for a service and gets notified when this is available. In JAVA, this often follows a [1..n] relationship (with the infamous addListener(..) and removeListener(..) constructs), but sometimes a dispatcher can be put in between to create an [m..n] relationship. The last scenario is one of my own favourites, as it basically is the same as a standard client-server model, with the exception that multiple providers can be hooked in. A dispatcher basically does little more than pass information around, but if it also implements the Whiteboard Pattern, the dispatcher is really upgraded to a broker. The Roundhouse provides the means of negotiating the requested services, instead of them being passed through. It is also possible that the petitioner responds to the provided service by sending back data, and so forth, which allows quite elaborate forms of communication.

In the latter two scenarios, the provider is either initiates, or responds to a request. However, if only one provider is used -which is the most common situation-, the provider can claim attention.  

It will have become clear that by not making any references to the intended functionality at the implementation level of the broker, we have given ourselves a lot of flexibility to configure the attendees to our needs.

The Software

An implementation of secure broker has been included in the following (Eclipse archive) zip-file    which includes the binary and sources plugin in the lib folder, and the source code in the src folder. You can also use the update site .The sourcecode is also available as git repository at Eclipselabs. They are distributed under the Eclipse open source library, which means that they are free to use and distribute. If you want to use the secure broker in your own projects, all you need is the org.eclipselabs.osgi.ds.broker bundle. Also included are a number of test plugins (server, client and provider) that can serve as a base implementation for the various scenarios depicted above.

The implementation also adds a number of commands to the OSGI console:

  • roundhouse: Lists all available commands
  • attendees: Lists all the attendees that are added to the Roundhouse.
  • matched: lists all the attendees that form an assembly
  • waiting: lists all the attendees who have not been matched yet.

Attention conflicts are logged, and will always result in one or more attendees in a 'waiting' state. If the Palaver is implemented correctly, then these attention conflicts will be the main source of problems.

Conclusion

The secure broker provides an additional layer over OSGI's declarative services mechanism, which has a very small memory footprint, makes DS highly configurable, enforces the Whiteboard Pattern, and allows a level of security that is worth considering especially when one is working on frameworks. I use the broker extensively and have found that it has greatly simplified the development and use of (secure) DS. But let me know what you think, and I welcome any improvements on the software!

References

Declarative Services

Whiteboard Pattern

AttachmentSize
broker.png13.76 KB
architecture.png21.15 KB
architecture_new.png22.89 KB
org.eclipselabs.osgi_.ds_.broker.site_.zip67.11 KB
Published at DZone with permission of its author, Kees Pieters. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)