From 8b6eeb259578cd0f4e8fd7227f5c186122e3feae Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Tue, 16 Aug 2016 15:01:18 -0700 Subject: [PATCH] HADOOP-13494. ReconfigurableBase can log sensitive information. Contributed by Sean Mackrory. (cherry picked from commit 4b689e7a758a55cec2ca8398727feefc8ac21bfd) (cherry picked from commit c91e6f28283d1f8ad4504a2338a01e284ced6b24) Conflicts: hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java --- .../apache/hadoop/conf/ConfigRedactor.java | 84 +++++++++++++++++++ .../hadoop/conf/ReconfigurableBase.java | 13 ++- .../fs/CommonConfigurationKeysPublic.java | 14 ++++ .../src/main/resources/core-default.xml | 10 +++ .../hadoop/conf/TestConfigRedactor.java | 72 ++++++++++++++++ 5 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ConfigRedactor.java create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfigRedactor.java diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ConfigRedactor.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ConfigRedactor.java new file mode 100644 index 00000000000..0ba756c7d4b --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ConfigRedactor.java @@ -0,0 +1,84 @@ +/** + * 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.hadoop.conf; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import static org.apache.hadoop.fs.CommonConfigurationKeys.*; + +/** + * Tool for redacting sensitive information when displaying config parameters. + * + *

Some config parameters contain sensitive information (for example, cloud + * storage keys). When these properties are displayed in plaintext, we should + * redactor their values as appropriate. + */ +public class ConfigRedactor { + + private static final String REDACTED_TEXT = ""; + + private List compiledPatterns; + + public ConfigRedactor(Configuration conf) { + String sensitiveRegexList = conf.get( + HADOOP_SECURITY_SENSITIVE_CONFIG_KEYS, + HADOOP_SECURITY_SENSITIVE_CONFIG_KEYS_DEFAULT); + List sensitiveRegexes = Arrays.asList(sensitiveRegexList.split(",")); + compiledPatterns = new ArrayList(); + for (String regex : sensitiveRegexes) { + Pattern p = Pattern.compile(regex); + compiledPatterns.add(p); + } + } + + /** + * Given a key / value pair, decides whether or not to redact and returns + * either the original value or text indicating it has been redacted. + * + * @param key + * @param value + * @return Original value, or text indicating it has been redacted + */ + public String redact(String key, String value) { + if (configIsSensitive(key)) { + return REDACTED_TEXT; + } + return value; + } + + /** + * Matches given config key against patterns and determines whether or not + * it should be considered sensitive enough to redact in logs and other + * plaintext displays. + * + * @param key + * @return True if parameter is considered sensitive + */ + private boolean configIsSensitive(String key) { + for (Pattern regex : compiledPatterns) { + if (regex.matcher(key).find()) { + return true; + } + } + return false; + } +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ReconfigurableBase.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ReconfigurableBase.java index 681ca2bf777..bdd006dfa77 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ReconfigurableBase.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/ReconfigurableBase.java @@ -117,17 +117,24 @@ public abstract class ReconfigurableBase final Collection changes = parent.getChangedProperties(newConf, oldConf); Map> results = Maps.newHashMap(); + ConfigRedactor oldRedactor = new ConfigRedactor(oldConf); + ConfigRedactor newRedactor = new ConfigRedactor(newConf); for (PropertyChange change : changes) { String errorMessage = null; + String oldValRedacted = oldRedactor.redact(change.prop, change.oldVal); + String newValRedacted = newRedactor.redact(change.prop, change.newVal); if (!parent.isPropertyReconfigurable(change.prop)) { LOG.info(String.format( "Property %s is not configurable: old value: %s, new value: %s", - change.prop, change.oldVal, change.newVal)); + change.prop, + oldValRedacted, + newValRedacted)); continue; } LOG.info("Change property: " + change.prop + " from \"" - + ((change.oldVal == null) ? "" : change.oldVal) - + "\" to \"" + ((change.newVal == null) ? "" : change.newVal) + + ((change.oldVal == null) ? "" : oldValRedacted) + + "\" to \"" + + ((change.newVal == null) ? "" : newValRedacted) + "\"."); try { String effectiveValue = diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java index 772abb6ecec..587e1e4a84f 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java @@ -714,5 +714,19 @@ public class CommonConfigurationKeysPublic { "hadoop.shell.safely.delete.limit.num.files"; public static final long HADOOP_SHELL_SAFELY_DELETE_LIMIT_NUM_FILES_DEFAULT = 100; + + /** + * @see + * + * core-default.xml + */ + public static final String HADOOP_SECURITY_SENSITIVE_CONFIG_KEYS = + "hadoop.security.sensitive-config-keys"; + public static final String HADOOP_SECURITY_SENSITIVE_CONFIG_KEYS_DEFAULT = + "password$" + "," + + "fs.s3.*[Ss]ecret.?[Kk]ey" + "," + + "fs.azure\\.account.key.*" + "," + + "dfs.webhdfs.oauth2.[a-z]+.token" + "," + + HADOOP_SECURITY_SENSITIVE_CONFIG_KEYS; } diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index 1bfb545b0b1..d093a9305dc 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml @@ -430,6 +430,16 @@ + + hadoop.security.sensitive-config-keys + password$,fs.s3.*[Ss]ecret.?[Kk]ey,fs.azure.account.key.*,dfs.webhdfs.oauth2.[a-z]+.token,hadoop.security.sensitive-config-keys + A comma-separated list of regular expressions to match against + configuration keys that should be redacted where appropriate, for + example, when logging modified properties during a reconfiguration, + private credentials should not be logged. + + + hadoop.workaround.non.threadsafe.getpwuid true diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfigRedactor.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfigRedactor.java new file mode 100644 index 00000000000..81f8f717956 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfigRedactor.java @@ -0,0 +1,72 @@ +/** + * 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.hadoop.conf; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +/** + * Tests the tool (and the default expression) for deciding which config + * redact. + */ +public class TestConfigRedactor { + private static final String REDACTED_TEXT = ""; + + private static final String ORIGINAL_VALUE = "Hello, World!"; + + @Test + public void redact() throws Exception { + Configuration conf = new Configuration(); + ConfigRedactor redactor = new ConfigRedactor(conf); + String processedText; + + List sensitiveKeys = Arrays.asList( + "fs.s3a.secret.key", + "fs.s3n.awsSecretKey", + "fs.azure.account.key.abcdefg.blob.core.windows.net", + "dfs.webhdfs.oauth2.access.token", + "dfs.webhdfs.oauth2.refresh.token", + "ssl.server.keystore.keypassword", + "ssl.server.keystore.password", + "hadoop.security.sensitive-config-keys" + ); + for (String key : sensitiveKeys) { + processedText = redactor.redact(key, ORIGINAL_VALUE); + Assert.assertEquals( + "Config parameter wasn't redacted and should be: " + key, + REDACTED_TEXT, processedText); + } + + List normalKeys = Arrays.asList( + "fs.defaultFS", + "dfs.replication", + "ssl.server.keystore.location", + "hadoop.security.credstore.java-keystore-provider.password-file" + ); + for (String key : normalKeys) { + processedText = redactor.redact(key, ORIGINAL_VALUE); + Assert.assertEquals( + "Config parameter was redacted and shouldn't be: " + key, + ORIGINAL_VALUE, processedText); + } + } +}