May 28, 2017

Roadmap Apache ActiveMQ, HornetQ and Apache ActiveMQ Artemis

HornetQ was the built in Message Broker in JBoss EAP 6.

Red Hat bought FuseSource in 2012, the company behind ActiveMQ commercial support, and rebranded the product JBoss A-MQ server. This left Red Hat with two competing Message Brokers.

In 2014 Red Hat and the HornetQ community decided to join efforts with the ActiveMQ community and the Apache ActiveMQ Artemis was created.

The current Red Hat JBoss A-MQ releases, at the time of 7.0.0, was still based on the original ActiveMQ project. It is expected a future release will be based on the Artemis project. This future release will feature the external appearance of the current A-MQ product (administration interfaces based on Fabric, protocol supported, and native client libraries) but with many of the internals from HornetQ (asynchronous I/O engine and high performance journaled disk storage).

The JBoss EAP 7 is shipped with the Apache ActiveMQ Artemis.

May 18, 2017

NamingException in RPM Installed Tomcat 7 in RHEL 7 and CentoIOS 7

If you are using datasource in rpm installed tomcat 7 on RHEL 7 and CentOS and do not specify factory, you will get a NamingException.

For details see https://bugzilla.redhat.com/show_bug.cgi?id=819087.

<Context>

    ...
    <Resource name="jdbc/ReviewDb" auth="Container" type="javax.sql.DataSource"
              factory="org.apache.commons.dbcp.BasicDataSourceFactory"
              driverClassName="org.postgresql.Driver"
              url="jdbc:postgresql://dbhost:5432/reviewdb"
              username="gerrit2" password="******" maxActive="20"
              maxIdle="10" maxWait="-1"/>
</Context>
javax.naming.NamingException: Could not create resource factory instance [Root exception is java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp.BasicDataSourc
eFactory]
        at org.apache.naming.factory.ResourceFactory.getObjectInstance(ResourceFactory.java:119)
        at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:321)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:842)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:153)
        at org.apache.naming.NamingContextBindingsEnumeration.nextElementInternal(NamingContextBindingsEnumeration.java:117)
        at org.apache.naming.NamingContextBindingsEnumeration.next(NamingContextBindingsEnumeration.java:71)
        at org.apache.naming.NamingContextBindingsEnumeration.next(NamingContextBindingsEnumeration.java:34)
        at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:138)
        at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:110)
        at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.lifecycleEvent(GlobalResourcesLifecycleListener.java:82)
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
        at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
        at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:402)
        at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:347)
        at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:724)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.startup.Catalina.start(Catalina.java:689)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:321)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:455)
Caused by: java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:264)
        at org.apache.naming.factory.ResourceFactory.getObjectInstance(ResourceFactory.java:115)
        ... 22 more

How to Generate Elliptic Curve Keys in Java 8

// Supported Providers in Java 8
for (Provider provider : Security.getProviders()) {
    System.out.println("Provider : " + provider);
}

// Supported Algorithm in Java: "DiffieHellman", "DSA", "RSA" and "EC"
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "SunEC");
// SecureRandom.getInstanceStrong() (Java 8) is the Recommended way to use initialize random,
// it chooses automatically the strongest algorithm for your OS
keyGen.initialize(256, SecureRandom.getInstanceStrong());
KeyPair kp = keyGen.genKeyPair();
PrivateKey privateKey = kp.getPrivate();
PublicKey publicKey = kp.getPublic();
System.out.println("PrivateKey : " + privateKey);
System.out.println("PublicKey : " + publicKey);
// the key in its primary encoding format
byte[] publicKeyInfo = publicKey.getEncoded();

How to RPM Install JBoss EAP 7 on RHEL 7

First you need to add JBoss EAP 7 subscription. To do that you need the pool id for your subscription. To find that out list all available subscription and then look for JBoss subscription.

subscription-manager list --available --all > /tmp/subscription.txt
less /tmp/subscription.txt
Subscription Name:   JBoss ....
Provides:            Red Hat OpenShift Enterprise JBoss EAP add-on Beta
                     Red Hat JBoss A-MQ Clients
                     Red Hat Single Sign-On
                     Red Hat OpenShift Enterprise JBoss EAP add-on
                     JBoss Enterprise Application Platform
                     Red Hat JBoss Core Services
                     Red Hat JBoss Data Grid
                     JBoss Enterprise Web Server
                     Red Hat JBoss A-MQ Interconnect
...
Pool ID:             XXXXXXXXXXXXXX
...

Now add that subscription with above pool id.

subscription-manager attach --pool=XXXXXXXXXXXXXX

Then you need to enable the JBoss EAP 7 repo. There are multiple repos. Here we wil run JBoss EAP 7 on RHEL 7 and for that there are two repos:

  • Current JBoss EAP 7 Repository (repo id: jb-eap-7-for-rhel-7-server-rpms)
  • Recommended. Always use the latest.

  • Minor JBoss EAP 7 Repository (repo id: jb-eap-7.[MINOR_VERSION]-for-rhel-7-server-rpms, where MINOR_VERSION is the wanted lock down version, i.e. 0)
  • Only use a specific minor version and never upgrade to next minor, e.g. 7.1, 7.2, etc.

So to use the latest version enable repo by

subscription-manager repos --enable=jb-eap-7-for-rhel-7-server-rpms

Finally verify that JBoss EAP 7 repo had been enabled with yum.

yum repolist
repo id                                                                                            repo name
jb-eap-7-for-rhel-7-server-rpms/7Server/x86_64                                                     JBoss Enterprise Application Platform 7 (RHEL 7 Server) (RPMs)
rhel-7-server-rpms/7Server/x86_64                                                                  Red Hat Enterprise Linux 7 Server (RPMs)

Now we install JBoss EAP 7 as a RPM group. To list all

yum grouplist
Available Environment Groups:
   Minimal Install
...
Available Groups:
...
   JBoss EAP 7
...

And to also list hidden groups.

yum grouplist hidden

Before you install JBoss EAP 7, you want to install java (JRE) in a controlled way, so it is not transative installed.

yum install java-1.8.0-openjdk

Then install JBoss EAP 7 RPM group

yum groupinstall "JBoss EAP 7"

Finally auto start jboss on reboot

systemctl enable eap7-standalone

And then start it

systemctl enable eap7-standalone

JBoss EAP 7 binds default to any network interface.

netstat -tulnp | grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      2210/java           

And if this is a remote machine, you need to open port 8080 in local firewall.

firewall-cmd --add-port 8080/tcp --zone public --permanent
firewall-cmd --reload
firewall-cmd --list-all-zones

JBOSS_HOME: /opt/rh/eap7/root/usr/share/wildfly/

A note about jboss home long path. This is due to docker and to be able to ship different software version on same machine, then you cannot install software in default directory, i.e. jboss default directory is /usr/share/wildfly/. To read more, please see Red Hat Software Collections (RHSCL).

ll /opt/rh/eap7/root/usr/share/wildfly/
total 40
drwxrwxr-x. 2 root root    27 17 maj 21.59 appclient
drwxr-xr-x. 3 root root  4096 17 maj 21.59 bin
drwxrwxr-x. 2 root root    52 17 maj 21.59 docs
drwxr-xr-x. 2 root root    76 17 maj 21.59 domain
-rw-rw-r--. 1 root root   419 21 feb 10.15 JBossEULA.txt
lrwxrwxrwx. 1 root root    50 17 maj 21.59 jboss-modules.jar -> /opt/rh/eap7/root/usr/share/java/jboss-modules.jar
-rw-rw-r--. 1 root root 26530 21 feb 10.15 LICENSE.txt
drwxrwxr-x. 3 root root    20 17 maj 21.59 modules
drwxr-xr-x. 2 root root    91 17 maj 21.59 standalone
-rw-rw-r--. 1 root root    65 21 feb 10.15 version.txt
drwxrwxr-x. 4 root root   158 17 maj 21.59 welcome-content
# ll /opt/rh/eap7/root/usr/share/wildfly/standalone/
total 0
lrwxrwxrwx. 1 root root 35 17 maj 21.59 configuration -> /etc/opt/rh/eap7/wildfly/standalone
lrwxrwxrwx. 1 root root 44 17 maj 21.59 data -> /var/opt/rh/eap7/lib/wildfly/standalone/data
lrwxrwxrwx. 1 root root 51 17 maj 21.59 deployments -> /var/opt/rh/eap7/lib/wildfly/standalone/deployments
lrwxrwxrwx. 1 root root 43 17 maj 21.59 lib -> /var/opt/rh/eap7/lib/wildfly/standalone/lib
lrwxrwxrwx. 1 root root 39 17 maj 21.59 log -> /var/opt/rh/eap7/log/wildfly/standalone
lrwxrwxrwx. 1 root root 45 17 maj 21.59 tmp -> /var/opt/rh/eap7/cache/wildfly/standalone/tmp

May 15, 2017

Configure Java KeyStore and TrustStore

KeyStore

javax.net.ssl.keyStore
javax.net.ssl.keyStorePassword
javax.net.ssl.keyStoreType Typical [JKS|PKCS12]
javax.net.ssl.keyStoreProvider Rarely used, example sun.security.mscapi.SunMSCAPI

TrustStore

javax.net.ssl.trustStore
javax.net.ssl.trustStorePassword
javax.net.ssl.trustStoreType Typical [JKS|PKCS12]
javax.net.ssl.trustStoreProvider Rarely used, example sun.security.mscapi.SunMSCAPI

Miscellaneous

javax.net.debug [ssl|debug|all]
jdk.tls.client.protocols E.g. TLSv1,TLSv1.1

And how to use it.

$ java -Djavax.net.ssl.keyStore=/path/to/file \
	-Djavax.net.ssl.keyStorePassword=changeit \
	-Djavax.net.ssl.keyStoreType=PKCS12 \
	-Djavax.net.ssl.trustStore=/path/to/file \
	-Djavax.net.ssl.trustStorePassword=changeit \
	-Djavax.net.ssl.trustStoreType=JKS


Reference: Java Secure Socket Extension (JSSE) Reference Guide Customizing JSSE

May 14, 2017

Tomcat Standard Security Realms

Tomcat comes out of the box with the following security realms, i.e. modules that does Authentication and Authorization.

Name CIS Tomcat 8 Benchmark Note *
JDBCRealm NOT for Production
DataSourceRealm  
JNDIRealm (LDAP)  
UserDatabaseRealm NOT for Large-Scale Installations
MemoryRealm NOT for Production
JAASRealm NOT widely used and therefore the code is not as mature as the other realms.

*) CIS_Apache_Tomcat_8_Benchmark_v1.0.1.pdf

This leaves us with only two production ready realms: DataSourceRealm and JNDIRealm (LDAP)

There are two other Realms (CombinedRealm and LockOutRealm), but they do not do authentication and authorization.

How to handle Configuration in Tomcat with Context

Background

In Tomcat there seems no way to handle war deployment with version number in file name.

"When autoDeploy or deployOnStartup operations are performed by a Host, the name and context path of the web application are derived from the name(s) of the file(s) that define(s) the web application. Consequently, the context path may not be defined in a META-INF/context.xml embedded in the application and there is a close relationship between the context name, context path, context version and the base file name (the name minus any .war or .xml extension) of the file." [https://tomcat.apache.org/tomcat-8.0-doc/config/context.html]

So in your maven pom, you need to set <build><finalName>${project.artifactId}</finalName>...</build>.

Context Configuration

The context.xml can be placed:

  • $CATALINA_BASE/conf/[enginename]/[hostname]/[your_war_file_name].xml, e.g. $CATALINA_BASE/Catalina/localhost/example-tomcat.xml. Only visible inside example-tomcat.war.
  • $CATALINA_BASE/conf/context.xml. Global visible.
  • [your_war_file]/META-INF/context.xml. Deployed in your war file. This is not suited for configurable values.

The only sensible alternative of the above is the first. This is also good for RPM, since each application have a separate configuration file and no file collision from different RPM.

A little note about the path $CATALINA_BASE/conf/[enginename]/[hostname]/. If you look at your $CATALINA_BASE/conf/server.xml file, you see where [enginename] and [hostname] come from.

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">

    ...
    <Service name="Catalina">

        ...
        <Engine name="Catalina" defaultHost="localhost">

            <Host name="localhost" appBase="webapps" unpackWARs="true"
                autoDeploy="true">

                ...
            </Host>
        </Engine>
    </Service>
</Server>

More Configuration in Context

You can also put general configuration in you Context.

 <Context>
      ...
     <Parameter name="companyName" value="My Company, Incorporated"
          override="false"/>
       ...
 </Context>

   This is equivalent to the inclusion of the following element in the web application deployment descriptor (/WEB-INF/web.xml):

 <context-param>
       <param-name>companyName</param-name>
       <param-value>My Company, Incorporated</param-value>
 </context-param>

And retrieve it by

ServletContext sc = getServletContext();  
String companyName = sc.getInitParameter("companyName"); 

Tomcat JavaBean Resources Factory

Introduction

This works for Tomcat 7, 8 and 9, but here I will use Tomcat 8.

Purpose

Tomcat have DI (Dependency Injection), much the same found in Spring and Java EE, which can be used for mocking or a loose coupling architecture.

POJO

First lets write our POJO class that we want to lookup inside our application.

package se.magnuskkarlsson.example.tomcat;

public class FooBean {

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

XML Configuration

Now we are ready to declare our POJO in either $APP_HOME/META-INF/context.xml (only available inside appl) or $TOMCAT_HOME/context.xml (available across all appl).

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/example-tomcat">

    ...
    <Resource name="bean/FooBean" auth="Container"
        type="se.magnuskkarlsson.example.tomcat.FooBean" factory="org.apache.naming.factory.BeanFactory"
        text="Magnus K Karlsson" />
</Context>

Application

And this how to retrieve it inside your application.

package se.magnuskkarlsson.example.tomcat;

import java.io.IOException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "JavaBeanResourcesServlet", urlPatterns = "/bean")
public class JavaBeanResourcesServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            Context ctx = new InitialContext();
            FooBean bean = (FooBean) ctx.lookup("java:/comp/env/bean/FooBean");
            resp.getWriter().println("Data from bean " + bean.getText());
        } catch (Exception e) {
            super.log("FAILED to read from bean. Cause " + e.getMessage());
            resp.getWriter().println("FAILED to read from bean. Cause " + e.getMessage());
        }
    }
}

How to use DataSource in Tomcat 8

Introduction

This works for Tomcat 7, 8 and 9, but here I will use Tomcat 8.

Installation

Copy wanted jdbc driver to $TOMCAT_HOME/lib.

$ cp ~/.m2/repository/com/h2database/h2/1.4.185/h2-1.4.185.jar ~/bin/apache-tomcat-8.5.15/lib/

$ cp ~/.m2/repository/mysql/mysql-connector-java/5.1.34/mysql-connector-java-5.1.34.jar ~/bin/apache-tomcat-8.5.15/lib/

DataSource Configuration

Add global datasource configuration in $TOMCAT_HOME/conf/server.xml. Here is a minimal server.xml with H2 database.

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">

    <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
    <Listener className="org.apache.catalina.core.AprLifecycleListener"
        SSLEngine="on" />
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

    <GlobalNamingResources>

        <Resource name="UserDatabase" auth="Container"
            type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved"
            factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
            pathname="conf/tomcat-users.xml" />

        <Resource name="ExampleDS" auth="Container" type="javax.sql.DataSource"
            driverClassName="org.h2.Driver" url="jdbc:h2:mem:example" username="sa"
            password="" maxTotal="20" maxIdle="10" maxWaitMillis="-1" />
    </GlobalNamingResources>

    <Service name="Catalina">

        <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
            redirectPort="8443" />

        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

        <Engine name="Catalina" defaultHost="localhost">

            <Host name="localhost" appBase="webapps" unpackWARs="true"
                autoDeploy="true">

                <Valve className="org.apache.catalina.valves.AccessLogValve"
                    directory="logs" prefix="localhost_access_log" suffix=".txt"
                    pattern="%h %l %u %t "%r" %s %b" />

            </Host>
        </Engine>
    </Service>
</Server>

And here is for MySQL

<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource" 
    driverClassName="com.mysql.jdbc.Driver"
    url="jdbc:mysql://localhost:3306/javatest"
    username="javauser" password="javadude" 
    maxTotal="20" maxIdle="10" maxWaitMillis="-1" />

For a complete list of configuration for Apache Commons DBCP, see http://commons.apache.org/proper/commons-dbcp/configuration.html.

Application

Now add a resource link in you application $APP_HOME/META-INF/context.xml.

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/example-tomcat">

    <!-- java:/comp/env/jdbc/ExampleDS -->
    <ResourceLink name="jdbc/ExampleDS" global="ExampleDS"
        type="javax.sql.DataSource" />
</Context>

And to access inside your tomcat app.

package se.magnuskkarlsson.example.tomcat;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

@WebServlet(name = "HelloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            resp.getWriter().println("Data from database " + getDBValue());
        } catch (Exception e) {
            super.log("FAILED to read from database. Cause " + e.getMessage());
            resp.getWriter().println("FAILED to read from database. Cause " + e.getMessage());
        }
    }

    protected int getDBValue() throws NamingException, SQLException {
        Connection conn = null;
        PreparedStatement prepstmt = null;
        ResultSet rs = null;
        try {
            Context ctx = new InitialContext();
            DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/ExampleDS");
            conn = ds.getConnection();
            prepstmt = conn.prepareStatement("SELECT 1");
            rs = prepstmt.executeQuery();
            rs.next();
            return rs.getInt(1);
        } finally {
            closeQuietly(rs);
            closeQuietly(prepstmt);
            closeQuietly(conn);
        }
    }

    protected void closeQuietly(AutoCloseable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception IGNORE) {
            }
        }
    }

}

May 9, 2017

Remote JMS Client with Security in JBoss EAP 6

Background

We want to write a remote JMS client for the following MDB. And we are using JBoss EAP 6 standalone-full.xml, i.e. we are using the built in HornetQ in JBoss.

package se.magnuskkarlsson.example.ejb;

import java.security.Principal;
import java.util.logging.Logger;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RunAs;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.inject.Inject;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.security.auth.Subject;
import javax.security.jacc.PolicyContext;

@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "jms/queue/javaEE6SecurityQueue") })
@RunAs("ROLE_ADMIN")
// @org.jboss.ejb3.annotation.RunAsPrincipal("admin") vs [WEB-INF|META-INF]/jboss-ejb3.xml
// @org.jboss.ejb3.annotation.SecurityDomain("java-ee6-security") vs [WEB-INF|META-INF]/jboss-ejb3.xml
public class EchoMDB implements MessageListener {

    private Logger log = Logger.getLogger(EchoMDB.class.getName());

    @Resource
    private MessageDrivenContext messageDrivenContext;

    @PermitAll
    @Override
    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                log.info("MESSAGE BEAN: Message received: " + ((TextMessage) message).getText());

                // will always return "anonymous" when placed inside the MDB
                Principal principal = messageDrivenContext.getCallerPrincipal();
                log.info("Principal : " + principal);

                // will always return "anonymous" when placed inside the MDB
                Subject caller = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
                log.info("Caller : " + caller);
            } else {
                log.warning("Message of wrong type: " + message.getClass().getName());
            }
        } catch (JMSException e) {
            e.printStackTrace();
            messageDrivenContext.setRollbackOnly();
        } catch (Throwable te) {
            te.printStackTrace();
        }
    }
}

Remote JMS Client

To this to work we need a separate maven module that holds the JMS client and have no maven dependency to javax:javaee-api or any other JMS dependency.

Then we need to add $JBOSS_HOME/bin/client/jboss-client.jar to our maven module. This jar file contains all the jboss remoting, jms api and hornetq jms implementation for connection factory and queue.

package se.magnuskkarlsson.example.mdb;

import java.util.Properties;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;

public class EchoMDBTest {

    public static void main(String[] args) throws Exception {
        InitialContext initialContext = null;
        Connection connection = null;
        try {
            Properties jndiProps = new Properties();
            jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
            jndiProps.put("java.naming.factory.url.pkgs", "org.jboss.ejb.client.naming");
            jndiProps.put(Context.PROVIDER_URL, "remote://127.0.0.1:4447");
            // credentials remoting
            jndiProps.put(Context.SECURITY_PRINCIPAL, "remote");
            jndiProps.put(Context.SECURITY_CREDENTIALS, "ch5nge!t");
            initialContext = new InitialContext(jndiProps);

            ConnectionFactory connectionFactory = (ConnectionFactory) initialContext
                    .lookup("jms/RemoteConnectionFactory");
            // credentials messaging
            connection = connectionFactory.createConnection("remote", "ch5nge!t");
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

            Queue queue = (Queue) initialContext.lookup("jms/queue/javaEE6SecurityQueue");
            MessageProducer messageProducer = session.createProducer(queue);
            connection.start();

            messageProducer.send(session.createTextMessage("Hello from " + EchoMDBTest.class.getName()));
            System.out.println("SUCCESSFULLY SENT");
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (initialContext != null) {
                try {
                    initialContext.close();
                } catch (Exception IGNORE) {
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception IGNORE) {
                }
            }
        }
    }
}

Setting up HornetQ Queue

<subsystem xmlns="urn:jboss:domain:messaging:1.4">
    <hornetq-server>
        ...
        <jms-connection-factories>
            ...
            <connection-factory name="RemoteConnectionFactory">
                <connectors>
                    <connector-ref connector-name="netty"/>
                </connectors>
                <entries>
                    <entry name="java:jboss/exported/jms/RemoteConnectionFactory"/>
                </entries>
            </connection-factory>
        </jms-connection-factories>
        ...
        <jms-destinations>
            ...
            <jms-queue name="javaEE6SecurityQueue">
                <entry name="java:jboss/exported/jms/queue/javaEE6SecurityQueue"/>
            </jms-queue>
        </jms-destinations>
    </hornetq-server>
</subsystem>

NOTE: All JMS instances that are to be remote accessible must be prefixed with 'java:jboss/exported/'. In your MDB or JMS client code that prefix is NOT used.

Remoting and HornetQ Security

All code are now in place and queue configuration done. Now we need to understand how the clients calls HornetQ.

First is a remote JNDI lookup done, which goes through jboss remoting.

<subsystem xmlns="urn:jboss:domain:remoting:1.2">
    <connector name="remoting-connector" socket-binding="remoting" security-realm="ApplicationRealm"/>
</subsystem>

The jboss remoting is using security-realm="ApplicationRealm".

<management>
    <security-realms>
        ...
        <security-realm name="ApplicationRealm">
            <authentication>
                <local default-user="$local" allowed-users="*" skip-group-loading="true"/>
                <properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
            </authentication>
            <authorization>
                <properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
            </authorization>
        </security-realm>
    </security-realms>
    ...
</management>

Which default uses users and roles from properties file. Those are created with add-user.sh.

$ ~/bin/jboss-eap-6.4.0/bin$ ./add-user.sh 

What type of user do you wish to add? 
 a) Management User (mgmt-users.properties) 
 b) Application User (application-users.properties)
(a): b

Enter the details of the new user to add.
Using realm 'ApplicationRealm' as discovered from the existing property files.
Username : remote
Password requirements are listed below. To modify these restrictions edit the add-user.properties configuration file.
 - The password must not be one of the following restricted values {root, admin, administrator}
 - The password must contain at least 8 characters, 1 alphabetic character(s), 1 digit(s), 1 non-alphanumeric symbol(s)
 - The password must be different from the username
Password : <ch5nge!t>
Re-enter Password : <ch5nge!t>
What groups do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]: guest
About to add user 'remote' for realm 'ApplicationRealm'
Is this correct yes/no? yes
Added user 'remote' to file '/home/magnus/bin/jboss-eap-6.4.0/standalone/configuration/application-users.properties'
Added user 'remote' to file '/home/magnus/bin/jboss-eap-6.4.0/domain/configuration/application-users.properties'
Added user 'remote' with groups guest to file '/home/magnus/bin/jboss-eap-6.4.0/standalone/configuration/application-roles.properties'
Added user 'remote' with groups guest to file '/home/magnus/bin/jboss-eap-6.4.0/domain/configuration/application-roles.properties'
Is this new user going to be used for one AS process to connect to another AS process? 
e.g. for a slave host controller connecting to the master or for a Remoting connection for server to server EJB calls.
yes/no? no

After the remote JNDI lookup is a JMS javax.jms.Connection made. HornetQ default security domain is not default visible in the standalone-full.xml, but can be read with jboss-cli.sh.

$ ~/bin/jboss-eap-6.4.0/bin$ ./jboss-cli.sh -c
[standalone@localhost:9999 /] /subsystem=messaging:read-resource(include-defaults=true, include-runtime=true, recursive=true)
{
    "outcome" => "success",
    "result" => {
        "hornetq-server" => {"default" => {
...
            "security-domain" => "other",
            "security-enabled" => true,
...

So HornetQ is using default security domain 'other', which is using the 'ApplicationRealm', that we already had setup.

<subsystem xmlns="urn:jboss:domain:security:1.2">
    <security-domains>
        <security-domain name="other" cache-type="default">
            <authentication>
                <login-module code="Remoting" flag="optional">
                    <module-option name="password-stacking" value="useFirstPass"/>
                </login-module>
                <login-module code="RealmDirect" flag="required">
                    <module-option name="password-stacking" value="useFirstPass"/>
                </login-module>
            </authentication>
        </security-domain>
        ...
    </security-domains>
</subsystem>

So now we are ready to run the client and the server logs looks like.

$ ./standalone.sh -c standalone-full.xml
...
00:21:25,744 INFO  [se.magnuskkarlsson.example.ejb.EchoMDB] (Thread-0 (HornetQ-client-global-threads-1965998011)) MESSAGE BEAN: Message received: Hello from se.magnuskkarlsson.example.mdb.EchoMDBTest
00:21:25,746 INFO  [se.magnuskkarlsson.example.ejb.EchoMDB] (Thread-0 (HornetQ-client-global-threads-1965998011)) Principal : anonymous
00:21:25,746 INFO  [se.magnuskkarlsson.example.ejb.EchoMDB] (Thread-0 (HornetQ-client-global-threads-1965998011)) Caller : Subject:
 Principal: anonymous

00:21:25,749 INFO  [se.magnuskkarlsson.example.ejb.HelloSLSB] (Thread-0 (HornetQ-client-global-threads-1965998011))  *** helloAdmin from EJB : [roles=[ROLE_ADMIN],principal=admin]
00:21:25,750 INFO  [se.magnuskkarlsson.example.ejb.EchoMDB] (Thread-0 (HornetQ-client-global-threads-1965998011)) Hello from HelloSLSB : helloAdmin from EJB : [roles=[ROLE_ADMIN],principal=admin], guest : false, admin : true

May 8, 2017

Java EE 6 Security

Background

A general introduction can be found at Oracle Java EE 6 Tutorial Security. Here follow a summary with code examples and some pitfalls for JBoss EAP 6.

Web

URL security is done WEB-INF/web.xml, as which authentication technique you want - BASIC, FORM, DIGEST or CLIENT-CERT. Only one factor authentication is supported in EE 6.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Secure Content</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ROLE_GUEST</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>BASIC</auth-method>
    </login-config>

    <security-role>
        <role-name>ROLE_GUEST</role-name>
    </security-role>
    <security-role>
        <role-name>ROLE_ADMIN</role-name>
    </security-role>
</web-app>

Choosing security domain is done in the container specific web deployment descriptor. For jboss it is in WEB-INF/jboss-web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-web_7_0.xsd" version="7.0">

    <context-root>/java-ee6-security</context-root>
    <security-domain>java-ee6-security</security-domain>
</jboss-web>

In JBoss EAP 6 you do not need the security domain prefix - java:/jaas/. Do not use since it will be consistent when using security domain in jboss-ejb3.xml, where jndi prefix is not supported. It is only kept in jboss-web.xml for backward compatibility.

JAX-RS (REST)

JAX-RS annotated classes is technically a servlet, so use URL security in your web.xml. You can add EJB annotation (@javax.ejb.Stateless or @javax.ejb.Stateful) to your JAX-RS class for monitoring reason and even add EJB security annotations, but I would recommend to separate logic into separate EJB class.

JAX-WS (Web Service)

The same as for JAX-RS goes for JAX-WS. A JAX-WS annotated class is a servlet, use standard web.xml security and keep things easy and easy to understand, by putting all logic into separate EJB.

EJB

All EJB security annotations are in the javax.annotation.security package. Defines all roles at the class level and method specific role restrictions to methods.

package se.magnuskkarlsson.example.ejb;

import java.util.logging.Logger;

import javax.annotation.Resource;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;

@Stateless
@DeclareRoles(value = { "ROLE_ADMIN", "ROLE_GUEST" })
// @org.jboss.ejb3.annotation.SecurityDomain("java-ee6-security") vs [WEB-INF|META-INF]/jboss-ejb3.xml
public class HelloSLSB {

    private Logger log = Logger.getLogger(HelloSLSB.class.getName());

    @Resource
    private SessionContext sessionContext;

    @RolesAllowed(value = { "ROLE_GUEST" })
    public String helloGuest() {
        log.info(" *** helloGuest from EJB : " + sessionContext.getCallerPrincipal());
        return "helloGuest from EJB : " + sessionContext.getCallerPrincipal() + ", guest : "
                + sessionContext.isCallerInRole("ROLE_GUEST") + ", admin : "
                + sessionContext.isCallerInRole("ROLE_ADMIN");
    }

    @RolesAllowed(value = { "ROLE_ADMIN" })
    public String helloAdmin() {
        log.info(" *** helloAdmin from EJB : " + sessionContext.getCallerPrincipal());
        return "helloAdmin from EJB : " + sessionContext.getCallerPrincipal() + ", guest : "
                + sessionContext.isCallerInRole("ROLE_GUEST") + ", admin : "
                + sessionContext.isCallerInRole("ROLE_ADMIN");
    }
}

In JBoss EAP 6 there are EJB security annotations, but I discourage you to use them, to keep your code so vendor neutral as possible. Use vendor deployment descriptor instead.

jboss-ejb3.xml

<?xml version="1.0" encoding="UTF-8"?>
<jboss:jboss xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="urn:security:1.1" version="3.1" impl-version="2.0">

    <assembly-descriptor>
        <s:security>
            <ejb-name>*</ejb-name>
            <s:security-domain>java-ee6-security</s:security-domain>
        </s:security>
        ...
    </assembly-descriptor>
</jboss:jboss>

MDB

MDB is a special case. Java EE 6 does not support security on MDB, you can add security to your connection factory and queues, but not MDB. What you can do is add @javax.annotation.security.RunAs("role_name"), to enable MDB call a secured EJB.

package se.magnuskkarlsson.example.ejb;

import java.security.Principal;
import java.util.logging.Logger;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RunAs;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.inject.Inject;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.security.auth.Subject;
import javax.security.jacc.PolicyContext;

@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "jms/queue/javaEE6SecurityQueue") })
@RunAs("ROLE_ADMIN")
// @org.jboss.ejb3.annotation.RunAsPrincipal("admin") vs [WEB-INF|META-INF]/jboss-ejb3.xml
// @org.jboss.ejb3.annotation.SecurityDomain("java-ee6-security") vs [WEB-INF|META-INF]/jboss-ejb3.xml
public class EchoMDB implements MessageListener {

    private Logger log = Logger.getLogger(EchoMDB.class.getName());

    @Resource
    private MessageDrivenContext messageDrivenContext;

    @Inject
    private HelloSLSB hello;

    @PermitAll
    @Override
    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                log.info("MESSAGE BEAN: Message received: " + ((TextMessage) message).getText());

                // will always return "anonymous" when placed inside the MDB
                Principal principal = messageDrivenContext.getCallerPrincipal();
                log.info("Principal : " + principal);

                // will always return "anonymous" when placed inside the MDB
                Subject caller = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
                log.info("Caller : " + caller);

                log.info("Hello from HelloSLSB : " + hello.helloAdmin());
            } else {
                log.warning("Message of wrong type: " + message.getClass().getName());
            }
        } catch (JMSException e) {
            e.printStackTrace();
            messageDrivenContext.setRollbackOnly();
        } catch (Throwable te) {
            te.printStackTrace();
        }
    }
}

And ones again we don't use vendor specific annotations and instead use external xml file configuration.

jboss-ejb3.xml

<?xml version="1.0" encoding="UTF-8"?>
<jboss:jboss xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="urn:security:1.1" version="3.1" impl-version="2.0">

    <assembly-descriptor>
        <s:security>
            <ejb-name>*</ejb-name>
            <s:security-domain>java-ee6-security</s:security-domain>
        </s:security>
        <s:security>
            <ejb-name>EchoMDB</ejb-name>
            <s:security-domain>java-ee6-security</s:security-domain>
            <s:run-as-principal>admin</s:run-as-principal>
        </s:security>
    </assembly-descriptor>
</jboss:jboss>