You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2017/03/28 07:46:37 UTC

[5/5] camel git commit: CAMEL-10885 Add mask option to log EIP

CAMEL-10885 Add mask option to log EIP

Introduced a StringFormatter interface to plugin a formatter into the Log EIP processor. MaskingStringFormatter is the implementation which masks sensitive information like password and passphrase for key=value, XML and JSON. If logEipMask is set to true on CamelContext or route, MaskingStringFormatter is enabled with default options.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/4dc64385
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/4dc64385
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/4dc64385

Branch: refs/heads/master
Commit: 4dc64385bb7fd362fd4028def621a03837df41b3
Parents: 7f5d742
Author: Tomohisa Igarashi <tm...@gmail.com>
Authored: Fri Mar 17 17:18:13 2017 +0900
Committer: Claus Ibsen <da...@apache.org>
Committed: Tue Mar 28 09:46:16 2017 +0200

----------------------------------------------------------------------
 .../org/apache/camel/RuntimeConfiguration.java  |  14 ++
 .../apache/camel/impl/DefaultCamelContext.java  |   9 +
 .../apache/camel/impl/DefaultRouteContext.java  |  14 ++
 .../org/apache/camel/model/LogDefinition.java   |   8 +-
 .../org/apache/camel/model/RouteDefinition.java |  48 +++++
 .../apache/camel/processor/LogProcessor.java    |  12 +-
 .../camel/processor/MaskingStringFormatter.java | 181 +++++++++++++++++++
 .../org/apache/camel/spi/StringFormatter.java   |  34 ++++
 .../apache/camel/processor/LogEipMaskTest.java  |  86 +++++++++
 .../processor/MaskingStringFormatterTest.java   | 100 ++++++++++
 .../blueprint/CamelContextFactoryBean.java      |  10 +
 .../xml/AbstractCamelContextFactoryBean.java    |   5 +
 .../camel/spring/CamelContextFactoryBean.java   |  13 ++
 .../spring/processor/SpringLogEipMaskTest.java  |  67 +++++++
 .../processor/logEipCustomFormatterTest.xml     |  37 ++++
 .../camel/spring/processor/logEipMaskTest.xml   |  41 +++++
 16 files changed, 677 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/RuntimeConfiguration.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/RuntimeConfiguration.java b/camel-core/src/main/java/org/apache/camel/RuntimeConfiguration.java
index 7264ec0..d605b43 100644
--- a/camel-core/src/main/java/org/apache/camel/RuntimeConfiguration.java
+++ b/camel-core/src/main/java/org/apache/camel/RuntimeConfiguration.java
@@ -67,6 +67,20 @@ public interface RuntimeConfiguration {
     Boolean isMessageHistory();
 
     /**
+     * Sets whether security mask for Log EIP is enabled or not (default is disabled).
+     * 
+     * @param logEipMask <tt>true</tt> if mask is enabled
+     */
+    void setLogEipMask(Boolean logEipMask);
+
+    /**
+     * Gets whether security mask for Log EIP is enabled or not.
+     * 
+     * @return <tt>true</tt> if mask is enabled
+     */
+    Boolean isLogEipMask();
+
+    /**
      * Sets whether to log exhausted message body with message history.
      *
      * @param logExhaustedMessageBody whether message body should be logged

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
index a171645..c81d981 100644
--- a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
+++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
@@ -240,6 +240,7 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon
     private Boolean autoStartup = Boolean.TRUE;
     private Boolean trace = Boolean.FALSE;
     private Boolean messageHistory = Boolean.TRUE;
+    private Boolean logEipMask = Boolean.FALSE;
     private Boolean logExhaustedMessageBody = Boolean.FALSE;
     private Boolean streamCache = Boolean.FALSE;
     private Boolean handleFault = Boolean.FALSE;
@@ -2707,6 +2708,14 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon
         this.messageHistory = messageHistory;
     }
 
+    public void setLogEipMask(Boolean useLogEipMask) {
+        this.logEipMask = useLogEipMask;
+    }
+
+    public Boolean isLogEipMask() {
+        return logEipMask != null && logEipMask;
+    }
+
     public Boolean isLogExhaustedMessageBody() {
         return logExhaustedMessageBody;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/impl/DefaultRouteContext.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultRouteContext.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultRouteContext.java
index 594742e..3d6cadb 100644
--- a/camel-core/src/main/java/org/apache/camel/impl/DefaultRouteContext.java
+++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultRouteContext.java
@@ -63,6 +63,7 @@ public class DefaultRouteContext implements RouteContext {
     private boolean routeAdded;
     private Boolean trace;
     private Boolean messageHistory;
+    private Boolean logEipMask;
     private Boolean logExhaustedMessageBody;
     private Boolean streamCache;
     private Boolean handleFault;
@@ -310,6 +311,19 @@ public class DefaultRouteContext implements RouteContext {
         }
     }
 
+    public void setLogEipMask(Boolean logEipMask) {
+        this.logEipMask = logEipMask;
+    }
+
+    public Boolean isLogEipMask() {
+        if (logEipMask != null) {
+            return logEipMask;
+        } else {
+            // fallback to the option from camel context
+            return getCamelContext().isLogEipMask();
+        }
+    }
+
     public void setLogExhaustedMessageBody(Boolean logExhaustedMessageBody) {
         this.logExhaustedMessageBody = logExhaustedMessageBody;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/model/LogDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/LogDefinition.java b/camel-core/src/main/java/org/apache/camel/model/LogDefinition.java
index 7e6cd42..3d0b957 100644
--- a/camel-core/src/main/java/org/apache/camel/model/LogDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/LogDefinition.java
@@ -28,8 +28,10 @@ import org.apache.camel.Expression;
 import org.apache.camel.LoggingLevel;
 import org.apache.camel.Processor;
 import org.apache.camel.processor.LogProcessor;
+import org.apache.camel.processor.MaskingStringFormatter;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.RouteContext;
+import org.apache.camel.spi.StringFormatter;
 import org.apache.camel.util.CamelContextHelper;
 import org.apache.camel.util.CamelLogger;
 import org.apache.camel.util.ObjectHelper;
@@ -123,7 +125,11 @@ public class LogDefinition extends NoOutputDefinition<LogDefinition> {
         LoggingLevel level = getLoggingLevel() != null ? getLoggingLevel() : LoggingLevel.INFO;
         CamelLogger camelLogger = new CamelLogger(logger, level, getMarker());
 
-        return new LogProcessor(exp, camelLogger);
+        StringFormatter formatter = routeContext.getCamelContext().getRegistry().lookupByNameAndType("logEipFormatter", StringFormatter.class);
+        if (formatter == null && routeContext.isLogEipMask()) {
+            formatter = new MaskingStringFormatter();
+        }
+        return new LogProcessor(exp, camelLogger, formatter);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java b/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java
index 48f10a5..5e3fffb 100644
--- a/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java
@@ -77,6 +77,7 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> {
     private String streamCache;
     private String trace;
     private String messageHistory;
+    private String logEipMask;
     private String handleFault;
     private String delayer;
     private String autoStartup;
@@ -478,6 +479,27 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> {
     }
 
     /**
+     * Enable security mask in Log EIP for this route.
+     *
+     * @return the builder
+     */
+    public RouteDefinition logEipMask() {
+        setLogEipMask("true");
+        return this;
+    }
+
+    /**
+     * Sets whether security mask in Log EIP is enabled for this route.
+     *
+     * @param logEipMask whether to enable security mask in Log EIP (true or false), the value can be a property placeholder
+     * @return the builder
+     */
+    public RouteDefinition logEipMask(String logEipMask) {
+        setLogEipMask(logEipMask);
+        return this;
+    }
+
+    /**
      * Disable message history for this route.
      *
      * @return the builder
@@ -875,6 +897,21 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> {
     }
 
     /**
+     * Whether security mask for Log EIP is enabled on this route.
+     */
+    public String getLogEipMask() {
+        return logEipMask;
+    }
+
+    /**
+     * Whether security mask for Log EIP is enabled on this route.
+     */
+    @XmlAttribute @Metadata(defaultValue = "false")
+    public void setLogEipMask(String logEipMask) {
+        this.logEipMask = logEipMask;
+    }
+
+    /**
      * Whether handle fault is enabled on this route.
      */
     public String getHandleFault() {
@@ -1132,6 +1169,17 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> {
             }
         }
 
+        // configure Log EIP mask
+        if (logEipMask != null) {
+            Boolean isLogEipMask = CamelContextHelper.parseBoolean(camelContext, getLogEipMask());
+            if (isLogEipMask != null) {
+                routeContext.setLogEipMask(isLogEipMask);
+                if (isLogEipMask) {
+                    log.debug("Security mask for Log EIP is enabled on route: {}", getId());
+                }
+            }
+        }
+
         // configure stream caching
         if (streamCache != null) {
             Boolean isStreamCache = CamelContextHelper.parseBoolean(camelContext, getStreamCache());

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/processor/LogProcessor.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/processor/LogProcessor.java b/camel-core/src/main/java/org/apache/camel/processor/LogProcessor.java
index 2304afc..eaff482 100644
--- a/camel-core/src/main/java/org/apache/camel/processor/LogProcessor.java
+++ b/camel-core/src/main/java/org/apache/camel/processor/LogProcessor.java
@@ -22,6 +22,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.Expression;
 import org.apache.camel.Traceable;
 import org.apache.camel.spi.IdAware;
+import org.apache.camel.spi.StringFormatter;
 import org.apache.camel.support.ServiceSupport;
 import org.apache.camel.util.AsyncProcessorHelper;
 import org.apache.camel.util.CamelLogger;
@@ -36,10 +37,12 @@ public class LogProcessor extends ServiceSupport implements AsyncProcessor, Trac
     private String id;
     private final Expression expression;
     private final CamelLogger logger;
+    private final StringFormatter formatter;
 
-    public LogProcessor(Expression expression, CamelLogger logger) {
+    public LogProcessor(Expression expression, CamelLogger logger, StringFormatter formatter) {
         this.expression = expression;
         this.logger = logger;
+        this.formatter = formatter;
     }
 
     public void process(Exchange exchange) throws Exception {
@@ -51,6 +54,9 @@ public class LogProcessor extends ServiceSupport implements AsyncProcessor, Trac
         try {
             if (logger.shouldLog()) {
                 String msg = expression.evaluate(exchange, String.class);
+                if (formatter != null) {
+                    msg = formatter.format(msg);
+                }
                 logger.doLog(msg);
             }
         } catch (Exception e) {
@@ -87,6 +93,10 @@ public class LogProcessor extends ServiceSupport implements AsyncProcessor, Trac
         return logger;
     }
 
+    public StringFormatter getLogFormatter() {
+        return formatter;
+    }
+
     @Override
     protected void doStart() throws Exception {
         // noop

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/processor/MaskingStringFormatter.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/processor/MaskingStringFormatter.java b/camel-core/src/main/java/org/apache/camel/processor/MaskingStringFormatter.java
new file mode 100644
index 0000000..4d2ef6f
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/processor/MaskingStringFormatter.java
@@ -0,0 +1,181 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.processor;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.Future;
+import java.util.regex.Pattern;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.spi.ExchangeFormatter;
+import org.apache.camel.spi.StringFormatter;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriParams;
+import org.apache.camel.util.MessageHelper;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+
+/**
+ * The {@link StringFormatter} that searches the specified keywards in the source
+ * and replace its value with mask string. By default passphrase, password and secretKey
+ * are used as keywards to replace its value.
+ */
+public class MaskingStringFormatter implements StringFormatter {
+
+    private static final Set<String> DEFAULT_KEYWORDS = new HashSet<String>(Arrays.asList("passphrase", "password", "secretKey"));
+    private Set<String> keywords;
+    private boolean maskKeyValue;
+    private boolean maskXmlElement;
+    private boolean maskJson;
+    private String maskString = "xxxxx";
+    private Pattern keyValueMaskPattern;
+    private Pattern xmlElementMaskPattern;
+    private Pattern jsonMaskPattern;
+
+    public MaskingStringFormatter() {
+        this(DEFAULT_KEYWORDS, true, true, true);
+    }
+
+    public MaskingStringFormatter(boolean maskKeyValue, boolean maskXml, boolean maskJson) {
+        this(DEFAULT_KEYWORDS, maskKeyValue, maskXml, maskJson);
+    }
+
+    public MaskingStringFormatter(Set<String> keywords, boolean maskKeyValue, boolean maskXmlElement, boolean maskJson) {
+        this.keywords = keywords;
+        setMaskKeyValue(maskKeyValue);
+        setMaskXmlElement(maskXmlElement);
+        setMaskJson(maskJson);
+    }
+
+    public String format(String source) {
+        if (keywords == null || keywords.isEmpty()) {
+            return source;
+        }
+
+        String answer = source;
+        if (maskKeyValue) {
+            answer = keyValueMaskPattern.matcher(answer).replaceAll("$1\"" + maskString + "\"");
+        }
+        if (maskXmlElement) {
+            answer = xmlElementMaskPattern.matcher(answer).replaceAll("$1" + maskString + "$3");
+        }
+        if (maskJson) {
+            answer = jsonMaskPattern.matcher(answer).replaceAll("$1\"" + maskString + "\"");
+        }
+        return answer;
+    }
+
+    public boolean isMaskKeyValue() {
+        return maskKeyValue;
+    }
+
+    public void setMaskKeyValue(boolean maskKeyValue) {
+        this.maskKeyValue = maskKeyValue;
+        if (maskKeyValue) {
+            keyValueMaskPattern = createKeyValueMaskPattern(keywords);
+        } else {
+            keyValueMaskPattern = null;
+        }
+    }
+
+    public boolean isMaskXmlElement() {
+        return maskXmlElement;
+    }
+
+    public void setMaskXmlElement(boolean maskXml) {
+        this.maskXmlElement = maskXml;
+        if (maskXml) {
+            xmlElementMaskPattern = createXmlElementMaskPattern(keywords);
+        } else {
+            xmlElementMaskPattern = null;
+        }
+    }
+
+    public boolean isMaskJson() {
+        return maskJson;
+    }
+
+    public void setMaskJson(boolean maskJson) {
+        this.maskJson = maskJson;
+        if (maskJson) {
+            jsonMaskPattern = createJsonMaskPattern(keywords);
+        } else {
+            jsonMaskPattern = null;
+        }
+    }
+
+    public String getMaskString() {
+        return maskString;
+    }
+
+    public void setMaskString(String maskString) {
+        this.maskString = maskString;
+    }
+
+    protected Pattern createKeyValueMaskPattern(Set<String> keywords) {
+        StringBuilder regex = createOneOfThemRegex(keywords);
+        if (regex == null) {
+            return null;
+        }
+        regex.insert(0, "([\\w]*(?:");
+        regex.append(")[\\w]*[\\s]*?=[\\s]*?)([\\S&&[^'\",\\}\\]\\)]]+[\\S&&[^,\\}\\]\\)>]]*?|\"[^\"]*?\"|'[^']*?')");
+        return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
+    }
+
+    protected Pattern createXmlElementMaskPattern(Set<String> keywords) {
+        StringBuilder regex = createOneOfThemRegex(keywords);
+        if (regex == null) {
+            return null;
+        }
+        regex.insert(0, "(<([\\w]*(?:");
+        regex.append(")[\\w]*)(?:[\\s]+.+)*?>[\\s]*?)(?:[\\S&&[^<]]+(?:\\s+[\\S&&[^<]]+)*?)([\\s]*?</\\2>)");
+        return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
+    }
+
+    protected Pattern createJsonMaskPattern(Set<String> keywords) {
+        StringBuilder regex = createOneOfThemRegex(keywords);
+        if (regex == null) {
+            return null;
+        }
+        regex.insert(0, "(\"(?:[^\"]|(?:\\\"))*?(?:");
+        regex.append(")(?:[^\"]|(?:\\\"))*?\"\\s*?\\:\\s*?)(?:\"(?:[^\"]|(?:\\\"))*?\")");
+        return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
+    }
+
+    protected StringBuilder createOneOfThemRegex(Set<String> keywords) {
+        StringBuilder regex = new StringBuilder();
+        if (keywords == null || keywords.isEmpty()) {
+            return null;
+        }
+        String[] strKeywords = keywords.toArray(new String[0]);
+        regex.append(Pattern.quote(strKeywords[0]));
+        if (strKeywords.length > 1) {
+            for (int i = 1; i < strKeywords.length; i++) {
+                regex.append('|');
+                regex.append(Pattern.quote(strKeywords[i]));
+            }
+        }
+        return regex;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/main/java/org/apache/camel/spi/StringFormatter.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/spi/StringFormatter.java b/camel-core/src/main/java/org/apache/camel/spi/StringFormatter.java
new file mode 100644
index 0000000..c76e63e
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/spi/StringFormatter.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spi;
+
+import org.apache.camel.Exchange;
+
+/**
+ * A plugin used to format a log String, for example to mask security information
+ * like password or passphrase.
+ */
+public interface StringFormatter {
+
+    /**
+     * Format a given string.
+     *
+     * @param source the source string
+     * @return formatted string
+     */
+    String format(String source);
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/test/java/org/apache/camel/processor/LogEipMaskTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/processor/LogEipMaskTest.java b/camel-core/src/test/java/org/apache/camel/processor/LogEipMaskTest.java
new file mode 100644
index 0000000..a230aef
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/processor/LogEipMaskTest.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.processor;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.LoggingLevel;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.spi.StringFormatter;
+import org.apache.camel.util.jndi.JndiTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class LogEipMaskTest {
+
+    protected MockStringFormatter globalFormatter;
+    protected JndiRegistry registry;
+
+    protected CamelContext createCamelContext() throws Exception {
+        registry = new JndiRegistry(JndiTest.createInitialContext());
+        globalFormatter = new MockStringFormatter();
+        CamelContext context = new DefaultCamelContext(registry);
+        context.addRoutes(createRouteBuilder());
+        return context;
+    }
+
+    @Test
+    public void testLogEipMask() throws Exception {
+        CamelContext context = createCamelContext();
+        MockEndpoint mock = context.getEndpoint("mock:foo", MockEndpoint.class);
+        mock.expectedMessageCount(1);
+        context.setLogEipMask(true);
+        context.start();
+        context.createProducerTemplate().sendBody("direct:foo", "mask password=\"my passw0rd!\"");
+        context.createProducerTemplate().sendBody("direct:noMask", "no-mask password=\"my passw0rd!\"");
+        mock.assertIsSatisfied();
+        context.stop();
+    }
+
+    @Test
+    public void testCustomFormatter() throws Exception {
+        CamelContext context = createCamelContext();
+        registry.bind("logEipFormatter", globalFormatter);
+        context.start();
+        context.createProducerTemplate().sendBody("direct:foo", "mock password=\"my passw0rd!\"");
+        Assert.assertEquals("Got mock password=\"my passw0rd!\"", globalFormatter.received);
+        context.stop();
+    }
+
+    public static class MockStringFormatter implements StringFormatter {
+        private String received;
+        @Override
+        public String format(String source) {
+            received = source;
+            return source;
+        }
+    }
+
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:foo").routeId("foo").log("Got ${body}").to("mock:foo");
+                from("direct:noMask").routeId("noMask").logEipMask("false").log("Got ${body}").to("mock:noMask");
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/camel-core/src/test/java/org/apache/camel/processor/MaskingStringFormatterTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/processor/MaskingStringFormatterTest.java b/camel-core/src/test/java/org/apache/camel/processor/MaskingStringFormatterTest.java
new file mode 100644
index 0000000..aadb5a2
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/processor/MaskingStringFormatterTest.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.processor;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.impl.DefaultExchange;
+import org.apache.camel.impl.DefaultMessage;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class MaskingStringFormatterTest {
+
+    @Test
+    public void testDefaultOption() throws Exception {
+        MaskingStringFormatter formatter = new MaskingStringFormatter();
+        String answer = formatter.format("key=value, myPassword=foo,\n myPassphrase=\"foo bar\", secretKey='!@#$%^&*() -+[]{};:'");
+        Assert.assertEquals("key=value, myPassword=\"xxxxx\",\n myPassphrase=\"xxxxx\", secretKey=\"xxxxx\"", answer);
+
+        answer = formatter.format("<xmlPassword>\n foo bar \n</xmlPassword>\n<user password=\"asdf qwert\"/>");
+        Assert.assertEquals("<xmlPassword>\n xxxxx \n</xmlPassword>\n<user password=\"xxxxx\"/>", answer);
+
+        answer = formatter.format("{\"key\" : \"value\", \"My Password\":\"foo\", \"My SecretPassphrase\" : \"foo bar\", \"My SecretKey2\" : \"!@#$%^&*() -+[]{};:'\"}");
+        Assert.assertEquals("{\"key\" : \"value\", \"My Password\":\"xxxxx\", \"My SecretPassphrase\" : \"xxxxx\", \"My SecretKey2\" : \"xxxxx\"}", answer);
+    }
+
+    @Test
+    public void testDisableKeyValueMask() throws Exception {
+        MaskingStringFormatter formatter = new MaskingStringFormatter(false, true, true);
+        String answer = formatter.format("key=value, myPassword=foo,\n myPassphrase=\"foo bar\", secretKey='!@#$%^&*() -+[]{};:'");
+        Assert.assertEquals("key=value, myPassword=foo,\n myPassphrase=\"foo bar\", secretKey='!@#$%^&*() -+[]{};:'", answer);
+
+        answer = formatter.format("<xmlPassword>\n foo bar \n</xmlPassword>\n<user password=\"asdf qwert\"/>");
+        Assert.assertEquals("<xmlPassword>\n xxxxx \n</xmlPassword>\n<user password=\"asdf qwert\"/>", answer);
+
+        answer = formatter.format("{\"key\" : \"value\", \"My Password\":\"foo\", \"My SecretPassphrase\" : \"foo bar\", \"My SecretKey2\" : \"!@#$%^&*() -+[]{};:'\"}");
+        Assert.assertEquals("{\"key\" : \"value\", \"My Password\":\"xxxxx\", \"My SecretPassphrase\" : \"xxxxx\", \"My SecretKey2\" : \"xxxxx\"}", answer);
+    }
+
+    @Test
+    public void testDisableXmlElementMask() throws Exception {
+        MaskingStringFormatter formatter = new MaskingStringFormatter(true, false, true);
+        String answer = formatter.format("key=value, myPassword=foo,\n myPassphrase=\"foo bar\", secretKey='!@#$%^&*() -+[]{};:'");
+        Assert.assertEquals("key=value, myPassword=\"xxxxx\",\n myPassphrase=\"xxxxx\", secretKey=\"xxxxx\"", answer);
+
+        answer = formatter.format("<xmlPassword>\n foo bar \n</xmlPassword>\n<user password=\"asdf qwert\"/>");
+        Assert.assertEquals("<xmlPassword>\n foo bar \n</xmlPassword>\n<user password=\"xxxxx\"/>", answer);
+
+        answer = formatter.format("{\"key\" : \"value\", \"My Password\":\"foo\", \"My SecretPassphrase\" : \"foo bar\", \"My SecretKey2\" : \"!@#$%^&*() -+[]{};:'\"}");
+        Assert.assertEquals("{\"key\" : \"value\", \"My Password\":\"xxxxx\", \"My SecretPassphrase\" : \"xxxxx\", \"My SecretKey2\" : \"xxxxx\"}", answer);
+    }
+
+    @Test
+    public void testDisableJsonMask() throws Exception {
+        MaskingStringFormatter formatter = new MaskingStringFormatter(true, true, false);
+        String answer = formatter.format("key=value, myPassword=foo,\n myPassphrase=\"foo\u3000bar\", secretKey='!@#$%^&*() -+[]{};:'");
+        Assert.assertEquals("key=value, myPassword=\"xxxxx\",\n myPassphrase=\"xxxxx\", secretKey=\"xxxxx\"", answer);
+
+        answer = formatter.format("<xmlPassword>\n foo bar \n</xmlPassword>\n<user password=\"asdf qwert\"/>");
+        Assert.assertEquals("<xmlPassword>\n xxxxx \n</xmlPassword>\n<user password=\"xxxxx\"/>", answer);
+
+        answer = formatter.format("{\"key\" : \"value\", \"My Password\":\"foo\", \"My SecretPassphrase\" : \"foo bar\", \"My SecretKey2\" : \"!@#$%^&*() -+[]{};:'\"}");
+        Assert.assertEquals("{\"key\" : \"value\", \"My Password\":\"foo\", \"My SecretPassphrase\" : \"foo bar\", \"My SecretKey2\" : \"!@#$%^&*() -+[]{};:'\"}", answer);
+    }
+
+    @Test
+    public void testCustomMaskString() throws Exception {
+        MaskingStringFormatter formatter = new MaskingStringFormatter();
+        formatter.setMaskString("**********");
+        String answer = formatter.format("key=value, myPassword=foo,\n myPassphrase=\"foo\u3000bar\", secretKey='!@#$%^&*() -+[]{};:'");
+        Assert.assertEquals("key=value, myPassword=\"**********\",\n myPassphrase=\"**********\", secretKey=\"**********\"", answer);
+
+        answer = formatter.format("<xmlPassword>\n foo bar \n</xmlPassword>\n<user password=\"asdf qwert\"/>");
+        Assert.assertEquals("<xmlPassword>\n ********** \n</xmlPassword>\n<user password=\"**********\"/>", answer);
+
+        answer = formatter.format("{\"key\" : \"value\", \"My Password\":\"foo\", \"My SecretPassphrase\" : \"foo bar\", \"My SecretKey2\" : \"!@#$%^&*() -+[]{};:'\"}");
+        Assert.assertEquals("{\"key\" : \"value\", \"My Password\":\"**********\", \"My SecretPassphrase\" : \"**********\", \"My SecretKey2\" : \"**********\"}", answer);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
----------------------------------------------------------------------
diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
index ecc74f1..f4f9c5c 100644
--- a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
+++ b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
@@ -93,6 +93,8 @@ public class CamelContextFactoryBean extends AbstractCamelContextFactoryBean<Blu
     @XmlAttribute
     private String messageHistory;
     @XmlAttribute
+    private String logEipMask;
+    @XmlAttribute
     private String logExhaustedMessageBody;
     @XmlAttribute
     private String streamCache = "false";
@@ -536,6 +538,14 @@ public class CamelContextFactoryBean extends AbstractCamelContextFactoryBean<Blu
         this.messageHistory = messageHistory;
     }
 
+    public String getLogEipMask() {
+        return logEipMask;
+    }
+
+    public void setLogEipMask(String logEipMask) {
+        this.logEipMask = logEipMask;
+    }
+
     public String getLogExhaustedMessageBody() {
         return logExhaustedMessageBody;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java
----------------------------------------------------------------------
diff --git a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java
index 912e1af..fc92daa 100644
--- a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java
+++ b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java
@@ -727,6 +727,8 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex
 
     public abstract String getMessageHistory();
 
+    public abstract String getLogEipMask();
+
     public abstract String getLogExhaustedMessageBody();
 
     public abstract String getStreamCache();
@@ -822,6 +824,9 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex
         if (getMessageHistory() != null) {
             ctx.setMessageHistory(CamelContextHelper.parseBoolean(getContext(), getMessageHistory()));
         }
+        if (getLogEipMask() != null) {
+            ctx.setLogEipMask(CamelContextHelper.parseBoolean(getContext(), getLogEipMask()));
+        }
         if (getLogExhaustedMessageBody() != null) {
             ctx.setLogExhaustedMessageBody(CamelContextHelper.parseBoolean(getContext(), getLogExhaustedMessageBody()));
         }

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
----------------------------------------------------------------------
diff --git a/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java b/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
index 70a319e..be304eb 100644
--- a/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
+++ b/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
@@ -101,6 +101,8 @@ public class CamelContextFactoryBean extends AbstractCamelContextFactoryBean<Spr
     private String trace;
     @XmlAttribute @Metadata(defaultValue = "true")
     private String messageHistory;
+    @XmlAttribute @Metadata(defaultValue = "false")
+    private String logEipMask;
     @XmlAttribute
     private String logExhaustedMessageBody;
     @XmlAttribute
@@ -635,6 +637,17 @@ public class CamelContextFactoryBean extends AbstractCamelContextFactoryBean<Spr
         this.messageHistory = messageHistory;
     }
 
+    public String getLogEipMask() {
+        return logEipMask;
+    }
+
+    /**
+     * Sets whether security mask for Log EIP is enabled or not.
+     */
+    public void setLogEipMask(String logEipMask) {
+        this.logEipMask = logEipMask;
+    }
+
     public String getLogExhaustedMessageBody() {
         return logExhaustedMessageBody;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/components/camel-spring/src/test/java/org/apache/camel/spring/processor/SpringLogEipMaskTest.java
----------------------------------------------------------------------
diff --git a/components/camel-spring/src/test/java/org/apache/camel/spring/processor/SpringLogEipMaskTest.java b/components/camel-spring/src/test/java/org/apache/camel/spring/processor/SpringLogEipMaskTest.java
new file mode 100644
index 0000000..6bf1a70
--- /dev/null
+++ b/components/camel-spring/src/test/java/org/apache/camel/spring/processor/SpringLogEipMaskTest.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spring.processor;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.processor.LogEipMaskTest;
+import org.apache.camel.spi.StringFormatter;
+import org.apache.camel.spring.SpringCamelContext;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.support.AbstractXmlApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import static org.apache.camel.spring.processor.SpringTestHelper.createSpringCamelContext;
+
+public class SpringLogEipMaskTest {
+
+    @Test
+    public void testLogEipMask() throws Exception {
+        final AbstractXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("org/apache/camel/spring/processor/logEipMaskTest.xml");
+        SpringCamelContext context = SpringCamelContext.springCamelContext(applicationContext);
+        MockEndpoint mock = context.getEndpoint("mock:foo", MockEndpoint.class);
+        mock.expectedMessageCount(1);
+        context.setLogEipMask(true);
+        context.start();
+        context.createProducerTemplate().sendBody("direct:foo", "mask password=\"my passw0rd!\"");
+        context.createProducerTemplate().sendBody("direct:noMask", "no-mask password=\"my passw0rd!\"");
+        mock.assertIsSatisfied();
+        context.stop();
+    }
+
+    @Test
+    public void testCustomFormatter() throws Exception {
+        final AbstractXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("org/apache/camel/spring/processor/logEipCustomFormatterTest.xml");
+        SpringCamelContext context = SpringCamelContext.springCamelContext(applicationContext);
+        context.start();
+        MockStringFormatter customFormatter = applicationContext.getBean("logEipFormatter", MockStringFormatter.class);
+        context.createProducerTemplate().sendBody("direct:foo", "mock password=\"my passw0rd!\"");
+        Assert.assertEquals("Got mock password=\"my passw0rd!\"", customFormatter.received);
+        context.stop();
+    }
+
+    public static class MockStringFormatter implements StringFormatter {
+        private String received;
+        @Override
+        public String format(String source) {
+            received = source;
+            return source;
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipCustomFormatterTest.xml
----------------------------------------------------------------------
diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipCustomFormatterTest.xml b/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipCustomFormatterTest.xml
new file mode 100644
index 0000000..dc90e35
--- /dev/null
+++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipCustomFormatterTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+    <bean id="logEipFormatter" class="org.apache.camel.spring.processor.SpringLogEipMaskTest.MockStringFormatter"/>
+
+    <camelContext xmlns="http://camel.apache.org/schema/spring">
+
+        <route id="foo">
+            <from uri="direct:foo"/>
+            <log message="Got ${body}"/>
+            <to uri="mock:foo"/>
+        </route>
+
+    </camelContext>
+
+</beans>

http://git-wip-us.apache.org/repos/asf/camel/blob/4dc64385/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipMaskTest.xml
----------------------------------------------------------------------
diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipMaskTest.xml b/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipMaskTest.xml
new file mode 100644
index 0000000..577847d
--- /dev/null
+++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/processor/logEipMaskTest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+    <camelContext logEipMask="true" xmlns="http://camel.apache.org/schema/spring">
+
+        <route id="foo">
+            <from uri="direct:foo"/>
+            <log message="Got ${body}"/>
+            <to uri="mock:foo"/>
+        </route>
+
+        <route id="noMask" logEipMask="false">
+            <from uri="direct:noMask"/>
+            <log message="Got ${body}"/>
+            <to uri="mock:noMask"/>
+        </route>
+
+    </camelContext>
+
+</beans>