This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit f084a88b89c1d4baf9963a27340789f8d7afe677 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Jun 16 18:21:19 2022 +0200 CAMEL-18200: Sanitized uri should hide more sensitive keys. --- .../apache/camel/support/DefaultEndpointTest.java | 4 +- .../camel/management/ManagedSanitizeTest.java | 4 +- .../camel/support/ScheduledPollConsumer.java | 2 +- .../java/org/apache/camel/util/URISupport.java | 187 ++++++++++++--------- .../java/org/apache/camel/util/URISupportTest.java | 6 +- .../ROOT/pages/camel-3x-upgrade-guide-3_18.adoc | 5 + 6 files changed, 117 insertions(+), 91 deletions(-) diff --git a/core/camel-core/src/test/java/org/apache/camel/support/DefaultEndpointTest.java b/core/camel-core/src/test/java/org/apache/camel/support/DefaultEndpointTest.java index fff873da549..18dbe7c3102 100644 --- a/core/camel-core/src/test/java/org/apache/camel/support/DefaultEndpointTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/support/DefaultEndpointTest.java @@ -35,10 +35,10 @@ public class DefaultEndpointTest extends ContextTestSupport { assertSanitizedUriUnchanged("irc://irc.codehaus.org/camel"); assertSanitizedUriUnchanged("direct:foo?bar=123&cheese=yes"); assertSanitizedUriUnchanged("https://issues.apache.org/activemq/secure/AddComment!default.jspa?id=33239"); - assertEquals("ftp://host.mysite.com/records?passiveMode=true&user=someuser&password=xxxxxx", + assertEquals("ftp://host.mysite.com/records?passiveMode=true&user=xxxxxx&password=xxxxxx", URISupport.sanitizeUri("ftp://host.mysite.com/records?passiveMode=true&user=someuser&password=superSecret")); assertEquals( - "sftp://host.mysite.com/records?user=someuser&privateKeyFile=key.file&privateKeyFilePassphrase=xxxxxx&knownHostsFile=hosts.list", + "sftp://host.mysite.com/records?user=xxxxxx&privateKeyFile=xxxxxx&privateKeyFilePassphrase=xxxxxx&knownHostsFile=hosts.list", URISupport.sanitizeUri( "sftp://host.mysite.com/records?user=someuser&privateKeyFile=key.file&privateKeyFilePassphrase=superSecret&knownHostsFile=hosts.list")); } diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedSanitizeTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedSanitizeTest.java index c4ac80c6525..b22693f3ea0 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedSanitizeTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedSanitizeTest.java @@ -43,10 +43,10 @@ public class ManagedSanitizeTest extends ManagementTestSupport { public void testSanitize() throws Exception { MBeanServer mbeanServer = getMBeanServer(); - ObjectName name = getCamelObjectName(TYPE_ENDPOINT, "stub://foo\\?password=xxxxxx&username=foo"); + ObjectName name = getCamelObjectName(TYPE_ENDPOINT, "stub://foo\\?password=xxxxxx&username=xxxxxx"); assertTrue(mbeanServer.isRegistered(name), "Should be registered"); String uri = (String) mbeanServer.getAttribute(name, "EndpointUri"); - assertEquals("stub://foo?password=xxxxxx&username=foo", uri); + assertEquals("stub://foo?password=xxxxxx&username=xxxxxx", uri); } @Override diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java index 62cfc68041d..c12bfc67158 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java @@ -249,7 +249,7 @@ public abstract class ScheduledPollConsumer extends DefaultConsumer // let exception handler deal with the caused exception // but suppress this during shutdown as the logs may get flooded with exceptions during shutdown/forced shutdown try { - getExceptionHandler().handleException("Consumer " + this + " failed polling endpoint: " + getEndpoint() + getExceptionHandler().handleException("Failed polling endpoint: " + getEndpoint() + ". Will try again at next poll", cause); } catch (Throwable e) { diff --git a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java index b6467642dc5..c72f15b4e8b 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import static org.apache.camel.util.CamelURIParser.URI_ALREADY_NORMALIZED; @@ -36,15 +37,13 @@ import static org.apache.camel.util.CamelURIParser.URI_ALREADY_NORMALIZED; public final class URISupport { public static final String RAW_TOKEN_PREFIX = "RAW"; - public static final char[] RAW_TOKEN_START = { '(', '{' }; - public static final char[] RAW_TOKEN_END = { ')', '}' }; + public static final char[] RAW_TOKEN_START = {'(', '{'}; + public static final char[] RAW_TOKEN_END = {')', '}'}; // Match any key-value pair in the URI query string whose key contains // "passphrase" or "password" or secret key (case-insensitive). // First capture group is the key, second is the value. - private static final Pattern SECRETS = Pattern.compile( - "([?&][^=]*(?:passphrase|password|secretKey|accessToken|clientSecret|authorizationToken|saslJaasConfig)[^=]*)=(RAW(([{][^}]*[}])|([(][^)]*[)]))|[^&]*)", - Pattern.CASE_INSENSITIVE); + private static final Pattern ALL_SECRETS = createSecretsPattern(SensitiveUtils.getSensitiveKeys()); // Match the user password in the URI as second capture group // (applies to URI with authority component and userinfo token in the form @@ -65,16 +64,16 @@ public final class URISupport { /** * Removes detected sensitive information (such as passwords) from the URI and returns the result. * - * @param uri The uri to sanitize. - * @see #SECRETS and #USERINFO_PASSWORD for the matched pattern - * @return Returns null if the uri is null, otherwise the URI with the passphrase, password or secretKey - * sanitized. + * @param uri The uri to sanitize. + * @return Returns null if the uri is null, otherwise the URI with the passphrase, password or secretKey + * sanitized. + * @see #SECRETS and #USERINFO_PASSWORD for the matched pattern */ public static String sanitizeUri(String uri) { // use xxxxx as replacement as that works well with JMX also String sanitized = uri; if (uri != null) { - sanitized = SECRETS.matcher(sanitized).replaceAll("$1=xxxxxx"); + sanitized = ALL_SECRETS.matcher(sanitized).replaceAll("$1=xxxxxx"); sanitized = USERINFO_PASSWORD.matcher(sanitized).replaceFirst("$1xxxxxx$3"); } return sanitized; @@ -84,8 +83,8 @@ public final class URISupport { * Removes detected sensitive information (such as passwords) from the <em>path part</em> of an URI (that is, the * part without the query parameters or component prefix) and returns the result. * - * @param path the URI path to sanitize - * @return null if the path is null, otherwise the sanitized path + * @param path the URI path to sanitize + * @return null if the path is null, otherwise the sanitized path */ public static String sanitizePath(String path) { String sanitized = path; @@ -98,9 +97,9 @@ public final class URISupport { /** * Extracts the scheme specific path from the URI that is used as the remainder option when creating endpoints. * - * @param u the URI - * @param useRaw whether to force using raw values - * @return the remainder path + * @param u the URI + * @param useRaw whether to force using raw values + * @return the remainder path */ public static String extractRemainderPath(URI u, boolean useRaw) { String path = useRaw ? u.getRawSchemeSpecificPart() : u.getSchemeSpecificPart(); @@ -120,8 +119,8 @@ public final class URISupport { /** * Extracts the query part of the given uri * - * @param uri the uri - * @return the query parameters or <tt>null</tt> if the uri has no query + * @param uri the uri + * @return the query parameters or <tt>null</tt> if the uri has no query */ public static String extractQuery(String uri) { if (uri == null) { @@ -138,8 +137,8 @@ public final class URISupport { /** * Strips the query parameters from the uri * - * @param uri the uri - * @return the uri without the query parameter + * @param uri the uri + * @return the uri without the query parameter */ public static String stripQuery(String uri) { int idx = uri.indexOf('?'); @@ -156,12 +155,12 @@ public final class URISupport { * <tt>key=RAW(value)</tt> which tells Camel to not encode the value, and use the value as is (eg key=value) and the * value has <b>not</b> been encoded. * - * @param uri the uri - * @return the parameters, or an empty map if no parameters (eg never null) + * @param uri the uri + * @return the parameters, or an empty map if no parameters (eg never null) * @throws URISyntaxException is thrown if uri has invalid syntax. - * @see #RAW_TOKEN_PREFIX - * @see #RAW_TOKEN_START - * @see #RAW_TOKEN_END + * @see #RAW_TOKEN_PREFIX + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END */ public static Map<String, Object> parseQuery(String uri) throws URISyntaxException { return parseQuery(uri, false); @@ -174,13 +173,13 @@ public final class URISupport { * <tt>key=RAW(value)</tt> which tells Camel to not encode the value, and use the value as is (eg key=value) and the * value has <b>not</b> been encoded. * - * @param uri the uri - * @param useRaw whether to force using raw values - * @return the parameters, or an empty map if no parameters (eg never null) + * @param uri the uri + * @param useRaw whether to force using raw values + * @return the parameters, or an empty map if no parameters (eg never null) * @throws URISyntaxException is thrown if uri has invalid syntax. - * @see #RAW_TOKEN_PREFIX - * @see #RAW_TOKEN_START - * @see #RAW_TOKEN_END + * @see #RAW_TOKEN_PREFIX + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END */ public static Map<String, Object> parseQuery(String uri, boolean useRaw) throws URISyntaxException { return parseQuery(uri, useRaw, false); @@ -193,15 +192,15 @@ public final class URISupport { * <tt>key=RAW(value)</tt> which tells Camel to not encode the value, and use the value as is (eg key=value) and the * value has <b>not</b> been encoded. * - * @param uri the uri - * @param useRaw whether to force using raw values - * @param lenient whether to parse lenient and ignore trailing & markers which has no key or value which - * can happen when using HTTP components - * @return the parameters, or an empty map if no parameters (eg never null) + * @param uri the uri + * @param useRaw whether to force using raw values + * @param lenient whether to parse lenient and ignore trailing & markers which has no key or value which + * can happen when using HTTP components + * @return the parameters, or an empty map if no parameters (eg never null) * @throws URISyntaxException is thrown if uri has invalid syntax. - * @see #RAW_TOKEN_PREFIX - * @see #RAW_TOKEN_START - * @see #RAW_TOKEN_END + * @see #RAW_TOKEN_PREFIX + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END */ public static Map<String, Object> parseQuery(String uri, boolean useRaw, boolean lenient) throws URISyntaxException { if (uri == null || uri.isEmpty()) { @@ -226,12 +225,12 @@ public final class URISupport { * This is a companion method with {@link #isRaw(int, List)} and the returned value is supposed to be used as the * parameter of that method. * - * @param str the string to scan RAW tokens - * @return the list of pair indexes which represent the start and end positions of a RAW token - * @see #isRaw(int, List) - * @see #RAW_TOKEN_PREFIX - * @see #RAW_TOKEN_START - * @see #RAW_TOKEN_END + * @param str the string to scan RAW tokens + * @return the list of pair indexes which represent the start and end positions of a RAW token + * @see #isRaw(int, List) + * @see #RAW_TOKEN_PREFIX + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END */ public static List<Pair<Integer>> scanRaw(String str) { return URIScanner.scanRaw(str); @@ -244,13 +243,13 @@ public final class URISupport { * This is a companion method with {@link #scanRaw(String)} and is supposed to consume the returned value of that * method as the second parameter <tt>pairs</tt>. * - * @param index the index to be tested - * @param pairs the list of pair indexes which represent the start and end positions of a RAW token - * @return <tt>true</tt> if the index is within any pair of the indexes, <tt>false</tt> otherwise - * @see #scanRaw(String) - * @see #RAW_TOKEN_PREFIX - * @see #RAW_TOKEN_START - * @see #RAW_TOKEN_END + * @param index the index to be tested + * @param pairs the list of pair indexes which represent the start and end positions of a RAW token + * @return <tt>true</tt> if the index is within any pair of the indexes, <tt>false</tt> otherwise + * @see #scanRaw(String) + * @see #RAW_TOKEN_PREFIX + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END */ public static boolean isRaw(int index, List<Pair<Integer>> pairs) { if (pairs == null || pairs.isEmpty()) { @@ -271,8 +270,8 @@ public final class URISupport { /** * Parses the query parameters of the uri (eg the query part). * - * @param uri the uri - * @return the parameters, or an empty map if no parameters (eg never null) + * @param uri the uri + * @return the parameters, or an empty map if no parameters (eg never null) * @throws URISyntaxException is thrown if uri has invalid syntax. */ public static Map<String, Object> parseParameters(URI uri) throws URISyntaxException { @@ -307,10 +306,10 @@ public final class URISupport { * just the value. * * @param parameters the uri parameters - * @see #parseQuery(String) - * @see #RAW_TOKEN_PREFIX - * @see #RAW_TOKEN_START - * @see #RAW_TOKEN_END + * @see #parseQuery(String) + * @see #RAW_TOKEN_PREFIX + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END */ @SuppressWarnings("unchecked") public static void resolveRawParameterValues(Map<String, Object> parameters) { @@ -347,9 +346,9 @@ public final class URISupport { /** * Creates a URI with the given query * - * @param uri the uri - * @param query the query to append to the uri - * @return uri with the query appended + * @param uri the uri + * @param query the query to append to the uri + * @return uri with the query appended * @throws URISyntaxException is thrown if uri has invalid syntax. */ public static URI createURIWithQuery(URI uri, String query) throws URISyntaxException { @@ -380,9 +379,9 @@ public final class URISupport { * <p/> * Returns the value as-is if not starting with the prefix. * - * @param value the value - * @param prefix the prefix to remove from value - * @return the value without the prefix + * @param value the value + * @param prefix the prefix to remove from value + * @return the value without the prefix */ public static String stripPrefix(String value, String prefix) { if (value == null || prefix == null) { @@ -401,9 +400,9 @@ public final class URISupport { * <p/> * Returns the value as-is if not ending with the prefix. * - * @param value the value - * @param suffix the suffix to remove from value - * @return the value without the suffix + * @param value the value + * @param suffix the suffix to remove from value + * @return the value without the suffix */ public static String stripSuffix(final String value, final String suffix) { if (value == null || suffix == null) { @@ -420,9 +419,9 @@ public final class URISupport { /** * Assembles a query from the given map. * - * @param options the map with the options (eg key/value pairs) - * @return a query string with <tt>key1=value&key2=value2&...</tt>, or an empty string if there - * is no options. + * @param options the map with the options (eg key/value pairs) + * @return a query string with <tt>key1=value&key2=value2&...</tt>, or an empty string if there + * is no options. * @throws URISyntaxException is thrown if uri has invalid syntax. */ @SuppressWarnings("unchecked") @@ -433,10 +432,10 @@ public final class URISupport { /** * Assembles a query from the given map. * - * @param options the map with the options (eg key/value pairs) - * @param encode whether to URL encode the query string - * @return a query string with <tt>key1=value&key2=value2&...</tt>, or an empty string if there - * is no options. + * @param options the map with the options (eg key/value pairs) + * @param encode whether to URL encode the query string + * @return a query string with <tt>key1=value&key2=value2&...</tt>, or an empty string if there + * is no options. * @throws URISyntaxException is thrown if uri has invalid syntax. */ @SuppressWarnings("unchecked") @@ -464,7 +463,7 @@ public final class URISupport { // values if (value instanceof List) { List<String> list = (List<String>) value; - for (Iterator<String> it = list.iterator(); it.hasNext();) { + for (Iterator<String> it = list.iterator(); it.hasNext(); ) { String s = it.next(); appendQueryStringParameter(key, s, rc, encode); // append & separator if there is more in the list @@ -538,9 +537,9 @@ public final class URISupport { * It keeps the original parameters and if a new parameter is already defined in {@code originalURI}, it will be * replaced by its value in {@code newParameters}. * - * @param originalURI the original URI - * @param newParameters the parameters to add - * @return the URI with all the parameters + * @param originalURI the original URI + * @param newParameters the parameters to add + * @return the URI with all the parameters * @throws URISyntaxException is thrown if the uri syntax is invalid * @throws UnsupportedEncodingException is thrown if encoding error */ @@ -560,13 +559,13 @@ public final class URISupport { * <tt>key=RAW(value)</tt> which tells Camel to not encode the value, and use the value as is (eg key=value) and the * value has <b>not</b> been encoded. * - * @param uri the uri - * @return the normalized uri + * @param uri the uri + * @return the normalized uri * @throws URISyntaxException in thrown if the uri syntax is invalid * @throws UnsupportedEncodingException is thrown if encoding error - * @see #RAW_TOKEN_PREFIX - * @see #RAW_TOKEN_START - * @see #RAW_TOKEN_END + * @see #RAW_TOKEN_PREFIX + * @see #RAW_TOKEN_START + * @see #RAW_TOKEN_END */ public static String normalizeUri(String uri) throws URISyntaxException, UnsupportedEncodingException { // try to parse using the simpler and faster Camel URI parser @@ -726,7 +725,7 @@ public final class URISupport { public static Map<String, Object> extractProperties(Map<String, Object> properties, String optionPrefix) { Map<String, Object> rc = new LinkedHashMap<>(properties.size()); - for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext();) { + for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext(); ) { Map.Entry<String, Object> entry = it.next(); String name = entry.getKey(); if (name.startsWith(optionPrefix)) { @@ -787,4 +786,26 @@ public final class URISupport { return joined.toString(); } + + private static Pattern createSecretsPattern(Set<String> keywords) { + StringBuilder regex = createOneOfThemRegex(keywords); + regex.insert(0, "([?&][^=]*(?:"); + regex.append(")[^=]*)=(RAW(([{][^}]*[}])|([(][^)]*[)]))|[^&]*)"); + return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE); + } + + private static StringBuilder createOneOfThemRegex(Set<String> keywords) { + // from DefaultMaskingFormatter + StringBuilder regex = new StringBuilder(); + 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; + } + } diff --git a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java index 92bc58f73de..ff9f59cc486 100644 --- a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java +++ b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java @@ -301,7 +301,7 @@ public class URISupportTest { public void testSanitizeUriWithRawPassword() { String uri1 = "http://foo?username=me&password=RAW(me#@123)&foo=bar"; String uri2 = "http://foo?username=me&password=RAW{me#@123}&foo=bar"; - String expected = "http://foo?username=me&password=xxxxxx&foo=bar"; + String expected = "http://foo?username=xxxxxx&password=xxxxxx&foo=bar"; assertEquals(expected, URISupport.sanitizeUri(uri1)); assertEquals(expected, URISupport.sanitizeUri(uri2)); } @@ -310,7 +310,7 @@ public class URISupportTest { public void testSanitizeUriRawUnsafePassword() { String uri1 = "sftp://localhost/target?password=RAW(beforeAmp&afterAmp)&username=jrandom"; String uri2 = "sftp://localhost/target?password=RAW{beforeAmp&afterAmp}&username=jrandom"; - String expected = "sftp://localhost/target?password=xxxxxx&username=jrandom"; + String expected = "sftp://localhost/target?password=xxxxxx&username=xxxxxx"; assertEquals(expected, URISupport.sanitizeUri(uri1)); assertEquals(expected, URISupport.sanitizeUri(uri2)); } @@ -322,7 +322,7 @@ public class URISupportTest { String uriCurly = "http://foo?username=me&password=RAW{me#@123}&foo=bar&port=21&tempFileName=${file:name.noext}.tmp&anotherOption=true"; String expected - = "http://foo?username=me&password=xxxxxx&foo=bar&port=21&tempFileName=${file:name.noext}.tmp&anotherOption=true"; + = "http://foo?username=xxxxxx&password=xxxxxx&foo=bar&port=21&tempFileName=${file:name.noext}.tmp&anotherOption=true"; assertEquals(expected, URISupport.sanitizeUri(uriPlain)); assertEquals(expected, URISupport.sanitizeUri(uriCurly)); } diff --git a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_18.adoc b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_18.adoc index a2b598f1b60..97c28a76d1b 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_18.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_18.adoc @@ -6,6 +6,11 @@ from both 3.0 to 3.1 and 3.1 to 3.2. == Upgrading Camel 3.17 to 3.18 +=== camel-core + +Camel will now mask all known secret values when logging endpoint URIs to avoid leaking sensitive details +such as from stacktraces. Previously only a sub set of known _secret_ keys was masked. + === camel-console The `AbstractDevConsole` has changed the method `doCall` into two separate methods `doCallText` and `doCallJson`