Showing posts with label JMX. Show all posts
Showing posts with label JMX. Show all posts

Tuesday, May 12, 2015

Securing Tomcat

Securing Tomcat


Tomcat is a great web server. Though for production there is room for improvement.
There are some basic things that need to be done to make your tomcat secure. Some of them are simple like changing the default shutdown port to a different one. Others are simple but can be easily overlooked, like removing the default demo application and removing the management interface. For a list see: http://www.tomcatexpert.com/blog/2011/11/02/best-practices-securing-apache-tomcat-7.

Tomcat Users

If you want to use the tomcat manager application but what to use users, tomcat supports defining roles and users in the tomcat-users.xml file (see https://tomcat.apache.org/tomcat-7.0-doc/manager-howto.html). An example of this file is:
<tomcat-users>
                <user password="test" roles="manager-gui,manager-status,manager-script,manager-jmx" username="tomcat"/>
                <role rolename="admin"/>
</tomcat-users>
For the most part this is good enough, but what if you do not want your password in clear text? Tomcat supports the option to use MD5 instead of the clear text. To do this in the server.xml file, in the Realm node, you need to add the following:
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" digest="md5" resourceName="UserDatabase"/>

JMX Login

Most applications need a JMX MBean for debugging and monitoring. The JVM supports user name and password in the following way.
To your application you add the following JVM args:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8007
-Dcom.sun.management.jmxremote.local.only=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.password.file={file_location}\jmxremote.password
-Dcom.sun.management.jmxremote.access.file={file_location}\jmxremote.access
You will need to change the ACL of the file for the jmxremote.password else the solution will not work (http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html).

The problem is that the file with the password is still open and not encrypted. To fix this we need to implement our own Login Module using JASS (have a look at https://blogs.oracle.com/lmalventosa/entry/jmx_authentication_authorization).
The implement our own login module we need to change the JVM args to:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8007
-Dcom.sun.management.jmxremote.local.only=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.ext.dirs={dir_location}
-Dcom.sun.management.jmxremote.login.config=AMFConfig
-Djava.security.auth.login.config={file_location}\AMF.config
-Dcom.sun.management.jmxremote.access.file={file_location}\jmxremote.access

For the login to work, the user still needs to be in the jmxremote.access file with the proper access level (readonly, readwrite).
We have added two new entries:
java.security.auth.login.config – this entirety will tell the JVM to load the JASS configuration file, for example:
TestConfig {
   plugin.com.tomcat.login.MyLoginModule Requisite passwordFile="\user\abc\jmxremote.access";
};
This will tell the JVM to use the class LoginModule for the JASS login. Since the configuration file can have more than one definition, you need to specific which module to use with: com.sun.management.jmxremote.login.config.
What they don’t write is that you need to create a separate jar that includes your login module (plugin.com.tomcat.login.LoginModule). Once you have this jar you need to put it in the java external folder. Adding it to the classpath is not enough. No we usually don’t have access to the java external folder, so since java 6 you can add another path for the external dir: java.ext.dirs. This will load all default external jars and your login.
An example code for the login module is:
public class MyLoginModule implements LoginModule {

       private Subject subject;
       private CallbackHandler callbackHandler;
       private Map<String, ?> options;
       private JMXPrincipal user;

       private boolean succeeded = false;

       public MyLoginModule() {
       }


       @Override
       public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
              this.subject = subject;
              this.callbackHandler = callbackHandler;
              this.options = options;
              succeeded = false;
       }

       //function for map
       private Map<String,String> getUserPassword(String passwordFile){
              Map<String,String> map = new HashMap<String, String>();
              File file = new File(passwordFile);
              if (file.exists()) {
                     try (BufferedReader buffer = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
                           while (true) {
                                  String line = buffer.readLine();
                                  if (line == null)
                                         break;
                                  String[] fields = line.split("\\s+");
                                  String fileUser = fields[0];
                                  String filePassword = fields[1];
                                  map.put(fileUser, filePassword);
                           }
                     } catch (IOException e) {
                           return map;
                     }
              }
              else{
                     System.out.println("password file not found: " + passwordFile);
              }
              return map;
       }
      
       public boolean validateUser(String passwordFile, String userName, String password) {
              String md5 = EncryptionHelper.md5(password);
              Map<String, String> userPasswords = getUserPassword(passwordFile);
              String filePassword = userPasswords.get(userName);
              if (md5.equals(filePassword)) {
                     return true;
              }
              return false;
       }

       @Override
       public boolean login() throws LoginException {
              String passwordFile = (String) options.get("passwordFile");

              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());
              user = new JMXPrincipal(name);

              if (validateUser(passwordFile, name, password)) {
                     succeeded = true;
                     return succeeded;
              } else {
                     succeeded = false;
                     throw new FailedLoginException("Wrong Login! Incorrect User or Password");
              }
       }

       @Override
       public boolean commit() throws LoginException {
              subject.getPrincipals().add(user);

              return succeeded;
       }

       @Override
       public boolean logout() throws LoginException {
              subject.getPrincipals().remove(user);
              return false;
       }

       @Override
       public boolean abort() throws LoginException {
              return false;
       }
      
}

Debugging

To debug the login you have a few options. The regular attach process will work nicely and you can debug your login. In addition on the server side you can add the following system parameter: -Djava.security.debug=all.
Also on the client side you can run the jconsole from the command line with the following parameter:
Jconsole –debug. This will open a debug window and print the errors from the server side.



Wednesday, June 4, 2014

JMX, MXBeans – random thoughts and ideas

JMX, MXBeans – random thoughts and ideas


JMX has been around for a while. A very common use for JMX is to add it as a debug interface to a web application (https://rterp.wordpress.com/tag/mxbean/). The truth is that JMX is much more than that.

The core of JMX is MBean’s that are created in your application and can then be manipulated via the JMX framework. The concept of JMX reminds me of the DCOM of Microsoft architecture and CORBA (see https://jcp.org/en/jsr/detail?id=70 for a bridge between the two). In essence you create an MBean on the server (in the MBeanServer), and you can then access this information from any other machine.

The objects in JMX are organized in an ObjectName, which is a tree hierarchy of names. This concept is actually very similar to the SNMP MIB structure.

JMX supports attributes, operations and notifications. I have seen companies build full infrastructures of application monitoring on top of JMX (including synchronizing information between machines via JMX). Since you have attributes you can save state within the MBean. With the operations you can have method actions externalized, and with the notification you can have external client register for events without the hassle of creating a message bus or structure for notification (of course spring support of all JMX functionality: http://docs.spring.io/spring-integration/docs/2.0.0.RC1/reference/html/jmx.html).

Since the concept of the JMX is very open, multiple adapters have been created around the JMX ecosystem.  Java comes with the JConsole application, so you can easily manage your MBeans even remotely. There are HTML adapters, and even SNMP adapters. So if you have a center of data that you need to share with multiple clients in multiple formats you should have a look at JMX:




If you need export you data from JMX to other platforms (like GraphIte  or Gangila) there is a nice package called JMXTrans that comes with some build in  writers and a framework to create new ones (

If all you need is to pass the information over http protocol that you can have a look at jolokia (http://www.jolokia.org/).  

Since is seems for some there is confusion between the two frameworks, both authors answered in the following question on the stackoverflow site: http://stackoverflow.com/questions/10151536/what-is-the-difference-between-jolokia-jmxtrans-when-to-choose-one-over-the


MXBean

Any class can be added to the JMX MBeanServer. If all the external actions (attributes, operations, notifications) use basic java types then you have no problem. You can use a self-written class, though the reading client must have this class in its classpath – a big constraint. To solve this issue JMX added the MXBean (http://docs.oracle.com/javase/7/docs/api/javax/management/MXBean.html) to support “open” types of java objects.

Oracle: An MXBean is a type of MBean that references only a predefined set of data types.
In other words by constraining the data types of you bean, JMX can now translate you “private classes” to a generic format using CompositeData and TabularData classes.

To use an MXBean you either implement an interface with MXBean suffix or use the @MXBean annotation. The other constraint of MXBean is that you must add the annotation @ConstructorProperties to your constructors.

When you use a concrete class in your code the MBeanServer will serialize the class into a generic class of the types:  CompositeData (http://docs.oracle.com/javase/7/docs/api/javax/management/openmbean/CompositeData.html), TabularData http://docs.oracle.com/javase/7/docs/api/javax/management/openmbean/TabularData.html.

These classes are similar to a hashmap, though they include information as to the type of each field. This way and client that supports the JMX MXBean can serialize this object and use it. If your client has your propriety class then you can easily serialize the MXBean back to your class.

The MBeanServer will validate the field types of all MXBean’s to make sure you did not use invalid types. Since you can externalize complex classes, the MXBeanServer will serialize it to one of the classes in the javax.management.openmbean package.

For most cases you do not need to add any special code, since one the MXBean is registered in the MBeanServer any access to this server via a client, the server will property serialize the class. The only case that you need to write code is the case of notification and using the user data object.

Notifications

To send a broadcast notification you create the Notification Object (http://docs.oracle.com/javase/tutorial/jmx/notifs/) The notification object has some basic field like type, source, message and other fields. If you want to add your own typed payload you need to use the User Data object. For complex classes you should be using the CompositeData or the TabularData. 

To create this class you need to specify all the fields and their types, for example:
String[] names = {"poolName", "usage", "count" };
Object[] values = { null, null, null };
OpenType[] types = { SimpleType.STRING, memoryUsageCompositeType,
            SimpleType.LONG };
CompositeType compositeType = getCompositeType(names, types);
CompositeData data = new CompositeDataSupport(compositeType, names, values);

This is obviously not very type safe or developer oriented. The class we would like to use would be like:
public class PoolUsage {
       String poolName;
       MemoryUsage usage;
       Integer count;
}

To solve this issue we will use a trick with the MBeanServer. As stated above if your class is an attribute or used in an operation the MBeanServer will do all the transformations for you. The problem is in the User Data scenario where you need to manually add the class. The trick is to have a helper class that inherits from StandardMBean. This class will have an internal field with your typed class. The trick is then when you want to serialize your class, you create the help class, assign the field with the value and the get the same field but via the getAttribute of the StandardMBean. This way the JMX framework will do the serialization for you. The other direction is the same. You create the helper class and assign the field using the JMX setAttribute (setting the composite data you got in the notification) and the retrieve the specific class using the standard getter.

Sample code:

public class PoolUsageSerializer extends StandardMBean implements PoolUsageInterface{
       private final static String ATTRIBUTE_NAME = "PoolUsage";
       protected PoolUsage poolUsage;

       public PoolUsage getPoolUsage() { return poolUsage;    }

       public void setPoolUsage(PoolUsage poolUsage) { this.poolUsage = poolUsage; }

       public PoolUsageSerializer() { super(PoolUsageInterface.class, true); }


       public static Object serialize(PoolUsage poolUsage) {
              PoolUsageSerializer me = new PoolUsageSerializer();
              me.setPoolUsage(data);
              return me.getAttribute(ATTRIBUTE_NAME);
       }

       public static PoolUsage deserialize(Object userData) {
              PoolUsageSerializer me = new PoolUsageSerializer();
              Attribute attr = new Attribute(ATTRIBUTE_NAME, userData);
              me.setAttribute(attr);
              return me.getPoolUsage();
       }
}

For more information see:

Security

JMX also supports the option to secure the server with user name and password. You can also assign roles (read/write) per user, and even secure communications via ssl. For securing your JMX on tomcat see http://tomcat.apache.org/tomcat-7.0-doc/monitoring.html.