Communardo Software GmbH, Kleiststraße 10 a, D-01129 Dresden
0800 1 255 255

ActiveMQ. Location based authorization.

There are cir­cum­s­tan­ces when decisi­ons about authentication/authorization of a mes­sage queue cli­ent should be made based on its IP address. It might be done with dif­fe­rent goals, e.g. only cli­ents from the local host or the same sub-network can be gran­ted with admi­nis­tra­tor rights, location-based rights manage­ment etc.
This post has a goal to demons­trate an approach for buil­ding a sys­tem, allowing include secu­rity mecha­nisms, that makes decision about authentication/authorization based on the loca­tion of a cli­ent. It is based, namely on JAAS and ActiveMQ bro­ker plugins mechanism.

JAAS over­view.

As soon as a sys­tem desi­gner has deci­ded to secure own app­li­ca­tion from unaut­ho­ri­zed access, there ari­ses the issue which approach among the variety of exis­ting should be app­lied in this con­crete environment.
However, fur­ther deve­lo­p­ment of the soft­ware sys­tem might cause chan­ges in the envi­ron­ment, which was pre­sent at the design stage, and as a con­se­quence a need to review the authentication/authorization decisi­ons. This is of course an unplea­sant acti­vity and the­re­fore should be avoided.
The JAAS is inten­ded to avoid the pro­blem, descri­bed above. The idea is to hide the nice­ties of the func­tio­n­ing of a con­crete authentication/authorization sys­tem behind the well-defined uni­fied inter­face. In the figure below the princi­pal struc­ture of the app­li­ca­tion, that uses JAAS, is depic­ted. It beco­mes obvious, that in order to move from one secu­rity sys­tem to ano­t­her, only the part behind JAAS should be adap­ted, whe­reas Application stays unchanged.

ActiveMQ inter­cep­tors (bro­ker plugins)

ActiveMQ pro­vi­des a power­ful mecha­nism of plugins (working accord­ing to inter­cep­tors’ principle). The idea is that each event, occur­ring in bro­ker (new cli­ent is con­nec­ted, new con­nec­tor is crea­ted, new mes­sage is recei­ved etc.) is at first pro­ces­sed by a chain of plugins, and only after­wards is deli­ve­red to the MQ broker.
Solution
org.apache.activemq.broker.Broker is an inter­face, repre­sen­ting one link in the chain of inter­cep­tors. Each Broker instance should con­tain refe­rence to the next one, so that it could for­ward exe­cu­tion pro­cess fur­ther through the inter­cep­tors’ chain.
Method “addConnection(ConnectionContext con­text, ConnectionInfo info” of the Broker is invo­ked each time when new con­nec­tion is being established.
There exists an imple­men­ta­tion of the Breoker inter­face, which is dedi­ca­ted to pro­cess JAAS based authen­ti­ca­tion – org.apache.activemq.security.JaasAuthenticationBroker. It ope­ra­tes howe­ver only with login and pass­word of the user, who is going to con­nect. But let us con­si­der its addConnection method, to get an idea how JAAS and ActiveMQ worlds can be integrated.

public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
    if (context.getSecurityContext() == null) {
        // Set the TCCL since it seems JAAS needs it to find the login module classes.
        ClassLoader original = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader());
        try {
        // Do the login.
            try {
                JassCredentialCallbackHandler callback = new JassCredentialCallbackHandler(info .getUserName(), info.getPassword());
                LoginContext lc = new LoginContext(jassConfiguration, callback);
                lc.login();
                Subject subject = lc.getSubject();
                SecurityContext s = new JaasSecurityContext(info.getUserName(), subject);
                context.setSecurityContext(s);
                securityContexts.add(s);
           } catch (Exception e) {
               throw (SecurityException)new SecurityException("User name or password is invalid.").initCause(e);
           }
       } finally {
           Thread.currentThread().setContextClassLoader(original);
       }
    }
    super.addConnection(context, info);
 }

The code is per­forming actual run­time inte­gra­tion of the JAAS. This code is quite stan­dard and might be read in more details in dif­fe­rent JAAS tuto­ri­als. What is inte­res­ting in our con­text is high­ligh­ted as bold.
Here is necessary to under­stand the idea of JAAS call­back hand­lers. As it was already dis­cus­sed, JAAS pro­vi­des uni­fied access to the dif­fe­rent sources of authen­ti­ca­tion infor­ma­tion. And the actual access is per­for­med namely by appro­priate LoginModule. However, LoginModule is not only respon­si­ble for extrac­ting authen­ti­ca­tion data, but also for its com­pa­ring and deci­ding, whe­ther the cur­rent user is authen­ti­ca­ted or not. It means that apart from extrac­tion data from the sto­rage, the data from the user side should be pro­vi­ded as well.
To make the pro­cess of pro­vi­ding user data more gene­ral and inde­pen­dent from con­crete envi­ron­ment, the con­cept of call­backs was intro­du­ced. During the log­ging in, the LoginModule asks pro­vi­ded call­back hand­ler to deli­ver infor­ma­tion, that is rele­vant for this LoginModule (e.g. if login module com­pa­res login and pass­word it asks for them to be pro­vi­ded, if it is cer­ti­fi­ca­ted based it asks for cer­ti­fi­cate infor­ma­tion etc.) CallbackHandler in turn is inten­ded to receive this infor­ma­tion (e.g. from the human user via UI, extract it from request etc.) and pro­vide it back to the login module.
To make our task more con­crete let’s con­si­der the fol­lowing pro­blem: it’s nee­ded to authen­ti­cate user accord­ing to the fol­lowing rule: if user is being con­nec­ted from the local host it is authen­ti­ca­ted without auto­ma­ti­cally, other­wise login and pass­word should be provided.
In the case of ActiveMQ JAAS imple­men­ta­tion, call back hand­ler is pro­vi­ded with the login and pass­word, which is extrac­ted from the con­nec­tion context.
To solve decla­red task it is necessary to pro­vide except pass­word and login the remote address of the cli­ent. It leads to the fol­lowing solution:

@Override
public void addConnection(ConnectionContext context, ConnectionInfo info)throws Exception {
    …
    JaasRemoteAddressCallbackHandler callback = new JaasRemoteAddressCallbackHandler (
        info.getUserName(), info.getPassword(), context.getConnection().getRemoteAddress());
    …
    super.addConnection(context, info);
}

Now LoginModule can ask for the remote address of the cli­ent and make appro­priate decisions:

@Override
public boolean login() throws LoginException {
    LOG.debug("login...");
    Callback[] callbacks = new Callback[3];
    callbacks[0] = new NameCallback("Username: ");
    callbacks[1] = new PasswordCallback("Password: ", false);
    callbacks[2] = new RemoteAddressCallBack();
    try {
        callbackHandler.handle(callbacks);
    } catch (IOException e) {
        throw new LoginException(e.getMessage());
    } catch (UnsupportedCallbackException e) {
        throw new LoginException(e.getMessage());
    }
    String lUserName = ((NameCallback) callbacks[0]).getName();
    if (lUserName == null
        && isAddressLocal(((RemoteAddressCallBack) callbacks[2]).getRemoteAddress())) {
        LOG.debug("local login succeed");
        this.userName = LOCAL_USER;
        return true;
    }
    char[] typedPass = ((PasswordCallback) callbacks[1]).getPassword();
    char[] correctPass = getMQUser(lUserName).getPassword();
    if (Arrays.equals(correctPass, typedPass)) {
        LOG.debug("login succeed");
        this.userName = lUserName;
        return true;
    } else {
        LOG.debug("login failed");
        throw new LoginException("Password does not match");
    }
}

Implementation of the JaasRemoteAddressCallbackHandler’s handle method is now quite trivial:

@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
    for (Callback callback : callbacks) {
        if (callback instanceof RemoteAddressCallBack) {
            ((RemoteAddressCallBack) callback).setRemoteAddress(remoteAddress);
        } else {
            credentialsCallBackHandler.handle(new Callback[] { callback });
        }
    }
}

Other aspects

There might be still ques­ti­ons, how should MQ pro­vi­der be exp­lai­ned to use imple­men­ted BrokerFilter ins­tead of the stan­dard one, pro­vi­ded with JaasAuthenticationPlugin, and how should JAAS reco­gnize, that exactly this login module should be used.
These two ques­ti­ons are sol­ved during the pro­cess of plugins instal­la­tion. In the con­text of ActiveMQ this pro­cess is imple­men­ted through BrokerPlugin inter­face, which con­tains the only method “Broker installPlugin(Broker bro­ker)”. The input para­me­ter is the bro­ker, that is next in the chain, and which should receive all the invo­ca­tion, after they were pro­ces­sed by the cur­rent Broker. The return value is an instance of the cur­rent plugin.
This is also a good place where JAAS con­text might be initia­li­zed; so that it knows which authen­ti­ca­tion policy should be app­lied. In the con­text of the task, being sol­ved, the stan­dard mecha­nism with login.config file is used. There exist a num­ber of tuto­ri­als describ­ing its struc­ture. Furthermore, org.apache.activemq.security.JaasAuthenticationPlugin con­tains a method, which imple­ments all necessary pro­ce­du­res. All what is necessary to do is just to exp­lain, where login.config file should be found. It is sup­po­sed to be done through the appro­priate sys­tem pro­perty: System.setProperty("java.security.auth.login.config", loginConfURL);
Taking into account facts, men­tio­ned above, the imple­men­ta­tion of the plugin class is as following:

public class RemoteAddressJaasAuthenticationPlugin extends JaasAuthenticationPlugin {

    @Override
    public Broker installPlugin(Broker broker) {
        initialiseJaas();
        return new RemoteAddressJaasAuthenticationBroker(broker, configuration);
    }
}

Conclusion

In this post, the pro­blem of loca­tion based authentication/authorization using JAAS in the con­text of ActiveMQ was con­si­de­red. The solu­tion con­sists in inter­cep­t­ing inco­m­ing con­nec­tion with the plugins mecha­nism pro­vi­ded by ActiveMQ. Extracted infor­ma­tion is pro­vi­ded to a login module, accord­ing to the rules requi­red by JAAS and che­cked against pro­vi­ded policy.
It would be a legi­ti­mate ques­tion, why do all these pro­blems with JAAS are nee­ded at all, if the descri­bed effect could have been achie­ved just with acceptance/ignoring inco­m­ing con­nec­tions directly in the inter­cep­tor. The advan­tage con­sists in the fle­xi­bi­lity to com­bine other LoginModules via stan­dard mecha­nisms, pro­vi­ded with JAAS.

1. August 2012

Pin It on Pinterest