Implementing a Custom Preprocessor in HiveMQ Enterprise Security Extension (ESE)

Implementing a Custom Preprocessor in HiveMQ Enterprise Security Extension (ESE)

This article demonstrates how to implement a custom preprocessor using the HiveMQ Enterprise Security Extension (ESE) Customization SDK. The example shows how to examine the Common Name (CN) in a client certificate and override the authorization role key with a default value if the CN exceeds a certain length.

Prerequisites

  • HiveMQ Enterprise Edition with the Enterprise Security Extension enabled.

  • Ready Keystore and trustore certificates. (You can also follow our documentation to create self-signed certificates if not ready already)

  • Java development environment (JDK 21+).

  • Access to the HiveMQ ESE Customization SDK and valid Security extension license

  • Familiarity with MQTT, TLS, and X.509 certificates.

 Instructions

  1. Skip this step if you already configured HiveMQ config.xml for mTLS:

    1. Configure tls-tcp-listener listener for enabling mtls connection;

      <tls-tcp-listener> <port>8883</port> <name>tls-cert-required-listener</name> <bind-address>0.0.0.0</bind-address> <tls> <keystore> <path>conf/hivemq.jks</path> <password>changeme</password> <private-key-password>changeme</private-key-password> </keystore> <client-authentication-mode>REQUIRED</client-authentication-mode> <truststore> <path>conf/hivemq-trust-store.jks</path> <password>changeme</password> </truststore> </tls> </tls-tcp-listener>
  2. Create the Custom Preprocessor:

    1. Clone the reference customization SDK project:
      HiveMQ ESE Hello World Customization (GitHub)

    2. Create the EseVariableCommonPreprocessor class with the following logic:

      package com.hivemq.extensions.enterprise.security.customizations.helloworld; import com.hivemq.extensions.enterprise.security.api.preprocessor.*; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; public class EseVariableCommonPreprocessor implements CommonPreprocessor { private static final Logger log = LoggerFactory.getLogger(EseVariableCommonPreprocessor.class); @Override public void process( final @NotNull CommonPreprocessorProcessInput input, final @NotNull CommonPreprocessorProcessOutput output) { input.getEseVariablesInput().getAuthorizationRoleKey().ifPresent(authorizationRoleKey -> { log.info("EseVariableCommonPreprocessor Process started, authorizationRoleKey ::: " + authorizationRoleKey); if (authorizationRoleKey.getFirst().length() > 8) { log.info("CN length > 8, setting role key to 'generic-device'"); output.getEseVariablesOutput().setAuthorizationRoleKey(Collections.singletonList("generic-device")); } }); } }

      ⚠️ Note: This is a simplified example for demonstration and not production-ready.

    3. Run the code and build the Jar file, then copy the resulting JAR into:

      /opt/hivemq/extensions/hivemq-enterprise-security-extension/customizations/
  3. Update HiveMQ Enterprise Security Extension config.xml. Modify your ESE configuration to include the new preprocessor in the listener pipeline:

    <listener-pipeline name="tls-pipeline" listener="tls-cert-required-listener"> <allow-all-authentication-manager name="allowAllAuthn"/> <authorization-preprocessors> <x509-preprocessor> <x509-extractions> <x509-extraction> <x509-field>subject-common-name</x509-field> <ese-variable>authorization-role-key</ese-variable> </x509-extraction> </x509-extractions> </x509-preprocessor> <logging-preprocessor> <name>log-cert-pre</name> <level>info</level> <message>Using CN as Role Key='${authorization-role-key}'</message> </logging-preprocessor> <custom-preprocessor> <implementation>com.hivemq.extensions.enterprise.security.customizations.helloworld.EseVariableCommonPreprocessor</implementation> </custom-preprocessor> <logging-preprocessor> <name>log-cert-post</name> <level>info</level> <message>After custom preprocessor: Role Key='${authorization-role-key}'</message> </logging-preprocessor> </authorization-preprocessors> <file-authorization-manager name="fileAuthz"> <realm>rolesRealm</realm> <use-authorization-key>false</use-authorization-key> <use-authorization-role-key>true</use-authorization-role-key> </file-authorization-manager> </listener-pipeline>
  4. Restart the Extension and Test

    1. Restart the HiveMQ ESE extension (or the HiveMQ broker) to apply changes.

    2. Use a MQTT CLI with a certificate to test: Enter the private key password when prompted.

      mqtt sub -h localhost -p 8883 -t "test/one-/#" \ --cafile hivemq-server-cert.pem \ --key mqtt-client-key-3.pem \ --cert mqtt-client-cert-3.pem -d
    3. Expected Output in Logs

      If configured correctly, you will see logs like:

      INFO - HiveMQ Enterprise Security Extension: Started HiveMQ Enterprise Security Extension successfully in 134ms. INFO - Extension "HiveMQ Enterprise Security Extension" version 4.39.0 started successfully. INFO - CERT Pipeline (8883): Using CN='largercnname' as Role Key='largercnname' INFO - EseVariableCommonPreprocessor Process started authorizationRoleKey ::: [largercnname] INFO - CN length is > 8 hence reset authorizationRoleKey=generic-device INFO - CERT Pipeline (8883): Using CN='{{generic-device}}' as Role Key='{{generic-device}}'

      This confirms that the custom logic was applied and the role key was overridden based on CN length.

       

 Related articles