PageRenderTime 40ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/sharelib/oozie/src/main/java/org/apache/oozie/action/hadoop/PasswordMasker.java

https://github.com/apache/oozie
Java | 177 lines | 71 code | 23 blank | 83 comment | 13 complexity | aefd5a98d849973a2b144cfbcd3b46d0 MD5 | raw file
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.oozie.action.hadoop;
  19. import com.google.common.collect.Maps;
  20. import javax.annotation.Nonnull;
  21. import java.util.Map;
  22. import java.util.regex.Matcher;
  23. import java.util.regex.Pattern;
  24. /**
  25. * A generic password masker that masks {@code Map<String, String>} values given that its keys are considered password keys.
  26. * <p>
  27. * Tested with {@link System#getProperties()} and {@link System#getenv()}.
  28. */
  29. public class PasswordMasker {
  30. /**
  31. * The mask that is applied to recognized passwords.
  32. **/
  33. private static final String PASSWORD_MASK = "*****";
  34. /**
  35. * A key is considered a password key, if it contains {{pass}}, case ignored.
  36. **/
  37. private static final String PASSWORD_KEY = "pass";
  38. /**
  39. * Tells us whether a given string contains a password fragment. A password fragment is something that looks
  40. * like {{-Djavax.net.ssl.trustStorePassword=password}} or {{HADOOP_CREDSTORE_PASSWORD=pwd123}}
  41. *
  42. **/
  43. private static final String PASSWORD_CONTAINING_REGEX =
  44. "(.*)([\\w[.\\w]*]*(?i)" + PASSWORD_KEY + "[\\w]*=)([\\w]+)(.*)";
  45. private static final Pattern PASSWORD_CONTAINING_PATTERN = Pattern
  46. .compile(PASSWORD_CONTAINING_REGEX);
  47. /**
  48. * Extracts a password fragment from a given string.
  49. * <p/>
  50. * {@see java.util.Matcher#find()}
  51. **/
  52. private static final String PASSWORD_EXTRACTING_REGEX =
  53. "([\\w[.\\w]*]*(?i)pass[\\w]*=)([\\w]+)";
  54. private static final Pattern PASSWORD_EXTRACTING_PATTERN = Pattern
  55. .compile(PASSWORD_EXTRACTING_REGEX);
  56. /**
  57. * Returns a map where values are masked if they are considered a password.
  58. * There are two cases when passwords are masked:
  59. * 1. The key contains the string "pass". In this case, the entire value is considered a password and replaced completely with
  60. * a masking string.
  61. * 2. The value matches a regular expression. Strings like "HADOOP_CREDSTORE_PASSWORD=pwd123" or
  62. * "-Djavax.net.ssl.trustStorePassword=password" are considered password definition strings and the text after the equal sign
  63. * is replaced with a masking string.
  64. *
  65. * @param unmasked key-value map
  66. * @return A new map where values are changed based on the replace algorithm described above
  67. */
  68. public Map<String, String> mask(Map<String, String> unmasked) {
  69. return Maps.transformEntries(unmasked, new Maps.EntryTransformer<String, String, String>() {
  70. @Override
  71. public String transformEntry(@Nonnull String key, @Nonnull String value) {
  72. return mask(key, value);
  73. }
  74. });
  75. }
  76. /**
  77. * Returns a the value of the entry masked if its considered as a password
  78. * There are two cases when passwords are masked:
  79. * 1. The key contains the string "pass". In this case, the entire value is considered a password and replaced completely with
  80. * a masking string.
  81. * 2. The value matches a regular expression. Strings like "HADOOP_CREDSTORE_PASSWORD=pwd123" or
  82. * "-Djavax.net.ssl.trustStorePassword=password" are considered password definition strings and the text after the equal sign
  83. * is replaced with a masking string.
  84. *
  85. * @param unmasked key-value entry
  86. * @return The value of the entry changed based on the replace algorithm described above
  87. */
  88. public String mask(Map.Entry<String, String> unmasked) {
  89. return mask(unmasked.getKey(), unmasked.getValue());
  90. }
  91. /**
  92. * Returns a the value of the entry masked if its considered as a password
  93. * There are two cases when passwords are masked:
  94. * 1. The key contains the string "pass". In this case, the entire value is considered a password and replaced completely with
  95. * a masking string.
  96. * 2. The value matches a regular expression. Strings like "HADOOP_CREDSTORE_PASSWORD=pwd123" or
  97. * "-Djavax.net.ssl.trustStorePassword=password" are considered password definition strings and the text after the equal sign
  98. * is replaced with a masking string.
  99. * @param key key of entry
  100. * @param value value of entry
  101. * @return The value of the entry changed based on the replace algorithm described above
  102. */
  103. private String mask(String key, String value) {
  104. if (key == null || value == null || value.isEmpty()) {
  105. return value;
  106. }
  107. if (isPasswordKey(key)) {
  108. return PASSWORD_MASK;
  109. }
  110. return maskPasswordsIfNecessary(value);
  111. }
  112. /**
  113. * Masks passwords inside a string. A substring is subject to password masking if it looks like
  114. * "HADOOP_CREDSTORE_PASSWORD=pwd123" or "-Djavax.net.ssl.trustStorePassword=password". The text after the equal sign is
  115. * replaced with a masking string.
  116. *
  117. * @param unmasked String which might contain passwords
  118. * @return The same string where passwords are replaced with a masking string. If there is no password inside, the original
  119. * string is returned.
  120. */
  121. public String maskPasswordsIfNecessary(String unmasked) {
  122. if (containsPasswordFragment(unmasked)) {
  123. return maskPasswordFragments(unmasked);
  124. } else {
  125. return unmasked;
  126. }
  127. }
  128. private boolean isPasswordKey(String key) {
  129. return key.toLowerCase().contains(PASSWORD_KEY);
  130. }
  131. private boolean containsPasswordFragment(String maybePasswordFragments) {
  132. if (maybePasswordFragments == null || maybePasswordFragments.length() == 0) {
  133. return false;
  134. }
  135. return PASSWORD_CONTAINING_PATTERN
  136. .matcher(maybePasswordFragments)
  137. .matches();
  138. }
  139. private String maskPasswordFragments(String maybePasswordFragments) {
  140. StringBuilder maskedBuilder = new StringBuilder();
  141. Matcher passwordFragmentsMatcher = PASSWORD_EXTRACTING_PATTERN
  142. .matcher(maybePasswordFragments);
  143. int start = 0, end;
  144. while (passwordFragmentsMatcher.find()) {
  145. end = passwordFragmentsMatcher.start();
  146. maskedBuilder.append(maybePasswordFragments.substring(start, end));
  147. maskedBuilder.append(passwordFragmentsMatcher.group(1));
  148. maskedBuilder.append(PASSWORD_MASK);
  149. start = passwordFragmentsMatcher.end();
  150. }
  151. maskedBuilder.append(maybePasswordFragments.substring(start));
  152. return maskedBuilder.toString();
  153. }
  154. }