ソースを参照

cef format fixes and refactoring

jrivard@gmail.com 6 年 前
コミット
8e5cf44b94

+ 36 - 0
server/src/main/java/password/pwm/svc/event/AuditFormatter.java

@@ -0,0 +1,36 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.svc.event;
+
+import password.pwm.PwmApplication;
+import password.pwm.error.PwmUnrecoverableException;
+
+public interface AuditFormatter
+{
+    String convertAuditRecordToMessage(
+            PwmApplication pwmApplication,
+            AuditRecord auditRecord
+    )
+            throws PwmUnrecoverableException;
+
+}

+ 197 - 0
server/src/main/java/password/pwm/svc/event/CEFAuditFormatter.java

@@ -0,0 +1,197 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.svc.event;
+
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
+import password.pwm.config.Configuration;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.macro.MacroMachine;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class CEFAuditFormatter implements AuditFormatter
+{
+    private static final String CEF_EXTENSION_SEPARATOR = "|";
+    private static final Map<String, String> CEF_VALUE_ESCAPES;
+
+    static
+    {
+        final Map<String, String> map = new LinkedHashMap<>( );
+        map.put( "\\", "\\\\" );
+        map.put( "=", "\\=" );
+        map.put( "|", "\\|" );
+        map.put( "\n", "\\n" );
+        CEF_VALUE_ESCAPES = Collections.unmodifiableMap( map );
+    }
+
+    enum CEFAuditField
+    {
+        cat( AuditField.type ),
+        act( AuditField.eventCode ),
+        rt( AuditField.timestamp ),
+        msg( AuditField.message ),
+        reason( AuditField.narrative ),
+        suid( AuditField.perpetratorID ),
+        suser( AuditField.perpetratorDN ),
+        src( AuditField.sourceAddress ),
+        srchost( AuditField.sourceHost ),
+        duid( AuditField.targetID ),
+        duser( AuditField.targetDN ),
+        dvchost( null ),
+        dtz( null ),;
+
+        private final AuditField auditField;
+
+        CEFAuditField( final AuditField auditField )
+        {
+            this.auditField = auditField;
+        }
+
+        public AuditField getAuditField()
+        {
+            return auditField;
+        }
+    }
+
+    @Override
+    public String convertAuditRecordToMessage(
+            final PwmApplication pwmApplication,
+            final AuditRecord auditRecord
+    )
+            throws PwmUnrecoverableException
+    {
+        final Configuration configuration = pwmApplication.getConfig();
+        final String cefTimezone = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_TIMEZONE );
+
+        final Map<String, Object> auditRecordMap = JsonUtil.deserializeMap( JsonUtil.serialize( auditRecord ) );
+
+        final String headerSeverity = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_HEADER_SEVERITY );
+        final String headerProduct = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_HEADER_PRODUCT );
+        final String headerVendor = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_HEADER_VENDOR );
+        final Optional<String> srcHost = JavaHelper.deriveLocalServerHostname( configuration );
+
+        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific( pwmApplication, SessionLabel.SYSTEM_LABEL );
+
+        final String cefFieldName = LocaleHelper.getLocalizedMessage(
+                PwmConstants.DEFAULT_LOCALE,
+                auditRecord.getEventCode().getMessage(),
+                configuration
+        );
+
+        final StringBuilder cefOutput = new StringBuilder(  );
+
+        // cef header
+        {
+            // cef declaration:version prefix
+            cefOutput.append( "CEF:0" );
+
+            // Device Vendor
+            cefOutput.append( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR );
+            cefOutput.append( macroMachine.expandMacros( headerVendor ) );
+
+            // Device Product
+            cefOutput.append( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR );
+            cefOutput.append( macroMachine.expandMacros( headerProduct ) );
+
+            // Device Version
+            cefOutput.append( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR );
+            cefOutput.append( PwmConstants.SERVLET_VERSION );
+
+            // Device Event Class ID
+            cefOutput.append( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR );
+            cefOutput.append( auditRecord.getEventCode() );
+
+            // field name
+            cefOutput.append( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR );
+            cefOutput.append( cefFieldName );
+
+            // severity
+            cefOutput.append( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR );
+            cefOutput.append( macroMachine.expandMacros( headerSeverity ) );
+        }
+
+        cefOutput.append( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR );
+
+        srcHost.ifPresent( s -> appendCefValue( CEFAuditField.dvchost.name(), s, cefOutput ) );
+
+        if ( StringUtil.isEmpty( cefTimezone ) )
+        {
+            appendCefValue( CEFAuditField.dtz.name(), cefTimezone, cefOutput );
+        }
+
+        for ( final CEFAuditField cefAuditField : CEFAuditField.values() )
+        {
+            if ( cefAuditField.getAuditField() != null )
+            {
+                final String auditFieldName = cefAuditField.getAuditField().name();
+                final Object value = auditRecordMap.get( auditFieldName );
+                if ( value != null )
+                {
+                    final String valueString = value.toString();
+                    appendCefValue( auditFieldName, valueString, cefOutput );
+                }
+            }
+        }
+
+        final int cefLength = CEFAuditFormatter.CEF_EXTENSION_SEPARATOR.length();
+        if ( cefOutput.substring( cefOutput.length() - cefLength ).equals( CEFAuditFormatter.CEF_EXTENSION_SEPARATOR ) )
+        {
+            cefOutput.replace( cefOutput.length() - cefLength, cefOutput.length(), "" );
+        }
+
+        return cefOutput.toString();
+    }
+
+    private static void appendCefValue( final String name, final String value, final StringBuilder cefOutput )
+    {
+        if ( !StringUtil.isEmpty( value ) && !StringUtil.isEmpty( name ) )
+        {
+            cefOutput.append( " " );
+            cefOutput.append( name );
+            cefOutput.append( "=" );
+            cefOutput.append( escapeCEFValue( value ) );
+        }
+    }
+
+    private static String escapeCEFValue( final String value )
+    {
+        String replacedValue = value;
+        for ( final Map.Entry<String, String> entry : CEFAuditFormatter.CEF_VALUE_ESCAPES.entrySet() )
+        {
+            final String pattern = entry.getKey();
+            final String replacement = entry.getValue();
+            replacedValue = replacedValue.replace( pattern, replacement );
+        }
+        return replacedValue;
+    }
+}

+ 0 - 69
server/src/main/java/password/pwm/svc/event/CEFExtension.java

@@ -1,69 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2018 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-package password.pwm.svc.event;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public enum CEFExtension
-{
-    cat( AuditField.type ),
-    act( AuditField.eventCode ),
-    rt( AuditField.timestamp ),
-    msg( AuditField.message ),
-    reason ( AuditField.narrative ),
-    suid( AuditField.perpetratorID ),
-    suser( AuditField.perpetratorDN ),
-    src ( AuditField.sourceAddress ),
-    srchost( AuditField.sourceHost ),
-    duid( AuditField.targetID ),
-    duser( AuditField.targetDN ),
-    dvchost( null ),
-    dtz( null ),;
-
-    private final AuditField auditField;
-
-    public static final String CEF_EXTENSION_SEPARATOR = "|";
-
-    public static final Map<String, String> CEF_VALUE_ESCAPES;
-
-    static
-    {
-        final Map<String, String> map = new LinkedHashMap<>( );
-        map.put( "\\", "\\\\" );
-        map.put( "=", "\\=" );
-        map.put( "|", "\"" );
-        CEF_VALUE_ESCAPES = Collections.unmodifiableMap( map );
-    }
-
-    CEFExtension( final AuditField auditField )
-    {
-        this.auditField = auditField;
-    }
-
-    public AuditField getAuditField()
-    {
-        return auditField;
-    }
-}

+ 95 - 0
server/src/main/java/password/pwm/svc/event/JsonAuditFormatter.java

@@ -0,0 +1,95 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.svc.event;
+
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.config.Configuration;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JsonUtil;
+
+public class JsonAuditFormatter implements AuditFormatter
+{
+    @Override
+    public String convertAuditRecordToMessage(
+            final PwmApplication pwmApplication,
+            final AuditRecord auditRecord
+    )
+            throws PwmUnrecoverableException
+    {
+        final Configuration configuration = pwmApplication.getConfig();
+        final int maxLength = Integer.parseInt( configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_MAX_MESSAGE_LENGTH ) );
+        String jsonValue = "";
+        final StringBuilder message = new StringBuilder();
+        message.append( PwmConstants.PWM_APP_NAME );
+        message.append( " " );
+
+        jsonValue = JsonUtil.serialize( auditRecord );
+
+        if ( message.length() + jsonValue.length() <= maxLength )
+        {
+            message.append( jsonValue );
+        }
+        else
+        {
+            final AuditRecord inputRecord = JsonUtil.cloneUsingJson( auditRecord, auditRecord.getClass() );
+            inputRecord.message = inputRecord.message == null ? "" : inputRecord.message;
+            inputRecord.narrative = inputRecord.narrative == null ? "" : inputRecord.narrative;
+
+            final String truncateMessage = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_TRUNCATE_MESSAGE );
+            final AuditRecord copiedRecord = JsonUtil.cloneUsingJson( auditRecord, auditRecord.getClass() );
+            copiedRecord.message = "";
+            copiedRecord.narrative = "";
+            final int shortenedMessageLength = message.length()
+                    + JsonUtil.serialize( copiedRecord ).length()
+                    + truncateMessage.length();
+            final int maxMessageAndNarrativeLength = maxLength - ( shortenedMessageLength + ( truncateMessage.length() * 2 ) );
+            int maxMessageLength = inputRecord.getMessage().length();
+            int maxNarrativeLength = inputRecord.getNarrative().length();
+
+            {
+                int top = maxMessageAndNarrativeLength;
+                while ( maxMessageLength + maxNarrativeLength > maxMessageAndNarrativeLength )
+                {
+                    top--;
+                    maxMessageLength = Math.min( maxMessageLength, top );
+                    maxNarrativeLength = Math.min( maxNarrativeLength, top );
+                }
+            }
+
+            copiedRecord.message = inputRecord.getMessage().length() > maxMessageLength
+                    ? inputRecord.message.substring( 0, maxMessageLength ) + truncateMessage
+                    : inputRecord.message;
+
+            copiedRecord.narrative = inputRecord.getNarrative().length() > maxNarrativeLength
+                    ? inputRecord.narrative.substring( 0, maxNarrativeLength ) + truncateMessage
+                    : inputRecord.narrative;
+
+            message.append( JsonUtil.serialize( copiedRecord ) );
+        }
+
+        return message.toString();
+    }
+
+}

+ 23 - 220
server/src/main/java/password/pwm/svc/event/SyslogAuditService.java

@@ -40,7 +40,6 @@ import org.graylog2.syslog4j.impl.net.udp.UDPNetSyslogConfig;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.SyslogOutputFormat;
@@ -53,32 +52,26 @@ import password.pwm.health.HealthStatus;
 import password.pwm.health.HealthTopic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.localdb.LocalDBStoredQueue;
 import password.pwm.util.localdb.WorkQueueProcessor;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.secure.X509Utils;
 
 import javax.net.SocketFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.X509TrustManager;
 import java.io.Serializable;
-import java.net.URI;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
 
 
 public class SyslogAuditService
@@ -98,17 +91,15 @@ public class SyslogAuditService
 
     private final Configuration configuration;
     private final PwmApplication pwmApplication;
-    private final SyslogOutputFormat syslogOutputFormat;
-    private final String cefTimezone;
+    private final AuditFormatter auditFormatter;
 
-    public SyslogAuditService( final PwmApplication pwmApplication )
+
+    SyslogAuditService( final PwmApplication pwmApplication )
             throws LocalDBException
     {
-        syslogOutputFormat = pwmApplication.getConfig().readSettingAsEnum( PwmSetting.AUDIT_SYSLOG_OUTPUT_FORMAT, SyslogOutputFormat.class );
         this.pwmApplication = pwmApplication;
         this.configuration = pwmApplication.getConfig();
         this.certificates = configuration.readSettingAsCertificate( PwmSetting.AUDIT_SYSLOG_CERTIFICATES );
-        this.cefTimezone = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_TIMEZONE );
 
         final List<String> syslogConfigStringArray = configuration.readSettingAsStringArray( PwmSetting.AUDIT_SYSLOG_SERVERS );
         try
@@ -126,6 +117,24 @@ public class SyslogAuditService
             LOGGER.error( "error parsing syslog configuration for  syslogConfigStrings ERROR: " + e.getMessage() );
         }
 
+        {
+            final SyslogOutputFormat syslogOutputFormat = pwmApplication.getConfig().readSettingAsEnum( PwmSetting.AUDIT_SYSLOG_OUTPUT_FORMAT, SyslogOutputFormat.class );
+            switch ( syslogOutputFormat )
+            {
+                case JSON:
+                    auditFormatter = new JsonAuditFormatter();
+                    break;
+
+                case CEF:
+                    auditFormatter = new CEFAuditFormatter();
+                    break;
+
+                default:
+                    JavaHelper.unhandledSwitchStatement( syslogOutputFormat );
+                    throw new IllegalStateException();
+            }
+        }
+
         final WorkQueueProcessor.Settings settings = WorkQueueProcessor.Settings.builder()
                 .maxEvents( Integer.parseInt( configuration.readAppProperty( AppProperty.QUEUE_SYSLOG_MAX_COUNT ) ) )
                 .retryDiscardAge( TimeDuration.of( Long.parseLong( configuration.readAppProperty( AppProperty.QUEUE_SYSLOG_MAX_AGE_MS ) ), TimeDuration.Unit.MILLISECONDS ) )
@@ -204,23 +213,9 @@ public class SyslogAuditService
     {
 
         final String syslogMsg;
-
         try
         {
-            switch ( syslogOutputFormat )
-            {
-                case JSON:
-                    syslogMsg = convertAuditRecordToSyslogMessage( event, configuration );
-                    break;
-
-                case CEF:
-                    syslogMsg = convertAuditRecordToCEFMessage( event, configuration );
-                    break;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement( syslogOutputFormat );
-                    throw new IllegalStateException();
-            }
+            syslogMsg = auditFormatter.convertAuditRecordToMessage( pwmApplication, event );
         }
         catch ( PwmUnrecoverableException e )
         {
@@ -230,6 +225,7 @@ public class SyslogAuditService
         }
 
 
+
         try
         {
             workQueueProcessor.submit( syslogMsg );
@@ -294,175 +290,6 @@ public class SyslogAuditService
         syslogInstance = null;
     }
 
-
-    private static String convertAuditRecordToSyslogMessage(
-            final AuditRecord auditRecord,
-            final Configuration configuration
-    )
-    {
-        final int maxLength = Integer.parseInt( configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_MAX_MESSAGE_LENGTH ) );
-        String jsonValue = "";
-        final StringBuilder message = new StringBuilder();
-        message.append( PwmConstants.PWM_APP_NAME );
-        message.append( " " );
-
-        jsonValue = JsonUtil.serialize( auditRecord );
-
-        if ( message.length() + jsonValue.length() <= maxLength )
-        {
-            message.append( jsonValue );
-        }
-        else
-        {
-            final AuditRecord inputRecord = JsonUtil.cloneUsingJson( auditRecord, auditRecord.getClass() );
-            inputRecord.message = inputRecord.message == null ? "" : inputRecord.message;
-            inputRecord.narrative = inputRecord.narrative == null ? "" : inputRecord.narrative;
-
-            final String truncateMessage = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_TRUNCATE_MESSAGE );
-            final AuditRecord copiedRecord = JsonUtil.cloneUsingJson( auditRecord, auditRecord.getClass() );
-            copiedRecord.message = "";
-            copiedRecord.narrative = "";
-            final int shortenedMessageLength = message.length()
-                    + JsonUtil.serialize( copiedRecord ).length()
-                    + truncateMessage.length();
-            final int maxMessageAndNarrativeLength = maxLength - ( shortenedMessageLength + ( truncateMessage.length() * 2 ) );
-            int maxMessageLength = inputRecord.getMessage().length();
-            int maxNarrativeLength = inputRecord.getNarrative().length();
-
-            {
-                int top = maxMessageAndNarrativeLength;
-                while ( maxMessageLength + maxNarrativeLength > maxMessageAndNarrativeLength )
-                {
-                    top--;
-                    maxMessageLength = Math.min( maxMessageLength, top );
-                    maxNarrativeLength = Math.min( maxNarrativeLength, top );
-                }
-            }
-
-            copiedRecord.message = inputRecord.getMessage().length() > maxMessageLength
-                    ? inputRecord.message.substring( 0, maxMessageLength ) + truncateMessage
-                    : inputRecord.message;
-
-            copiedRecord.narrative = inputRecord.getNarrative().length() > maxNarrativeLength
-                    ? inputRecord.narrative.substring( 0, maxNarrativeLength ) + truncateMessage
-                    : inputRecord.narrative;
-
-            message.append( JsonUtil.serialize( copiedRecord ) );
-        }
-
-        return message.toString();
-    }
-
-
-
-    private String convertAuditRecordToCEFMessage(
-            final AuditRecord auditRecord,
-            final Configuration configuration
-    )
-            throws PwmUnrecoverableException
-    {
-        final Map<String, Object> auditRecordMap = JsonUtil.deserializeMap( JsonUtil.serialize( auditRecord ) );
-
-        final String headerSeverity = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_HEADER_SEVERITY );
-        final String headerProduct = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_HEADER_PRODUCT );
-        final String headerVendor = configuration.readAppProperty( AppProperty.AUDIT_SYSLOG_CEF_HEADER_VENDOR );
-        final Optional<String> srcHost = deriveLocalServerHostname( configuration );
-
-        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific( pwmApplication, SessionLabel.SYSTEM_LABEL );
-
-        final String cefFieldName = LocaleHelper.getLocalizedMessage(
-                PwmConstants.DEFAULT_LOCALE,
-                auditRecord.getEventCode().getMessage(),
-                configuration
-        );
-
-        final StringBuilder cefOutput = new StringBuilder(  );
-
-        // cef header
-        {
-            // cef declaration:version prefix
-            cefOutput.append( "CEF:0" );
-
-            // Device Vendor
-            cefOutput.append( CEFExtension.CEF_EXTENSION_SEPARATOR );
-            cefOutput.append( macroMachine.expandMacros( headerVendor ) );
-
-            // Device Product
-            cefOutput.append( CEFExtension.CEF_EXTENSION_SEPARATOR );
-            cefOutput.append( macroMachine.expandMacros( headerProduct ) );
-
-            // Device Version
-            cefOutput.append( CEFExtension.CEF_EXTENSION_SEPARATOR );
-            cefOutput.append( PwmConstants.SERVLET_VERSION );
-
-            // Device Event Class ID
-            cefOutput.append( CEFExtension.CEF_EXTENSION_SEPARATOR );
-            cefOutput.append( auditRecord.getEventCode() );
-
-            // field name
-            cefOutput.append( CEFExtension.CEF_EXTENSION_SEPARATOR );
-            cefOutput.append( cefFieldName );
-
-            // severity
-            cefOutput.append( CEFExtension.CEF_EXTENSION_SEPARATOR );
-            cefOutput.append( macroMachine.expandMacros( headerSeverity ) );
-        }
-
-        cefOutput.append( CEFExtension.CEF_EXTENSION_SEPARATOR );
-
-        srcHost.ifPresent( s -> appendCefValue( CEFExtension.dvchost.name(), s, cefOutput ) );
-
-        if ( StringUtil.isEmpty( cefTimezone ) )
-        {
-            appendCefValue( CEFExtension.dtz.name(), cefTimezone, cefOutput );
-        }
-
-        for ( final CEFExtension cefExtension : CEFExtension.values() )
-        {
-            if ( cefExtension.getAuditField() != null )
-            {
-                final String auditFieldName = cefExtension.getAuditField().name();
-                final Object value = auditRecordMap.get( auditFieldName );
-                if ( value != null )
-                {
-                    final String valueString = value.toString();
-                    appendCefValue( auditFieldName, valueString, cefOutput );
-                }
-            }
-        }
-
-        final int cefLength = CEFExtension.CEF_EXTENSION_SEPARATOR.length();
-        if ( cefOutput.substring( cefOutput.length() - cefLength ).equals( CEFExtension.CEF_EXTENSION_SEPARATOR ) )
-        {
-            cefOutput.replace( cefOutput.length() - cefLength, cefOutput.length(), "" );
-        }
-
-        return cefOutput.toString();
-    }
-
-    private static void appendCefValue( final String name, final String value, final StringBuilder cefOutput )
-    {
-        if ( !StringUtil.isEmpty( value ) && !StringUtil.isEmpty( name ) )
-        {
-            cefOutput.append( " " );
-            cefOutput.append( name );
-            cefOutput.append( "=" );
-            cefOutput.append( escapeCEFValue( value ) );
-        }
-    }
-
-    private static String escapeCEFValue( final String value )
-    {
-        String replacedValue = value;
-        for ( final Map.Entry<String, String> entry : CEFExtension.CEF_VALUE_ESCAPES.entrySet() )
-        {
-            final String pattern = entry.getKey();
-            final String replacement = entry.getValue();
-            replacedValue = replacedValue.replace( pattern, replacement );
-        }
-        return replacedValue;
-    }
-
     @Getter
     @AllArgsConstructor( access = AccessLevel.PRIVATE )
     public static class SyslogConfig implements Serializable
@@ -567,28 +394,4 @@ public class SyslogAuditService
             return newClass;
         }
     }
-
-    private static Optional<String> deriveLocalServerHostname( final Configuration configuration )
-    {
-        if ( configuration != null )
-        {
-            final String siteUrl = configuration.readSettingAsString( PwmSetting.PWM_SITE_URL );
-            if ( !StringUtil.isEmpty( siteUrl ) )
-            {
-                try
-                {
-                    final URI parsedUri = URI.create( siteUrl );
-                    {
-                        final String uriHost = parsedUri.getHost();
-                        return Optional.ofNullable( uriHost );
-                    }
-                }
-                catch ( IllegalArgumentException e )
-                {
-                    LOGGER.trace( () -> " error parsing siteURL hostname: " + e.getMessage() );
-                }
-            }
-        }
-        return Optional.empty();
-    }
 }

+ 3 - 0
server/src/main/java/password/pwm/svc/event/UserAuditRecord.java

@@ -22,6 +22,8 @@
 
 package password.pwm.svc.event;
 
+import lombok.Builder;
+
 import java.io.Serializable;
 import java.time.Instant;
 
@@ -37,6 +39,7 @@ public class UserAuditRecord extends AuditRecord implements Serializable
     protected String sourceHost;
 
     @SuppressWarnings( "checkstyle:ParameterNumber" )
+    @Builder
     protected UserAuditRecord(
             final Instant timestamp,
             final AuditEvent eventCode,

+ 27 - 0
server/src/main/java/password/pwm/util/java/JavaHelper.java

@@ -28,6 +28,8 @@ import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.io.IOUtils;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
 import password.pwm.http.ContextManager;
 import password.pwm.util.logging.PwmLogger;
 
@@ -45,6 +47,7 @@ import java.lang.management.LockInfo;
 import java.lang.management.MonitorInfo;
 import java.lang.management.ThreadInfo;
 import java.lang.reflect.Method;
+import java.net.URI;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.time.Instant;
@@ -700,4 +703,28 @@ public class JavaHelper
         properties.forEach( ( key, value ) -> returnMap.put( ( String ) key, (String) value ) );
         return returnMap;
     }
+
+    public static Optional<String> deriveLocalServerHostname( final Configuration configuration )
+    {
+        if ( configuration != null )
+        {
+            final String siteUrl = configuration.readSettingAsString( PwmSetting.PWM_SITE_URL );
+            if ( !StringUtil.isEmpty( siteUrl ) )
+            {
+                try
+                {
+                    final URI parsedUri = URI.create( siteUrl );
+                    {
+                        final String uriHost = parsedUri.getHost();
+                        return Optional.ofNullable( uriHost );
+                    }
+                }
+                catch ( IllegalArgumentException e )
+                {
+                    LOGGER.trace( () -> " error parsing siteURL hostname: " + e.getMessage() );
+                }
+            }
+        }
+        return Optional.empty();
+    }
 }

+ 55 - 0
server/src/test/java/password/pwm/svc/event/CEFAuditFormatterTest.java

@@ -0,0 +1,55 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.svc.event;
+
+import org.junit.Assert;
+import org.junit.Test;
+import password.pwm.PwmApplication;
+import password.pwm.config.Configuration;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JsonUtil;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CEFAuditFormatterTest
+{
+    @Test
+    public void testCEFFormatting() throws PwmUnrecoverableException
+    {
+        final String jsonInput = "{\"perpetratorID\":\"per|son\",\"perpetratorDN\":\"cn=per|son,o=org\",\"perpetratorLdapProfile\":\"default\",\"sourceAddress\":\"2001:DB8:D:B8:35cc::/64\",\"sourceHost\":\"ws31222\",\"type\":\"USER\",\"eventCode\":\"ACTIVATE_USER\",\"guid\":\"16ee0bf8-b0c9-41d7-8c24-b40110fc727e\",\"timestamp\":\"2000-01-01T00:00:00Z\",\"message\":\"message pipe|Escape, slash\\\\Escape, equal=Escape, \\nsecondLine\",\"xdasTaxonomy\":\"XDAS_AE_CREATE_SESSION\",\"xdasOutcome\":\"XDAS_OUT_SUCCESS\"}";
+        final UserAuditRecord auditRecord = JsonUtil.deserialize( jsonInput, UserAuditRecord.class );
+
+        final String expectedOutput = "CEF:0|PWM|PWM|v b0 r0|ACTIVATE_USER|Activate Account|Medium| type=USER eventCode=ACTIVATE_USER timestamp="
+                + "2000-01-01T00:00:00Z"
+                + " message=message pipe\\|Escape, slash\\\\Escape, equal\\=Escape, \\nsecondLine"
+                + " perpetratorID=per\\|son perpetratorDN=cn\\=per\\|son,o\\=org sourceAddress=2001:DB8:D:B8:35cc::/64 sourceHost=ws31222";
+
+        final CEFAuditFormatter cefAuditFormatter = new CEFAuditFormatter();
+        final PwmApplication pwmApplication = mock(PwmApplication.class);
+        when(pwmApplication.getConfig()).thenReturn(new Configuration( StoredConfigurationImpl.newStoredConfiguration()));
+        final String output = cefAuditFormatter.convertAuditRecordToMessage( pwmApplication, auditRecord );
+        Assert.assertEquals( expectedOutput, output );
+    }
+}

+ 52 - 0
server/src/test/java/password/pwm/svc/event/JsonAuditFormatterTest.java

@@ -0,0 +1,52 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.svc.event;
+
+import org.junit.Assert;
+import org.junit.Test;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.config.Configuration;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JsonUtil;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class JsonAuditFormatterTest
+{
+    @Test
+    public void testCEFFormatting() throws PwmUnrecoverableException
+    {
+        final String jsonInput = "{\"perpetratorID\":\"per|son\",\"perpetratorDN\":\"cn=per|son,o=org\",\"perpetratorLdapProfile\":\"default\",\"sourceAddress\":\"2001:DB8:D:B8:35cc::/64\",\"sourceHost\":\"ws31222\",\"type\":\"USER\",\"eventCode\":\"ACTIVATE_USER\",\"guid\":\"16ee0bf8-b0c9-41d7-8c24-b40110fc727e\",\"timestamp\":\"2000-01-01T00:00:00Z\",\"message\":\"message pipe|Escape, slash\\\\Escape, equal=Escape, \\nsecondLine\",\"xdasTaxonomy\":\"XDAS_AE_CREATE_SESSION\",\"xdasOutcome\":\"XDAS_OUT_SUCCESS\"}";
+        final UserAuditRecord auditRecord = JsonUtil.deserialize( jsonInput, UserAuditRecord.class );
+        final String expectedOutput = PwmConstants.PWM_APP_NAME + " " + jsonInput;
+        final AuditFormatter auditFormatter = new JsonAuditFormatter();
+        final PwmApplication pwmApplication = mock(PwmApplication.class);
+        when(pwmApplication.getConfig()).thenReturn(new Configuration( StoredConfigurationImpl.newStoredConfiguration()));
+        final String output = auditFormatter.convertAuditRecordToMessage( pwmApplication, auditRecord );
+        Assert.assertEquals( expectedOutput, output );
+    }
+
+}