Wednesday, December 17, 2008

JBoss and custom LoginModules for data sources

This is rather a specialized topic, but precious little has been said about it, so I figure it's worth a mention.

A data source in JBoss is typically either a JMS or JDBC connection pool, more or less. It represents a resource within JBoss that you can turn to to obtain the thing you want, use it, then throw it away (typically the connections are wrapped with code that catches the "close()" call and rather than closing the connection returns it to the available pool).

If the resource represents a database or remote JMS server, you're going to need to authenticate - typically by providing a username and password. In most enterprise situations, you don't want those passwords to be in plaintext in the configuration files. Rather, you have either some sort of configurable password encryption system or a password fetching system. In every shop I've worked in so far where this has been an issue, there's been a pre-existing mechanism that is set in concrete and must be used more or less without modification to obtain passwords to connect to enterprise resources.

Well, if you're configuring a data source within JBoss, you give that data source an <application-and-security-domain>, which is a <application-policy> node within server/___/conf/login-config.xml, which contains a <authentication> section which provides the username and password. The question is, how can you provide the bridge between JBoss and your established mechanism for providing username and password? Naively, the answer is that you must write your own custom JAAS login module.

Fortunately, there is a much easier way.

JBoss provides a ConfiguredIdentityLoginModule, which is simply a wrapper for a plaintext username and password. The simplest way to wire in your own password fetching code is to extend this module. You simply need to replace this method in the class:

public void initialize(Subject subject, CallbackHandler handler, Map sharedState, Map options)

The "options" map is a map of the list of module-options from login-config.xml. You simply call super.initialize() with all of the same arguments, but with a different options map. The new options map will have in it the "username" and plaintext "password" options that are required by the ConfiguredIdentityLoginModule.

For example, if you have a CryptoWidget that can decrypt passwords, you'd write something like this:


public void initialize(Subject subject, CallbackHandler handler, Map sharedState, Map options) {
Map newOptions = new HashMap(options);
String pw = (String) newOptions.get("password");
pw = CryptoWidget.decrypt(pw);
newOptions.put("password", pw);
newOptions = Collections.unmodifiableMap(newOptions);
super.initialize(subject, handler, sharedState, options);
}


And that, more or less, is the entire class. You can do anything you need to modify the options map, so long as the options map you pass in to super.initialize() is what the superclass expects to get. It might be a good idea to remove any options that you require that the superclass does not require. For instance, if a "salt" option was required to pass some additional argument into the CryptoWidget, you'd fetch it, then remove it from newOptions.

Incidentally, it is necessary to make a copy of the options map because it is an unmodifiable map (note that we make newOptions unmodifiable before calling super.initialize() also).

2 comments:

Anonymous said...

In such a scenario how do you manage the passphrase or other secret needed for your CryptoWidget?
Wouldn't that information have to be stored in the clear on the server? So then all secrets have been reduced to one, but the goal of eliminating all secrets from the server box hasn't been achieved, has it?

Nick said...

It depends on the organization. In most cases the development environment is considered insecure, so the crypto stuff is widely known. But in production environments, it is usually closely held.

One way to solve the problem is with LDAP. If a particular user/machine pair has permission, it can look up the password of a protected resource. The credentials for LDAP are closely held in production, and developers simply use their own credentials.