Starting a JMX agent with a custom JAAS login module with which login () always returns true
I am creating a custom JAAS module for a JMX instance. The file that is being executed 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 in a JAR file named MBeanSecure.jar.
I have enabled jmx agent with
-Dcom.sun.management.jmxremote
I set it up to run on localhost on port 1234:
-Dcom.sun.management.jmxremote.port=1234
I configured the JMX agent to use the specified JAAS configuration entry:
-Dcom.sun.management.login.config=Sample
and specified the path to the Jaas config 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 it returns true.
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 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 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 main 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
Code is executed that regularly outputs
Name = defaultName, number = 100
Then I try to access the JMX agent through JConsole
jconsole
and this will open a Jconsole window showing the process.
However, when I try to connect to it as a remote process, I get a "Connection Error" error. This is difficult to debug as I don't see any logs where this is happening. Am I correct in thinking that using
com.sun.management.jmxremote.login.config
Am I overriding the default loginbehaviour username? In this case, it should check my Jaas config, run the login module, which always returns true, and allow the user under the control of the Role monitor?
I believe the error is in the config file, but it's hard to confirm settings or debugs given the scanty documentation.
source to share
Solved:
I could debug the problems by running:
jconsole -debug
which pointed out that my config file has a syntax error and is required:
Sample {
sample.module.SampleLoginModule required debug=true
authIdentity=monitorRole;
};
instead
Sample {
sample.module.SampleLoginModule required debug=true;
authIdentity=monitorRole;
};
notice one semicolon
source to share
I would like to fix a bug in this code. Changing the login method only:
System.out.println("Login Module - login called");
if (callbackHandler == null) {
throw new LoginException("Oops, callbackHandler is null");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("name:");
callbacks[1] = new PasswordCallback("password:", false);
try {
callbackHandler.handle(callbacks);
} catch (IOException e) {
throw new LoginException("Oops, IOException calling handle on callbackHandler");
} catch (UnsupportedCallbackException e) {
throw new LoginException("Oops, UnsupportedCallbackException calling handle on callbackHandler");
}
NameCallback nameCallback = (NameCallback) callbacks[0];
PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];
String name = nameCallback.getName();
String password = new String(passwordCallback.getPassword());
if ("sohanb".equals(name) && "welcome".equals(password)) {
System.out.println("Success! You get to log in!");
user = new JMXPrincipal(name);
succeeded = true;
return succeeded;
} else {
System.out.println("Failure! You don't get to log in");
succeeded = false;
throw new FailedLoginException("Sorry! No login for you.");
}
Added: user = new JMXPrincipal(name);
Also to comment out all lines of code in the commit () nd function just add:
subject.getPrincipals().add(user);
source to share