Search code examples
javaauthenticationjmxjaasmbeans

Launching a JMX agent with a custom JAAS login module, setting login() to always return true


I'm building a custom JAAS module for a JMX instance. The file that is being run is the following:

MBean Interface

package com.this.mbean;

public interface ImplementationMBean {

    public void setName(String name);
    public String getName();

    public void setNumber(int number);
    public int getNumber();
    public boolean getKilled();
    public void setKilled(boolean killed);
}

Implementation Class

package com.test.mbean;

    public class Implementation implements ImplementationMBean {

        private String name ;
        private int number;
        private boolean killed = false;

        public Implementation(String name, int number) {
            this.name = name;
            this.number = number;
        }

        @Override
        public void setName(String name) {
            this.name = name;

        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public void setNumber(int number) {
            this.number = number;
        }

        @Override
        public int getNumber() {
            return number;
        }

        @Override
        public boolean getKilled() {
            return killed;
        }

        @Override
        public void setKilled(boolean killed) {
            this.killed = killed;

        }

    }

RunningImplementation Class

package com.test.running;

import java.lang.management.ManagementFactory;
import com.test.mbean.*;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

public class RunningImplementation {

    public static final String name = "defaultName";
    public static final int number = 100;

    public static void main(String[] args) 
            throws MalformedObjectNameException, InterruptedException, 
            InstanceAlreadyExistsException, MBeanRegistrationException, 
            NotCompliantMBeanException{

        //get MBean Server
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        //register MBean
        ImplementationMBean mBean = new Implementation(name, number);
        ObjectName name = new ObjectName("com.bard.mbean:type=ConcreteImplementation");

        mbs.registerMBean(mBean, name);

        do{
            Thread.sleep(1000);
            System.out.println("Name = " + mBean.getName() + ", number = " + mBean.getNumber());
        }while(mBean.getKilled() == false);

    }
}

This is packaged into a JAR file called MBeanSecure.jar.

I've enabled the jmx agent with:

-Dcom.sun.management.jmxremote

I've set it to run on localhost on port 1234:

-Dcom.sun.management.jmxremote.port=1234

I've configured the JMX agent to use a specified JAAS configuration entry:

-Dcom.sun.management.login.config=Sample

and specified the path to the Jaas configuration file:

-Djava.security.auth.login.config=sample_jaas.config

sample_jaas.config

Sample {
   sample.module.SampleLoginModule required debug=true;
   authIdentity=monitorRole;
};

monitor role is specified in jmxremote.access

-Dcom.sun.management.jmxremote.access.file=jmxremote.access

and looks like this:

monitorRole readonly
controleRole readwrite

my Loginmodule is simple in that is returns true whatever.

SampleLoginModule

package sample.module;

import java.util.*;
import java.io.IOException;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import sample.principal.SamplePrincipal;

public class SampleLoginModule implements LoginModule {

    // initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;

    // configurable option
    private boolean debug = false;

    // the authentication status
    private boolean succeeded = false;
    private boolean commitSucceeded = false;

    // username and password
    private String username;
    private char[] password;

    // testUser's SamplePrincipal
    private SamplePrincipal userPrincipal;

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

        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.sharedState = sharedState;
        this.options = options;

        // initialize any configured options
        debug = "true".equalsIgnoreCase((String)options.get("debug"));
    }

    public boolean login() throws LoginException {
        return true;
    }

    public boolean commit() throws LoginException {
        if (succeeded == false) {
            return false;
        } else {
            // add a Principal (authenticated identity)
            // to the Subject

            // assume the user we authenticated is the SamplePrincipal
            userPrincipal = new SamplePrincipal(username);
            if (!subject.getPrincipals().contains(userPrincipal))
                subject.getPrincipals().add(userPrincipal);

            if (debug) {
                System.out.println("\t\t[SampleLoginModule] " +
                    "added SamplePrincipal to Subject");
            }

            // in any case, clean out state
            username = null;
            for (int i = 0; i < password.length; i++)
            password[i] = ' ';
            password = null;

            commitSucceeded = true;
            return true;
        }
    }

    public boolean abort() throws LoginException {
        if (succeeded == false) {
            return false;
        } else if (succeeded == true && commitSucceeded == false) {
            // login succeeded but overall authentication failed
            succeeded = false;
            username = null;
            if (password != null) {
                for (int i = 0; i < password.length; i++)
                    password[i] = ' ';
                password = null;
                }
            userPrincipal = null;
        } else {
            // overall authentication succeeded and commit succeeded,
            // but someone else's commit failed
            logout();
        }
        return true;
    }

    public boolean logout() throws LoginException {

        subject.getPrincipals().remove(userPrincipal);
        succeeded = false;
        succeeded = commitSucceeded;
        username = null;
        if (password != null) {
            for (int i = 0; i < password.length; i++)
            password[i] = ' ';
            password = null;
        }
        userPrincipal = null;
        return true;
    }
}

with the principal class:

SamplePrincipal

package sample.principal;

import java.security.Principal;

public class SamplePrincipal implements Principal, java.io.Serializable {

    private String name;


    public SamplePrincipal(String name) {
    if (name == null)
        throw new NullPointerException("illegal null input");

    this.name = name;
    }


    public String getName() {
    return name;
    }


    public String toString() {
    return("SamplePrincipal:  " + name);
    }


    public boolean equals(Object o) {
    if (o == null)
        return false;

        if (this == o)
            return true;

        if (!(o instanceof SamplePrincipal))
            return false;
        SamplePrincipal that = (SamplePrincipal)o;

    if (this.getName().equals(that.getName()))
        return true;
    return false;
    }


    public int hashCode() {
    return name.hashCode();
    }
}

When I run the code using:

java -Dcom.sun.management.jmxremote.port=1234 -Dcom.sun.management.jmxremote.login.config=Sample -Dcom.java.security.auth.login.config=sample_jaas.config -Djava.security.policy==sampleazn.policy -Dcom.sun.management.jmxremote.access.file=jmxremote.access -jar MBeanSecure.jar

The code runs, regularly outputting

Name = defaultName, number = 100

I then try to access the JMX agent via JConsole

jconsole

and this will open the Jconsole window showing the process running.

However when I try to connect to it as a remote process, I get a Connection Failed error. This is difficult to debug as I can't see any log where this is failing. Am I right in thinking that by using

com.sun.management.jmxremote.login.config

I override the default loginbehaviour? In which case it should check my Jaas config, run the loginmodule, which always returns true, and allow the user under the monitorRole specified?

I believe the error lies in the configuration file, but it's difficult to confirm the settings or debug, given the scarce documentation.


Solution

  • Solved:

    I could debug the issues by running:

    jconsole -debug
    

    which indicated that my config file had a syntax error, and required:

    Sample {
       sample.module.SampleLoginModule required debug=true
       authIdentity=monitorRole;
    };
    

    in place of

    Sample {
       sample.module.SampleLoginModule required debug=true;
       authIdentity=monitorRole;
    };
    

    note the single semicolon