Register MBeans in Apache Karaf

Just a quick note about how your OSGi application can register MBeans in Karaf’s platform.
Apache Karaf comes with its own JMX server and a global configuration you can find under its etc directory. Configuration goes from the server’s URL and remote access security, to a fine-grained permission system (e.g. who can access this or that MBean). For more details about what this, you can refer to the official documentation of Karaf.

Now, let’s assume you want to expose custom MBeans in Karaf’s platform.
How to proceed? Well, Karaf’s management feature comes with Apache Aries. The JMX component of this project is the Reference Implementation of the OSGi JMX Management Model Specification. This specification indicates how one can easily register MBeans within OSGi thanks to the white board pattern.

The only thing you have to do is to register a service that respects JMX naming conventions and that has the jmx.objectname property. Every time such a service is registered, Aries is notified and registers a MBean from it. If you remove the service, the MBean will be unregistered too. So, you only have to register a service! And that’s great. Then, you are plugged with Karaf’s JMX configuration, meaning you can easily integrate with other tools like Apache Decanter and its JMX collector.

How to register such a service?
You can do this programmatically…

BundleContext bundleCtx = osgiHelper.findBundleContext();
if( bundleCtx != null ) {

	this.logger.fine( "Running in an OSGi environment. Trying to register a MBean for the messaging." );
	Dictionary<String,String> properties = new Hashtable<> ();
	properties.put( "jmx.objectname", "net.roboconf:type=messaging" );
	try {
		this.serviceReg = bundleCtx.registerService( MessagingApiMBean.class, this, properties );
		this.logger.fine( "A MBean was successfully registered for the messaging." );

	} catch( Exception e ) {
		this.logger.severe( "A MBean could not be registered for the messaging." );
		Utils.logException( this.logger, e );
	}
}

… or with a framework, like iPojo (here with a metadata.xml file).

<ipojo 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="org.apache.felix.ipojo http://felix.apache.org/ipojo/schemas/CURRENT/core.xsd"
		xmlns="org.apache.felix.ipojo">

	<!-- Servlets registration -->
	<component classname="net.roboconf.dm.rest.services.internal.ServletRegistrationComponent" name="roboconf-dm-rest-component" immediate="true" public="false">

		<!-- Mandatory -->
		<requires field="manager" optional="false" />
		<requires field="httpService" optional="false" />

		<!-- Optional: Maven resolver -->
		<requires field="mavenResolver" optional="true">
			<callback type="bind" method="mavenResolverAppears" />
			<callback type="unbind" method="mavenResolverDisappears" />
		</requires>

		<!-- Optional: scheduler -->
		<requires field="scheduler" optional="true">
			<callback type="bind" method="schedulerAppears" />
			<callback type="unbind" method="schedulerDisappears" />
		</requires>

		<callback transition="validate" method="starting" />
		<callback transition="invalidate" method="stopping" />

		<properties pid="net.roboconf.dm.rest.services.configuration">
			<property name="enable-cors" method="setEnableCors" value="false" />
			<property name="enable-authentication" method="setEnableAuthentication" value="false" />
			<property name="authentication-realm" method="setAuthenticationRealm" value="karaf" />
			<property name="session-period" method="setSessionPeriod" value="-1" />
		</properties>

		<!-- Register OSGi services -->
		<!-- The implementation class also registers additional services... -->
		<provides specifications="net.roboconf.dm.rest.services.jmx.RestServicesMBean">
			<property name="jmx.objectname" value="net.roboconf:type=web" type="java.lang.String" />
		</provides>
	</component>

	<instance component="roboconf-dm-rest-component" name="Roboconf - DM REST services" />
</ipojo>

My example may look a little bit complex.
The interesting part is the provides section, which declares the service to register. Notice the service property. You can obviously use any framework (Blueprint, Declarative Services), no matter how you register your service, provided you actually do register it.

Here are some pointers to real implementations.
The code approach is used here to create MBeans on the fly for objects that are not managed as OSGi services. The framework approach is perfect when you want to associate MBeans with predefined OSGi services.

As usual, the interface of your service must be exported outside your bundle. This is fundamental! You must also respect JMX conventions. All my service interfaces for JMX have their name that end with MBean (and it is case sensitive). My implentation classes can use whatever names they want. You will find additional information on Apache Aries’s web site.

Registering a servlet filter in Apache Karaf

I have already written several articles about deploying web applications in OSGi, and in particular in Apache Karaf. This blog post is a small addition to indicate how to deploy a filter in such an environment.

My first attempt was trying to register my filter directly in the HTTP service. Unfortunately, there is no method for this. After searching the web, I finally found an example here. Apache Karaf comes with Pax-Web. And Pax-Web has extender modules. Basically, all you have to do is registering your filter as an OSGi service. Pax-Web extender modules will be notified about this service (white board pattern) and automatically register it as a servlet filter in the web server (which is Jetty, by the way).

Personnaly, I use this approach to add authentication to REST services. They are implemented with Jersey 1.x and served by Karaf’s web server. The filter intercepts requests and verifies they have the identity token. Obviously, it aims at being used with HTTPS.

So, here are some code snippets.
Let’s start with the filter itself.

public class AuthenticationFilter implements Filter {

	private final Logger logger = Logger.getLogger( getClass().getName());

	private AuthenticationManager authenticationMngr;
	private boolean authenticationEnabled;
	private long sessionPeriod;


	@Override
	public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chain )
	throws IOException, ServletException {

		if( ! this.authenticationEnabled ) {
			chain.doFilter( req, resp );

		} else {
			HttpServletRequest request = (HttpServletRequest) req;
			HttpServletResponse response = (HttpServletResponse) resp;
			String requestedPath = request.getRequestURI();
			this.logger.info( "Path for auth: " + requestedPath );

			// Find the session ID in the cookies
			String sessionId = null;
			Cookie[] cookies = request.getCookies();
			if( cookies != null ) {
				for( Cookie cookie : cookies ) {
					if( UrlConstants.SESSION_ID.equals( cookie.getName())) {
						sessionId = cookie.getValue();
						break;
					}
				}
			}

			// Is there a valid session?
			boolean loggedIn = false;
			if( ! Utils.isEmptyOrWhitespaces( sessionId )) {
				loggedIn = this.authenticationMngr.isSessionValid( sessionId, this.sessionPeriod );
				this.logger.finest( "Session " + sessionId + (loggedIn ? " was successfully " : " failed to be ") + "validated." );
			} else {
				this.logger.finest( "No session ID was found in the cookie. Authentication cannot be performed." );
			}

			// Valid session, go on. Send an error otherwise.
			// No redirection, we mainly deal with our web socket and REST API.
			boolean loginRequest = requestedPath.endsWith( IAuthenticationResource.PATH + IAuthenticationResource.LOGIN_PATH );
			if( loggedIn || loginRequest ) {
				chain.doFilter( request, response );
			} else {
				response.sendError( 403, "Authentication is required." );
			}
		}
	}


	@Override
	public void destroy() {
		// nothing
	}


	@Override
	public void init( FilterConfig filterConfig ) throws ServletException {
		// nothing
	}

	public void setAuthenticationEnabled( boolean authenticationEnabled ) {
		this.authenticationEnabled = authenticationEnabled;
	}

	public void setAuthenticationManager( AuthenticationManager authenticationMngr ) {
		this.authenticationMngr = authenticationMngr;
	}

	public void setSessionPeriod( long sessionPeriod ) {
		this.sessionPeriod = sessionPeriod;
	}
}

And here is the section in my class that registers my filter.
It is the same method that registers my servlets in the HTTP service.

this.authenticationFilter = new AuthenticationFilter();
this.authenticationFilter.setAuthenticationEnabled( this.enableAuthentication );
this.authenticationFilter.setAuthenticationManager( this.authenticationMngr );
this.authenticationFilter.setSessionPeriod( this.sessionPeriod );

initParams = new Hashtable<> ();
initParams.put( "urlPatterns", "*" );

// Consider the bundle context can be null (e.g. when used outside of OSGi)
if( this.bundleContext != null )
	this.filterServiceRegistration = this.bundleContext.registerService( Filter.class, this.authenticationFilter, initParams );
else
	this.logger.warning( "No bundle context was available, the authentication filter was not registered." );

In Karaf 4.x, this is enough to register a servlet filter.
There are other alternatives, but this one requires less code. Read Achim’s answer here for more details.

Use Karaf’s JAAS implementation in your own Bundle

This article acts as both a help for people and as a reminder for myself (just in case).

So, I have a REST API based on Jersey 1.x that runs inside Apache Karaf. And I would like to add authentication for this REST API. The most important part is that the authentication should be delegated to Karaf. Indeed, Karaf provides an extensible and easy-to- use solution to authenticate with various backends: properties files, LDAP, databases, etc.

Personally, I was not an expert with JAAS (JAAS is the framework used by Karaf to manage users, roles, and in a more general way, authentication and authorizations). I spent a couple of hours to find how to easily plug with Karaf’s JAAS implementation. And believe or not, but the solution is very easy.

Here is a code snippet to put in your bundle, and that’s it!

public void authenticate( String user, String pwd, String realm ) throws LoginException {

	// If the authentication fails, a LoginException will be thrown
	LoginContext loginCtx = new LoginContext( realm, new RoboconfCallbackHandler( user, pwd ));
	loginCtx.login();
}

/**
* A callback handler for JAAS.
*/
static final class RoboconfCallbackHandler implements CallbackHandler {
	private final String username, password;

	/**
	 * Constructor.
	 * @param username
	 * @param password
	 */
	public RoboconfCallbackHandler( String username, String password ) {
		this.username = username;
		this.password = password;
	}

	@Override
	public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {

		for( Callback callback : callbacks ) {
			if (callback instanceof NameCallback )
				((NameCallback) callback).setName( this.username );
			else if( callback instanceof PasswordCallback )
				((PasswordCallback) callback).setPassword( this.password.toCharArray());
			else
				throw new UnsupportedCallbackException( callback );
		}
	}
}

By default, the configured REALM in Karaf is named “karaf”. It is based on properties files. So, you can test the code above with authenticate( “karaf”, “karaf”, “karaf” ). REALMs configuration is then documented on Karaf’s web site.

Executing Karaf Commands in PAX-Exam Tests

I recently had to write an integration test with PAX-Exam that required to install bundles in Karaf during the test execution. Usually, when you need a bundle to be installed with such tests, you add them in your initial PAX configuration. But this time, I had to install a bundle during the test, which means it could not be installed first.

The solution to that was to inject a Karaf (console) session in my test, and execute a command (bundle:install) from it.
Since I had to search for quite a moment, I thought it would be useful to make a post about it. Is is widely inspired from some integration tests from Karaf. At the moment, I have not found a better way to do it, but at least it is working.

Here is the snippet.

package whatever;

import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureSecurity;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.security.auth.Subject;

import org.apache.karaf.features.BootFinished;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.jaas.boot.principal.RolePrincipal;
import org.apache.karaf.shell.api.console.Session;
import org.apache.karaf.shell.api.console.SessionFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.ProbeBuilder;
import org.ops4j.pax.exam.TestProbeBuilder;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerMethod;

@RunWith( RoboconfPaxRunner.class )
@ExamReactorStrategy( PerMethod.class )
public class DelayedInstallationOfTargetHandlerTest extends DmTest {

	@Inject
	protected Manager manager;

	@Inject
	protected FeaturesService featuresService;

	@Inject
	protected SessionFactory sessionFactory;

	// Wait for all the boot features to be installed.
	@Inject
	protected BootFinished bootFinished;

	private Session session;
	private final ExecutorService executor = Executors.newCachedThreadPool();
	private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
	private final PrintStream printStream = new PrintStream( this.byteArrayOutputStream );
	private final PrintStream errStream = new PrintStream( this.byteArrayOutputStream );


	@Before
	public void setUp() throws Exception {
		this.session = this.sessionFactory.create( System.in, this.printStream, this.errStream );
	}


	@Override
	@Configuration
	public Option[] config() throws Exception {

		// Usual PAX configuration from a super class
		List<Option> options = new ArrayList<> ();
		options.addAll( Arrays.asList( super.config()));

		// Disable the JMX server. Not sure it is really useful...
		options.add( configureSecurity().disableKarafMBeanServerBuilder());

		return options.toArray( new Option[ options.size()]);
	}


	@Test
	public void run() throws Exception {

		// Do whatever you want before...

		// Prepare the execution
		this.byteArrayOutputStream.flush();
		this.byteArrayOutputStream.reset();

		// What we want to execute...
		final Callable<String> commandCallable = new Callable<String> () {
			@Override
			public String call() throws Exception {

				try {
					DelayedInstallationOfTargetHandlerTest.this.session.execute( "bundle:install mvn:/..." );

				} catch( Exception e ) {
					e.printStackTrace( System.err );
				}

				DelayedInstallationOfTargetHandlerTest.this.printStream.flush();
				DelayedInstallationOfTargetHandlerTest.this.errStream.flush();
				return DelayedInstallationOfTargetHandlerTest.this.byteArrayOutputStream.toString();
			}
		};

		// We will use "bundle:install", which requires some privileges.
		// So, we must enclose our invocation in a privileged action.
		String response;
		FutureTask<String> commandFuture = new FutureTask<String>( new Callable<String>() {
			@Override
			public String call() {

				// FIXME: isn't there a better way? "admin"???
				// The question was asked on Karaf's mailing-list.
				Subject subject = new Subject();
				subject.getPrincipals().addAll( Arrays.asList( new RolePrincipal( "admin" )));
				try {
					return Subject.doAs( subject, new PrivilegedExceptionAction<String> () {
						@Override
						public String run() throws Exception {
							return commandCallable.call();
						}
					});

				} catch( PrivilegedActionException e ) {
					e.printStackTrace( System.err );
				}

				return null;
			}
		});

		// Execute our privileged action.
		try {
			this.executor.submit( commandFuture );

			// Give up to 30 seconds for the command to complete...
			response = commandFuture.get( 30L, TimeUnit.SECONDS );

		} catch( Exception e ) {
			e.printStackTrace( System.err );
			response = "SHELL COMMAND TIMED OUT: ";
		}

		System.err.println( response );

		// Do whatever you want after...
	}
}

As you can see, the main issue is that some actions require special privileges.
It is the case of bundle:install. So, we have to wrap our execution so that it works. When you miss this part and simply use the session, you will only have access to the basic commands, those that generally are read-only (e.g. bundle:info).

You can find the original class in Roboconf’s main repository. I also created a Gist here.