I amfacing one issue with mbean authentication. Issue is i need to always change my mbean jmx.access file to match with different users for authorization rule. Somehow i need to bypass this jmx.access file and authenticate using my custom JAAS login module only which call the rest api at backend.
Please suggest. Also to do this any other approach better than this is appreciated!
Here is my all code
public class SystemConfigManagement {
private static final int DEFAULT_NO_THREADS = 10;
private static final String DEFAULT_SCHEMA = "default";
private static String response = null;
public static void main(String[] args) throws MalformedObjectNameException, InterruptedException,
InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
// Get the MBean server
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// register the MBean
SystemConfig mBean = new SystemConfig(DEFAULT_NO_THREADS, DEFAULT_SCHEMA);
ObjectName name = new ObjectName("com.sigma.jmx:type=SystemConfig");
mbs.registerMBean(mBean, name);
do {
Thread.sleep(3000);
System.out.println("Thread Count=" + mBean.getThreadCount() + ":::Schema Name="
+ mBean.getSchemaName());
if (mBean.getSchemaName().equalsIgnoreCase("NewSchema")) {
System.out.println("Yes, you got right shcema name with token " + mBean.getToken());
response = RestClient.callPost("/validate-token", mBean.getToken(), "{}");
System.out.println("Toekn validation response " + response);
if (response.contains("\"valid\":true")) {
System.out.println("You are Logged In....");
} else {
System.out.println("Your Token is invalid, you cannot login...");
}
} else {
System.out.println("Schema name is invalid");
}
} while (mBean.getThreadCount() != 0);
}
}
JAAS login Module
package com.sigma.loginmodule;
import java.util.*;
import java.io.IOException;
import javax.management.remote.JMXPrincipal;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import com.sigma.loginmodule.SamplePrincipal;
public class SampleLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
// configurable option
private boolean debug = false;
private boolean succeeded = false;
private boolean commitSucceeded = false;
// username and password
private String username;
private char[] password;
private JMXPrincipal user;
// testUser's SamplePrincipal
private SamplePrincipal userPrincipal;
public SampleLoginModule() {
System.out.println("Login Module - constructor called");
}
public boolean abort() throws LoginException {
System.out.println("Login Module - abort called");
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;
// return false;
}
public boolean commit() throws LoginException {
System.out.println("Login Module - commit called");
subject.getPrincipals().add(user);
return succeeded;
}
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
System.out.println("Login Module - initialize called");
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
// System.out.println("testOption value: " + (String) options.get("testOption"));
debug = "true".equalsIgnoreCase((String) options.get("debug"));
succeeded = false;
}
public boolean login() throws LoginException {
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.");
}
// return true;
}
public boolean logout() throws LoginException {
System.out.println("Login Module - logout called");
return false;
}
}
JMX client code :
package client;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import com.sigma.SystemConfigMBean;
public class SystemConfigClient {
public static final String HOST = "localhost";
public static final String PORT = "8888";
public static void main(String[] args) throws IOException, MalformedObjectNameException {
JMXServiceURL url =
new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + HOST + ":" + PORT + "/jmxrmi");
//service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi
// for passing credentials for password
Map<String, String[]> env = new HashMap<>();
String[] credentials = { "sohanb", "welcome" };
env.put(JMXConnector.CREDENTIALS, credentials);
JMXConnector jmxConnector = JMXConnectorFactory.connect(url,env);
MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
//ObjectName should be same as your MBean name
ObjectName mbeanName = new ObjectName("com.sigma.jmx:type=SystemConfig");
//Get MBean proxy instance that will be used to make calls to registered MBean
SystemConfigMBean mbeanProxy =
(SystemConfigMBean) MBeanServerInvocationHandler.newProxyInstance(
mbeanServerConnection, mbeanName, SystemConfigMBean.class, true);
//let's make some calls to mbean through proxy and see the results.
System.out.println("Current SystemConfig::" + mbeanProxy.doConfig());
String autenticate = RestClient.authenticate("handong", "welcome", true);
System.out.println("Got autenticate Toekn id as " + autenticate);
mbeanProxy.setToken(autenticate);
mbeanProxy.setSchemaName("NewSchema");
mbeanProxy.setThreadCount(5);
System.out.println("New SystemConfig::" + mbeanProxy.doConfig());
//let's terminate the mbean by making thread count as 0
// mbeanProxy.setThreadCount(0);
//close the connection
jmxConnector.close();
}
}
Sample JAAS file:
Sample {
com.sigma.loginmodule.SampleLoginModule required debug=true ;
};
I can see only way to resolve this is to write your own custom JAAS autheticator which implements JMXAuthenticator .
Code snippet of my main authenticate method used for authentication. This method call invoke my login module passed in constructor of JAAS authenticator,
@SuppressWarnings("unchecked")
public final Subject authenticate(final Object credentials) throws SecurityException {
Map<String, Object> myCredentials = new HashMap<String, Object>();
if (credentials instanceof String[]) {
// JConsole sends the credentials as string array
// credentials[0] is the username
// credentials[1] is the password
String[] args = (String[]) credentials;
if (args.length == 2) {
myCredentials.put(USERNAME, args[0]);
char[] pw = null;
if (args[1] != null) {
pw = args[1].toCharArray();
}
myCredentials.put(PASSWORD, pw);
} else {
throw new SecurityException();
}
} else if (credentials instanceof Map) {
myCredentials.putAll((Map) credentials);
if (sslEnabled && myCredentials.containsKey(CERTIFICATE)) {
throw new SecurityException();
}
} else {
throw new SecurityException();
}
LoginContext lc = null;
try {
lc = new LoginContext(systemName, new CredentialCallbackHandler(systemName, myCredentials));
System.out.println("JAAS authenticator called ...");
} catch (LoginException le) {
le.printStackTrace();
}
try {
lc.login();
try {
Subject.doAsPrivileged(lc.getSubject(), new PrintCodeBaseAndPrincipalsAction(), null);
} catch (PrivilegedActionException ex) {
if (ex.getException() instanceof SecurityException) {
throw (SecurityException) ex.getException();
} else {
throw new SecurityException(ex.getException());
}
}
return lc.getSubject();
} catch (LoginException ex) {
throw new SecurityException(ex);
} catch (SecurityException ex) {
throw ex;
} catch (Throwable ex) {
throw new SecurityException(ex);
}
}
Here is how i invoke and set my JAAS authenticator constructor ,
Map<String, Object> env = new HashMap<String, Object>();
JAASJMXAuthenticator authenticator = new JAASJMXAuthenticator(jaasConfigName, false);
if (authenticator != null) {
System.out.println("JAASJMXAuthenticator is not null");
env.put(JMXConnectorServer.AUTHENTICATOR, authenticator);
}
Hope this helps someone in future. I can provide full code sample if asked.
Cheers!